@things-factory/headless-twin 10.0.15 โ†’ 10.0.18

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 (513) hide show
  1. package/dist-server/engine/attention-digest.d.ts +4 -4
  2. package/dist-server/engine/attention-digest.js +7 -7
  3. package/dist-server/engine/attention-digest.js.map +1 -1
  4. package/dist-server/engine/canonical-ingest.d.ts +18 -82
  5. package/dist-server/engine/canonical-ingest.js +183 -18
  6. package/dist-server/engine/canonical-ingest.js.map +1 -1
  7. package/dist-server/engine/command-routing.js +1 -1
  8. package/dist-server/engine/command-routing.js.map +1 -1
  9. package/dist-server/engine/declared-stimulus.js +1 -1
  10. package/dist-server/engine/declared-stimulus.js.map +1 -1
  11. package/dist-server/engine/energy-topology.js +4 -4
  12. package/dist-server/engine/energy-topology.js.map +1 -1
  13. package/dist-server/engine/ingest-dedupe.d.ts +38 -0
  14. package/dist-server/engine/ingest-dedupe.js +145 -0
  15. package/dist-server/engine/ingest-dedupe.js.map +1 -0
  16. package/dist-server/engine/ingest-health.d.ts +90 -10
  17. package/dist-server/engine/ingest-health.js +125 -16
  18. package/dist-server/engine/ingest-health.js.map +1 -1
  19. package/dist-server/engine/integration-probes.d.ts +3 -3
  20. package/dist-server/engine/integration-probes.js +6 -6
  21. package/dist-server/engine/integration-probes.js.map +1 -1
  22. package/dist-server/engine/kpi-baseline.js +4 -4
  23. package/dist-server/engine/kpi-baseline.js.map +1 -1
  24. package/dist-server/engine/kpi-fold.d.ts +212 -14
  25. package/dist-server/engine/kpi-fold.js +374 -21
  26. package/dist-server/engine/kpi-fold.js.map +1 -1
  27. package/dist-server/engine/kpi-query.d.ts +11 -11
  28. package/dist-server/engine/kpi-query.js +281 -36
  29. package/dist-server/engine/kpi-query.js.map +1 -1
  30. package/dist-server/engine/live-feed-registry.d.ts +1 -1
  31. package/dist-server/engine/live-feed-registry.js +1 -1
  32. package/dist-server/engine/live-feed-registry.js.map +1 -1
  33. package/dist-server/engine/load-meter.d.ts +1 -1
  34. package/dist-server/engine/load-meter.js.map +1 -1
  35. package/dist-server/engine/local-declarations.d.ts +7 -7
  36. package/dist-server/engine/local-declarations.js +15 -14
  37. package/dist-server/engine/local-declarations.js.map +1 -1
  38. package/dist-server/engine/log.d.ts +1 -1
  39. package/dist-server/engine/log.js +1 -1
  40. package/dist-server/engine/log.js.map +1 -1
  41. package/dist-server/engine/measured-estimator.js +1 -1
  42. package/dist-server/engine/measured-estimator.js.map +1 -1
  43. package/dist-server/engine/measured-yield.js +1 -1
  44. package/dist-server/engine/measured-yield.js.map +1 -1
  45. package/dist-server/engine/model-basis.d.ts +1 -1
  46. package/dist-server/engine/model-basis.js +1 -1
  47. package/dist-server/engine/model-basis.js.map +1 -1
  48. package/dist-server/engine/model-gap.d.ts +3 -3
  49. package/dist-server/engine/model-gap.js +1 -1
  50. package/dist-server/engine/model-gap.js.map +1 -1
  51. package/dist-server/engine/model-vocabulary.js +3 -3
  52. package/dist-server/engine/model-vocabulary.js.map +1 -1
  53. package/dist-server/engine/oee-accumulator.d.ts +6 -5
  54. package/dist-server/engine/oee-accumulator.js +6 -5
  55. package/dist-server/engine/oee-accumulator.js.map +1 -1
  56. package/dist-server/engine/property-effects.js +2 -2
  57. package/dist-server/engine/property-effects.js.map +1 -1
  58. package/dist-server/engine/read-time.d.ts +22 -0
  59. package/dist-server/engine/read-time.js +81 -0
  60. package/dist-server/engine/read-time.js.map +1 -0
  61. package/dist-server/engine/restart-policy.js +1 -1
  62. package/dist-server/engine/restart-policy.js.map +1 -1
  63. package/dist-server/engine/runtime-key.js +1 -1
  64. package/dist-server/engine/runtime-key.js.map +1 -1
  65. package/dist-server/engine/spec-coverage.d.ts +2 -2
  66. package/dist-server/engine/spec-coverage.js +1 -1
  67. package/dist-server/engine/spec-coverage.js.map +1 -1
  68. package/dist-server/engine/stage-path.d.ts +4 -4
  69. package/dist-server/engine/stage-path.js +1 -1
  70. package/dist-server/engine/stage-path.js.map +1 -1
  71. package/dist-server/engine/state-axes.d.ts +1 -1
  72. package/dist-server/engine/state-axes.js +2 -2
  73. package/dist-server/engine/state-axes.js.map +1 -1
  74. package/dist-server/engine/travel-estimator.d.ts +2 -2
  75. package/dist-server/engine/travel-estimator.js +1 -1
  76. package/dist-server/engine/travel-estimator.js.map +1 -1
  77. package/dist-server/engine/twin-engine.d.ts +307 -54
  78. package/dist-server/engine/twin-engine.js +822 -196
  79. package/dist-server/engine/twin-engine.js.map +1 -1
  80. package/dist-server/engine/warm-start.d.ts +2 -2
  81. package/dist-server/engine/warm-start.js +5 -5
  82. package/dist-server/engine/warm-start.js.map +1 -1
  83. package/dist-server/index.js +1 -1
  84. package/dist-server/index.js.map +1 -1
  85. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +1 -1
  86. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -1
  87. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +3 -3
  88. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -1
  89. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +2 -2
  90. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -1
  91. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.d.ts +5 -0
  92. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js +54 -0
  93. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +1 -0
  94. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.d.ts +5 -0
  95. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js +44 -0
  96. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +1 -0
  97. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.d.ts +6 -0
  98. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js +84 -0
  99. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +1 -0
  100. package/dist-server/migrations/index.js +7 -1
  101. package/dist-server/migrations/index.js.map +1 -1
  102. package/dist-server/routes.d.ts +1 -0
  103. package/dist-server/routes.js +69 -5
  104. package/dist-server/routes.js.map +1 -1
  105. package/dist-server/service/index.d.ts +2 -2
  106. package/dist-server/service/index.js +30 -24
  107. package/dist-server/service/index.js.map +1 -1
  108. package/dist-server/service/reference/control-routing.d.ts +1 -1
  109. package/dist-server/service/reference/control-routing.js +1 -1
  110. package/dist-server/service/reference/control-routing.js.map +1 -1
  111. package/dist-server/service/reference/discovery-result.d.ts +2 -2
  112. package/dist-server/service/reference/discovery-result.js +3 -3
  113. package/dist-server/service/reference/discovery-result.js.map +1 -1
  114. package/dist-server/service/reference/hook-contract.d.ts +43 -0
  115. package/dist-server/service/reference/hook-contract.js +59 -0
  116. package/dist-server/service/reference/hook-contract.js.map +1 -0
  117. package/dist-server/service/reference/ingest-space.d.ts +1 -1
  118. package/dist-server/service/reference/ingest-space.js +4 -4
  119. package/dist-server/service/reference/ingest-space.js.map +1 -1
  120. package/dist-server/service/reference/knob-defaults.d.ts +1 -1
  121. package/dist-server/service/reference/knob-defaults.js +4 -4
  122. package/dist-server/service/reference/knob-defaults.js.map +1 -1
  123. package/dist-server/service/reference/reference-adapter.d.ts +116 -9
  124. package/dist-server/service/reference/reference-adapter.js +27 -0
  125. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  126. package/dist-server/service/reference/reference-hook.d.ts +20 -0
  127. package/dist-server/service/reference/reference-hook.js +133 -0
  128. package/dist-server/service/reference/reference-hook.js.map +1 -0
  129. package/dist-server/service/reference/reference-live.d.ts +2 -2
  130. package/dist-server/service/reference/reference-live.js +76 -13
  131. package/dist-server/service/reference/reference-live.js.map +1 -1
  132. package/dist-server/service/reference/reference-master.d.ts +25 -16
  133. package/dist-server/service/reference/reference-master.js +35 -13
  134. package/dist-server/service/reference/reference-master.js.map +1 -1
  135. package/dist-server/service/reference/reference-resolver.d.ts +1 -1
  136. package/dist-server/service/reference/reference-resolver.js +12 -12
  137. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  138. package/dist-server/service/reference/template-registry.d.ts +1 -1
  139. package/dist-server/service/reference/template-registry.js.map +1 -1
  140. package/dist-server/service/reference/twin-reference.d.ts +3 -3
  141. package/dist-server/service/reference/twin-reference.js.map +1 -1
  142. package/dist-server/service/twin-attention/twin-attention-query.d.ts +1 -1
  143. package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
  144. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  145. package/dist-server/service/twin-audit/command-audit.d.ts +1 -1
  146. package/dist-server/service/twin-audit/command-audit.js.map +1 -1
  147. package/dist-server/service/twin-audit/twin-audit-event.d.ts +1 -1
  148. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
  149. package/dist-server/service/twin-audit/twin-audit-query.js +1 -1
  150. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -1
  151. package/dist-server/service/twin-backfill/backfill-period-facts.d.ts +24 -0
  152. package/dist-server/service/twin-backfill/backfill-period-facts.js +96 -0
  153. package/dist-server/service/twin-backfill/backfill-period-facts.js.map +1 -0
  154. package/dist-server/service/twin-backfill/backfill-shape.d.ts +34 -0
  155. package/dist-server/service/twin-backfill/backfill-shape.js +75 -0
  156. package/dist-server/service/twin-backfill/backfill-shape.js.map +1 -0
  157. package/dist-server/service/twin-backfill/index.d.ts +2 -0
  158. package/dist-server/service/twin-backfill/index.js +6 -0
  159. package/dist-server/service/twin-backfill/index.js.map +1 -0
  160. package/dist-server/service/twin-backfill/twin-backfill-resolver.d.ts +10 -0
  161. package/dist-server/service/twin-backfill/twin-backfill-resolver.js +44 -0
  162. package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +1 -0
  163. package/dist-server/service/twin-control/twin-control-mutation.js +3 -3
  164. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  165. package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
  166. package/dist-server/service/twin-event/twin-event-keys.js +2 -2
  167. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  168. package/dist-server/service/twin-event/twin-event-type.d.ts +1 -0
  169. package/dist-server/service/twin-event/twin-event-type.js +8 -1
  170. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  171. package/dist-server/service/twin-event/twin-event.d.ts +2 -2
  172. package/dist-server/service/twin-event/twin-event.js +32 -4
  173. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  174. package/dist-server/service/twin-forecast/forecast-metrics.js +1 -1
  175. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -1
  176. package/dist-server/service/twin-forecast/gap-analytics.d.ts +1 -1
  177. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  178. package/dist-server/service/twin-forecast/twin-forecast-query.js +29 -11
  179. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  180. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +1 -1
  181. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +1 -1
  182. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -1
  183. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +1 -1
  184. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -1
  185. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  186. package/dist-server/service/twin-journal/event-type-split-shape.d.ts +50 -0
  187. package/dist-server/service/twin-journal/event-type-split-shape.js +111 -0
  188. package/dist-server/service/twin-journal/event-type-split-shape.js.map +1 -0
  189. package/dist-server/service/twin-journal/event-type-split.d.ts +15 -0
  190. package/dist-server/service/twin-journal/event-type-split.js +102 -0
  191. package/dist-server/service/twin-journal/event-type-split.js.map +1 -0
  192. package/dist-server/service/twin-journal/journal-order.d.ts +15 -0
  193. package/dist-server/service/twin-journal/journal-order.js +17 -0
  194. package/dist-server/service/twin-journal/journal-order.js.map +1 -0
  195. package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -5
  196. package/dist-server/service/twin-journal/twin-journal-query.js +232 -21
  197. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  198. package/dist-server/service/twin-lifecycle/domain-catalog.js +2 -1
  199. package/dist-server/service/twin-lifecycle/domain-catalog.js.map +1 -1
  200. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +27 -1
  201. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +59 -3
  202. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  203. package/dist-server/service/twin-metrics/twin-metrics-query.js +14 -1
  204. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  205. package/dist-server/service/twin-model/axis-journal-evidence.js +1 -1
  206. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -1
  207. package/dist-server/service/twin-model/epcis-coverage.js +34 -3
  208. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  209. package/dist-server/service/twin-model/iec61850-coverage.js +1 -1
  210. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  211. package/dist-server/service/twin-model/isa95-coverage.d.ts +12 -9
  212. package/dist-server/service/twin-model/isa95-coverage.js +33 -9
  213. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  214. package/dist-server/service/twin-model/item-ref.js +2 -2
  215. package/dist-server/service/twin-model/item-ref.js.map +1 -1
  216. package/dist-server/service/twin-model/name-index.d.ts +1 -1
  217. package/dist-server/service/twin-model/name-index.js +2 -2
  218. package/dist-server/service/twin-model/name-index.js.map +1 -1
  219. package/dist-server/service/twin-model/project-structure.d.ts +1 -1
  220. package/dist-server/service/twin-model/project-structure.js +7 -6
  221. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  222. package/dist-server/service/twin-model/twin-equipment.d.ts +2 -2
  223. package/dist-server/service/twin-model/twin-equipment.js +3 -3
  224. package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
  225. package/dist-server/service/twin-model/twin-lineage-query.js +16 -5
  226. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  227. package/dist-server/service/twin-model/twin-location.d.ts +1 -1
  228. package/dist-server/service/twin-model/twin-location.js.map +1 -1
  229. package/dist-server/service/twin-model/twin-model-item-query.d.ts +3 -3
  230. package/dist-server/service/twin-model/twin-model-item-query.js +15 -15
  231. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  232. package/dist-server/service/twin-model/twin-model-mutation.d.ts +2 -2
  233. package/dist-server/service/twin-model/twin-model-mutation.js +4 -4
  234. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -1
  235. package/dist-server/service/twin-model/twin-model-query.js +51 -30
  236. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  237. package/dist-server/service/twin-model/twin-model-tree-query.js +23 -1
  238. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  239. package/dist-server/service/twin-model/twin-operation.d.ts +1 -1
  240. package/dist-server/service/twin-model/twin-operation.js +1 -1
  241. package/dist-server/service/twin-model/twin-operation.js.map +1 -1
  242. package/dist-server/service/twin-space/move-space.js +2 -2
  243. package/dist-server/service/twin-space/move-space.js.map +1 -1
  244. package/dist-server/service/twin-space/representation-role.d.ts +2 -0
  245. package/dist-server/service/twin-space/representation-role.js +18 -0
  246. package/dist-server/service/twin-space/representation-role.js.map +1 -0
  247. package/dist-server/service/twin-space/space-integrity.d.ts +1 -1
  248. package/dist-server/service/twin-space/space-integrity.js.map +1 -1
  249. package/dist-server/service/twin-space/twin-space-representation.d.ts +2 -0
  250. package/dist-server/service/twin-space/twin-space-representation.js +16 -0
  251. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  252. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  253. package/dist-server/service/twin-space/twin-space-resolver.js +37 -8
  254. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  255. package/dist-server/service/twin-structure/twin-structure.js +3 -3
  256. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  257. package/dist-server/service/twin-subject/event-subjects.d.ts +43 -0
  258. package/dist-server/service/twin-subject/event-subjects.js +137 -0
  259. package/dist-server/service/twin-subject/event-subjects.js.map +1 -0
  260. package/dist-server/service/twin-subject/index.d.ts +9 -0
  261. package/dist-server/service/twin-subject/index.js +16 -0
  262. package/dist-server/service/twin-subject/index.js.map +1 -0
  263. package/dist-server/service/twin-subject/item-resolver.d.ts +10 -0
  264. package/dist-server/service/twin-subject/item-resolver.js +93 -0
  265. package/dist-server/service/twin-subject/item-resolver.js.map +1 -0
  266. package/dist-server/service/twin-subject/subject-query-shape.d.ts +22 -0
  267. package/dist-server/service/twin-subject/subject-query-shape.js +88 -0
  268. package/dist-server/service/twin-subject/subject-query-shape.js.map +1 -0
  269. package/dist-server/service/twin-subject/twin-subject-backfill.d.ts +23 -0
  270. package/dist-server/service/twin-subject/twin-subject-backfill.js +159 -0
  271. package/dist-server/service/twin-subject/twin-subject-backfill.js.map +1 -0
  272. package/dist-server/service/twin-subject/twin-subject-event.d.ts +14 -0
  273. package/dist-server/service/twin-subject/twin-subject-event.js +111 -0
  274. package/dist-server/service/twin-subject/twin-subject-event.js.map +1 -0
  275. package/dist-server/service/twin-subject/twin-subject-fast-path.d.ts +39 -0
  276. package/dist-server/service/twin-subject/twin-subject-fast-path.js +295 -0
  277. package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +1 -0
  278. package/dist-server/service/twin-subject/twin-subject-query.d.ts +27 -0
  279. package/dist-server/service/twin-subject/twin-subject-query.js +232 -0
  280. package/dist-server/service/twin-subject/twin-subject-query.js.map +1 -0
  281. package/dist-server/service/twin-subject/twin-subject-writer.d.ts +27 -0
  282. package/dist-server/service/twin-subject/twin-subject-writer.js +166 -0
  283. package/dist-server/service/twin-subject/twin-subject-writer.js.map +1 -0
  284. package/dist-server/service/twin-target/twin-target-resolver.js +1 -1
  285. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
  286. package/dist-shared/entity-delta.d.ts +1 -1
  287. package/dist-shared/entity-delta.js +4 -4
  288. package/dist-shared/entity-delta.js.map +1 -1
  289. package/dist-shared/touched-items.js +2 -2
  290. package/dist-shared/touched-items.js.map +1 -1
  291. package/dist-shared/twin-level.js +1 -1
  292. package/dist-shared/twin-level.js.map +1 -1
  293. package/package.json +7 -6
  294. package/server/engine/attention-digest.ts +9 -9
  295. package/server/engine/canonical-ingest.ts +202 -87
  296. package/server/engine/command-routing.ts +1 -1
  297. package/server/engine/declared-stimulus.ts +1 -1
  298. package/server/engine/energy-topology.ts +2 -2
  299. package/server/engine/ingest-dedupe.ts +149 -0
  300. package/server/engine/ingest-health.ts +171 -22
  301. package/server/engine/integration-probes.ts +7 -7
  302. package/server/engine/kpi-baseline.ts +4 -4
  303. package/server/engine/kpi-fold.ts +592 -41
  304. package/server/engine/kpi-query.ts +307 -48
  305. package/server/engine/live-feed-registry.ts +1 -1
  306. package/server/engine/load-meter.ts +1 -1
  307. package/server/engine/local-declarations.ts +15 -14
  308. package/server/engine/log.ts +1 -1
  309. package/server/engine/measured-estimator.ts +1 -1
  310. package/server/engine/measured-yield.ts +1 -1
  311. package/server/engine/model-basis.ts +2 -2
  312. package/server/engine/model-gap.ts +3 -3
  313. package/server/engine/model-vocabulary.ts +2 -2
  314. package/server/engine/oee-accumulator.ts +8 -6
  315. package/server/engine/property-effects.ts +2 -2
  316. package/server/engine/read-time.ts +77 -0
  317. package/server/engine/restart-policy.ts +1 -1
  318. package/server/engine/runtime-key.ts +1 -1
  319. package/server/engine/spec-coverage.ts +3 -3
  320. package/server/engine/stage-path.ts +5 -5
  321. package/server/engine/state-axes.ts +2 -2
  322. package/server/engine/travel-estimator.ts +2 -2
  323. package/server/engine/twin-engine.ts +906 -205
  324. package/server/engine/warm-start.ts +6 -6
  325. package/server/index.ts +1 -1
  326. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +1 -1
  327. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +3 -3
  328. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +2 -2
  329. package/server/migrations/1786300000000-IndexEventTypeByTime.ts +48 -0
  330. package/server/migrations/1786400000000-IndexEventCreatedAt.ts +38 -0
  331. package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +93 -0
  332. package/server/migrations/index.ts +7 -1
  333. package/server/routes.ts +73 -5
  334. package/server/service/index.ts +7 -1
  335. package/server/service/reference/control-routing.ts +1 -1
  336. package/server/service/reference/discovery-result.ts +3 -3
  337. package/server/service/reference/hook-contract.ts +103 -0
  338. package/server/service/reference/ingest-space.ts +4 -4
  339. package/server/service/reference/knob-defaults.ts +4 -4
  340. package/server/service/reference/reference-adapter.ts +145 -10
  341. package/server/service/reference/reference-hook.ts +145 -0
  342. package/server/service/reference/reference-live.ts +87 -15
  343. package/server/service/reference/reference-master.ts +51 -20
  344. package/server/service/reference/reference-resolver.ts +11 -11
  345. package/server/service/reference/template-registry.ts +1 -1
  346. package/server/service/reference/twin-reference.ts +3 -3
  347. package/server/service/twin-attention/twin-attention-query.ts +1 -1
  348. package/server/service/twin-audit/command-audit.ts +1 -1
  349. package/server/service/twin-audit/twin-audit-event.ts +2 -2
  350. package/server/service/twin-audit/twin-audit-query.ts +1 -1
  351. package/server/service/twin-backfill/backfill-period-facts.ts +168 -0
  352. package/server/service/twin-backfill/backfill-shape.ts +86 -0
  353. package/server/service/twin-backfill/index.ts +3 -0
  354. package/server/service/twin-backfill/twin-backfill-resolver.ts +35 -0
  355. package/server/service/twin-control/twin-control-mutation.ts +2 -2
  356. package/server/service/twin-event/twin-event-keys.ts +2 -2
  357. package/server/service/twin-event/twin-event-type.ts +18 -1
  358. package/server/service/twin-event/twin-event.ts +32 -6
  359. package/server/service/twin-forecast/forecast-metrics.ts +1 -1
  360. package/server/service/twin-forecast/gap-analytics.ts +1 -1
  361. package/server/service/twin-forecast/twin-forecast-query.ts +30 -11
  362. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +1 -1
  363. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +1 -1
  364. package/server/service/twin-instance/twin-instance.ts +4 -4
  365. package/server/service/twin-journal/event-type-split-shape.ts +112 -0
  366. package/server/service/twin-journal/event-type-split.ts +144 -0
  367. package/server/service/twin-journal/journal-order.ts +20 -0
  368. package/server/service/twin-journal/twin-journal-query.ts +224 -20
  369. package/server/service/twin-lifecycle/domain-catalog.ts +2 -1
  370. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +58 -4
  371. package/server/service/twin-metrics/twin-metrics-query.ts +14 -1
  372. package/server/service/twin-model/axis-journal-evidence.ts +1 -1
  373. package/server/service/twin-model/epcis-coverage.ts +34 -3
  374. package/server/service/twin-model/iec61850-coverage.ts +1 -1
  375. package/server/service/twin-model/isa95-coverage.ts +43 -16
  376. package/server/service/twin-model/item-ref.ts +2 -2
  377. package/server/service/twin-model/name-index.ts +2 -2
  378. package/server/service/twin-model/project-structure.ts +7 -6
  379. package/server/service/twin-model/twin-equipment.ts +16 -4
  380. package/server/service/twin-model/twin-lineage-query.ts +16 -5
  381. package/server/service/twin-model/twin-location.ts +1 -1
  382. package/server/service/twin-model/twin-model-item-query.ts +15 -15
  383. package/server/service/twin-model/twin-model-mutation.ts +4 -4
  384. package/server/service/twin-model/twin-model-query.ts +49 -28
  385. package/server/service/twin-model/twin-model-tree-query.ts +26 -2
  386. package/server/service/twin-model/twin-operation.ts +2 -2
  387. package/server/service/twin-space/move-space.ts +3 -3
  388. package/server/service/twin-space/representation-role.ts +15 -0
  389. package/server/service/twin-space/space-integrity.ts +1 -1
  390. package/server/service/twin-space/twin-space-representation.ts +37 -0
  391. package/server/service/twin-space/twin-space-resolver.ts +40 -8
  392. package/server/service/twin-structure/twin-structure.ts +3 -3
  393. package/server/service/twin-subject/event-subjects.ts +155 -0
  394. package/server/service/twin-subject/index.ts +10 -0
  395. package/server/service/twin-subject/item-resolver.ts +92 -0
  396. package/server/service/twin-subject/subject-query-shape.ts +84 -0
  397. package/server/service/twin-subject/twin-subject-backfill.ts +196 -0
  398. package/server/service/twin-subject/twin-subject-event.ts +101 -0
  399. package/server/service/twin-subject/twin-subject-fast-path.ts +346 -0
  400. package/server/service/twin-subject/twin-subject-query.ts +210 -0
  401. package/server/service/twin-subject/twin-subject-writer.ts +189 -0
  402. package/server/service/twin-target/twin-target-resolver.ts +1 -1
  403. package/shared/entity-delta.ts +4 -4
  404. package/shared/touched-items.ts +2 -2
  405. package/shared/twin-level.ts +1 -1
  406. package/test/adopt-structure-live.test.ts +3 -3
  407. package/test/aggregate-not-findone.test.ts +112 -0
  408. package/test/attention-digest.test.ts +6 -6
  409. package/test/axis-read.test.ts +6 -5
  410. package/test/backfill-shape.test.ts +89 -0
  411. package/test/boot-resume.test.ts +20 -7
  412. package/test/broadcast-cost-baseline.test.ts +2 -1
  413. package/test/broadcast-period.test.ts +2 -2
  414. package/test/broadcast-queue-overflow.test.ts +244 -0
  415. package/test/canonical-ingest-vocabularies.test.ts +259 -8
  416. package/test/canonical-quantity-door.test.ts +2 -2
  417. package/test/capability-mapping.test.ts +1 -1
  418. package/test/checkpoint-refuses-empty.test.ts +2 -2
  419. package/test/connector-capability-declaration.test.ts +88 -0
  420. package/test/contract-layer-guard.test.ts +48 -0
  421. package/test/control-capability.test.ts +1 -1
  422. package/test/cursor-stall-not-read-failure.test.ts +2 -2
  423. package/test/declaration-reaches-model.test.ts +7 -5
  424. package/test/declared-stimulus.test.ts +4 -4
  425. package/test/discovery-result.test.ts +2 -2
  426. package/test/entity-delta.test.ts +4 -4
  427. package/test/equipment-identity-reaches.test.ts +57 -0
  428. package/test/event-subjects.test.ts +147 -0
  429. package/test/event-time-column.test.ts +3 -3
  430. package/test/event-type-split.test.ts +155 -0
  431. package/test/fact-scope-wiring.test.ts +96 -0
  432. package/test/fold-gap-speaks.test.ts +53 -0
  433. package/test/forecast-metrics.test.ts +1 -1
  434. package/test/forecast-tuning.test.ts +1 -1
  435. package/test/generated-not-counted.test.ts +99 -0
  436. package/test/ingest-bench.test.ts +2 -1
  437. package/test/ingest-health-engine.test.ts +2 -2
  438. package/test/ingest-health-wiring.test.ts +7 -5
  439. package/test/ingest-health.test.ts +6 -6
  440. package/test/ingest-history.test.ts +2 -2
  441. package/test/ingest-idempotent.test.ts +132 -0
  442. package/test/ingest-space.test.ts +3 -3
  443. package/test/ingest-wiring-guard.test.ts +197 -0
  444. package/test/integration-probes.test.ts +3 -3
  445. package/test/integration-runner.test.ts +1 -1
  446. package/test/item-ref.test.ts +1 -1
  447. package/test/journal-order-axis.test.ts +80 -0
  448. package/test/journal-read-discipline.test.ts +16 -6
  449. package/test/journal-retention.test.ts +171 -29
  450. package/test/journal-sort-axis.test.ts +1 -1
  451. package/test/journal-write-door.test.ts +3 -3
  452. package/test/journal-write-trend.test.ts +1 -1
  453. package/test/kernel-kind-guard.test.ts +3 -3
  454. package/test/knob-defaults.test.ts +1 -1
  455. package/test/kpi-baseline-db.test.ts +2 -2
  456. package/test/kpi-fold.test.ts +266 -14
  457. package/test/kpi-query-bench.test.ts +3 -3
  458. package/test/live-cursor-wiring.test.ts +6 -6
  459. package/test/live-kernel-facts.test.ts +11 -11
  460. package/test/live-mirror-parity.test.ts +9 -6
  461. package/test/load-meter.test.ts +1 -1
  462. package/test/local-declarations.test.ts +13 -12
  463. package/test/log-stamp.test.ts +1 -1
  464. package/test/master-to-twin.test.ts +2 -2
  465. package/test/measured-yield.test.ts +2 -2
  466. package/test/merge-streams.test.ts +70 -0
  467. package/test/mirror-resumes-from-checkpoint.test.ts +13 -13
  468. package/test/model-basis.test.ts +2 -2
  469. package/test/model-gap.test.ts +4 -4
  470. package/test/model-vocabulary.test.ts +2 -2
  471. package/test/move-space.test.ts +1 -1
  472. package/test/oee-accumulator.test.ts +20 -5
  473. package/test/operational-vocabulary.test.ts +2 -2
  474. package/test/project-structure-db.test.ts +2 -2
  475. package/test/projection-reaches-screen.test.ts +7 -2
  476. package/test/projection-reads-declared.test.ts +109 -0
  477. package/test/property-effects.test.ts +10 -6
  478. package/test/read-failure-ledger.test.ts +106 -0
  479. package/test/read-failure-visible.test.ts +7 -7
  480. package/test/read-time.test.ts +118 -0
  481. package/test/reference-grounding.test.ts +1 -1
  482. package/test/reference-hook.test.ts +86 -0
  483. package/test/registry-key-guard.test.ts +1 -1
  484. package/test/rename-twin.test.ts +81 -0
  485. package/test/restart-policy.test.ts +3 -3
  486. package/test/resync-origin-site.test.ts +3 -3
  487. package/test/revision-axis.test.ts +14 -8
  488. package/test/runtime-key.test.ts +3 -3
  489. package/test/scale-twin-bench.test.ts +2 -1
  490. package/test/snapshot-freshness.test.ts +4 -4
  491. package/test/source-outcome-audit.test.ts +1 -1
  492. package/test/space-integrity.test.ts +1 -1
  493. package/test/space-representation.test.ts +66 -0
  494. package/test/space-scope-includes-stopped.test.ts +55 -0
  495. package/test/spec-coverage.test.ts +1 -1
  496. package/test/standard-coverage.test.ts +19 -21
  497. package/test/status-tally.test.ts +1 -1
  498. package/test/structure-revision-db.test.ts +7 -7
  499. package/test/subject-fast-path.test.ts +148 -0
  500. package/test/subject-read-model.test.ts +175 -0
  501. package/test/touched-items.test.ts +2 -2
  502. package/test/twin-audit.test.ts +1 -1
  503. package/test/twin-event-keys.test.ts +1 -1
  504. package/test/twin-model-item-db.test.ts +5 -5
  505. package/test/twin-model-tree-db.test.ts +35 -2
  506. package/test/twin-origin-resync.test.ts +1 -1
  507. package/test/vocabulary-guard.test.ts +1 -1
  508. package/test/warm-start-seam.test.ts +1 -1
  509. package/test/warm-start.test.ts +6 -6
  510. package/test/withheld-door.test.ts +2 -2
  511. package/test/yield-loop.test.ts +4 -4
  512. package/tsconfig.shared.tsbuildinfo +1 -1
  513. package/tsconfig.tsbuildinfo +1 -1
@@ -10,12 +10,15 @@
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.TwinEngine = void 0;
12
12
  const log_js_1 = require("./log.js");
13
+ const read_time_js_1 = require("./read-time.js");
13
14
  const shell_1 = require("@things-factory/shell");
14
15
  /* ์ €๋„์„ ์ž๋ฅผ ์กฐ๊ฑด์€ SQL ์ด ์•ˆ๋‹ค โ€” ๊ด€์šฉ๊ตฌ๋งŒ ์“ด๋‹ค(์›์‹œ SQL ์€ 5๊ฐœ ๋“œ๋ผ์ด๋ฒ„์—์„œ ๊ฐˆ๋ผ์ง„๋‹ค). */
15
16
  const typeorm_1 = require("typeorm");
16
17
  const cache_service_1 = require("@things-factory/cache-service");
17
18
  const twin_event_js_1 = require("../service/twin-event/twin-event.js");
18
19
  const twin_event_keys_js_1 = require("../service/twin-event/twin-event-keys.js");
20
+ const twin_subject_writer_js_1 = require("../service/twin-subject/twin-subject-writer.js");
21
+ const item_resolver_js_1 = require("../service/twin-subject/item-resolver.js");
19
22
  const warm_start_js_1 = require("./warm-start.js");
20
23
  const local_declarations_js_1 = require("./local-declarations.js");
21
24
  const runtime_key_js_1 = require("./runtime-key.js");
@@ -50,9 +53,11 @@ const live_attentions_js_1 = require("./live-attentions.js");
50
53
  const attention_digest_js_1 = require("./attention-digest.js");
51
54
  const live_feed_registry_js_1 = require("./live-feed-registry.js");
52
55
  const ingest_health_js_1 = require("./ingest-health.js");
53
- const twin_kernel_1 = require("@operato/twin-kernel");
56
+ const ingest_dedupe_js_1 = require("./ingest-dedupe.js");
57
+ const ops_contract_1 = require("@operato/ops-contract");
54
58
  /* ์ปค๋„ ๋Ÿฐํƒ€์ž„ ๋กœ๋“œ โ€” CJS ๋ฒˆ๋“ค(dist-cjs). ํƒ€์ž…์€ ์œ„ import type ๋กœ. replay = ์ด๋ฒคํŠธ์—ดโ†’์ƒํƒœ ์žฌ๊ตฌ์„ฑ(๋ณต๊ตฌยท์‹œ๊ฐ„์—ฌํ–‰). */
55
- const { WmsKernel, YmsKernel, MesKernel, EmsKernel, TwinRuntime, StateProjector, replay, replayFrom, replayWithCheckpoint, replaySegments, readBoardLocations, readBoardEquipment, DOMAIN_CATALOG, OP_EVENT, validateScenario } = require('@operato/twin-kernel');
59
+ const { WmsKernel, YmsKernel, MesKernel, EmsKernel, TwinRuntime, StateProjector, replay, replayFrom, replayWithCheckpoint, replaySegments } = require('@operato/twin-kernel');
60
+ const { readBoardLocations, readBoardEquipment, DOMAIN_CATALOG, OP_EVENT, validateScenario } = require('@operato/ops-contract');
56
61
  const KERNELS = { wms: WmsKernel, yms: YmsKernel, mes: MesKernel, ems: EmsKernel };
57
62
  /**
58
63
  * ์‹œ๊ฐ์ถ•์„ ๊ตฌํ•  ๋•Œ ํ•œ ๋ฒˆ์— ๋„์šฐ๋Š” ํŠธ์œˆ ์ˆ˜.
@@ -95,11 +100,11 @@ function parseTime(v) {
95
100
  * ์ข…๋ฅ˜ ๋ฌธ์ž์—ด โ†’ ์ปค๋„. **๋ชจ๋ฅด๋Š” ๊ฐ’์ด๋ฉด ์˜ค๋ฅ˜๋ฅผ ๋‚ธ๋‹ค.**
96
101
  *
97
102
  * ์˜ˆ์ „์—๋Š” ํ‘œ๋ฅผ ์ฐพ๊ณ  ์—†์œผ๋ฉด WmsKernel ๋กœ ๋–จ์–ด์กŒ๋‹ค. `kind` ๋Š” ๊ฒ€์ฆ ์—†๋Š” ์ž์œ  ๋ฌธ์ž์—ด(`@Arg('kind') kind: string`)
98
- * ์ด๋ผ ์˜คํƒ€ ํ•˜๋‚˜ยท๋Œ€์†Œ๋ฌธ์ž ํ•˜๋‚˜๋กœ ์•ผ๋“œ/์ƒ์‚ฐ ํŠธ์œˆ์ด **์กฐ์šฉํžˆ ์ฐฝ๊ณ  ์ปค๋„๋กœ ๋Œ์•˜๋‹ค.** ์˜ค๋ฅ˜๊ฐ€ ์—†์œผ๋‹ˆ ํ™”๋ฉด์—๋Š”
103
+ * ์ด๋ผ ์˜คํƒ€ ํ•˜๋‚˜ยท๋Œ€์†Œ๋ฌธ์ž ํ•˜๋‚˜๋กœ ์•ผ๋“œ/์ƒ์‚ฐ ํŠธ์œˆ์ด **์˜ค๋ฅ˜ ์—†์ด ์ฐฝ๊ณ  ์ปค๋„๋กœ ๋Œ์•˜๋‹ค.** ์˜ค๋ฅ˜๊ฐ€ ์—†์œผ๋‹ˆ ํ™”๋ฉด์—๋Š”
99
104
  * ํŠธ์œˆ์ด ์ •์ƒ์œผ๋กœ ๋ณด์ด๊ณ , ์•ˆ์—์„œ ๋„๋Š” ๊ทœ์น™๋งŒ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์˜ ๊ฒƒ์ด๋‹ค. ์˜ˆ์ธก ๊ฒฝ๋กœ๊ฐ€ ๊ฐ€์žฅ ๋‚˜์˜๋‹ค โ€”
100
105
  * ์•ผ๋“œ์˜ ๋ฏธ๋ž˜๋ฅผ ์ฐฝ๊ณ  ๊ทœ์น™์œผ๋กœ ์‹คํ–‰ํ•ด ๋†“๊ณ  ์ˆซ์ž๋งŒ ๋œฌ๋‹ค.
101
106
  *
102
- * ํ‹€๋ฆฐ ๊ณต์žฅ์„ ์กฐ์šฉํžˆ ๋„์šฐ๋Š” ๊ฒƒ๋ณด๋‹ค ๋œจ์ง€ ์•Š๋Š” ํŽธ์ด ๋‚ซ๋‹ค.
107
+ * ํ‹€๋ฆฐ ๊ณต์žฅ์„ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋„์šฐ๋Š” ๊ฒƒ๋ณด๋‹ค ๋œจ์ง€ ์•Š๋Š” ํŽธ์ด ๋‚ซ๋‹ค.
103
108
  */
104
109
  function kernelFor(kind) {
105
110
  const K = KERNELS[kind];
@@ -113,16 +118,34 @@ let observeUnavailableWarned = false;
113
118
  * **์ด ์ปค๋„์˜ ์ง„์‹ค์ด ์›๋ณธ์—์„œ ์˜จ๋‹ค๊ณ  ์„ ์–ธํ•œ๋‹ค** โ€” ๊ทธ๋ฆฌ๊ณ  ์„ ์–ธ์ด ๋‹ฟ์ง€ ์•Š์•˜์œผ๋ฉด ๋งํ•œ๋‹ค.
114
119
  *
115
120
  * โ”€โ”€ ๋ฌด์—‡์ด ํ‹€๋ ธ๋‚˜ (2026-08-21) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
116
- * ์˜ˆ์ „์—๋Š” `kernel.observe?.()` ์˜€๋‹ค. ์˜ต์…”๋„ ํ˜ธ์ถœ์ด๋ผ **๋ฉ”์„œ๋“œ๊ฐ€ ์—†์œผ๋ฉด ์กฐ์šฉํžˆ ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๋Š”๋‹ค.**
121
+ * ์˜ˆ์ „์—๋Š” `kernel.observe?.()` ์˜€๋‹ค. ์˜ต์…”๋„ ํ˜ธ์ถœ์ด๋ผ **๋ฉ”์„œ๋“œ๊ฐ€ ์—†์œผ๋ฉด ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๋Š”๋‹ค.**
117
122
  * ์„ค์น˜๋œ ์ปค๋„ 0.7.39 ์—๋Š” ๊ทธ ๋ฉ”์„œ๋“œ๊ฐ€ ์—†์œผ๋ฏ€๋กœ, ์ง€๊ธˆ ๋ฐฐํฌ๋ณธ์—์„œ๋Š” ๋ฏธ๋Ÿฌ๊ฐ€ ๊ด€์ธก ๊ตฌ๋™์œผ๋กœ ์„ ์–ธ๋˜์ง€
118
123
  * ์•Š๋Š”๋‹ค โ€” ๊ทธ๋Ÿฐ๋ฐ ์ฝ”๋“œ๋ฅผ ์ฝ์œผ๋ฉด ์„ ์–ธํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค.
119
124
  *
120
125
  * ๊ทธ ์ฐจ์ด๊ฐ€ ์‹ค์ œ ๊ฑฐ๋™์„ ๊ฐ€๋ฅธ๋‹ค: ๊ด€์ธก ๊ตฌ๋™์€ ์›๋ณธ์˜ ๋นˆํ‹ˆ์„ ๋ฐ›์•„๋“ค์ด๊ณ  ์„ธ์ง€๋งŒ, ์‹œ๋ฎฌ๋กœ ์˜ค์ธ๋œ ๋ฏธ๋Ÿฌ๋Š”
121
- * **๋ฉˆ์ถ˜๋‹ค.** ์ฆ‰ ใ€Œ๊ด€์šฉ์ด ์ผœ์ง„ ์ค„ ์•Œ์•˜๋Š”๋ฐ ์•ˆ ์ผœ์ ธ ์žˆ๋‹คใ€๊ฐ€ ์กฐ์šฉํžˆ ์ง€๋‚˜๊ฐ€๊ณ , ์žฅ์• ๊ฐ€ ๋‚ฌ์„ ๋•Œ ์›์ธ์ด
126
+ * **๋ฉˆ์ถ˜๋‹ค.** ์ฆ‰ ใ€Œ๊ด€์šฉ์ด ์ผœ์ง„ ์ค„ ์•Œ์•˜๋Š”๋ฐ ์•ˆ ์ผœ์ ธ ์žˆ๋‹คใ€๊ฐ€ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์ง€๋‚˜๊ฐ€๊ณ , ์žฅ์• ๊ฐ€ ๋‚ฌ์„ ๋•Œ ์›์ธ์ด
122
127
  * ์ปค๋„์— ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค โ€” ์›์ธ์€ ๋ฒ„์ „์ด๋‹ค.
123
128
  *
124
129
  * ๊ทธ๋ž˜์„œ ์˜ต์…”๋„ ํ˜ธ์ถœ์„ ์—†์• ๊ณ , ์—†์œผ๋ฉด **ํ•œ ๋ฒˆ ๊ฒฝ๊ณ ํ•œ๋‹ค.** ์ปค๋„์ด ๋ฐฐํฌ๋˜๋ฉด ์ด ๊ฒฝ๊ณ ๋Š” ์‚ฌ๋ผ์ง„๋‹ค.
125
130
  */
131
+ /**
132
+ * ์ €์žฅ๋œ ๊ธฐ๋ก์„ **๋„๋Š” ๋™์•ˆ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋กœ** ์ฒ˜๋ฆฌํ•˜๊ฒŒ ํ•œ๋‹ค (2026-08-27).
133
+ *
134
+ * โ”€โ”€ ๋ฌด์—‡์ด ํ‹€๋ ธ๋‚˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
135
+ * ๋„๋Š” ๋™์•ˆ ์‚ฌ๊ฑด์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์€ ๋„๋ฉ”์ธ ์ปค๋„์ด๋‹ค(`startLive` ๊ฐ€ `new Kernel(...)` ์„ ์„ธ์šด๋‹ค). ๊ทธ๋Ÿฐ๋ฐ
136
+ * ์ €์žฅ๋œ ๊ธฐ๋ก์œผ๋กœ ๊ณผ๊ฑฐ ์‹œ์  ์ƒํƒœ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒฝ๋กœ๋Š” ์ปค๋„ ์ข…๋ฅ˜๋ฅผ ๋„˜๊ธฐ์ง€ ์•Š์•„์„œ ๋„๋ฉ”์ธ ๊ทœ์น™ ์—†์ด
137
+ * ์ฒ˜๋ฆฌํ–ˆ๋‹ค. ๊ทธ ๊ฒฐ๊ณผ ใ€Œ์–ด์ œ ์˜คํ›„ 3์‹œ์— ์ด ๋ฐœ์ „์†Œ๊ฐ€ ์–ด๋• ๋‚˜ใ€๋ฅผ ๋ฌผ์œผ๋ฉด ๋ฐœ์ „๋Ÿ‰๊ณผ ๊ณ„๋Ÿ‰์ด ๋Š˜ ๋น„์–ด ์žˆ์—ˆ๋‹ค โ€”
138
+ * ๋ฐ›์•„์„œ ์ €์žฅ๊นŒ์ง€ ํ•ด ๋‘” ๊ฐ’์ธ๋ฐ ์ฝ๋Š” ์ชฝ์ด ๊ฑด๋„ˆ๋›ด ๊ฒƒ์ด๋‹ค.
139
+ *
140
+ * ์ข…๋ฅ˜๋Š” ๋ชจ๋ธ์— ์—†๋‹ค(๋“ฑ๋ก๋ถ€๊ฐ€ ๋“ ๋‹ค). ๊ทธ๋ž˜์„œ ํ˜ธ์ŠคํŠธ๊ฐ€ ๋งํ•ด ์ค€๋‹ค.
141
+ */
142
+ function replayOptions(reg, domainId) {
143
+ return {
144
+ kind: reg?.kind,
145
+ productionSpec: reg?.model?.productionSpec,
146
+ tenantId: domainId
147
+ };
148
+ }
126
149
  function declareObserved(kernel, what) {
127
150
  if (typeof kernel?.observe === 'function') {
128
151
  kernel.observe();
@@ -158,7 +181,7 @@ class TwinEngine {
158
181
  *
159
182
  * ํŒ์ •์„ ์˜ˆ์‚ฐ(500ms)์ด ์•„๋‹ˆ๋ผ **๊ตถ๊น€ ๋ฌธํ„ฑ**์œผ๋กœ ๋”ฐ๋กœ ๋‘”๋‹ค: ์กฐ๊ธˆ ๋А๋ฆฐ ํŠธ์œˆ์€ ๊ณ„๊ธฐํŒ์ด ๋งํ•˜๊ฒŒ ๋‘๊ณ 
160
183
  * (๊ฒฝ๊ณ ), ํ˜ธ์ŠคํŠธ๋ฅผ ๊ตถ๊ธฐ๋Š” ํŠธ์œˆ๋งŒ ๋ฉˆ์ถ˜๋‹ค. ํ•œ ๋ฒˆ์œผ๋กœ ๋ฉˆ์ถ”์ง€ ์•Š๋Š”๋‹ค โ€” ์›œ์Šคํƒ€ํŠธ ์งํ›„์˜ ์ฒซ ํ‹ฑ์€ ์›๋ž˜
161
- * ๋ฌด๊ฒ๋‹ค(๋ณต๊ตฌํ•œ ์ƒํƒœ๋ฅผ ์ฒ˜์Œ ์ ‘๋Š”๋‹ค). **์—ฐ์†**์œผ๋กœ ์ด์–ด์งˆ ๋•Œ๊ฐ€ ๊ตฌ์กฐ์ ์œผ๋กœ ๋А๋ฆฐ ๊ฒƒ์ด๋‹ค.
184
+ * ๋ฌด๊ฒ๋‹ค(๋ณต๊ตฌํ•œ ์ƒํƒœ๋ฅผ ์ฒ˜์Œ ๊ณ„์‚ฐํ•œ๋‹ค). **์—ฐ์†**์œผ๋กœ ์ด์–ด์งˆ ๋•Œ๊ฐ€ ๊ตฌ์กฐ์ ์œผ๋กœ ๋А๋ฆฐ ๊ฒƒ์ด๋‹ค.
162
185
  */
163
186
  /*
164
187
  * โ”€โ”€ ํ‹ฑ์„ **ํ•œ ์ˆœ๊ฐ„์— ๋ชฐ์ง€ ์•Š๋Š”๋‹ค** (2026-08-21 ์‹ค์ธก) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -176,7 +199,7 @@ class TwinEngine {
176
199
  /**
177
200
  * ํฉ์–ด์ง„ ์ฒซ ๋ฐœํ™” ๋’ค ์ฃผ๊ธฐ ํ‹ฑ. **ํ•ธ๋“ค์€ ํ•ญ์ƒ ์ง„์งœ ํƒ€์ด๋จธ**๋‹ค โ€” ์ •์ง€ํ•˜๋Š” ์ชฝ์ด `clearInterval(inst.timer)`
178
201
  * ํ•˜๋‚˜๋กœ ๋๋‚ด์•ผ ํ•˜๋ฏ€๋กœ, ์ฒซ ๋ฐœํ™” ์ „์—๋Š” ๊ทธ `setTimeout` ์„, ์ดํ›„์—๋Š” `setInterval` ์„ ๊ฐ™์€ ์ž๋ฆฌ์— ๋‘”๋‹ค
179
- * (๊ฐ์‹ผ ๊ฐ์ฒด๋ฅผ ์ฃผ๋ฉด `clearInterval` ์ด ์•„๋ฌด ์ผ๋„ ํ•˜์ง€ ์•Š๊ณ  ํŠธ์œˆ์ด ๋ฉˆ์ถ”์ง€ ์•Š๋Š”๋‹ค โ€” ์กฐ์šฉํ•œ ๊ฒฐํ•จ์ด ๋œ๋‹ค).
202
+ * (๊ฐ์‹ผ ๊ฐ์ฒด๋ฅผ ์ฃผ๋ฉด `clearInterval` ์ด ์•„๋ฌด ์ผ๋„ ํ•˜์ง€ ์•Š๊ณ  ํŠธ์œˆ์ด ๋ฉˆ์ถ”์ง€ ์•Š๋Š”๋‹ค โ€” ๋“ค์–ด์˜จ ๊ฒƒ์ด ์—†๋Š” ๊ฒฐํ•จ์ด ๋œ๋‹ค).
180
203
  */
181
204
  static startTickTimer(fn, hold) {
182
205
  const spread = Math.max(1, Math.round(this.TICK_MS / 20));
@@ -207,7 +230,7 @@ class TwinEngine {
207
230
  * ์ €๋„์€ ๊ทธ๋Œ€๋กœ ์ง„์‹ค(์‹œ๊ฐ„์—ฌํ–‰/history replay ๋ฌด๋ณ€๊ฒฝ) โ€” ์บ์‹œ๋Š” display-only ์›œ์Šคํƒ€ํŠธ ์ตœ์ ํ™”. */
208
231
  static { this.SNAPSHOT_CACHE_ID = 'twin-snapshot'; }
209
232
  /*
210
- * โ”€โ”€ ์žฌ๊ฐœ์  **์‚ฌ์Šฌ** โ€” ๊ณผ๊ฑฐ๋ฅผ ๋ฌผ์—ˆ์„ ๋•Œ ๋ชฉํ‘œ ์ง์ „์—์„œ ์ ‘๊ธฐ ์œ„ํ•ด (2026-08-18) โ”€โ”€
233
+ * โ”€โ”€ ์žฌ๊ฐœ์  **์‚ฌ์Šฌ** โ€” ๊ณผ๊ฑฐ๋ฅผ ๋ฌผ์—ˆ์„ ๋•Œ ๋ชฉํ‘œ ์ง์ „์—์„œ ๊ณ„์‚ฐ ์œ„ํ•ด (2026-08-18) โ”€โ”€
211
234
  *
212
235
  * ์ตœ์‹  ์žฌ๊ฐœ์  ํ•˜๋‚˜๋กœ๋Š” ์‹œ๊ฐ„์—ฌํ–‰์„ ๋„์šธ ์ˆ˜ ์—†๋‹ค: ๊ทธ๊ฒƒ์€ ์–ธ์ œ๋‚˜ ๋ชฉํ‘œ๋ณด๋‹ค **๋’ค**์— ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ง€์ ์„
213
236
  * ์—ฌ๋Ÿฌ ๊ฐœ ๋‚จ๊ธด๋‹ค. ๋‹ค๋งŒ ๊ทธ๊ฒƒ๋“ค์€ ๊ฐ๊ฐ ์ƒํƒœ ์ „์ฒด๋ฅผ ๋“ค๊ณ  ์žˆ์–ด ๋ฌด๊ฒ๋‹ค โ€” ํ•œ ํ–‰์— ๋ชฐ์•„ ๋„ฃ์œผ๋ฉด ๊ฑฐ๋Œ€ํ•œ
@@ -215,13 +238,20 @@ class TwinEngine {
215
238
  *
216
239
  * ๊ฐ„๊ฒฉ์€ ๋ฆฌ๋น„์ „ ๋ˆˆ๊ธˆ์œผ๋กœ ์žก๋Š”๋‹ค(`CHAIN_STRIDE`): ๋ˆˆ๊ธˆ์„ ๋„˜์„ ๋•Œ๋งŒ ํ•œ ์ง€์ ์„ ๋‚จ๊ธฐ๋ฏ€๋กœ, ์ €๋„์ด ๋น ๋ฅด๊ฒŒ
217
240
  * ์ž๋ผ๋Š” ํŠธ์œˆ์—์„œ๋„ ์ง€์  ์ˆ˜๊ฐ€ ํญ๋ฐœํ•˜์ง€ ์•Š๋Š”๋‹ค. ์˜ค๋ž˜๋œ ๊ฒƒ๋ถ€ํ„ฐ ๋ฒ„๋ฆฌ๊ณ  ์ตœ๊ทผ `CHAIN_KEEP` ๊ฐœ๋งŒ ๋“ ๋‹ค โ€”
218
- * ๊ณผ๊ฑฐ๋กœ ๊นŠ์ด ๊ฐˆ์ˆ˜๋ก ์ง€์ ์ด ์—†์–ด 0๋ถ€ํ„ฐ ์ ‘๋Š” ๊ฒƒ์€ **์•Œ๋ ค์ง„ ํ•œ๊ณ„**๋‹ค(๋ฌดํ•œ ๋ณด๊ด€๋ณด๋‹ค ์ •์งํ•˜๋‹ค).
241
+ * ๊ณผ๊ฑฐ๋กœ ๊นŠ์ด ๊ฐˆ์ˆ˜๋ก ์ง€์ ์ด ์—†์–ด 0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•˜๋Š” ๊ฒƒ์€ **์•Œ๋ ค์ง„ ํ•œ๊ณ„**๋‹ค(๋ฌดํ•œ ๋ณด๊ด€๋ณด๋‹ค ์ •์งํ•˜๋‹ค).
219
242
  */
220
243
  static { this.CHAIN_CACHE_ID = 'twin-fold-chain'; }
221
244
  static { this.CHAIN_INDEX_CACHE_ID = 'twin-fold-chain-index'; }
222
245
  static { this.CHAIN_STRIDE = 5000; } // ๋ฆฌ๋น„์ „ ๋ˆˆ๊ธˆ โ€” ์ด ๊ฐ„๊ฒฉ์„ ๋„˜์„ ๋•Œ๋งŒ ํ•œ ์ง€์ ์„ ๋‚จ๊ธด๋‹ค
223
- static { this.CHAIN_KEEP = 5; } // ์ตœ๊ทผ ๋ช‡ ์ง€์ ์„ ๋“ค๊ณ  ์žˆ๋‚˜(๊ทธ๋ณด๋‹ค ๊ณผ๊ฑฐ๋Š” 0๋ถ€ํ„ฐ ์ ‘๋Š”๋‹ค)
246
+ static { this.CHAIN_KEEP = 5; } // ์ตœ๊ทผ ๋ช‡ ์ง€์ ์„ ๋“ค๊ณ  ์žˆ๋‚˜(๊ทธ๋ณด๋‹ค ๊ณผ๊ฑฐ๋Š” 0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•œ๋‹ค)
224
247
  static { this.SNAPSHOT_TTL_S = 7 * 24 * 3600; } // 7์ผ โ€” ์ •์ƒ ๋‹ค์šดํƒ€์ž„ ์ƒ์กด, ๋งŒ๋ฃŒ ์‹œ ์ €๋„ replay ํด๋ฐฑ
248
+ /**
249
+ * ๋ฉˆ์ถ˜ ํŠธ์œˆ์˜ ๋งˆ์ง€๋ง‰ ์ƒํƒœ๋ฅผ ๋‘๋Š” ๊ธฐ๊ฐ„ โ€” ์‚ฌ์‹ค์ƒ ๋งŒ๋ฃŒ ์—†์Œ (2026-08-27).
250
+ *
251
+ * ์บ์‹œ ๊ณ„์ธต์— ใ€Œ๋งŒ๋ฃŒ ์—†์Œใ€์„ ๋œปํ•˜๋Š” ๊ฐ’์ด ์—†์–ด์„œ ํฐ ์ˆ˜๋ฅผ ์“ด๋‹ค(`ICacheService` ๋Š” ์ดˆ ๋‹จ์œ„ TTL ํ•˜๋‚˜๋งŒ
252
+ * ๋ฐ›๋Š”๋‹ค). 100๋…„์ด๋ฉด ์ด ์‹œ์Šคํ…œ์˜ ์ˆ˜๋ช…๋ณด๋‹ค ๊ธธ๋‹ค.
253
+ */
254
+ static { this.SNAPSHOT_KEEP_S = 100 * 365 * 24 * 3600; }
225
255
  static { this.CHECKPOINT_MS = 20000; } // ์ฒดํฌํฌ์ธํŠธ ์ฃผ๊ธฐ(ํ•ซ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ๊ฒฝ๋กœ์™€ ๋ถ„๋ฆฌ, O(state) ์Šค๋กœํ‹€)
226
256
  /**
227
257
  * **์ €๋„ ๋ณด์กด โ€” ์ง€์šฐ๋Š” ๊ฒƒ์€ ์„ ์–ธ์ด ์žˆ์„ ๋•Œ๋งŒ ํ•œ๋‹ค.**
@@ -234,28 +264,39 @@ class TwinEngine {
234
264
  * โ”€โ”€ ๊ทธ๋Ÿฐ๋ฐ ์ €๋„์„ ์ง€์šฐ๋Š” ๊ฒƒ์€ ์‚ฌ์‹ค์„ ์žƒ๋Š” ์ผ์ด๋‹ค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
235
265
  * ๊ทธ๋ž˜์„œ ์„ธ ๊ทœ์œจ์„ ์ง€ํ‚จ๋‹ค.
236
266
  *
237
- * โ‘  **์„ ์–ธ์ด ์—†์œผ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ์ง€์šฐ์ง€ ์•Š๋Š”๋‹ค.** ๊ธฐ๋ณธ๊ฐ’์€ ์—†์Œ์ด๋‹ค โ€” ์กฐ์šฉํžˆ ์ง€์šฐ๋Š” ํŽธ์ด ์กฐ์šฉํžˆ ์Œ“๋Š”
267
+ * โ‘  **์„ ์–ธ์ด ์—†์œผ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ์ง€์šฐ์ง€ ์•Š๋Š”๋‹ค.** ๊ธฐ๋ณธ๊ฐ’์€ ์—†์Œ์ด๋‹ค โ€” ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์ง€์šฐ๋Š” ํŽธ์ด ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์Œ“๋Š”
238
268
  * ๊ฒƒ๋ณด๋‹ค ๋‚˜์˜๋‹ค. ์ง€์šฐ๋Š” ๊ฒƒ์€ ์‚ฌ๋žŒ์ด ์ •ํ•œ๋‹ค.
239
269
  * โ‘ก **์ฒดํฌํฌ์ธํŠธ๊ฐ€ ๋Œ€์‹ ํ•  ์ˆ˜ ์žˆ๋Š” ๋งŒํผ๋งŒ.** ์Šค๋ƒ…์ƒท์ด ์—†๊ฑฐ๋‚˜ ๊ทธ ๋ฆฌ๋น„์ „์„ ๋„˜๋Š” ์ž๋ฆฌ๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.
240
270
  * ์ฃผ์„์ด ใ€Œ๋งŒ๋ฃŒ ์‹œ ์ €๋„ replay ํด๋ฐฑใ€์ด๋ผ๊ณ  ์ ์–ด ๋‘” ๊ทธ๋Œ€๋กœ โ€” ์Šค๋ƒ…์ƒท์ด ์‚ฌ๋ผ์ง€๋ฉด ์ €๋„์ด **์œ ์ผํ•œ**
241
271
  * ๋ณต๊ตฌ ์ˆ˜๋‹จ์ด๋ฏ€๋กœ, ๋‘˜์„ ํ•จ๊ป˜ ์žƒ์œผ๋ฉด ๊ทธ ํŠธ์œˆ์˜ ์ƒํƒœ๋Š” ๋˜๋Œ๋ฆด ์ˆ˜ ์—†๋‹ค.
242
- * โ‘ข **์ง€์šด ๊ฒƒ์„ ๋งํ•œ๋‹ค.** ๋ช‡ ๊ฑด์„ ์–ด๋А ์‹œ๊ฐ๊นŒ์ง€ ์ง€์› ๋Š”์ง€ ๋กœ๊ทธ์— ๋‚จ๊ธด๋‹ค. ์กฐ์šฉํžˆ ์ค„์–ด๋“  ์ €๋„์€
272
+ * โ‘ข **์ง€์šด ๊ฒƒ์„ ๋งํ•œ๋‹ค.** ๋ช‡ ๊ฑด์„ ์–ด๋А ์‹œ๊ฐ๊นŒ์ง€ ์ง€์› ๋Š”์ง€ ๋กœ๊ทธ์— ๋‚จ๊ธด๋‹ค. ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์ค„์–ด๋“  ์ €๋„์€
243
273
  * ใ€Œ์—†์—ˆ๋˜ ์ผใ€๊ณผ ๊ตฌ๋ณ„๋˜์ง€ ์•Š๋Š”๋‹ค.
244
274
  *
245
275
  * ๊ทธ๋ฆฌ๊ณ  ๋ณด์กด ๊ธฐ๊ฐ„์€ **์Šค๋ƒ…์ƒท TTL(7์ผ)๋ณด๋‹ค ์งง์„ ์ˆ˜ ์—†๋‹ค.** ๋” ์งง์œผ๋ฉด ์Šค๋ƒ…์ƒท์ด ์‚ด์•„ ์žˆ๋Š”๋ฐ ๊ทธ๊ฒƒ์ด
246
- * ๊ฐ€๋ฆฌํ‚ค๋Š” ์•ž์ชฝ ์ €๋„์ด ์—†๋Š” ๊ตฌ๊ฐ„์ด ์ƒ๊ธฐ๊ณ , ์‹œ๊ฐ„์—ฌํ–‰ยท๊ณ„๋ณด ์ถ”์ ์ด ๊ทธ ๊ตฌ๊ฐ„์—์„œ ์กฐ์šฉํžˆ ๋นˆ๋‹ค.
276
+ * ๊ฐ€๋ฆฌํ‚ค๋Š” ์•ž์ชฝ ์ €๋„์ด ์—†๋Š” ๊ตฌ๊ฐ„์ด ์ƒ๊ธฐ๊ณ , ์‹œ๊ฐ„์—ฌํ–‰ยท๊ณ„๋ณด ์ถ”์ ์ด ๊ทธ ๊ตฌ๊ฐ„์—์„œ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋นˆ๋‹ค.
247
277
  */
248
278
  static { this.JOURNAL_RETENTION_DAYS = undefined; }
249
279
  static { this.RETENTION_SWEEP_MS = 10 * 60 * 1000; } // 10๋ถ„๋งˆ๋‹ค ํ•œ ๋ฒˆ โ€” ์ง€์šฐ๋Š” ์ผ์€ ๊ธ‰ํ•˜์ง€ ์•Š๋‹ค
250
280
  /** ์ตœ์‹  ์Šค๋ƒ…์ƒท์„ cache-service ์— ์ฒดํฌํฌ์ธํŠธ(๋„๋ฉ”์ธ+instanceId ํ‚ค). display-onlyยท๋น„์ฐจ๋‹จยท์˜ค๋ฅ˜ํก์ˆ˜. */
251
- static async persistSnapshot(domainId, instanceId) {
281
+ static async persistSnapshot(domainId, instanceId,
282
+ /**
283
+ * ๋งŒ๋ฃŒ๋ฅผ ๋‘์ง€ ์•Š๋Š”๋‹ค โ€” **ํŠธ์œˆ์„ ๋ฉˆ์ถœ ๋•Œ๋งŒ** ์ฐธ์ด๋‹ค (2026-08-27).
284
+ *
285
+ * ๋„๋Š” ํŠธ์œˆ์€ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋‹ค์‹œ ์“ฐ๋ฏ€๋กœ 7์ผ ๋งŒ๋ฃŒ๊ฐ€ ๊ฑธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ๋ฉˆ์ถ˜ ํŠธ์œˆ์€ ์•„๋ฌด๋„ ์“ฐ์ง€ ์•Š์•„์„œ
286
+ * 7์ผ ๋’ค ๋งŒ๋ฃŒ๋˜๊ณ , ๊ทธ๋Ÿฌ๋ฉด ๊ทธ ํŠธ์œˆ์˜ ๋งˆ์ง€๋ง‰ ์ƒํƒœ๋ฅผ ์•„๋Š” ๊ฒƒ์ด ์•„๋ฌด๊ฒƒ๋„ ์—†๋‹ค. ๋ฉˆ์ถ”๋Š” ์ˆœ๊ฐ„์ด ๊ทธ
287
+ * ์ƒํƒœ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์— ์žˆ๋Š” ๋งˆ์ง€๋ง‰ ์ˆœ๊ฐ„์ด๋ฏ€๋กœ ๊ทธ๋•Œ ๋งŒ๋ฃŒ ์—†์ด ์ ๋Š”๋‹ค.
288
+ *
289
+ * ๋งŒ๋ฃŒ๋œ ๋’ค์—๋„ ๊ทธ ํŠธ์œˆ์€ ๋ณด๊ด€๋œ ๊ตฌ๊ฐ„์—์„œ ๋‹ค์‹œ ์„ธ์›Œ์ง„๋‹ค(๋ณด๊ด€ ๊ธฐ๊ฐ„๋งŒํผ์ด๋ฏ€๋กœ ์œ ํ•œํ•˜๋‹ค). ๋‹ค๋งŒ ๊ทธ
290
+ * ๊ตฌ๊ฐ„ ์•ž์˜ ์ƒํƒœ๋Š” ์•Œ ์ˆ˜ ์—†๋‹ค. ๋ฉˆ์ถ”๋Š” ์ˆœ๊ฐ„์— ์ ์–ด ๋‘๋ฉด ๊ทธ ์†์‹ค์ด ์—†๋‹ค.
291
+ */
292
+ keepForever = false) {
252
293
  const inst = this.instances[(0, runtime_key_js_1.runtimeKey)(domainId, instanceId)];
253
294
  if (!inst)
254
295
  return;
255
296
  /*
256
297
  * **๋ด‰ํˆฌ๊ฐ€ ์•„๋‹ˆ๋ผ ์ƒํƒœ๋ฅผ ์ €์žฅํ•œ๋‹ค.** `snapshot()` ์€ ์‹œ๋ฎฌ์—์„œ `runtime.resync()` ๋ด‰ํˆฌ๋ฅผ ์ฃผ๋Š”๋ฐ,
257
298
  * ๊ทธ๊ฒƒ์„ ๋‹ค์‹œ `{revision, state}` ๋กœ ๊ฐ์‹ธ ๋„ฃ์–ด ์™”๋‹ค โ†’ ๊บผ๋‚ธ ๊ฐ’์— ์ถ•์ด ํ•˜๋‚˜๋„ ์—†์–ด ์›œ์Šคํƒ€ํŠธ๊ฐ€
258
- * ์กฐ์šฉํžˆ ๋„˜์–ด๊ฐ”๋‹ค(์ €๋„์— ์ˆ˜์ฒœ ๊ฑด์ด ์žˆ์–ด๋„ ํŠธ์œˆ์ด ๋นˆ ์ฑ„๋กœ ๋–ด๋‹ค).
299
+ * ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋„˜์–ด๊ฐ”๋‹ค(์ €๋„์— ์ˆ˜์ฒœ ๊ฑด์ด ์žˆ์–ด๋„ ํŠธ์œˆ์ด ๋นˆ ์ฑ„๋กœ ๋–ด๋‹ค).
259
300
  */
260
301
  const state = (0, warm_start_js_1.unwrapState)(this.snapshot(domainId, instanceId));
261
302
  if (!state)
@@ -278,7 +319,7 @@ class TwinEngine {
278
319
  *
279
320
  * ์›๋ณธ์ด ์‹ค์ œ๋กœ ใ€Œ๋‹ค ๋น„์—ˆ๋‹คใ€๊ณ  ๋งํ•œ ๊ฒฝ์šฐ๋Š” ๋ง‰์ง€ ์•Š๋Š”๋‹ค โ€” ๊ทธ๋•Œ๋Š” ์œ ์ž…์ด ์žˆ์—ˆ์œผ๋ฏ€๋กœ ์ด ๋ฌธ์„ ์ง€๋‚œ๋‹ค.
280
321
  *
281
- * ๊ทธ๋ฆฌ๊ณ  **๊ฑฐ์ ˆ์„ ๋งํ•œ๋‹ค**: ์กฐ์šฉํžˆ ๊ฑฐ์ ˆํ•˜๋ฉด ์™œ ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ๋‚ก์•„ ๊ฐ€๋Š”์ง€ ์•„๋ฌด๋„ ๋ชจ๋ฅธ๋‹ค.
322
+ * ๊ทธ๋ฆฌ๊ณ  **๊ฑฐ์ ˆ์„ ๋งํ•œ๋‹ค**: ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๊ฑฐ์ ˆํ•˜๋ฉด ์™œ ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ๋‚ก์•„ ๊ฐ€๋Š”์ง€ ์•„๋ฌด๋„ ๋ชจ๋ฅธ๋‹ค.
282
323
  */
283
324
  if (!(inst.metrics?.ingestedTotal > 0)) {
284
325
  const observed = (s) => (s?.items?.length ?? 0) + (s?.orders?.length ?? 0) + (s?.tasks?.length ?? 0);
@@ -297,52 +338,52 @@ class TwinEngine {
297
338
  /* ๊ตฌ์กฐ ๋ฆฌ๋น„์ „๋„ ํ•จ๊ป˜ โ€” ์ฝ๋Š” ์ชฝ์ด "์ด ์ƒํƒœ๊ฐ€ ์ง€๊ธˆ์˜ ๊ณต์žฅ์ธ๊ฐ€" ๋ฅผ ๊ฐ€๋ฆด ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค. */
298
339
  const { structureRev } = await this.tipOf(domainId, instanceId).catch(() => ({ structureRev: null }));
299
340
  /*
300
- * โ”€โ”€ **์ด์–ด ์ ‘์„ ์”จ์•—์„ ํ•จ๊ป˜ ์ ๋Š”๋‹ค** (2026-08-24) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
341
+ * โ”€โ”€ **์ด์–ด ๊ณ„์‚ฐํ•  ์”จ์•—์„ ํ•จ๊ป˜ ์ ๋Š”๋‹ค** (2026-08-24) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
301
342
  *
302
343
  * ์ด ํ•จ์ˆ˜๋Š” ์˜ค๋žซ๋™์•ˆ `{revision, state, structureRev}` ๋งŒ ์ ์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ **์žฌ๊ฐœ์ ์„ ์ฝ๋Š” ์ชฝ์€ ๋‹ค
303
- * ์žˆ๋Š”๋ฐ ์“ฐ๋Š” ์ชฝ์ด ์—†์—ˆ๋‹ค**: ์กฐํšŒ ๊ฒฝ๋กœ(`recover`)๋Š” `fold` ๊ฐ€ ์žˆ์œผ๋ฉด ๊ผฌ๋ฆฌ๋งŒ ์ ‘๊ณ , ์—†์œผ๋ฉด ์ €๋„์„
304
- * 0๋ถ€ํ„ฐ ์ ‘๋Š”๋‹ค. ์‹ค์ธก์œผ๋กœ ์ €์žฅ๋œ ์Šค๋ƒ…์ƒท 34๊ฑด ์ „๋ถ€ `fold` ๊ฐ€ ๋น„์–ด ์žˆ์—ˆ๊ณ , ์ €๋„์€ 2,960๋งŒ ์ค„์ด์—ˆ๋‹ค โ€”
305
- * ๊ทธ๋ž˜์„œ ์žฌ๊ธฐ๋™ยท์กฐํšŒ๋งˆ๋‹ค ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์ ‘์—ˆ๋‹ค. ๊ทœ๋ชจ ๊ธฐ์ค€(์—”ํ‹ฐํ‹ฐ 10๋งŒยทํ’ˆ๋ชฉ 100๋งŒ)์—์„œ ์ด๊ฒƒ์€
344
+ * ์žˆ๋Š”๋ฐ ์“ฐ๋Š” ์ชฝ์ด ์—†์—ˆ๋‹ค**: ์กฐํšŒ ๊ฒฝ๋กœ(`recover`)๋Š” `fold` ๊ฐ€ ์žˆ์œผ๋ฉด ๊ผฌ๋ฆฌ๋งŒ ๊ณ„์‚ฐํ•˜๊ณ , ์—†์œผ๋ฉด ์ €๋„์„
345
+ * 0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•œ๋‹ค. ์‹ค์ธก์œผ๋กœ ์ €์žฅ๋œ ์Šค๋ƒ…์ƒท 34๊ฑด ์ „๋ถ€ `fold` ๊ฐ€ ๋น„์–ด ์žˆ์—ˆ๊ณ , ์ €๋„์€ 2,960๋งŒ ์ค„์ด์—ˆ๋‹ค โ€”
346
+ * ๊ทธ๋ž˜์„œ ์žฌ๊ธฐ๋™ยท์กฐํšŒ๋งˆ๋‹ค ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ๊ณ„์‚ฐํ–ˆ๋‹ค. ๊ทœ๋ชจ ๊ธฐ์ค€(์—”ํ‹ฐํ‹ฐ 10๋งŒยทํ’ˆ๋ชฉ 100๋งŒ)์—์„œ ์ด๊ฒƒ์€
306
347
  * ๋А๋ฆฐ ๊ฒƒ์ด ์•„๋‹ˆ๋ผ **๋ชป ํ•˜๋Š” ๊ฒƒ**์ด๋‹ค.
307
348
  *
308
349
  * ์”จ์•—์€ ์ƒํƒœ๊ฐ€ ๋Œ€์‹ ํ•  ์ˆ˜ ์—†๋‹ค: ๋ฆฌ๋“€์„œ๋Š” ์†Œ๋น„์ฒ˜๊ฐ€ ๋ณด๋Š” ๊ฐ’ ๋ง๊ณ ๋„ ๋“ ๋‹ค(๋ถ€๋ชจ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋‹ด๊น€ยท์ง‘๊ณ„
309
- * ์ค‘์ธ ์ˆ˜๋Ÿ‰ยท๋‹ด์„ ์ค„ ๋ชฐ๋ผ ์„ธ์–ด ๋‘” ์‚ฌ๊ฑด). ์ƒํƒœ๋งŒ ๋˜๋Œ๋ฆฌ๊ณ  ๋’ค๋ฅผ ์ ‘์œผ๋ฉด 0๋ถ€ํ„ฐ ์ ‘์€ ๊ฒฐ๊ณผ์™€ **์กฐ์šฉํžˆ**
350
+ * ์ค‘์ธ ์ˆ˜๋Ÿ‰ยท๋‹ด์„ ์ค„ ๋ชฐ๋ผ ์„ธ์–ด ๋‘” ์‚ฌ๊ฑด). ์ƒํƒœ๋งŒ ๋˜๋Œ๋ฆฌ๊ณ  ๋’ค๋ฅผ ๊ณ„์‚ฐํ•˜๋ฉด 0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•œ ๊ฒฐ๊ณผ์™€ **์•Œ๋ฆฌ์ง€ ์•Š๊ณ **
310
351
  * ๋‹ฌ๋ผ์ง„๋‹ค. ๊ทธ ๋™์น˜๋Š” ์ปค๋„ ์‹œํ—˜์ด ์ฆ๋ช…ํ•œ๋‹ค(`observed-checkpoint.test.ts`).
311
352
  *
312
353
  * ๊ด€์ธก ๊ตฌ๋™์ด ์•„๋‹ˆ๋ฉด ์”จ์•—์ด ์—†๋‹ค โ€” ์‹œ๋ฎฌ์€ ๋ฆฌ๋“€์„œ๋ฅผ ๊ฐ–์ง€ ์•Š๊ณ , ๊ทธ ์ƒํƒœ์˜ ๊ถŒ์œ„๋Š” ์ปค๋„ ์ž์‹ ์ด๋‹ค.
313
- * ๊ทธ๋•Œ๋Š” `fold` ๋ฅผ **๋„ฃ์ง€ ์•Š๋Š”๋‹ค**(๋นˆ ์”จ์•—์„ ๋„ฃ์œผ๋ฉด ์ฝ๋Š” ์ชฝ์ด ใ€Œ์ด์–ด ์ ‘์„ ์ˆ˜ ์žˆ๋‹คใ€๊ณ  ์ž˜๋ชป ๋ณธ๋‹ค).
354
+ * ๊ทธ๋•Œ๋Š” `fold` ๋ฅผ **๋„ฃ์ง€ ์•Š๋Š”๋‹ค**(๋นˆ ์”จ์•—์„ ๋„ฃ์œผ๋ฉด ์ฝ๋Š” ์ชฝ์ด ใ€Œ์ด์–ด ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ๋‹คใ€๊ณ  ์ž˜๋ชป ๋ณธ๋‹ค).
314
355
  */
315
356
  const reducer = inst.kernel?.observedCheckpoint?.();
316
357
  const fold = reducer && inst.oee ? { reducer, oee: inst.oee.serialize() } : undefined;
317
- await cache_service_1.cacheService.setInCache(this.SNAPSHOT_CACHE_ID, { domainId, instanceId }, { revision, state, structureRev, ...(fold ? { fold } : {}) }, this.SNAPSHOT_TTL_S);
358
+ await cache_service_1.cacheService.setInCache(this.SNAPSHOT_CACHE_ID, { domainId, instanceId }, { revision, state, structureRev, ...(fold ? { fold } : {}) }, keepForever ? this.SNAPSHOT_KEEP_S : this.SNAPSHOT_TTL_S);
318
359
  }
319
360
  /**
320
- * ์ ‘๊ธฐ์˜ **์žฌ๊ฐœ์ ** โ€” ๋ฆฌ๋“€์„œ ๋‚ด๋ถ€ ์ƒํƒœ ์ „๋ถ€ + ๊ฐ€๋™ ๋ˆ„์ ๊ธฐ.
361
+ * ๊ณ„์‚ฐ์˜ **์žฌ๊ฐœ์ ** โ€” ๋ฆฌ๋“€์„œ ๋‚ด๋ถ€ ์ƒํƒœ ์ „๋ถ€ + ๊ฐ€๋™ ๋ˆ„์ ๊ธฐ.
321
362
  *
322
- * ์Šค๋ƒ…์ƒท(`state`)์€ ์†Œ๋น„์ฒ˜๊ฐ€ ๋ณด๋Š” ๊ฐ’์ด๋ผ ์ด์–ด ์ ‘๊ธฐ์˜ ์”จ์•—์ด ๋˜์ง€ ๋ชปํ•œ๋‹ค(๋ณด๋ฅ˜๋œ ๋‹ด๊น€ยท์ง‘ํ•ฉยท๋ฐ˜์˜ ๋ชป ํ•œ
323
- * ์‚ฌ๊ฑด ์ง‘๊ณ„๊ฐ€ ์—†๋‹ค โ€” ๊ทธ ์ƒํƒœ๋กœ ๋’ค๋ฅผ ์ ‘์œผ๋ฉด 0๋ถ€ํ„ฐ ์ ‘์€ ๊ฒฐ๊ณผ์™€ ์กฐ์šฉํžˆ ๋‹ฌ๋ผ์ง„๋‹ค). ๊ทธ๋ž˜์„œ ์”จ์•—์€ ๋”ฐ๋กœ ๋“ ๋‹ค.
363
+ * ์Šค๋ƒ…์ƒท(`state`)์€ ์†Œ๋น„์ฒ˜๊ฐ€ ๋ณด๋Š” ๊ฐ’์ด๋ผ ์ด์–ด ๊ณ„์‚ฐ์˜ ์”จ์•—์ด ๋˜์ง€ ๋ชปํ•œ๋‹ค(๋ณด๋ฅ˜๋œ ๋‹ด๊น€ยท์ง‘ํ•ฉยท๋ฐ˜์˜ ๋ชป ํ•œ
364
+ * ์‚ฌ๊ฑด ์ง‘๊ณ„๊ฐ€ ์—†๋‹ค โ€” ๊ทธ ์ƒํƒœ๋กœ ๋’ค๋ฅผ ๊ณ„์‚ฐํ•˜๋ฉด 0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•œ ๊ฒฐ๊ณผ์™€ ์˜ค๋ฅ˜ ์—†์ด ๋‹ฌ๋ผ์ง„๋‹ค). ๊ทธ๋ž˜์„œ ์”จ์•—์€ ๋”ฐ๋กœ ๋“ ๋‹ค.
324
365
  */
325
366
  static { this.FOLD_NOTE = 'reducer + oee checkpoint โ€” the seed for folding only the tail'; }
326
367
  /**
327
- * ์žฌ๊ธฐ๋™์— ์“ธ **์›œ์Šคํƒ€ํŠธ ์”จ์•—**์„ ๋งŒ๋“ ๋‹ค โ€” ์ƒํƒœ + ์ด์–ด ์ ‘์„ ์žฌ๊ฐœ์ .
368
+ * ์žฌ๊ธฐ๋™์— ์“ธ **์›œ์Šคํƒ€ํŠธ ์”จ์•—**์„ ๋งŒ๋“ ๋‹ค โ€” ์ƒํƒœ + ์ด์–ด ๊ณ„์‚ฐํ•  ์žฌ๊ฐœ์ .
328
369
  *
329
370
  * โ”€โ”€ ์™œ ์ด ์ž๋ฆฌ๊ฐ€ ์ƒ๊ฒผ๋‚˜ (2026-08-24) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
330
371
  * ๋‘ ํ˜ธ์ถœ๋ถ€๊ฐ€ ๊ฐ™์€ ์ผ์„ ์กฐ๊ธˆ์”ฉ ๋‹ค๋ฅด๊ฒŒ ํ•˜๊ณ  ์žˆ์—ˆ๊ณ (๊ฒนํฌ์žฅ์„ ํ•œ์ชฝ๋งŒ ๋ฒ—๊ฒผ๋‹ค), ๋‘˜ ๋‹ค **์žฌ๊ฐœ์ ์„ ๋ฒ„๋ฆฌ๊ณ **
331
372
  * ์ƒํƒœ๋งŒ ๋“ค๊ณ  ๊ฐ”๋‹ค. ๊ทธ๋ž˜์„œ ์ €์žฅ๋œ ์žฌ๊ฐœ์ ์„ ์ฝ๋Š” ์ชฝ์ด ๋‹ค ์žˆ๋Š”๋ฐ๋„ ์žฌ๊ธฐ๋™์€ ๋งค๋ฒˆ ์ €๋„์„ ์ฒ˜์Œ๋ถ€ํ„ฐ
332
- * ์ ‘์—ˆ๋‹ค(์‹ค์ธก: ์ €๋„ 2,960๋งŒ ์ค„).
373
+ * ๊ณ„์‚ฐํ–ˆ๋‹ค(์‹ค์ธก: ์ €๋„ 2,960๋งŒ ์ค„).
333
374
  *
334
375
  * ์—ฌ๊ธฐ์„œ ํ•˜๋Š” ์ผ ์…‹:
335
376
  * โ‘  ๊ฒนํฌ์žฅ์„ ๋ฒ—๊ธด๋‹ค โ€” ์˜› ํ˜•์‹์œผ๋กœ ์ €์žฅ๋œ ๊ฐ’์ด ํ•œ ๋ฒˆ์€ ๋ฐ˜๋“œ์‹œ ๋‚˜์˜จ๋‹ค
336
377
  * โ‘ก **๊ทธ ๊ณต์žฅ์ด ์•„์ง ๊ทธ ๊ณต์žฅ์ธ์ง€** ์‹ฌํŒํ•œ๋‹ค โ€” ์•„๋‹ˆ๋ฉด ์”จ์•—์„ ๋ฒ„๋ฆฐ๋‹ค(์•„๋ž˜)
337
- * โ‘ข ์”จ์•—์ด ์ €๋„ ๋๋ณด๋‹ค ์•ž์„œ ์žˆ์œผ๋ฉด **๊ทธ ๊ผฌ๋ฆฌ๋งŒ ์ ‘์–ด** ๋๊นŒ์ง€ ๋ฐ€์–ด ๋‘”๋‹ค
378
+ * โ‘ข ์”จ์•—์ด ์ €๋„ ๋๋ณด๋‹ค ์•ž์„œ ์žˆ์œผ๋ฉด **๊ทธ ๊ผฌ๋ฆฌ๋งŒ ๊ณ„์‚ฐํ•ด** ๋๊นŒ์ง€ ๋ฐ€์–ด ๋‘”๋‹ค
338
379
  *
339
380
  * โ‘ก๊ฐ€ ํ•„์š”ํ•œ ์ด์œ : ์žฌ๊ฐœ์ ์€ ๊ทธ๋•Œ์˜ ๋ณด๋“œ ์œ„์—์„œ ๋งŒ๋“ค์–ด์ง„ ๊ฒƒ์ด๋‹ค. ๊ทธ ๋’ค ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ์—ˆ๋‹ค๋ฉด(์ž๋ฆฌ๊ฐ€
340
381
  * ๋น ์กŒ๋‹คยท์„ค๋น„๊ฐ€ ์˜ฎ๊ฒจ์กŒ๋‹ค) ๋˜์„ธ์šด ๋ฆฌ๋“€์„œ๋Š” **์ง€๊ธˆ ์—†๋Š” ์ž๋ฆฌ์™€ ์„ค๋น„๋ฅผ ๋“ ๋‹ค** โ€” ์—†๋Š” ๋ƒ‰์žฅ์‹ค์ด ํ™”๋ฉด์—
341
382
  * ๋‚˜์˜ค๊ณ  ๊ทธ ์ž๋ฆฌ์˜ ํŒ์ •์ด ๊ณ„์† ๋Œ์•„๊ฐ„๋‹ค. ์˜ค๋ฅ˜ ์—†์ด ํ‹€๋ฆฌ๋ฏ€๋กœ ๋ˆˆ์— ๋„์ง€ ์•Š๋Š”๋‹ค.
342
383
  *
343
384
  * โ‘ข์ด ํ•„์š”ํ•œ ์ด์œ : ์Šค๋ƒ…์ƒท์€ ์ฒดํฌํฌ์ธํŠธ ์ฃผ๊ธฐ๋กœ ์“ฐ์ด๋ฏ€๋กœ ๋งˆ์ง€๋ง‰ ์ฃผ๊ธฐ ์ดํ›„์˜ ์‚ฌ์‹ค์€ ์ €๋„์—๋งŒ ์žˆ๋‹ค.
344
- * ๊ทธ๊ฒƒ์„ ๋นผ๊ณ  ๋˜์„ธ์šฐ๋ฉด ๊ทธ๋งŒํผ์ด ์กฐ์šฉํžˆ ์‚ฌ๋ผ์ง„๋‹ค โ€” ใ€Œ๋ชจ๋ฆ„ใ€์„ ใ€Œ์—†์Œใ€์œผ๋กœ ์ ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ถ€๋ฅ˜๋‹ค.
345
- * ์ ‘๋Š” ๊ตฌ๊ฐ„์€ **๊ทธ ํ‹ˆ๋ฟ**์ด๊ณ (์ €๋„ ์ „์ฒด๊ฐ€ ์•„๋‹ˆ๋‹ค), `recover` ๊ฐ€ ๊ทธ ์ž๋ฆฌ์—์„œ ์ƒˆ ์žฌ๊ฐœ์ ์„ ๋‚จ๊ฒจ ์ค€๋‹ค.
385
+ * ๊ทธ๊ฒƒ์„ ๋นผ๊ณ  ๋˜์„ธ์šฐ๋ฉด ๊ทธ๋งŒํผ์ด ์˜ค๋ฅ˜ ์—†์ด ์‚ฌ๋ผ์ง„๋‹ค โ€” ใ€Œ๋ชจ๋ฆ„ใ€์„ ใ€Œ์—†์Œใ€์œผ๋กœ ์ ๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ถ€๋ฅ˜๋‹ค.
386
+ * ๊ณ„์‚ฐํ•˜๋Š” ๊ตฌ๊ฐ„์€ **๊ทธ ํ‹ˆ๋ฟ**์ด๊ณ (์ €๋„ ์ „์ฒด๊ฐ€ ์•„๋‹ˆ๋‹ค), `recover` ๊ฐ€ ๊ทธ ์ž๋ฆฌ์—์„œ ์ƒˆ ์žฌ๊ฐœ์ ์„ ๋‚จ๊ฒจ ์ค€๋‹ค.
346
387
  */
347
388
  static async warmSeedFor(domainId, instanceId) {
348
389
  let cached = await this.loadSnapshot(domainId, instanceId).catch(() => null);
@@ -357,18 +398,43 @@ class TwinEngine {
357
398
  `at ${tip.structureRev} โ€” starting without it (the journal is folded from the beginning instead).`);
358
399
  return { revision: cached.revision, state: (0, warm_start_js_1.unwrapState)(cached.state) };
359
400
  }
360
- if (tip && (cached.revision ?? 0) < tip.revision) {
401
+ /*
402
+ * โ”€โ”€ **๋ฌป์ง€ ๋ชปํ–ˆ์œผ๋ฉด ๊ทธ ์‚ฌ์‹ค์„ ๋งํ•œ๋‹ค** (2026-08-28) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
403
+ *
404
+ * ์—ฌ๊ธฐ๊ฐ€ `if (tip && โ€ฆ)` ์ด์—ˆ๋‹ค. ์œ„์—์„œ `tipOf` ๊ฐ€ ์‹คํŒจํ•˜๋ฉด `null` ์ด ๋˜๊ณ , ๊ทธ๋Ÿฌ๋ฉด ์ด ๊ฐˆ๋ž˜๊ฐ€
405
+ * **์•„๋ฌด ๋ง ์—†์ด ๊ฑด๋„ˆ๋›ด๋‹ค** โ€” ์ €์žฅ๋ณธ ๋’ค์˜ ์‚ฌ๊ฑด์„ ๋ฐ˜์˜ํ•˜์ง€ ๋ชปํ•œ ์ฑ„ ๋œจ๊ณ , ๋กœ๊ทธ์— ์•„๋ฌด ํ”์ ์ด ์—†๋‹ค.
406
+ * ์‹ค์ธก(2026-08-28 10:34): ์ €์žฅ๋ณธ 3,372,228 ยท ์ €๋„ ๋จธ๋ฆฌ 3,374,894 ์ธ๋ฐ ๊ณ„์‚ฐํ–ˆ๋‹ค๋Š” ์ค„๋„, ๋ชป ํ–ˆ๋‹ค๋Š”
407
+ * ์ค„๋„ ์—†์—ˆ๋‹ค.
408
+ */
409
+ if (!tip) {
410
+ (0, log_js_1.twinWarn)(`[twin-engine] "${instanceId}": could not read the journal tip โ€” starting from the stored seed at revision ` +
411
+ `${cached.revision ?? 0} without folding what came after it. Some recent facts may be missing from state.`);
412
+ return { revision: cached.revision, ...seedOf(cached), state: (0, warm_start_js_1.unwrapState)(cached.state) };
413
+ }
414
+ if ((cached.revision ?? 0) < tip.revision) {
361
415
  /*
362
- * ํ‹ˆ์„ ์ ‘๋Š”๋‹ค โ€” `recover` ๊ฐ€ ์ด ์”จ์•—์œผ๋กœ **๊ผฌ๋ฆฌ๋งŒ** ์ ‘๊ณ , ๋ ์ง€์ ์˜ ์ƒˆ ์žฌ๊ฐœ์ ์„ ๋‚จ๊ธด๋‹ค.
416
+ * ํ‹ˆ์„ ๊ณ„์‚ฐํ•œ๋‹ค โ€” `recover` ๊ฐ€ ์ด ์”จ์•—์œผ๋กœ **๊ผฌ๋ฆฌ๋งŒ** ๊ณ„์‚ฐํ•˜๊ณ , ๋ ์ง€์ ์˜ ์ƒˆ ์žฌ๊ฐœ์ ์„ ๋‚จ๊ธด๋‹ค.
363
417
  * ์•„์ง ์ด ํŠธ์œˆ์˜ ๋Ÿฐํƒ€์ž„์ด ์—†์œผ๋ฏ€๋กœ `recover` ๋Š” ๋ฉ”๋ชจ๋ฆฌ ๋Œ€์‹  ์ €๋„ ๊ฒฝ๋กœ๋ฅผ ํƒ„๋‹ค(๊ทธ๊ฒƒ์ด ์—ฌ๊ธฐ์˜ ์ „์ œ๋‹ค).
364
418
  */
365
419
  const gap = tip.revision - (cached.revision ?? 0);
366
- await this.recover(domainId, instanceId).catch(err => (0, log_js_1.twinWarn)(`[twin-engine] "${instanceId}": could not fold the ${gap} event(s) after the checkpoint โ€” ${err?.message ?? err}`));
420
+ let failed;
421
+ await this.recover(domainId, instanceId).catch(err => {
422
+ failed = err?.message ?? String(err);
423
+ (0, log_js_1.twinWarn)(`[twin-engine] "${instanceId}": could not fold the ${gap} event(s) after the checkpoint โ€” ${failed}`);
424
+ });
367
425
  const advanced = await this.loadSnapshot(domainId, instanceId).catch(() => null);
368
426
  if (advanced?.state && (advanced.revision ?? 0) > (cached.revision ?? 0)) {
369
427
  (0, log_js_1.twinLog)(`[twin-engine] "${instanceId}": folded ${gap} event(s) after the checkpoint โ†’ revision ${advanced.revision}.`);
370
428
  cached = advanced;
371
429
  }
430
+ else if (!failed) {
431
+ /*
432
+ * **์•„๋ฌด ์ผ๋„ ํ•˜์ง€ ์•Š์€ ๊ฒƒ์„ ๋งํ•œ๋‹ค.** ๊ณ„์‚ฐ์ด ์˜ค๋ฅ˜ ์—†์ด ๋๋‚ฌ๋Š”๋ฐ ์žฌ๊ฐœ์ ์ด ๋‚˜์•„๊ฐ€์ง€ ์•Š์•˜๋‹ค๋Š” ๋œป์ด๊ณ ,
433
+ * ๊ทธ๋Ÿฌ๋ฉด ๊ทธ ์‚ฌ๊ฑด๋“ค์˜ ๊ฒฐ๊ณผ๊ฐ€ ์ƒํƒœ์— ์—†๋‹ค. ์˜ˆ์ „์—๋Š” ์ด ๊ฐˆ๋ž˜๊ฐ€ ์กฐ๊ฑด์— ๊ฑธ๋ฆฌ์ง€ ์•Š์•„ ์นจ๋ฌตํ–ˆ๋‹ค.
434
+ */
435
+ (0, log_js_1.twinWarn)(`[twin-engine] "${instanceId}": folding the ${gap} event(s) after the checkpoint left the seed at revision ` +
436
+ `${cached.revision ?? 0} โ€” state starts without those facts. The screens may show them as absent.`);
437
+ }
372
438
  }
373
439
  return { revision: cached.revision, state: (0, warm_start_js_1.unwrapState)(cached.state), ...seedOf(cached) };
374
440
  }
@@ -378,10 +444,10 @@ class TwinEngine {
378
444
  return entry?.value ?? null;
379
445
  }
380
446
  /**
381
- * ์ ‘์€ ์ƒํƒœ๋ฅผ ์Šค๋ƒ…์ƒท์œผ๋กœ ๋‚จ๊ธด๋‹ค โ€” **๋ผ์ด๋ธŒ๊ฐ€ ์•„๋‹ˆ์–ด๋„.**
447
+ * ๊ณ„์‚ฐํ•œ ์ƒํƒœ๋ฅผ ์Šค๋ƒ…์ƒท์œผ๋กœ ๋‚จ๊ธด๋‹ค โ€” **๋ผ์ด๋ธŒ๊ฐ€ ์•„๋‹ˆ์–ด๋„.**
382
448
  *
383
449
  * `persistSnapshot` ์€ ๊ธฐ๋™ ์ค‘์ธ ์ธ์Šคํ„ด์Šค์—์„œ๋งŒ ๋œฌ๋‹ค(๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์ง„์‹ค์ด๋ฏ€๋กœ ์˜ณ๋‹ค). ๊ทธ๋Ÿฐ๋ฐ **๋ฉˆ์ถ˜**
384
- * ํŠธ์œˆ์„ ์กฐํšŒํ•  ๋•Œ๋งˆ๋‹ค ์ €๋„์„ ์ „๋Ÿ‰ ๋‹ค์‹œ ์ ‘๊ณ  ์žˆ์—ˆ๋‹ค(23,731๊ฑด์งœ๋ฆฌ ํŠธ์œˆ์—์„œ 4.6์ดˆ). ๊ทธ ํด๋“œ์˜ ๊ฒฐ๊ณผ๋ฅผ
450
+ * ํŠธ์œˆ์„ ์กฐํšŒํ•  ๋•Œ๋งˆ๋‹ค ์ €๋„์„ ์ „๋Ÿ‰ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜๊ณ  ์žˆ์—ˆ๋‹ค(23,731๊ฑด์งœ๋ฆฌ ํŠธ์œˆ์—์„œ 4.6์ดˆ). ๊ทธ ํด๋“œ์˜ ๊ฒฐ๊ณผ๋ฅผ
385
451
  * ๋‚จ๊ฒจ ๋‘๋ฉด **์ฒ˜์Œ ํ•œ ๋ฒˆ๋งŒ ๋А๋ฆฌ๋‹ค.**
386
452
  *
387
453
  * `structureRev` ๋ฅผ ํ•จ๊ป˜ ์ ๋Š”๋‹ค: ์ด๋ฒคํŠธ๊ฐ€ ํ•˜๋‚˜๋„ ์•ˆ ๋Š˜์–ด๋„ ๊ตฌ์กฐ๋ฅผ ๊ฐˆ์•„์น˜์šฐ๋ฉด(์žฌํ”„๋กœ๋น„์ €๋‹) ๊ทธ
@@ -401,11 +467,11 @@ class TwinEngine {
401
467
  return Array.isArray(revs) ? revs.filter((r) => Number.isFinite(r)).sort((a, b) => a - b) : [];
402
468
  }
403
469
  /**
404
- * ๋ชฉํ‘œ ์ด์ „์˜ **๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์ง€์ **์„ ๊ณ ๋ฅธ๋‹ค โ€” ์—†์œผ๋ฉด `null`(0๋ถ€ํ„ฐ ์ ‘๋Š”๋‹ค).
470
+ * ๋ชฉํ‘œ ์ด์ „์˜ **๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์ง€์ **์„ ๊ณ ๋ฅธ๋‹ค โ€” ์—†์œผ๋ฉด `null`(0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•œ๋‹ค).
405
471
  *
406
472
  * ์‹œ๊ฐ์œผ๋กœ ๋ฌผ์—ˆ์œผ๋ฉด ๊ทธ ์ง€์ ์˜ ๋งˆ์ง€๋ง‰ ์‚ฌ์‹ค ์‹œ๊ฐ์ด ๋ชฉํ‘œ ์ด๋‚ด์—ฌ์•ผ ํ•œ๋‹ค(๋ฆฌ๋น„์ „๋งŒ ๋ณด๋ฉด ๋ชฉํ‘œ๋ณด๋‹ค ๋’ค์˜
407
473
  * ์‚ฌ์‹ค์ด ์”จ์•—์— ์„ž์ธ๋‹ค). ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€ ํŠธ์œˆ์—์„œ๋Š” ์“ฐ์ง€ ์•Š๋Š”๋‹ค โ€” ๋งˆ๋””๋ฅผ ๊ฑด๋„ˆ๋›ด ์”จ์•—์€ ๊ทธ ๊ฒฝ๊ณ„์˜
408
- * ํŒ์ •์„ ์žƒ๋Š”๋‹ค(๊ทธ ๊ฒฝ์šฐ๋Š” 0๋ถ€ํ„ฐ ์ ‘๋Š” ๊ฒƒ์ด ์˜ณ๋‹ค).
474
+ * ํŒ์ •์„ ์žƒ๋Š”๋‹ค(๊ทธ ๊ฒฝ์šฐ๋Š” 0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•˜๋Š” ๊ฒƒ์ด ์˜ณ๋‹ค).
409
475
  */
410
476
  static async chainSeedFor(domainId, instanceId, target) {
411
477
  const revs = await this.chainIndex(domainId, instanceId);
@@ -456,19 +522,49 @@ class TwinEngine {
456
522
  }
457
523
  /** ์ด ํŠธ์œˆ ์ €๋„์˜ ๋ ๋ฆฌ๋น„์ „ ยท ์ตœ์‹  ๊ตฌ์กฐ ๋ฆฌ๋น„์ „ โ€” ์Šค๋ƒ…์ƒท์ด ์ง€๊ธˆ์˜ ์‚ฌ์‹ค์ธ์ง€ ๊ฐ€๋ฆฌ๋Š” ๋‘ ๊ฐ’. */
458
524
  static async tipOf(domainId, instanceId) {
525
+ /*
526
+ * โ”€โ”€ **๊ด€๊ณ„๋กœ ๋ฌป์ง€ ์•Š๋Š”๋‹ค** (2026-08-28) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
527
+ *
528
+ * `findOne({ where: { domain: { id } } })` ๋Š” ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง„ ์—”ํ‹ฐํ‹ฐ์—์„œ ๋‘ ๋‹จ๊ณ„ ์งˆ์˜๊ฐ€ ๋˜๊ณ  **์•ˆ์ชฝ์—
529
+ * ์ƒํ•œ์ด ์—†๋‹ค** โ€” ์กฐ๊ฑด์— ๋งž๋Š” ํ–‰์„ ์ „๋ถ€ ๋งŒ๋“ค์–ด ๋†“๊ณ  ๋ฐ–์—์„œ ํ•œ ์ค„์„ ๊ณ ๋ฅธ๋‹ค. ์ด ํŒŒ์ผ์ด ๊ฐ™์€ ๊ฒƒ์„
530
+ * ์‹ค์ธกํ•ด ์ ์–ด ๋‘์—ˆ๋‹ค(ยง`twinEvents`: 0.024์ดˆ ๋Œ€ 12์ดˆ, ์ €๋„ 375๋งŒ ํ–‰).
531
+ *
532
+ * ์ด ํ•จ์ˆ˜๋Š” **๋ถ€ํŒ…๋งˆ๋‹ค ํŠธ์œˆ๋งˆ๋‹ค** ๋ถˆ๋ฆฐ๋‹ค. ์Šนํ™”ํ‘ธ๋“œ ์ €๋„์ด 337๋งŒ ํ–‰์ด๋ฏ€๋กœ ๊ทธ ํ•œ ๋ฒˆ์ด ๋ถ€ํŒ…์„
533
+ * ๋ถ™์žก๋Š”๋‹ค. ๊ด€๊ณ„๋ฅผ ์ปฌ๋Ÿผ์œผ๋กœ ๋ฌผ์œผ๋ฉด ๊ฐ์‹ธ์ง€ ์•Š๊ณ  ์ƒ‰์ธ(`domain, instance, revision`)์˜ ์ฒซ ์ค„๋กœ ๋๋‚œ๋‹ค.
534
+ */
459
535
  const [tip, newest] = await Promise.all([
460
- (0, shell_1.getRepository)(twin_event_js_1.TwinEvent).findOne({ where: { domain: { id: domainId }, instanceId }, order: { revision: 'DESC' } }),
461
- (0, shell_1.getRepository)(twin_structure_js_1.TwinStructure).findOne({ where: { domain: { id: domainId }, instanceId }, order: { rev: 'DESC' } })
536
+ (0, shell_1.getRepository)(twin_event_js_1.TwinEvent)
537
+ .createQueryBuilder('e')
538
+ .select('e.revision', 'revision')
539
+ .where('e.domain = :domainId', { domainId })
540
+ .andWhere('e.instanceId = :instanceId', { instanceId })
541
+ .orderBy('e.revision', 'DESC')
542
+ .limit(1)
543
+ .getRawOne(),
544
+ (0, shell_1.getRepository)(twin_structure_js_1.TwinStructure)
545
+ .createQueryBuilder('s')
546
+ .select('s.rev', 'rev')
547
+ .where('s.domain = :domainId', { domainId })
548
+ .andWhere('s.instanceId = :instanceId', { instanceId })
549
+ .orderBy('s.rev', 'DESC')
550
+ .limit(1)
551
+ .getRawOne()
462
552
  ]);
463
- return { revision: tip?.revision ?? 0, structureRev: newest?.rev ?? null };
553
+ const revision = Number(tip?.revision);
554
+ const structureRev = Number(newest?.rev);
555
+ return {
556
+ revision: Number.isFinite(revision) ? revision : 0,
557
+ structureRev: Number.isFinite(structureRev) ? structureRev : null
558
+ };
464
559
  }
465
560
  /**
466
- * **์ €๋„์„ ๋ณด์กด ๊ธฐ๊ฐ„๊นŒ์ง€๋งŒ ๋‘”๋‹ค** โ€” ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ๋Œ€์‹ ํ•  ์ˆ˜ ์žˆ๋Š” ๋งŒํผ๋งŒ ์ง€์šด๋‹ค.
561
+ * **์ €๋„์„ ๋ณด์กด ๊ธฐ๊ฐ„๊นŒ์ง€๋งŒ ๋‘”๋‹ค.**
467
562
  *
468
- * ํ•œ ์ธ์Šคํ„ด์Šค์—์„œ ์ง€์šฐ๋Š” ์กฐ๊ฑด์€ **๋‘˜ ๋‹ค** ๋งŒ์กฑํ•ด์•ผ ํ•œ๋‹ค.
563
+ * ์ง€์šฐ๋Š” ์กฐ๊ฑด์€ ํ•˜๋‚˜๋‹ค โ€” `createdAt` ์ด ๋ณด์กด ๊ธฐ๊ฐ„๋ณด๋‹ค ์˜ค๋ž˜๋๋‹ค(**ํ–‰์ด ์“ฐ์ธ ์‹ค์ œ ์‹œ๊ฐ**์ด ๊ธฐ์ค€์ด๋‹ค).
469
564
  *
470
- * ยท `createdAt` ์ด ๋ณด์กด ๊ธฐ๊ฐ„๋ณด๋‹ค ์˜ค๋ž˜๋๋‹ค โ€” **ํ–‰์ด ์“ฐ์ธ ์‹ค์ œ ์‹œ๊ฐ**์ด ๊ธฐ์ค€์ด๋‹ค
471
- * ยท `revision` ์ด **์ฒดํฌํฌ์ธํŠธ ๋ฆฌ๋น„์ „ ์ดํ•˜**๋‹ค โ€” ๊ทธ ์•ž์€ ์Šค๋ƒ…์ƒท์ด ๋Œ€์‹ ํ•œ๋‹ค
565
+ * **๋„๋Š” ํŠธ์œˆ**์—์„œ๋งŒ ์กฐ๊ฑด์ด ํ•˜๋‚˜ ๋” ๋ถ™๋Š”๋‹ค: `revision` ์ด ์ฒดํฌํฌ์ธํŠธ ๋ฆฌ๋น„์ „ ์ดํ•˜์—ฌ์•ผ ํ•œ๋‹ค. ๋„๋Š”
566
+ * ํŠธ์œˆ์€ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ด์–ด ์ง‘๊ณ„ํ•˜๋ฏ€๋กœ, ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ์•„์ง ๋ฐ˜์˜ํ•˜์ง€ ๋ชปํ•œ ์‚ฌ๊ฑด์„ ์ง€์šฐ๋ฉด ๊ทธ ๊ตฌ๊ฐ„์ด
567
+ * ์–ด๋””์—๋„ ์—†์–ด์ง„๋‹ค. ๋ฉˆ์ถ˜ ํŠธ์œˆ์—๋Š” ์ด์–ด ์ง‘๊ณ„ํ•  ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์—†์–ด์„œ ์ด ์กฐ๊ฑด์ด ์—†๋‹ค.
472
568
  *
473
569
  * โ”€โ”€ ์™œ `eventTime` ์ด ์•„๋‹ˆ๋ผ `createdAt` ์ธ๊ฐ€ (2026-08-22 ์‹ค์ธก์œผ๋กœ ๊ณ ์นจ) โ”€โ”€โ”€โ”€
474
570
  * ์ฒ˜์Œ์— `eventTime` ์œผ๋กœ ์ ์—ˆ๋‹ค. ๊ทธ๊ฒƒ์€ **ํŠธ์œˆ์˜ ์‹œ๊ณ„**๋‹ค โ€” ์‹œ๋ฎฌ๋ ˆ์ด์…˜์€ ์ž๊ธฐ ์‹œ๊ณ„๋กœ ์‚ฌ๊ฑด์„ ์ฐ๊ณ ,
@@ -483,9 +579,17 @@ class TwinEngine {
483
579
  * `createdAt` ์€ ๊ทธ ํ–‰์ด DB ์— ์“ฐ์ธ ์‹œ๊ฐ์ด๊ณ  1,599๋งŒ ํ–‰ ์ „๋ถ€ ์ฑ„์›Œ์ ธ ์žˆ๋‹ค(ํ™•์ธํ•จ). ๋„๋ฉ”์ธ ์‹œ๊ฐ์„
484
580
  * ์ •์ฑ…์— ์“ฐ์ง€ ์•Š๋Š”๋‹ค โ€” ๊ทธ ๋‘˜์„ ์„ž์œผ๋ฉด ์‹œ๋ฎฌ๊ณผ ๋ฏธ๋Ÿฌ์—์„œ ๊ฐ™์€ ์„ค์ •์ด ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.
485
581
  *
486
- * ์Šค๋ƒ…์ƒท์ด ์—†์œผ๋ฉด **๊ทธ ์ธ์Šคํ„ด์Šค๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.** ์ €๋„์ด ์œ ์ผํ•œ ๋ณต๊ตฌ ์ˆ˜๋‹จ์ธ ์ƒํƒœ์ด๋ฏ€๋กœ, ์ง€์šฐ๋ฉด
487
- * ๊ทธ ํŠธ์œˆ์˜ ์ƒํƒœ๋ฅผ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†๋‹ค. ใ€Œ์ง€์šธ ์ˆ˜ ์—†์—ˆ๋‹คใ€๋Š” ์‚ฌ์‹ค๋„ ํ•จ๊ป˜ ์„ผ๋‹ค โ€” ์กฐ์šฉํžˆ ๋„˜๊ธฐ๋ฉด ใ€Œ๋ณด์กด์ด
488
- * ๋„๋Š”๋ฐ ์™œ ์•ˆ ์ค„์–ด๋“œ๋‚˜ใ€๊ฐ€ ๋œ๋‹ค.
582
+ * โ”€โ”€ ์™œ ์ฒดํฌํฌ์ธํŠธ๋ฅผ ์ „์ œ ์กฐ๊ฑด์œผ๋กœ ๋‘์ง€ ์•Š๋‚˜ (2026-08-27) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
583
+ * ์˜ˆ์ „์—๋Š” ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ์—†์œผ๋ฉด ๊ทธ ํŠธ์œˆ์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์•˜๋‹ค. ใ€Œ์ €๋„์ด ์œ ์ผํ•œ ๋ณต๊ตฌ ์ˆ˜๋‹จใ€์ด๋ผ๋Š” ์ „์ œ
584
+ * ๋•Œ๋ฌธ์ด์—ˆ๊ณ , ๊ทธ ์ „์ œ๊ฐ€ ํ‹€๋ ธ๋‹ค. ํŠธ์œˆ์ด ์ž๊ธฐ ์ €๋„๊ณผ ์–ด๋–ค ๊ด€๊ณ„์ธ์ง€๋Š” `restartPolicy` ๊ฐ€ ์„ ์–ธํ•œ๋‹ค โ€”
585
+ * `resync` ๋Š” ์—ฐ๊ฒฐ๋œ ์‹œ์Šคํ…œ์—์„œ ๋‹ค์‹œ ์ฝ๊ณ , `reset` ์€ ์”จ์•—๋ถ€ํ„ฐ ๋Œ๋ฆฌ๊ณ , `resume` ์€ ๋ณด๊ด€๋œ ๊ตฌ๊ฐ„์—์„œ
586
+ * ์„ธ์šด๋‹ค. ์…‹ ๋‹ค ๋ณด๊ด€ ๊ธฐ๊ฐ„ ๋ฐ–์˜ ์‚ฌ๊ฑด์„ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.
587
+ *
588
+ * ๋ณด์กด ๊ธฐ๊ฐ„์„ 7์ผ๋กœ ์„ ์–ธํ•œ ๊ฒƒ์ด ๊ณง ใ€Œ7์ผ ์ „ ์ƒํƒœ๋กœ๋Š” ์„ธ์šฐ์ง€ ์•Š๋Š”๋‹คใ€๋Š” ๋œป์ด๋‹ค. ๊ทธ ์ „์ œ๋ฅผ ์ง€ํ‚ค๋А๋ผ
589
+ * ์ง€์šฐ์ง€ ์•Š์œผ๋ฉด, ๋ฉˆ์ถ˜ ํŠธ์œˆ์˜ ์ €๋„์ด ์˜๊ตฌํžˆ ๋‚จ๋Š”๋‹ค โ€” ์‹ค์ธก์œผ๋กœ ์„ธ ํŠธ์œˆ์— 2,900๋งŒ ํ–‰์ด ๊ทธ๋ ‡๊ฒŒ ๋‚จ์•˜๋‹ค.
590
+ *
591
+ * ใ€Œ์ง€์šธ ์ˆ˜ ์—†์—ˆ๋‹คใ€๋Š” ์‚ฌ์‹ค์€ ๊ทธ๋Œ€๋กœ ์„ผ๋‹ค(๋„๋Š”๋ฐ ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ์•„์ง ์—†๋Š” ๊ฒฝ์šฐ) โ€” ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋„˜๊ธฐ๋ฉด
592
+ * ใ€Œ๋ณด์กด์ด ๋„๋Š”๋ฐ ์™œ ์•ˆ ์ค„์–ด๋“œ๋‚˜ใ€๊ฐ€ ๋œ๋‹ค.
489
593
  *
490
594
  * `createdAt` ์ด ๋นˆ ์˜› ํ–‰์€ **์ง€์šฐ์ง€ ์•Š๋Š”๋‹ค**(์‹œ๊ฐ์„ ๋ชจ๋ฅด๋Š” ๊ฒƒ์„ ใ€Œ์˜ค๋ž˜๋๋‹คใ€๋กœ ์ฝ์ง€ ์•Š๋Š”๋‹ค).
491
595
  *
@@ -496,52 +600,193 @@ class TwinEngine {
496
600
  const perDomain = this.retentionDaysOf ? await this.retentionDaysOf(domainId).catch(() => undefined) : undefined;
497
601
  const days = perDomain ?? this.JOURNAL_RETENTION_DAYS;
498
602
  if (!days || days <= 0)
499
- return { deleted: 0, instances: 0, skipped: [] };
500
- /* ๋ณด์กด ๊ธฐ๊ฐ„์€ ์Šค๋ƒ…์ƒท TTL ๋ณด๋‹ค ์งง์„ ์ˆ˜ ์—†๋‹ค โ€” ์งง์œผ๋ฉด ์Šค๋ƒ…์ƒท์ด ๊ฐ€๋ฆฌํ‚ค๋Š” ์•ž์ชฝ์ด ๋น„๋Š” ๊ตฌ๊ฐ„์ด ์ƒ๊ธด๋‹ค. */
501
- const minDays = this.SNAPSHOT_TTL_S / 86400;
502
- const effectiveDays = Math.max(days, minDays);
503
- if (effectiveDays !== days) {
504
- (0, log_js_1.twinWarn)(`[twin-engine] journal retention ${days}์ผ์€ ์Šค๋ƒ…์ƒท TTL(${minDays}์ผ)๋ณด๋‹ค ์งง๋‹ค โ€” ${effectiveDays}์ผ๋กœ ์˜ฌ๋ฆฐ๋‹ค ` +
505
- '(๋” ์งง์œผ๋ฉด ์Šค๋ƒ…์ƒท์ด ์‚ด์•„ ์žˆ๋Š”๋ฐ ๊ทธ๊ฒƒ์ด ๊ฐ€๋ฆฌํ‚ค๋Š” ์•ž์ชฝ ์ €๋„์ด ์—†๋Š” ๊ตฌ๊ฐ„์ด ์ƒ๊ธด๋‹ค)');
506
- }
603
+ return { deleted: 0, instances: 0 };
604
+ /*
605
+ * โ”€โ”€ ๋ณด์กด ๊ธฐ๊ฐ„์„ ์Šค๋ƒ…์ƒท TTL ๋กœ ์˜ฌ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค (2026-08-27) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
606
+ *
607
+ * ์—ฌ๊ธฐ๊ฐ€ `max(๋ณด์กด ๊ธฐ๊ฐ„, ์Šค๋ƒ…์ƒท TTL)` ์ด์—ˆ๋‹ค. ๋‘˜ ๋‹ค 7์ผ์ด๋ผ ์ƒํ–ฅ์€ ์—†์—ˆ์ง€๋งŒ, ๊ทธ ์‹์€ ๋‘ ๊ฐ’์„
608
+ * ๋ฌถ์–ด ๋†“์•„์„œ ํ•˜๋‚˜๋ฅผ ์ค„์ด๋ฉด ๋‹ค๋ฅธ ํ•˜๋‚˜๊ฐ€ ๋”ฐ๋ผ์˜ค๊ฒŒ ๋งŒ๋“ ๋‹ค. ๋‘ ๊ฐ’์€ ๋‹ค๋ฅธ ๊ฒƒ์„ ์ •ํ•œ๋‹ค.
609
+ *
610
+ * ๋ณด์กด ๊ธฐ๊ฐ„ ์ด๋ ฅ์„ ๋ฉฐ์น  ๋‘๋‚˜
611
+ * ์Šค๋ƒ…์ƒท TTL ๋น ๋ฅด๊ฒŒ ์„ธ์šฐ๊ธฐ ์œ„ํ•œ ์ €์žฅ๋ณธ์ด ๋ฉฐ์น  ์‚ฌ๋‚˜
612
+ *
613
+ * ์ €์žฅ๋ณธ์ด ๋งŒ๋ฃŒ๋˜์–ด๋„ ํŠธ์œˆ์€ ๋ณด๊ด€๋œ ๊ตฌ๊ฐ„์—์„œ ์„ธ์›Œ์ง„๋‹ค. ๊ทธ๋ž˜์„œ ๋ณด์กด ๊ธฐ๊ฐ„์ด ๋” ์งง์•„๋„ ์„ฑ๋ฆฝํ•œ๋‹ค.
614
+ */
615
+ const effectiveDays = days;
507
616
  const cutoff = new Date(Date.now() - effectiveDays * 86400 * 1000);
508
617
  const rows = await (0, shell_1.getRepository)(twin_instance_js_1.TwinInstance).find({ where: { domain: { id: domainId } } });
509
618
  let deleted = 0;
510
619
  let instances = 0;
511
- const skipped = [];
512
620
  for (const r of rows) {
621
+ /*
622
+ * โ”€โ”€ ์ฒดํฌํฌ์ธํŠธ๋Š” **์ƒํ•œ**์ด์ง€ ์ „์ œ ์กฐ๊ฑด์ด ์•„๋‹ˆ๋‹ค (2026-08-27 ์‚ฌ์šฉ์ž ์ง€์‹œ๋กœ ๊ณ ์นจ) โ”€โ”€
623
+ *
624
+ * ์—ฌ๊ธฐ๊ฐ€ ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ์—†์œผ๋ฉด ๊ฑด๋„ˆ๋›ฐ์—ˆ๋‹ค. ๊ทธ ๊ทœ์น™์€ ใ€Œ์ €๋„์ด ์œ ์ผํ•œ ๋ณต๊ตฌ ์ˆ˜๋‹จใ€์ด๋ผ๋Š” ์ „์ œ์—์„œ
625
+ * ๋‚˜์™”๋Š”๋ฐ, ๊ทธ ์ „์ œ๊ฐ€ ํ‹€๋ ธ๋‹ค. ํŠธ์œˆ์ด ์ž๊ธฐ ์ €๋„๊ณผ ์–ด๋–ค ๊ด€๊ณ„์ธ์ง€๋Š” `restartPolicy` ๊ฐ€ ์„ ์–ธํ•œ๋‹ค.
626
+ *
627
+ * resync ์—ฐ๊ฒฐ๋œ ์‹œ์Šคํ…œ์—์„œ ๋‹ค์‹œ ์ฝ๋Š”๋‹ค ์ €๋„์€ ๋ณต๊ตฌ ์ˆ˜๋‹จ์ด ์•„๋‹ˆ๋‹ค
628
+ * reset ์”จ์•—๋ถ€ํ„ฐ ๋‹ค์‹œ ๋Œ๋ฆฐ๋‹ค ์ €๋„์€ ๋ณต๊ตฌ ์ˆ˜๋‹จ์ด ์•„๋‹ˆ๋‹ค
629
+ * resume ์ €๋„์„ ์ด์–ด ์„ธ์šด๋‹ค ๋ณด๊ด€๋œ ๊ตฌ๊ฐ„์—์„œ ์„ธ์šด๋‹ค
630
+ *
631
+ * ์…‹ ๋‹ค ๋ณด๊ด€ ๊ธฐ๊ฐ„ ๋ฐ–์˜ ์‚ฌ๊ฑด์„ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ณด๊ด€ ๊ธฐ๊ฐ„ 7์ผ์ด๋ผ๋Š” ์„ ์–ธ์ด ๊ณง ใ€Œ7์ผ ์ „ ์ƒํƒœ๋กœ๋Š”
632
+ * ์„ธ์šฐ์ง€ ์•Š๋Š”๋‹คใ€๋Š” ๋œป์ด๋‹ค. ๊ทธ๋ž˜์„œ ์ •๋ฆฌ๋Š” ์ฒดํฌํฌ์ธํŠธ ์—†์ด๋„ ์ง€์šด๋‹ค.
633
+ *
634
+ * ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ํ•˜๋Š” ์ผ์€ ํ•˜๋‚˜ ๋‚จ๋Š”๋‹ค. ์ฒดํฌํฌ์ธํŠธ๊ฐ€ **์žˆ์œผ๋ฉด** ๊ทธ ๋ฆฌ๋น„์ „์„ ์ƒํ•œ์œผ๋กœ ์“ด๋‹ค โ€” ๋‹ค์Œ
635
+ * ๊ธฐ๋™์ด ๊ทธ ์ง€์ ์—์„œ ์ด์–ด ์ง‘๊ณ„ํ•˜๋ฏ€๋กœ, ๊ทธ ๋’ค์˜ ์‚ฌ๊ฑด์„ ์ง€์šฐ๋ฉด ๊ทธ ๊ตฌ๊ฐ„์ด ์–ด๋””์—๋„ ์—†์–ด์ง„๋‹ค.
636
+ *
637
+ * **์—†์œผ๋ฉด ์ƒํ•œ๋„ ์—†๋‹ค.** ์ด์–ด ์ง‘๊ณ„ํ•  ์ง€์ ์ด ์—†์œผ๋‹ˆ ์ง€ํ‚ฌ ๊ฒƒ๋„ ์—†๊ณ , ๋ณด๊ด€ ๊ธฐ๊ฐ„ ์กฐ๊ฑด๋งŒ์œผ๋กœ ์ง€์šด๋‹ค.
638
+ * ์—†๋‹ค๋Š” ์ด์œ ๋กœ ๊ฑด๋„ˆ๋›ฐ๋ฉด ๋ฉˆ์ถ˜ ํŠธ์œˆ์€ ์˜๊ตฌํžˆ ์ค„์ง€ ์•Š๋Š”๋‹ค(์‹ค์ธก: ์„ธ ํŠธ์œˆ์— 2,900๋งŒ ํ–‰).
639
+ */
513
640
  const snap = await this.loadSnapshot(domainId, r.instanceId).catch(() => null);
514
- const upTo = Number(snap?.revision ?? 0);
515
- if (!upTo) {
516
- /* ์Šค๋ƒ…์ƒท์ด ์—†๋‹ค โ€” ์ €๋„์ด ์œ ์ผํ•œ ๋ณต๊ตฌ ์ˆ˜๋‹จ์ด๋ฏ€๋กœ ์†๋Œ€์ง€ ์•Š๋Š”๋‹ค. */
517
- skipped.push(r.instanceId);
518
- continue;
641
+ const at = Number(snap?.revision ?? 0);
642
+ const upTo = at > 0 ? at : Number.MAX_SAFE_INTEGER;
643
+ /*
644
+ * โ”€โ”€ ๋‚˜๋ˆ ์„œ ์ง€์šด๋‹ค (2026-08-25 ์‹ค์ธก์œผ๋กœ ๊ณ ์นจ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
645
+ *
646
+ * ์—ฌ๊ธฐ๊ฐ€ **ํ•œ ๋ฌธ์žฅ์œผ๋กœ ํ†ต์งธ** ์ง€์› ๋‹ค. ์ง€์šธ ๊ฒƒ์ด ๋ฉฐ์น ์น˜๋ฉด ๊ทธ ํ•œ ๋ฌธ์žฅ์ด ์ˆ˜๋ฐฑ๋งŒ ํ–‰์ด ๋˜๊ณ , sqlite ๋Š”
647
+ * ์“ฐ๊ธฐ๊ฐ€ ํ•˜๋‚˜์ด๋ฏ€๋กœ ๊ทธ๋™์•ˆ ํ˜ธ์ŠคํŠธ๊ฐ€ ์•„๋ฌด ์ผ๋„ ๋ชป ํ•œ๋‹ค. ์‹ค์ธก: ํ•œ ๋ฒˆ์˜ ์ง€์šฐ๊ธฐ๊ฐ€ 74์ดˆยท79์ดˆ์˜€๊ณ ,
648
+ * ๊ทธ ๋’ค์— ํ•œ ์ค„์งœ๋ฆฌ `domains` ์กฐํšŒ๊ฐ€ 67์ดˆ๋กœ ์ฐํ˜”๋‹ค โ€” ๊ทธ ์งˆ์˜์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ค„์„ ์„  ๊ฐ’์ด๋‹ค.
649
+ * ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ๋ณด๊ด€ ๊ธฐ๊ฐ„ 7์ผ์— 13์ผ์น˜๊ฐ€ ์Œ“์—ฌ ์žˆ์—ˆ๊ณ , ๊ทธ ๋ฐ€๋ฆฐ ๋ชซ์ด ํ•œ ๋ฒˆ์— ๋‚˜์™”๋‹ค.
650
+ *
651
+ * ๊ทธ๋ž˜์„œ **ํ•œ ๋ฒˆ์— ์ง€์šฐ๋Š” ์ˆ˜๋ฅผ ๋ฌถ๊ณ **, ๋ฐฐ์น˜ ์‚ฌ์ด์— ๋ฃจํ”„๋ฅผ ๋น„์›Œ ์ค€๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ•œ ์ฐฝ์—์„œ ์“ฐ๋Š”
652
+ * ์‹œ๊ฐ„์—๋„ ์ƒํ•œ์„ ๋‘”๋‹ค โ€” ๋‚จ์€ ๊ฒƒ์€ ๋‹ค์Œ ์ฐฝ์ด ์ด์–ด์„œ ์ง€์šด๋‹ค. ์ง€์šฐ๋Š” ์ด๋Ÿ‰์€ ๊ฐ™๊ณ , ๊ทธ ์‚ฌ์ด์—
653
+ * ์‚ฌ๋žŒ์˜ ์š”์ฒญ์ด ์ฒ˜๋ฆฌ๋œ๋‹ค.
654
+ *
655
+ * `DELETE โ€ฆ LIMIT` ์€ ๋“œ๋ผ์ด๋ฒ„๋งˆ๋‹ค ๋‹ค๋ฅด๋ฏ€๋กœ ์“ฐ์ง€ ์•Š๋Š”๋‹ค(sqlite ๋Š” ๊ธฐ๋ณธ ๋นŒ๋“œ์—์„œ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค).
656
+ * ๋Œ€์‹  **๊ฒฝ๊ณ„ ๋ฆฌ๋น„์ „์„ ๋จผ์ € ์ฐพ์•„** ๊ทธ ์•„๋ž˜๋งŒ ์ง€์šด๋‹ค โ€” ๋‹ค์„ฏ ๋“œ๋ผ์ด๋ฒ„์—์„œ ๊ฐ™์€ ๋œป์ด ๋˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.
657
+ */
658
+ let n = 0;
659
+ const startedMs = Date.now();
660
+ for (;;) {
661
+ /*
662
+ * โ”€โ”€ ๊ฒฝ๊ณ„๋ฅผ **์‹œ๊ฐ**์œผ๋กœ ์ฐพ๋Š”๋‹ค (2026-08-27 ์‹ค์ธก์œผ๋กœ ๊ณ ์นจ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
663
+ *
664
+ * ์—ฌ๊ธฐ๊ฐ€ `ORDER BY revision` ์ด์—ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๊ณ„ํš๊ธฐ๊ฐ€ ๋ฆฌ๋น„์ „ ์ƒ‰์ธ์„ ๊ณ ๋ฅด๊ณ , ์ง€์šธ ๋Œ€์ƒ์ธ์ง€๋Š”
665
+ * ํ–‰๋งˆ๋‹ค `created_at` ์„ ์—ด์–ด ํ™•์ธํ•œ๋‹ค. ์ง€์šธ ๊ฒƒ์ด ์ ์œผ๋ฉด ๊ทธ ํŠธ์œˆ์˜ ์ €๋„์„ ๋๊นŒ์ง€ ๊ฑธ์–ด์•ผ
666
+ * ใ€Œํ•œ ๋ฐฐ์น˜๋ฅผ ๋ชป ์ฑ„์› ๋‹คใ€๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค โ€” 1,029๋งŒ ํ–‰์—์„œ ๊ทธ๊ฒƒ์ด 2์ดˆ ์˜ˆ์‚ฐ์„ ๋‹ค ์ผ๊ณ  44๊ฑด๋งŒ
667
+ * ์ง€์šฐ๊ณ  ๋ฉˆ์ท„๋‹ค.
668
+ *
669
+ * ์‹œ๊ฐ์ˆœ์œผ๋กœ ๋ฌผ์œผ๋ฉด `(domain, instance, created_at)` ์ƒ‰์ธ์„ ๊ทธ๋Œ€๋กœ ์“ด๋‹ค. ์‹ค์ธก ๊ณ„ํš:
670
+ *
671
+ * ORDER BY revision SEARCH USING INDEX ix_twin_event_0 โ† created_at ์„ ํ–‰๋งˆ๋‹ค ํ™•์ธ
672
+ * ORDER BY createdAt SEARCH USING COVERING INDEX ix_twin_event_8 โ† ํ…Œ์ด๋ธ”์„ ์—ด์ง€ ์•Š๋Š”๋‹ค
673
+ *
674
+ * ์ง€์šฐ๊ธฐ๋Š” ์ •๋ ฌ์ด ์—†์–ด ์ƒ‰์ธ๋งŒ ์žˆ์œผ๋ฉด ๋ฐ”๋กœ ๊ทธ๊ฒƒ์„ ์“ด๋‹ค(์‹ค์ธก ํ™•์ธ).
675
+ */
676
+ const edge = await (0, shell_1.getRepository)(twin_event_js_1.TwinEvent)
677
+ .createQueryBuilder('e')
678
+ .select('e.createdAt', 'createdAt')
679
+ .where('e.domain = :domainId', { domainId })
680
+ .andWhere('e.instanceId = :instanceId', { instanceId: r.instanceId })
681
+ .andWhere('e.createdAt IS NOT NULL')
682
+ .andWhere('e.createdAt < :cutoff', { cutoff })
683
+ .orderBy('e.createdAt', 'ASC')
684
+ .offset(this.JOURNAL_PRUNE_BATCH - 1)
685
+ .limit(1)
686
+ .getRawOne();
687
+ /*
688
+ * ๊ฒฝ๊ณ„๊ฐ€ ์—†์œผ๋ฉด ๋‚จ์€ ๊ฒƒ์ด ํ•œ ๋ฐฐ์น˜ ์•ˆ์ด๋‹ค โ€” ๊ทธ๋•Œ๋Š” ๋ณด๊ด€ ๊ธฐ๊ฐ„ ๊ฒฝ๊ณ„๊นŒ์ง€ ์ง€์šฐ๊ณ  ๋๋‚œ๋‹ค.
689
+ *
690
+ * ๊ฒฝ๊ณ„๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ ์‹œ๊ฐ **์ดํ•˜**๊นŒ์ง€ ์ง€์šด๋‹ค(`<=`). ๊ฐ™์€ ์‹œ๊ฐ์˜ ํ–‰์ด ์—ฌ๋Ÿฟ์ด๋ฉด ํ•œ ๋ฐฐ์น˜๊ฐ€ ์กฐ๊ธˆ
691
+ * ์ปค์ง€์ง€๋งŒ, `<` ๋กœ ๋‘๋ฉด ๊ทธ ์‹œ๊ฐ์˜ ํ–‰๋“ค์ด ๋‚จ์•„ ๋‹ค์Œ ๋ฐฐ์น˜๊ฐ€ ๊ฐ™์€ ๊ฒฝ๊ณ„๋ฅผ ๋‹ค์‹œ ์ฐพ๋Š”๋‹ค(์ œ์ž๋ฆฌ๊ฑธ์Œ).
692
+ */
693
+ /*
694
+ * ์›์‹œ ๊ฒฐ๊ณผ์˜ ์ปฌ๋Ÿผ ๊ฐ’์€ **๋“œ๋ผ์ด๋ฒ„์—๊ฒŒ ํ•ด์„์‹œํ‚จ๋‹ค**(ยง`hydratedDate`). sqlite ๊ฐ€ ์ฃผ๋Š” ๊ฐ’์—๋Š”
695
+ * ์‹œ๊ฐ„๋Œ€๊ฐ€ ์—†์–ด์„œ ๊ทธ๋Œ€๋กœ `new Date()` ๋กœ ์ฝ์œผ๋ฉด ์ง€์—ญ ์‹œ๊ฐ„์œผ๋กœ ํ•ด์„๋œ๋‹ค โ€” ๊ฒฝ๊ณ„๊ฐ€ ๊ทธ๋งŒํผ ์ด๋ฅด๊ฒŒ
696
+ * ๋ฐ€๋ฆฌ๊ณ , ๊ทธ๋Ÿฌ๋ฉด **ํ•œ ๊ฑด๋„ ์ง€์šฐ์ง€ ๋ชปํ•ด ์ •๋ฆฌ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋ฉˆ์ถ˜๋‹ค**(0๊ฑด์ด๋ฉด ๋ฐ˜๋ณต์„ ๋๋‚ธ๋‹ค).
697
+ */
698
+ const batchCutoff = (0, read_time_js_1.hydratedDate)((0, shell_1.getRepository)(twin_event_js_1.TwinEvent), 'createdAt', edge?.createdAt);
699
+ const del = (0, shell_1.getRepository)(twin_event_js_1.TwinEvent)
700
+ .createQueryBuilder()
701
+ .delete()
702
+ .from(twin_event_js_1.TwinEvent)
703
+ .where('domain_id = :domainId', { domainId })
704
+ .andWhere('instance_id = :instanceId', { instanceId: r.instanceId })
705
+ /* ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ์•„์ง ๋ฐ˜์˜ํ•˜์ง€ ๋ชปํ•œ ์‚ฌ๊ฑด์€ ์ง€์šฐ์ง€ ์•Š๋Š”๋‹ค(๋„๋Š” ํŠธ์œˆ์—์„œ๋งŒ ์ƒํ•œ์ด ๊ฑธ๋ฆฐ๋‹ค). */
706
+ .andWhere('revision <= :upTo', { upTo })
707
+ .andWhere('created_at IS NOT NULL');
708
+ const res = await (batchCutoff
709
+ ? del.andWhere('created_at <= :batchCutoff', { batchCutoff })
710
+ : del.andWhere('created_at < :cutoff', { cutoff })).execute();
711
+ const batch = res.affected ?? 0;
712
+ n += batch;
713
+ if (!batch)
714
+ break;
715
+ if (Date.now() - startedMs >= this.JOURNAL_PRUNE_BUDGET_MS) {
716
+ (0, log_js_1.twinLog)(`[twin-engine] journal prune paused "${r.instanceId}" โ€” ์ด๋ฒˆ ์ฐฝ์—์„œ ${n}๊ฑด๊นŒ์ง€ ์ง€์› ๋‹ค. ` +
717
+ '๋‚จ์€ ๊ฒƒ์€ ๋‹ค์Œ ์ฐฝ์ด ์ด์–ด์„œ ์ง€์šด๋‹ค(ํ˜ธ์ŠคํŠธ๊ฐ€ ๊ทธ ์‚ฌ์ด์— ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค).');
718
+ break;
719
+ }
720
+ /* ๋ฃจํ”„๋ฅผ ๋น„์›Œ ์ค€๋‹ค โ€” ์ด ํ•œ ์ค„์ด ์—†์œผ๋ฉด ๋ฐฐ์น˜๋กœ ๋‚˜๋ˆˆ ๋œป์ด ์—†๋‹ค(๊ฐ™์€ ํ‹ฑ์—์„œ ๊ณ„์† ์ง€์šด๋‹ค). */
721
+ await new Promise(resolve => setImmediate(resolve));
519
722
  }
520
- const res = await (0, shell_1.getRepository)(twin_event_js_1.TwinEvent)
521
- .createQueryBuilder()
522
- .delete()
523
- .from(twin_event_js_1.TwinEvent)
524
- .where('domain_id = :domainId', { domainId })
525
- .andWhere('instance_id = :instanceId', { instanceId: r.instanceId })
526
- .andWhere('revision <= :upTo', { upTo })
527
- .andWhere('created_at IS NOT NULL')
528
- .andWhere('created_at < :cutoff', { cutoff })
529
- .execute();
530
- const n = res.affected ?? 0;
531
723
  if (n > 0) {
532
724
  deleted += n;
533
725
  instances++;
534
- /* **์ง€์šด ๊ฒƒ์„ ๋งํ•œ๋‹ค** โ€” ์กฐ์šฉํžˆ ์ค„์–ด๋“  ์ €๋„์€ ใ€Œ์—†์—ˆ๋˜ ์ผใ€๊ณผ ๊ตฌ๋ณ„๋˜์ง€ ์•Š๋Š”๋‹ค. */
535
- (0, log_js_1.twinLog)(`[twin-engine] journal pruned "${r.instanceId}" โ€” ${n}๊ฑด (revision โ‰ค ${upTo} ยท ${cutoff.toISOString()} ์ด์ „). ` +
536
- '๊ทธ ์•ž์€ ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ๋Œ€์‹ ํ•œ๋‹ค.');
726
+ /* **์ง€์šด ๊ฒƒ์„ ๋งํ•œ๋‹ค** โ€” ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์ค„์–ด๋“  ์ €๋„์€ ใ€Œ์—†์—ˆ๋˜ ์ผใ€๊ณผ ๊ตฌ๋ณ„๋˜์ง€ ์•Š๋Š”๋‹ค. */
727
+ (0, log_js_1.twinLog)(`[twin-engine] journal pruned "${r.instanceId}" โ€” ${n}๊ฑด (${cutoff.toISOString()} ์ด์ „` +
728
+ `${upTo < Number.MAX_SAFE_INTEGER ? ` ยท revision โ‰ค ${upTo} ๊นŒ์ง€๋งŒ, ๊ทธ ๋’ค๋Š” ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ๋Œ€์‹ ํ•˜์ง€ ๋ชปํ•œ๋‹ค` : ''}).`);
729
+ }
730
+ /*
731
+ * โ”€โ”€ ์กฐํšŒ์šฉ ํŒŒ์ƒ ํ‘œ๋„ ๊ฐ™์€ ์ฐฝ์—์„œ ์ค„์ธ๋‹ค (2026-08-27) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
732
+ *
733
+ * ์ด ํ‘œ๋Š” **๋ฒ„๋ฆด ์ˆ˜ ์žˆ๋Š” ๊ฐ’**์ด๋‹ค(์‚ฌ๊ฑด์—์„œ ํŒŒ์ƒ๋œ ๊ฒƒ์ด๋ฏ€๋กœ). ์ง€์šฐ๋ฉด ๊ทธ ๊ตฌ๊ฐ„์˜ ์ด๋ ฅ ์กฐํšŒ๊ฐ€ ์ €๋„๋กœ
734
+ * ๋„˜์–ด๊ฐ€ ๋А๋ ค์ง€๊ณ , ์‚ฌ์‹ค์€ ์‚ฌ๋ผ์ง€์ง€ ์•Š๋Š”๋‹ค.
735
+ *
736
+ * ๊ทธ๋ž˜์„œ ์ฒดํฌํฌ์ธํŠธ ์ƒํ•œ์„ ๋ณด์ง€ ์•Š๋Š”๋‹ค โ€” ๊ทธ ์ƒํ•œ์€ ใ€Œ์‚ฌ์‹ค์„ ์žƒ์ง€ ์•Š๊ธฐ ์œ„ํ•œ ๊ฒƒใ€์ด๊ณ  ์—ฌ๊ธฐ์—๋Š” ์žƒ์„
737
+ * ์‚ฌ์‹ค์ด ์—†๋‹ค. ๋ณด๊ด€ ๊ธฐ๊ฐ„๋งŒ ๋ณธ๋‹ค.
738
+ */
739
+ const subjectPruned = await (0, twin_subject_writer_js_1.pruneSubjectRows)(domainId, r.instanceId, cutoff, this.JOURNAL_PRUNE_BUDGET_MS, this.JOURNAL_PRUNE_BATCH).catch(() => 0);
740
+ if (subjectPruned > 0) {
741
+ (0, log_js_1.twinLog)(`[twin-subject] pruned "${r.instanceId}" โ€” ${subjectPruned}์ค„ (${cutoff.toISOString()} ์ด์ „).`);
537
742
  }
538
743
  }
539
- if (skipped.length) {
540
- (0, log_js_1.twinLog)(`[twin-engine] journal prune skipped ${skipped.length} instance(s) with no checkpoint โ€” ` +
541
- `์ €๋„์ด ์œ ์ผํ•œ ๋ณต๊ตฌ ์ˆ˜๋‹จ์ด๋ผ ์†๋Œ€์ง€ ์•Š์•˜๋‹ค: ${skipped.slice(0, 5).join(', ')}${skipped.length > 5 ? ' โ€ฆ' : ''}`);
542
- }
543
- return { deleted, instances, skipped };
744
+ return { deleted, instances };
544
745
  }
746
+ /**
747
+ * ํ•œ ๋ฒˆ์˜ ์ง€์šฐ๊ธฐ๊ฐ€ ๋‹ค๋ฃจ๋Š” ํ–‰ ์ˆ˜ โ€” ์“ฐ๊ธฐ ์ž ๊ธˆ์„ ์ด ๋งŒํผ๋งŒ ์žก๋Š”๋‹ค.
748
+ *
749
+ * โ”€โ”€ 20,000 ์ด ์™œ ์ปธ๋‚˜ (2026-08-28 ์‹ค์ธก) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
750
+ * ์•ž ํŒ์€ 20,000 ์ด์—ˆ๊ณ  ๊ทผ๊ฑฐ๋Š” ใ€Œํ•œ ๋ฌธ์žฅ์œผ๋กœ ์ˆ˜๋ฐฑ๋งŒ ํ–‰์„ ์ง€์šฐ๋ฉด 74์ดˆ์˜€๋‹คใ€์˜€๋‹ค. ๊ทธ๊ฒƒ์€ **๋” ๋‚˜์œ
751
+ * ์ชฝ๊ณผ ๊ฒฌ์ค€ ์ˆ˜**์ด๊ณ , 20,000 ์ž์ฒด๋ฅผ ์žฐ ๊ฒƒ์ด ์•„๋‹ˆ์—ˆ๋‹ค. ์‹ค์ œ๋กœ ์žฌ ๋ณด๋‹ˆ ์ด๋ ‡๋‹ค(์„œ๋ฒ„ ๊ธฐ๋ก):
752
+ *
753
+ * hatio-mx1 20,123์ค„ 5.6์ดˆ
754
+ * hatio-mx2 20,130์ค„ 5.2์ดˆ
755
+ * hatio-us 20,139์ค„ 4.2์ดˆ
756
+ * ํ•œ ์ฐฝ์˜ ํ•ฉ๊ณ„ ์•ฝ 15์ดˆ
757
+ *
758
+ * sqlite ๋Š” ์—ฐ๊ฒฐ์ด ํ•˜๋‚˜๋‹ค. ๊ทธ๋ž˜์„œ ๊ทธ 4~5์ดˆ ๋™์•ˆ **ํ™”๋ฉด์˜ ๋ชจ๋“  ์งˆ์˜๊ฐ€ ์ค„์„ ์„ ๋‹ค** โ€” ์‹ค์ธก์œผ๋กœ ์ฒซ
759
+ * ํ™”๋ฉด ์กฐํšŒ๊ฐ€ 12.2์ดˆ์˜€๋‹ค(์ •์ƒ ์ƒํƒœ์—์„œ๋Š” 40๋ฐ€๋ฆฌ์ดˆ๋‹ค).
760
+ *
761
+ * ๊ทธ๋ฆฌ๊ณ  ์˜ˆ์‚ฐ(`JOURNAL_PRUNE_BUDGET_MS` = 2์ดˆ)์ด **๋ฌธ์žฅ์ด ๋๋‚œ ๋’ค์—** ๊ฒ€์‚ฌ๋˜๋ฏ€๋กœ, ํ•œ ๋ฌธ์žฅ์ด ์˜ˆ์‚ฐ์„
762
+ * 2~3๋ฐฐ ๋„˜๊ธด๋‹ค. ๋ฐฐ์น˜๋ฅผ ๋‚˜๋ˆˆ ๋œป์ด ๊ทธ๋งŒํผ ์—†์–ด์ง„๋‹ค.
763
+ *
764
+ * โ”€โ”€ 2,000 ์˜ ๊ทผ๊ฑฐ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
765
+ * ์œ„ ์‹ค์ธก์ด ์ค„๋‹น ์•ฝ 0.26๋ฐ€๋ฆฌ์ดˆ๋‹ค(5.2์ดˆ / 20,130์ค„). 2,000์ค„์ด๋ฉด **ํ•œ ๋ฌธ์žฅ์ด ์•ฝ 0.5์ดˆ**๋‹ค. ํ™”๋ฉด์ด
766
+ * ํ•œ ๋ฒˆ ๋ฉˆ์ถ”๋Š” ๊ธธ์ด๊ฐ€ 5์ดˆ์—์„œ 0.5์ดˆ๋กœ ์ค„๊ณ , ์˜ˆ์‚ฐ ๊ฒ€์‚ฌ๋„ ๋œป์„ ๊ฐ–๋Š”๋‹ค(ํ•œ ์ฐฝ์— ๋ฌธ์žฅ ๋„ท).
767
+ *
768
+ * ์ด๋Ÿ‰์€ ๊ฐ™๊ณ  ๋‚˜๋ˆ ์„œ ์น˜๋ฅธ๋‹ค โ€” ์ฐฝ๋งˆ๋‹ค ์ง€์šฐ๋Š” ์ˆ˜๊ฐ€ ์ค„์ง€๋งŒ ์ง€์šฐ๋Š” ์ผ์€ ๊ธ‰ํ•˜์ง€ ์•Š๋‹ค(์ฐฝ์ด 10๋ถ„๋งˆ๋‹ค ์˜จ๋‹ค).
769
+ *
770
+ * ๊ฐ’์„ ๋ฐ”๊พธ๋ ค๋ฉด ๋‹ค์‹œ ์žฌ๊ณ  ์ด ๋ฌธ์žฅ์„ ๊ณ ์น  ๊ฒƒ.
771
+ */
772
+ static { this.JOURNAL_PRUNE_BATCH = 2_000; }
773
+ /**
774
+ * ํ•œ ์ฐฝ์—์„œ ์ง€์šฐ๋Š” ๋ฐ ์“ฐ๋Š” ์‹œ๊ฐ„ ์ƒํ•œ(ms) โ€” ๋‚จ์€ ๊ฒƒ์€ ๋‹ค์Œ ์ฐฝ์ด ์ด์–ด์„œ ์ง€์šด๋‹ค.
775
+ *
776
+ * ๋ฐ€๋ฆฐ ๋ชซ์ด ๋ฉฐ์น ์น˜๋ฉด ํ•œ ์ฐฝ์—์„œ ๋‹ค ์ง€์šธ ์ˆ˜ ์—†๋‹ค. ๋‹ค ์ง€์šฐ๋ ค ๋“ค๋ฉด ๊ทธ๋™์•ˆ ํ˜ธ์ŠคํŠธ๊ฐ€ ๋ฉˆ์ถ”๊ณ , ๊ทธ๊ฒƒ์ด ๋ฐ”๋กœ
777
+ * ๊ณ ์น˜๋ ค๋Š” ์ฆ์ƒ์ด๋‹ค. ์ด๋Ÿ‰์€ ๊ฐ™๊ณ  ๋‚˜๋ˆ ์„œ ์น˜๋ฅธ๋‹ค.
778
+ *
779
+ * โ”€โ”€ 2์ดˆ์—์„œ 5์ดˆ๋กœ ์˜ฌ๋ฆฐ ์ด์œ  (2026-08-28) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
780
+ * ๋ฐฐ์น˜๋ฅผ 20,000 โ†’ 2,000 ์œผ๋กœ ์ค„์˜€๋‹ค(ยง`JOURNAL_PRUNE_BATCH`). ์˜ˆ์‚ฐ์„ ๊ทธ๋Œ€๋กœ ๋‘๋ฉด **์ฐฝ๋งˆ๋‹ค ์ง€์šฐ๋Š”
781
+ * ์–‘์ด 2.5๋ฐฐ ์ค„์–ด** ๋ฐ€๋ฆฐ ๋ชซ์ด ๋น ์ง€์ง€ ์•Š๋Š”๋‹ค.
782
+ *
783
+ * ์˜ˆ์‚ฐ์„ 5์ดˆ๋กœ ๋‘๋ฉด ํ•œ ์ฐฝ์— ๋ฌธ์žฅ ์—ด ๊ฐœ(๊ฐ 0.5์ดˆ)๊ฐ€ ๋Œ์•„ **์ง€์šฐ๋Š” ์–‘์€ ์ „๊ณผ ๊ฐ™๊ณ **, ์ž ๊ธˆ์€ ์—ด ๋ฒˆ
784
+ * ๋‚˜๋‰˜์–ด ๊ทธ ์‚ฌ์ด์— ํ™”๋ฉด ์งˆ์˜๊ฐ€ ์ฒ˜๋ฆฌ๋œ๋‹ค. ๊ณ ์น˜๋ ค๋˜ ๊ฒƒ์€ ์ด ์‹œ๊ฐ„์ด ์•„๋‹ˆ๋ผ **ํ•œ ๋ฒˆ์— ๋ฉˆ์ถ”๋Š” ๊ธธ์ด**๋‹ค.
785
+ *
786
+ * ์ „ ํ•œ ๋ฌธ์žฅ 5.2์ดˆ ยท ๊ทธ๋™์•ˆ ๋ชจ๋“  ์กฐํšŒ๊ฐ€ ์ค„์„ ์„ ๋‹ค
787
+ * ํ›„ ๋ฌธ์žฅ ์—ด ๊ฐœ ร— 0.5์ดˆ ยท ์‚ฌ์ด๋งˆ๋‹ค ๋ฃจํ”„๋ฅผ ๋น„์›Œ ์ค€๋‹ค
788
+ */
789
+ static { this.JOURNAL_PRUNE_BUDGET_MS = 5_000; }
545
790
  /** ๋ณด์กด ์ •๋ฆฌ ์ฃผ๊ธฐ ๊ธฐ๋™(1ํšŒ) โ€” ์„ ์–ธ์ด ์—†์œผ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๋Š”๋‹ค. */
546
791
  static startRetentionLoop(domainId) {
547
792
  if (this.retentionTimer)
@@ -641,7 +886,7 @@ class TwinEngine {
641
886
  * `bootstrap()` ์€ ์ƒํƒœ๋งŒ ๋˜์ฐพ์•„ `recovered` ์— ๋‹ด์•˜๊ณ , ์ปค๋„์„ ์„ธ์šฐ๋Š” ๊ฒƒ์€ **๋ช…์‹œ mutation ๋ฟ**์ด์—ˆ๋‹ค
642
887
  * (์ด ํŒŒ์ผ ์œ„์ชฝ ์ฃผ์„์ด ใ€Œํ–ฅํ›„ใ€๋ผ๊ณ  ์ ์–ด ๋‘” ๊ทธ ์ž๋ฆฌ๋‹ค). ๊ทธ๋ž˜์„œ ์„œ๋ฒ„๋ฅผ ํ•œ ๋ฒˆ ์žฌ๊ธฐ๋™ํ•˜๋ฉด ๋“ฑ๋ก๋ถ€๋Š”
643
888
  * `running` ์ด๋ผ ๋งํ•˜๋Š”๋ฐ **์•„๋ฌด ์ปค๋„๋„ ๋Œ์ง€ ์•Š์•˜๋‹ค** โ€” ํ™”๋ฉด์€ ๋„๋Š” ํŠธ์œˆ์„, ์‹ค์ œ๋กœ๋Š” ๋ฉˆ์ถ˜ ํŠธ์œˆ์„.
644
- * ๋ฏธ๋Ÿฌ ํŠธ์œˆ์—์„œ๋Š” ๋” ๋‚˜์˜๋‹ค: ๊ณ„์ธก์ด ์กฐ์šฉํžˆ ๋Š๊ธฐ๊ณ , ์‚ฌ๋žŒ์€ ใ€Œ๊ฐ’์ด ์•ˆ ๋ณ€ํ•˜๋„คใ€๋กœ ์•Œ๊ฒŒ ๋œ๋‹ค.
889
+ * ๋ฏธ๋Ÿฌ ํŠธ์œˆ์—์„œ๋Š” ๋” ๋‚˜์˜๋‹ค: ๊ณ„์ธก์ด ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋Š๊ธฐ๊ณ , ์‚ฌ๋žŒ์€ ใ€Œ๊ฐ’์ด ์•ˆ ๋ณ€ํ•˜๋„คใ€๋กœ ์•Œ๊ฒŒ ๋œ๋‹ค.
645
890
  *
646
891
  * โ”€โ”€ ๋ชจ๋“œ๋ฅผ ์ง€์–ด๋‚ด์ง€ ์•Š๋Š”๋‹ค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
647
892
  * ๋ฏธ๋Ÿฌ์˜€๋˜ ํŠธ์œˆ์„ ์‹œ๋ฎฌ๋กœ ๋˜์‚ด๋ฆฌ๋ฉด **์—†๋˜ ์›€์ง์ž„์„ ๋งŒ๋“ค์–ด ๋‚ธ๋‹ค**(๊ด€์ธก ํŠธ์œˆ์ด ์Šค์Šค๋กœ ๋ฌผ๊ฑด์„ ์˜ฎ๊ธด๋‹ค).
@@ -752,7 +997,7 @@ class TwinEngine {
752
997
  ...(plan.attentionSinceCount ? [`${plan.attentionSinceCount} attention start time(s)`] : [])
753
998
  ].join(', ');
754
999
  (0, log_js_1.twinLog)(`[twin-engine] warm-started "${id}" โ€” restored ${restored}.`);
755
- /* ๋บ€ ๊ฒƒ์€ ์กฐ์šฉํžˆ ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค โ€” ์ง€์–ด๋‚ด์ง€ ์•Š์•˜๋‹ค๋Š” ์‚ฌ์‹ค ์ž์ฒด๋ฅผ ๋งํ•ด์•ผ ํ™”๋ฉด์˜ ๋นˆ์นธ์ด ์ฝํžŒ๋‹ค. */
1000
+ /* ๋บ€ ๊ฒƒ์€ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค โ€” ์ง€์–ด๋‚ด์ง€ ์•Š์•˜๋‹ค๋Š” ์‚ฌ์‹ค ์ž์ฒด๋ฅผ ๋งํ•ด์•ผ ํ™”๋ฉด์˜ ๋นˆ์นธ์ด ์ฝํžŒ๋‹ค. */
756
1001
  if (plan.ordersWithoutDemand > 0) {
757
1002
  (0, log_js_1.twinWarn)(`[twin-engine] "${id}": ${plan.ordersWithoutDemand} order(s) could not be restored โ€” they carry progress only, ` +
758
1003
  'with no requested/fulfilled counts, so the remaining demand is unknown. They are left out rather than guessed.');
@@ -765,7 +1010,7 @@ class TwinEngine {
765
1010
  * `model.operations`(๋งˆ์Šคํ„ฐ ์ธ์ œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผ์‹œํ‚จ ISA-95 OperationsSegment ๋ช…์„ธ)๋ฅผ ์ปค๋„์ด ์†Œ๋น„ํ•œ๋‹ค.
766
1011
  * ์—†์œผ๋ฉด ์ปค๋„ ๊ธฐ๋ณธ ์ƒ์ˆ˜๋กœ ๊ตด๋Ÿฌ๊ฐ€๊ณ , ์ปค๋„ `specCoverage()` ๊ฐ€ ๋ฌด์—‡์„ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ผ๋Š”์ง€ ๋ณด๊ณ ํ•œ๋‹ค.
767
1012
  *
768
- * ์ปค๋„์ด ์•„์ง ์ด API ๋ฅผ ๊ฐ–์ง€ ์•Š์€ ๋ฒ„์ „์ด๋ฉด(๋ฐœํ–‰ ์ด์ „) **์กฐ์šฉํžˆ ๋„˜์–ด๊ฐ€์ง€ ์•Š๊ณ  ๊ฒฝ๊ณ ํ•œ๋‹ค** โ€” ๋ช…์„ธ๋ฅผ
1013
+ * ์ปค๋„์ด ์•„์ง ์ด API ๋ฅผ ๊ฐ–์ง€ ์•Š์€ ๋ฒ„์ „์ด๋ฉด(๋ฐœํ–‰ ์ด์ „) **์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋„˜์–ด๊ฐ€์ง€ ์•Š๊ณ  ๊ฒฝ๊ณ ํ•œ๋‹ค** โ€” ๋ช…์„ธ๋ฅผ
769
1014
  * ์„ ์–ธํ–ˆ๋Š”๋ฐ ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š” ์ƒํƒœ๋ฅผ ๋ชจ๋ฅด๊ณ  ์ง€๋‚˜๊ฐ€๋ฉด, ์˜ˆ์ธก์ด ์ƒ์ˆ˜๋กœ ๋Œ์•„๊ฐ„ ๊ฒƒ์„ ์•„๋ฌด๋„ ์•Œ ์ˆ˜ ์—†๋‹ค.
770
1015
  */
771
1016
  static applyOperations(kernel, model, id) {
@@ -787,7 +1032,7 @@ class TwinEngine {
787
1032
  kernel.declareDurations(declared);
788
1033
  }
789
1034
  catch (err) {
790
- /* ์ปค๋„์ด ๊ฑฐ์ ˆํ•œ ๊ฐ’์€ ์กฐ์šฉํžˆ ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค โ€” ํ™”๋ฉด์€ ใ€Œ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹คใ€๋ผ๊ณ  ๋งํ•œ ๊ฐ’์ด๋‹ค. */
1035
+ /* ์ปค๋„์ด ๊ฑฐ์ ˆํ•œ ๊ฐ’์€ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค โ€” ํ™”๋ฉด์€ ใ€Œ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹คใ€๋ผ๊ณ  ๋งํ•œ ๊ฐ’์ด๋‹ค. */
791
1036
  (0, log_js_1.twinWarn)(`[twin-engine] "${id}": declared operation duration rejected by the kernel โ€” ${err?.message ?? err}`);
792
1037
  }
793
1038
  }
@@ -827,7 +1072,7 @@ class TwinEngine {
827
1072
  * ๋‘˜ ๋‹ค ๋ชป ๋งŒ๋“ค๋ฉด ์ฃผ์ž…ํ•˜์ง€ ์•Š๋Š”๋‹ค โ€” ์ปค๋„์ด ๋ช…์„ธยท์ƒ์ˆ˜๋กœ ๊ตด๋Ÿฌ๊ฐ€๊ณ  `specCoverage()` ๊ฐ€ ๊ทธ ์‚ฌ์‹ค์„ ๋‚จ๊ธด๋‹ค.
828
1073
  *
829
1074
  * ์‹ค์ธก์€ DB ์กฐํšŒ๋ผ ๋น„๋™๊ธฐ๋‹ค. ๊ทธ๋ž˜์„œ ์ด ํ•จ์ˆ˜๋Š” **await ํ•˜์ง€ ์•Š๋Š” ์ชฝ์—์„œ๋„ ์•ˆ์ „**ํ•˜๋„๋ก ์‹คํŒจ๋ฅผ ์‚ผํ‚ค๋˜,
830
- * ๋ฌด์—‡์„ ์™œ ๋ชป ๋„ฃ์—ˆ๋Š”์ง€๋Š” ๋กœ๊ทธ๋กœ ๋‚จ๊ธด๋‹ค(์กฐ์šฉํ•œ ๋ฌดํšจํ™” ๊ธˆ์ง€).
1075
+ * ๋ฌด์—‡์„ ์™œ ๋ชป ๋„ฃ์—ˆ๋Š”์ง€๋Š” ๋กœ๊ทธ๋กœ ๋‚จ๊ธด๋‹ค(๋“ค์–ด์˜จ ๊ฒƒ์ด ์—†๋Š” ๋ฌดํšจํ™” ๊ธˆ์ง€).
831
1076
  */
832
1077
  static async installEstimators(kernel, domainId, instanceId, model) {
833
1078
  if (!kernel || typeof kernel !== 'object')
@@ -845,7 +1090,7 @@ class TwinEngine {
845
1090
  /*
846
1091
  * **์–‘ํ’ˆ๋ฅ ๋„ ์ด๋ ฅ์—์„œ ๋ฐฐ์šด๋‹ค** (2026-08-19) โ€” ์†Œ์š”์™€ ๊ฐ™์€ ์ž๋ฆฌ์—์„œ ๋ถ™์ธ๋‹ค.
847
1092
  *
848
- * ์ปค๋„์ด ๊ทธ ์‹œ์ž„์„ ๊ฐ–์ง€ ์•Š์€ ๋ฒ„์ „์ด๋ฉด(๋ฐœํ–‰ ์ด์ „) ์กฐ์šฉํžˆ ๋„˜์–ด๊ฐ€์ง€ ์•Š๊ณ  ๋งํ•œ๋‹ค: ์ˆ˜์œจ์ด ์ƒ์ˆ˜๋กœ ๋‚จ์€
1093
+ * ์ปค๋„์ด ๊ทธ ์‹œ์ž„์„ ๊ฐ–์ง€ ์•Š์€ ๋ฒ„์ „์ด๋ฉด(๋ฐœํ–‰ ์ด์ „) ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋„˜์–ด๊ฐ€์ง€ ์•Š๊ณ  ๋งํ•œ๋‹ค: ์ˆ˜์œจ์ด ์ƒ์ˆ˜๋กœ ๋‚จ์€
849
1094
  * ์ด์œ ๋ฅผ ๋ชจ๋ฅด๊ณ  ์ง€๋‚˜๊ฐ€๋ฉด, ํ™”๋ฉด์˜ ๋ถˆ๋Ÿ‰ ํŒ์ •์ด ๊ทธ ํ˜„์žฅ์˜ ์‚ฌ์‹ค์ด ์•„๋‹ˆ๋ผ ์šฐ๋ฆฌ ์ƒ์ˆ˜์˜ ๊ฒฐ๊ณผ๋‹ค.
850
1095
  */
851
1096
  const yields = await this.measuredYield(domainId, instanceId);
@@ -874,7 +1119,7 @@ class TwinEngine {
874
1119
  `${travel.estimator ? `, travel from distance (speeds: ${Object.keys(travel.speedsByKind).join(',')})` : `, travel not derived (${travel.reasons.join('; ')})`}`);
875
1120
  }
876
1121
  /**
877
- * ์‹ค์ธก ์ถ”์ •๊ธฐ โ€” **์˜ˆ์ธก ์š”์ฒญ๋งˆ๋‹ค ์ €๋„์„ ๋‹ค์‹œ ์ ‘์ง€ ์•Š๋Š”๋‹ค.**
1122
+ * ์‹ค์ธก ์ถ”์ •๊ธฐ โ€” **์˜ˆ์ธก ์š”์ฒญ๋งˆ๋‹ค ์ €๋„์„ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜์ง€ ์•Š๋Š”๋‹ค.**
878
1123
  *
879
1124
  * ์˜ˆ์ธก ์ปค๋„์€ ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ๋กœ ์„ธ์›Œ์ง€๊ณ (๋ฏธ๋Ÿฌ ์˜ˆ์ธกยท๋ฐฑํ…Œ์ŠคํŠธ), ํ™”๋ฉด์€ ์‹œ๊ฐ์„ ๊ธ์œผ๋ฉด ๊ณ„์† ์žฌ์˜ˆ์ธกํ•œ๋‹ค.
880
1125
  * ๊ฑฐ๊ธฐ์— KPI ์กฐํšŒ๋ฅผ ๊ทธ๋Œ€๋กœ ๋‹ฌ๋ฉด ์š”์ฒญ๋‹น ์ €๋„ ์Šค์บ”์ด ํ•˜๋‚˜์”ฉ ๋ถ™๋Š”๋‹ค โ€” ์‹ค์ธก์€ ๋ถ„ ๋‹จ์œ„๋กœ ๋ฐ”๋€Œ์ง€ ์•Š์œผ๋ฏ€๋กœ
@@ -893,10 +1138,10 @@ class TwinEngine {
893
1138
  * ๋‹ค์‹œ ๋„ฃ์œผ๋ฏ€๋กœ). ๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์ด ์†ํ•ด๊ฐ€ ์•„๋‹Œ ์ด์œ : ์ด ๊ฐ’์€ ์บ์‹œ์ด๊ณ , ์—†์œผ๋ฉด ๋‹ค์‹œ ๊ณ„์‚ฐํ•œ๋‹ค.
894
1139
  *
895
1140
  * ์ˆ˜๋ฅผ ํฌ๊ฒŒ ์žก๋Š”๋‹ค โ€” ํŠธ์œˆ ๊ทœ๋ชจ๋Š” ๋Š˜ ํฌ๊ณ , ํ•ญ๋ชฉ ํ•˜๋‚˜๋Š” ์ž‘์—… ์ข…๋ฅ˜๋ณ„ ์†Œ์š” ๋ช‡ ์ค„์ด๋‹ค. ์ƒํ•œ์ด ์ž‘์œผ๋ฉด
896
- * ์ •์ƒ ๊ทœ๋ชจ์—์„œ ์„œ๋กœ ๋ฐ€์–ด๋‚ด๋ฉฐ ์บ์‹œ๊ฐ€ ๋ฌด์˜๋ฏธํ•ด์ง„๋‹ค(๊ทธ๊ฒŒ ๋” ๋‚˜์˜๋‹ค: ์กฐ์šฉํžˆ ๋А๋ ค์ง„๋‹ค).
1141
+ * ์ •์ƒ ๊ทœ๋ชจ์—์„œ ์„œ๋กœ ๋ฐ€์–ด๋‚ด๋ฉฐ ์บ์‹œ๊ฐ€ ๋ฌด์˜๋ฏธํ•ด์ง„๋‹ค(๊ทธ๊ฒŒ ๋” ๋‚˜์˜๋‹ค: ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋А๋ ค์ง„๋‹ค).
897
1142
  */
898
1143
  static { this.MEASURED_MAX = 5_000; }
899
- /** ์ด ํŠธ์œˆ์ด ์ด๋ ฅ์—์„œ ๋ฐฐ์šด ์–‘ํ’ˆ๋ฅ  โ€” ์†Œ์š”์™€ **๊ฐ™์€ ํด๋“œยท๊ฐ™์€ ์บ์‹œ**์—์„œ ์˜จ๋‹ค(์ €๋„์„ ๋‘ ๋ฒˆ ์ ‘์ง€ ์•Š๋Š”๋‹ค). */
1144
+ /** ์ด ํŠธ์œˆ์ด ์ด๋ ฅ์—์„œ ๋ฐฐ์šด ์–‘ํ’ˆ๋ฅ  โ€” ์†Œ์š”์™€ **๊ฐ™์€ ํด๋“œยท๊ฐ™์€ ์บ์‹œ**์—์„œ ์˜จ๋‹ค(์ €๋„์„ ๋‘ ๋ฒˆ ๊ณ„์‚ฐํ•˜์ง€ ์•Š๋Š”๋‹ค). */
900
1145
  static async measuredYield(domainId, instanceId) {
901
1146
  await this.measuredEstimator(domainId, instanceId);
902
1147
  return this.measuredCache.get((0, runtime_key_js_1.runtimeKey)(domainId, instanceId))?.yields;
@@ -913,7 +1158,7 @@ class TwinEngine {
913
1158
  /* ์ž‘์—… ์ข…๋ฅ˜๋ณ„ ์‹ค์ธก โ€” ์ฐฝ์€ ๋„‰๋„‰ํžˆ(ํ•˜๋ฃจ) ๋‘๊ณ  ํ‘œ๋ณธ์ด ๋ชจ์ž๋ž€ ์ข…๋ฅ˜๋Š” ์ถ”์ •๊ธฐ๊ฐ€ ์Šค์Šค๋กœ ๋บ€๋‹ค. */
914
1159
  const kpi = await (0, kpi_query_js_1.computeTwinKpi)({ domainId, instanceId, windowMinutes: 24 * 60, groupBy: 'taskKind' });
915
1160
  value = (0, measured_estimator_js_1.buildMeasuredEstimator)(kpi?.groups?.items, {});
916
- /* ๊ฐ™์€ ๊ทธ๋ฃน์—์„œ ์–‘ํ’ˆ๋ฅ ๋„ ๋ฐฐ์šด๋‹ค โ€” ํ•œ ๋ฒˆ ์ ‘์€ ์ €๋„์„ ๋‘˜์ด ๋‚˜๋ˆ  ์“ด๋‹ค. */
1161
+ /* ๊ฐ™์€ ๊ทธ๋ฃน์—์„œ ์–‘ํ’ˆ๋ฅ ๋„ ๋ฐฐ์šด๋‹ค โ€” ํ•œ ๋ฒˆ ๊ณ„์‚ฐํ•œ ์ €๋„์„ ๋‘˜์ด ๋‚˜๋ˆ  ์“ด๋‹ค. */
917
1162
  yields = (0, measured_yield_js_1.buildYieldEstimator)(kpi?.groups?.items, {});
918
1163
  }
919
1164
  catch (err) {
@@ -955,7 +1200,7 @@ class TwinEngine {
955
1200
  }
956
1201
  const plan = (0, declared_stimulus_js_1.planStimulus)(config, { hasScenarioEngine: !!inst.runtime?.scenario, mode: inst.mode }, validateScenario);
957
1202
  if (plan.action === 'skip') {
958
- /* ์„ ์–ธ์ด ์—†๋Š” ๊ฒƒ์€ ์ •์ƒ์ด๋ฏ€๋กœ ์กฐ์šฉํžˆ ์ง€๋‚œ๋‹ค. ๋‚˜๋จธ์ง€ ์…‹์€ **๋งํ•œ๋‹ค** โ€” ์„ ์–ธํ–ˆ๋Š”๋ฐ ์•ˆ ์‹ค๋ฆฐ ์ƒํƒœ๋‹ค. */
1203
+ /* ์„ ์–ธ์ด ์—†๋Š” ๊ฒƒ์€ ์ •์ƒ์ด๋ฏ€๋กœ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์ง€๋‚œ๋‹ค. ๋‚˜๋จธ์ง€ ์…‹์€ **๋งํ•œ๋‹ค** โ€” ์„ ์–ธํ–ˆ๋Š”๋ฐ ์•ˆ ์‹ค๋ฆฐ ์ƒํƒœ๋‹ค. */
959
1204
  if (plan.reason !== 'none') {
960
1205
  (0, log_js_1.twinWarn)(`[twin-engine] "${instanceId}": a stimulus is declared on its source but was not loaded (${plan.reason}${plan.detail ? `: ${plan.detail}` : ''}).` +
961
1206
  (plan.reason === 'observed' ? ' This twin runs on observation โ€” we do not manufacture arrivals for it.' : ''));
@@ -1002,7 +1247,7 @@ class TwinEngine {
1002
1247
  * ๋ณด์—ฌ์•ผ ํ•˜๋Š” ํ™”๋ฉด์ด ์ •์ž‘ ๊ทธ๋•Œ ์•„๋ฌด ๋ง๋„ ๋ชป ํ•œ๋‹ค.
1003
1248
  *
1004
1249
  * ๊ทธ ๋นˆ์นธ์„ ์ด๋ ฅ๋กœ ๋ฉ”์šด๋‹ค: ์ €๋„์—์„œ ๋ฐฐ์šด ์ข…๋ฅ˜๋Š” **์ถ”์ •๊ธฐ๊ฐ€ ์ด๋ฏธ ๋‹ตํ•  ์ˆ˜ ์žˆ๋Š” ์ข…๋ฅ˜**์ด๋ฏ€๋กœ, ์ง€์–ด๋‚ด๋Š”
1005
- * ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์žˆ๋Š” ์‚ฌ์‹ค์„ ๊บผ๋‚ด๋Š” ๊ฒƒ์ด๋‹ค. ๊ฐ™์€ ์บ์‹œ(60์ดˆ)๋ฅผ ์“ฐ๋ฏ€๋กœ ์กฐํšŒ๋งˆ๋‹ค ์ €๋„์„ ๋‹ค์‹œ ์ ‘์ง€ ์•Š๋Š”๋‹ค.
1250
+ * ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์žˆ๋Š” ์‚ฌ์‹ค์„ ๊บผ๋‚ด๋Š” ๊ฒƒ์ด๋‹ค. ๊ฐ™์€ ์บ์‹œ(60์ดˆ)๋ฅผ ์“ฐ๋ฏ€๋กœ ์กฐํšŒ๋งˆ๋‹ค ์ €๋„์„ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜์ง€ ์•Š๋Š”๋‹ค.
1006
1251
  */
1007
1252
  static async measuredOperationKinds(domainId, instanceId) {
1008
1253
  const measured = await this.measuredEstimator(domainId, instanceId);
@@ -1030,7 +1275,7 @@ class TwinEngine {
1030
1275
  *
1031
1276
  * ๊ณ„์‚ฐ์€ ์ปค๋„์ด ์ž๊ธฐ ์ƒํƒœ์—์„œ ํ•œ๋‹ค(`kernel.capacity`). ์—ฌ๊ธฐ์„œ ํ•˜๋Š” ์ผ์€ **๊ธฐ์ค€ ์ฃผ๋ฅผ ์ •ํ•ด ์ฃผ๋Š”
1032
1277
  * ๊ฒƒ**๋ฟ์ด๋‹ค: ๊ณตํœด์ผ์ด ์—†๋Š” ํ‰์ƒ์ฃผ์—ฌ์•ผ ํ•œ๋‹ค โ€” ๊ณตํœด์ผ์€ ์—ฐ๊ฐ„ ๊ฐ€์šฉ๋Ÿ‰์„ ๋”ฐ๋กœ ๊นŽ์ง€, ์ด ๊ณต์žฅ์˜ ํ‰์ƒ์‹œ
1033
- * ์ƒํ•œ์„ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ปค๋„์ด ์ž„์˜๋กœ ๊ณ ๋ฅด๊ฒŒ ๋‘๋ฉด ๊ทธ ์ฃผ์— ๊ณตํœด์ผ์ด ๋ผ์—ˆ์„ ๋•Œ ์ƒํ•œ์ด ์กฐ์šฉํžˆ ๋‚ฎ์•„์ง„๋‹ค.
1278
+ * ์ƒํ•œ์„ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ปค๋„์ด ์ž„์˜๋กœ ๊ณ ๋ฅด๊ฒŒ ๋‘๋ฉด ๊ทธ ์ฃผ์— ๊ณตํœด์ผ์ด ๋ผ์—ˆ์„ ๋•Œ ์ƒํ•œ์ด ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋‚ฎ์•„์ง„๋‹ค.
1034
1279
  *
1035
1280
  * ํŠธ์œˆ์ด ๊ธฐ๋™ ์ค‘์ด ์•„๋‹ˆ๋ฉด `undefined` ๋‹ค โ€” 0 ์ด ์•„๋‹ˆ๋‹ค. ๊ธฐ๋™ํ•˜์ง€ ์•Š์€ ํŠธ์œˆ์˜ ์ƒํ•œ์„ 0 ์ด๋ผ๊ณ  ๋‹ตํ•˜๋ฉด ํ™”๋ฉด์€
1036
1281
  * "์ด ๊ณต์žฅ์€ ์•„๋ฌด๊ฒƒ๋„ ๋ชป ๋งŒ๋“ ๋‹ค" ๊ณ  ๋งํ•œ๋‹ค.
@@ -1085,7 +1330,7 @@ class TwinEngine {
1085
1330
  });
1086
1331
  }
1087
1332
  /**
1088
- * ๋ฏธ๋Ÿฌ ๊ธฐ๋™์˜ ์—ฐ์†์„ฑ ์”จ์•— โ€” ์ด์–ด๋ฐ›์€ ๊ฒƒ์€ **๋งํ•œ๋‹ค**(์กฐ์šฉํžˆ ์ž‡์ง€ ์•Š๋Š”๋‹ค).
1333
+ * ๋ฏธ๋Ÿฌ ๊ธฐ๋™์˜ ์—ฐ์†์„ฑ ์”จ์•— โ€” ์ด์–ด๋ฐ›์€ ๊ฒƒ์€ **๋งํ•œ๋‹ค**(์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์ž‡์ง€ ์•Š๋Š”๋‹ค).
1089
1334
  *
1090
1335
  * ์ปค๋„์ด ๊ทธ ๋ฌธ์„ ์—ด์–ด ๋‘์ง€ ์•Š์•˜์œผ๋ฉด ๊ทธ ์‚ฌ์‹ค๋„ ๋งํ•œ๋‹ค: ๊ทธ ํŠธ์œˆ์€ ์žฌ๊ธฐ๋™๋งˆ๋‹ค ์—ด๋ฆฐ ๊ตฌ๊ฐ„์„ ์žƒ๋Š”๋‹ค.
1091
1336
  */
@@ -1122,6 +1367,7 @@ class TwinEngine {
1122
1367
  return this.instances[key];
1123
1368
  const Kernel = kernelFor(kind);
1124
1369
  const kernel = new Kernel(domainId, undefined, this.productionSpecOf(model));
1370
+ kernel.scopeId = id;
1125
1371
  kernel.loadTwinModel(model); // ๊ตฌ์กฐ๋งŒ. ์ƒํƒœ๋Š” ์•„๋ž˜ ์›œ์Šคํƒ€ํŠธ๊ฐ€ ์ฃผ์ž…ํ•œ๋‹ค.
1126
1372
  this.applyOperations(kernel, model, id); // ์‹œ๊ฐ„ยท์ˆ˜์œจ ๋ช…์„ธ(์žˆ์œผ๋ฉด) โ€” ์—†์œผ๋ฉด ์ปค๋„ ๊ธฐ๋ณธ๊ฐ’
1127
1373
  /* ์ถ”์ •๊ธฐ๋Š” DB ์กฐํšŒ๋ฅผ ํฌํ•จํ•ด ๋น„๋™๊ธฐ โ€” ๊ธฐ๋™์„ ๋ง‰์ง€ ์•Š๊ณ  ๋ถ™๋Š”๋‹ค(๋ถ™๊ธฐ ์ „ ์ž‘์—…์€ ๋ช…์„ธยท์ƒ์ˆ˜๋กœ ์‚ฐ์ถœ). */
@@ -1173,6 +1419,7 @@ class TwinEngine {
1173
1419
  /* ์ •์ฑ…์€ ๋ถ€๋ฅด๋Š” ์ชฝ์ด ์„ ์–ธํ•œ๋‹ค โ€” ์—ฌ๊ธฐ์„œ ๊ณ ๋ฅด๋ฉด ๊ฐ™์€ ํŠธ์œˆ์ด ๋ถ€๋ฅด๋Š” ์ž๋ฆฌ์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ์žฌ๊ธฐ๋™ํ•œ๋‹ค. */
1174
1420
  restartPolicy: (0, restart_policy_js_1.readRestartPolicy)(restartPolicy, `start("${id}")`),
1175
1421
  spaceId: model?.spaceId,
1422
+ model,
1176
1423
  /*
1177
1424
  * **์‹œ๋ฎฌ๋„ ๊ณ„๊ธฐ๋ฅผ ๋“ ๋‹ค** (2026-08-20).
1178
1425
  *
@@ -1213,7 +1460,7 @@ class TwinEngine {
1213
1460
  * ๊ฒƒ์€ ์‹œ๋ฎฌ๋งŒ ์˜†๋ฌธ์œผ๋กœ ๋“ค์–ด์˜ค๊ธฐ ๋•Œ๋ฌธ"* ์ด๋ผ ์˜ˆ๊ณ ํ•œ ๊ทธ ์ž๋ฆฌ๋‹ค.
1214
1461
  *
1215
1462
  * **๋ฆฌ๋น„์ „์€ ์ปค๋„์˜ ๊ฒƒ์„ ๊ทธ๋Œ€๋กœ ๋“ ๋‹ค**(๋ผ์ด๋ธŒ๋Š” flush ๋•Œ ํ˜ธ์ŠคํŠธ๊ฐ€ ๋ถ€์—ฌํ•œ๋‹ค). ์‹œ๋ฎฌ์˜ ์ €๋„์€
1216
- * ์ปค๋„ ๋ฆฌ๋น„์ „์œผ๋กœ ์ ‘ํžˆ๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ ๋‹ค์‹œ ๋ฒˆํ˜ธ๋ฅผ ๋งค๊ธฐ๋ฉด ์‹œ๊ฐ„์—ฌํ–‰์ด ์–ด๊ธ‹๋‚œ๋‹ค.
1463
+ * ์ปค๋„ ๋ฆฌ๋น„์ „์œผ๋กœ ๋ฒˆํ˜ธ๊ฐ€ ์ •ํ•ด์ง€๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ ๋‹ค์‹œ ๋ฒˆํ˜ธ๋ฅผ ๋งค๊ธฐ๋ฉด ์‹œ๊ฐ„์—ฌํ–‰์ด ์–ด๊ธ‹๋‚œ๋‹ค.
1217
1464
  */
1218
1465
  ;
1219
1466
  (inst.pendingJournal ?? (inst.pendingJournal = [])).push({ event: msg.event, revision: msg.revision });
@@ -1251,7 +1498,7 @@ class TwinEngine {
1251
1498
  * ์ค„์ค„์ด ์ƒ๊ฒผ๋‹ค: ์˜ˆ์ธกํ•˜๋ ค๋ฉด ์ž„์‹œ ์ปค๋„์„ ์„ธ์›Œ์•ผ ํ–ˆ๊ณ (`buildForecastKernel`), ์ฃผ๋ชฉ ์‹ ํ˜ธ๋ฅผ ํ˜ธ์ŠคํŠธ๊ฐ€
1252
1499
  * ๋ง๋ถ™์—ฌ์•ผ ํ–ˆ๊ณ (`withLiveAttentions`), AI ์˜ˆ์ธก ๋„๊ตฌ๋Š” ๋ฏธ๋Ÿฌ์—์„œ "์ฐพ์„ ์ˆ˜ ์—†๋‹ค" ๋กœ ๋๋‚ฌ๋‹ค.
1253
1500
  *
1254
- * ์ด์ œ ๋ผ์ด๋ธŒ ์ธ์Šคํ„ด์Šค๋„ **์ปค๋„์ด๋‹ค.** ๊ฐ™์€ ๊ทœ์น™(`ObservedReducer`)์œผ๋กœ ์ด๋ฒคํŠธ๋ฅผ ์ ‘๊ณ , ์ฃผ๋ชฉ ์‹ ํ˜ธ๋ฅผ
1501
+ * ์ด์ œ ๋ผ์ด๋ธŒ ์ธ์Šคํ„ด์Šค๋„ **์ปค๋„์ด๋‹ค.** ๊ฐ™์€ ๊ทœ์น™(`ObservedReducer`)์œผ๋กœ ์ด๋ฒคํŠธ๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ , ์ฃผ๋ชฉ ์‹ ํ˜ธ๋ฅผ
1255
1502
  * ์Šค์Šค๋กœ ๋‚ด๊ณ , ๊ทธ ์ž๋ฆฌ์—์„œ `fork` ํ•ด ์˜ˆ์ธกํ•œ๋‹ค. ๊ตฌ๋™๋งŒ ๋‹ค๋ฅด๋‹ค โ€” sim ์€ `tick`, live ๋Š” `apply`.
1256
1503
  *
1257
1504
  * ์‹ค ์ด๋ฒคํŠธ์› = reference ์–ด๋Œ‘ํ„ฐ openLiveFeed โ†’ face2-adapter.ingest โ†’ CanonicalEnvelope โ†’ ingestLive().
@@ -1273,7 +1520,7 @@ class TwinEngine {
1273
1520
  * ์ด๋ฆ„์ด ๊ทธ๋Œ€๋กœ ๊ตณ์€ ๊ฒƒ), ์ผ๋ฐ˜ ๊ธฐ์ œ์— ํ•œ ์‹œ์Šคํ…œ ์ด๋ฆ„์ด ๋ถ™์–ด ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ฐฝ๊ณ  ํŠธ์œˆ์ด ์ด ์ž๋ฆฌ๋ฅผ
1274
1521
  * ์“ฐ์ง€ ๋ชปํ–ˆ๋‹ค. ๋ณ„๋ช…์œผ๋กœ ๋‚จ๊ฒจ ๋‘๋ฉด ๊ทธ ํ˜ผ๋™์ด ๊ณ„์†๋˜๋ฏ€๋กœ ํ•˜๋‚˜๋กœ ํ†ต์ผํ–ˆ๋‹ค.
1275
1522
  *
1276
- * ์˜› ์ด๋ฆ„๋งŒ ๊ฐ€์ง„ ๋ชจ๋ธ์ด ์žˆ์œผ๋ฉด **์กฐ์šฉํžˆ ์ƒ์‚ฐ ์„ ์–ธ์„ ์žƒ๋Š” ๋Œ€์‹  ๋ถ„๋ช…ํžˆ ๋ฉˆ์ถ˜๋‹ค** โ€” ๊ทธ ํŠธ์œˆ์€ ๊ณต์ •์ด
1523
+ * ์˜› ์ด๋ฆ„๋งŒ ๊ฐ€์ง„ ๋ชจ๋ธ์ด ์žˆ์œผ๋ฉด **์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์ƒ์‚ฐ ์„ ์–ธ์„ ์žƒ๋Š” ๋Œ€์‹  ๋ถ„๋ช…ํžˆ ๋ฉˆ์ถ˜๋‹ค** โ€” ๊ทธ ํŠธ์œˆ์€ ๊ณต์ •์ด
1277
1524
  * ์—†๋Š” ์ฑ„๋กœ ๋Œ๊ฒŒ ๋˜๊ณ (๋ผ์ธ์ด ์„œ ์žˆ๋Š” ์ฐฝ๊ณ ), ์›์ธ์„ ์ฐพ๊ธฐ ์–ด๋ ต๋‹ค.
1278
1525
  */
1279
1526
  static productionSpecOf(model) {
@@ -1314,6 +1561,7 @@ class TwinEngine {
1314
1561
  return this.instances[key];
1315
1562
  const Kernel = kernelFor(kind);
1316
1563
  const kernel = new Kernel(domainId, undefined, this.productionSpecOf(model));
1564
+ kernel.scopeId = id;
1317
1565
  kernel.loadTwinModel(model);
1318
1566
  /* **์„ธ์šฐ๋Š” ์ชฝ์ด ์•„๋Š” ์‚ฌ์‹ค์€ ์„ธ์šธ ๋•Œ ๋งํ•œ๋‹ค.** ์˜ˆ์ „์—๋Š” ์ฒซ ์ด๋ฒคํŠธ๊ฐ€ ๋„์ฐฉํ•ด์•ผ ์ปค๋„์ด ์Šค์Šค๋กœ๋ฅผ
1319
1567
  ๊ด€์ธก ๊ตฌ๋™์œผ๋กœ ์—ฌ๊ฒผ๊ณ , ๊ทธ๋ž˜์„œ ์•„์ง ์•„๋ฌด๊ฒƒ๋„ ๋ชป ๋ฐ›์€ ๋ฏธ๋Ÿฌ๋Š” ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ทจ๊ธ‰์„ ๋ฐ›์•˜๋‹ค. */
@@ -1322,11 +1570,11 @@ class TwinEngine {
1322
1570
  /* `projector` ํ•„๋“œ๋Š” ์˜› ์ด๋ฆ„์œผ๋กœ ๋‚จ๊ธด๋‹ค โ€” ์†Œ๋น„์ฒ˜๊ฐ€ `snapshot()` ์„ ๋ถ€๋ฅด๋ฏ€๋กœ ์–‡์€ ์–ด๋Œ‘ํ„ฐ๋กœ ์ž‡๋Š”๋‹ค.
1323
1571
  * (P3 ์—์„œ ์†Œ๋น„์ฒ˜๋ฅผ ์ปค๋„ ์–ดํœ˜๋กœ ๋ฐ”๊พธ๋ฉด ์‚ฌ๋ผ์ง„๋‹ค.) */
1324
1572
  const projector = { apply: (e) => kernel.apply(e), snapshot: () => kernel.getSnapshot() };
1325
- const inst = { id, domainId, mode: 'live', restartPolicy: 'resync', kernel, projector, oee: new oee_accumulator_js_1.OeeAccumulator(), spaceId: model?.spaceId, unsub: () => { } };
1573
+ const inst = { id, domainId, mode: 'live', restartPolicy: 'resync', kernel, projector, oee: new oee_accumulator_js_1.OeeAccumulator(), spaceId: model?.spaceId, model, unsub: () => { } };
1326
1574
  /*
1327
1575
  * โ”€โ”€ ์ปค๋„์ด **ํŒ์ •์œผ๋กœ ๋‚ธ ์‚ฌ์‹ค**๋„ ์ €๋„์— ๋‚จ๋Š”๋‹ค (2026-08-14 ์‹ค์ธก์œผ๋กœ ์žก์Œ) โ”€โ”€โ”€โ”€
1328
1576
  *
1329
- * ๋ผ์ด๋ธŒ๋Š” ์ธ์ž… ๋ด‰ํˆฌ๋งŒ ์ €๋„์— ์ ๊ณ  ์žˆ์—ˆ๋‹ค(`ingestLive`). ๊ทธ๋Ÿฐ๋ฐ ์ปค๋„์€ ๊ด€์ธก์„ ์ ‘๋‹ค๊ฐ€ **์ž๊ธฐ ์‚ฌ์‹ค**์„
1577
+ * ๋ผ์ด๋ธŒ๋Š” ์ธ์ž… ๋ด‰ํˆฌ๋งŒ ์ €๋„์— ์ ๊ณ  ์žˆ์—ˆ๋‹ค(`ingestLive`). ๊ทธ๋Ÿฐ๋ฐ ์ปค๋„์€ ๊ด€์ธก์„ ๊ณ„์‚ฐํ•˜๋‹ค๊ฐ€ **์ž๊ธฐ ์‚ฌ์‹ค**์„
1330
1578
  * ๋‚ธ๋‹ค โ€” ์—๋„ˆ์ง€์˜ ์ˆ˜์š” ๊ตฌ๊ฐ„ ๋งˆ๊ฐยทํ”ผํฌ ๊ฒฝ์‹ ยท๊ฐ์ถ• ์ œ์•ˆ์ด ๊ทธ๋ ‡๋‹ค(`emitOp`). ๊ทธ๊ฒƒ์„ ๊ตฌ๋…ํ•˜๋Š” ๊ณณ์ด
1331
1579
  * ์—†์–ด์„œ ๊ทธ ์‚ฌ์‹ค๋“ค์ด **์ปค๋„ ์•ˆ์—์„œ ์‚ฌ๋ผ์กŒ๋‹ค**: ํ‘œ๋ณธ 48๊ฑด์ด ์ €๋„์— ์Œ“์˜€๋Š”๋ฐ ๊ตฌ๊ฐ„ ๋งˆ๊ฐ์€ 0๊ฑด์ด์—ˆ๊ณ ,
1332
1580
  * ์ €๋„์„ ์ฝ๋Š” ์„ฑ๊ณผ ํ™”๋ฉด์˜ ์ „๋ ฅ ํƒ€์ผ์€ ์˜์›ํžˆ ๋‚˜์˜ค์ง€ ์•Š์•˜๋‹ค.
@@ -1353,7 +1601,7 @@ class TwinEngine {
1353
1601
  this.installEstimators(kernel, domainId, id, model).catch(err => (0, log_js_1.twinWarn)('[twin-engine] estimator install failed', err?.message));
1354
1602
  inst.metrics = this.newMetrics();
1355
1603
  this.instances[key] = inst;
1356
- /* ๋ฏธ๋Ÿฌ์—๋„ ๋ถ€๋ฅธ๋‹ค โ€” ์„ ์–ธ์ด ์žˆ์œผ๋ฉด ใ€Œ๋ฏธ๋Ÿฌ์—๋Š” ์‹ฃ์ง€ ์•Š๋Š”๋‹คใ€๊ณ  ๋งํ•ด์•ผ ํ•œ๋‹ค(์กฐ์šฉํ•œ ๋ฌด์‹œ ๊ธˆ์ง€). */
1604
+ /* ๋ฏธ๋Ÿฌ์—๋„ ๋ถ€๋ฅธ๋‹ค โ€” ์„ ์–ธ์ด ์žˆ์œผ๋ฉด ใ€Œ๋ฏธ๋Ÿฌ์—๋Š” ์‹ฃ์ง€ ์•Š๋Š”๋‹คใ€๊ณ  ๋งํ•ด์•ผ ํ•œ๋‹ค(๋“ค์–ด์˜จ ๊ฒƒ์ด ์—†๋Š” ๋ฌด์‹œ ๊ธˆ์ง€). */
1357
1605
  this.installStimulus(domainId, id, inst).catch(err => (0, log_js_1.twinWarn)(`[twin-engine] "${id}": stimulus check failed โ€” ${err?.message ?? err}`));
1358
1606
  /*
1359
1607
  * **์›์ฒœ์ด ๋˜ํ’€์–ด ์ฃผ์ง€ ์•Š๋Š” ๊ฒƒ๋งŒ ์ž‡๋Š”๋‹ค** (2026-08-18 ์‹ค์ธก์œผ๋กœ ๋ถ™์ž„).
@@ -1374,11 +1622,11 @@ class TwinEngine {
1374
1622
  * ์ •์ •์ด ์˜ค์ง€ ์•Š๋Š”๋‹ค: ์ปค์„œ๊ฐ€ ๋”ฐ๋ผ์žกํžŒ ๋’ค ์›๋ณธ์ด ๋ณ€ํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฏธ๋Ÿฌ๋Š” ์˜๊ตฌํžˆ ๋นˆ ์ฑ„๋กœ ๋‚จ๋Š”๋‹ค.
1375
1623
  * ๊ทธ๋ฆฌ๊ณ  ๊ทธ ๋นˆ ์ฑ„๋กœ ํ™”๋ฉด์ด ใ€Œ์ด์ƒ ์—†์Œใ€์„ ๋ณด์˜€๋‹ค โ€” ์‚ฌ์‹ค์ด ์‚ฌ๋ผ์ง€๋Š” ๋™์•ˆ ํ™”๋ฉด์ด ์•ˆ์‹ฌ์‹œํ‚จ ๊ฒƒ์ด๋‹ค.
1376
1624
  *
1377
- * ๋˜๋Œ๋ฆฌ๋Š” ๊ฒƒ์€ **์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ ์žฌ๊ฐœ์ **์ด๋‹ค. ์ƒํƒœ๋งŒ ์‹ฌ์œผ๋ฉด ๊ทธ ๋’ค๋ฅผ ์ด์–ด ์ ‘์€ ๊ฒฐ๊ณผ๊ฐ€ 0๋ถ€ํ„ฐ ์ ‘์€
1378
- * ๊ฒฐ๊ณผ์™€ ์กฐ์šฉํžˆ ๋‹ฌ๋ผ์ง„๋‹ค(๋ฆฌ๋“€์„œ๋Š” ๋ณด๋ฅ˜๋œ ๋‹ด๊น€ยท์ง‘๊ณ„ ์ค‘์ธ ์ˆ˜๋Ÿ‰๋„ ๋“ ๋‹ค). ๊ทธ ๋™์น˜๋Š” ์ปค๋„ ์‹œํ—˜์ด
1379
- * ์ฆ๋ช…ํ•œ๋‹ค(`observed-checkpoint.test.ts` โ€” ์žฌ๊ฐœ์  + ๊ผฌ๋ฆฌ == 0๋ถ€ํ„ฐ ์ ‘๊ธฐ).
1625
+ * ๋˜๋Œ๋ฆฌ๋Š” ๊ฒƒ์€ **์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ ์žฌ๊ฐœ์ **์ด๋‹ค. ์ƒํƒœ๋งŒ ์‹ฌ์œผ๋ฉด ๊ทธ ๋’ค๋ฅผ ์ด์–ด ๊ณ„์‚ฐํ•œ ๊ฒฐ๊ณผ๊ฐ€ 0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•œ
1626
+ * ๊ฒฐ๊ณผ์™€ ์˜ค๋ฅ˜ ์—†์ด ๋‹ฌ๋ผ์ง„๋‹ค(๋ฆฌ๋“€์„œ๋Š” ๋ณด๋ฅ˜๋œ ๋‹ด๊น€ยท์ง‘๊ณ„ ์ค‘์ธ ์ˆ˜๋Ÿ‰๋„ ๋“ ๋‹ค). ๊ทธ ๋™์น˜๋Š” ์ปค๋„ ์‹œํ—˜์ด
1627
+ * ์ฆ๋ช…ํ•œ๋‹ค(`observed-checkpoint.test.ts` โ€” ์žฌ๊ฐœ์  + ๊ผฌ๋ฆฌ == 0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•˜๊ธฐ).
1380
1628
  *
1381
- * ์”จ์•—์€ **๊ทธ ๊ณต์žฅ์ด ์•„์ง ๊ทธ ๊ณต์žฅ์ผ ๋•Œ๋งŒ** ์˜ค๊ณ , ๋งˆ์ง€๋ง‰ ์ฒดํฌํฌ์ธํŠธ ์ดํ›„์˜ ์‚ฌ์‹ค์€ ์ด๋ฏธ ์ ‘ํ˜€ ๋“ค์–ด
1629
+ * ์”จ์•—์€ **๊ทธ ๊ณต์žฅ์ด ์•„์ง ๊ทธ ๊ณต์žฅ์ผ ๋•Œ๋งŒ** ์˜ค๊ณ , ๋งˆ์ง€๋ง‰ ์ฒดํฌํฌ์ธํŠธ ์ดํ›„์˜ ์‚ฌ์‹ค์€ ์ด๋ฏธ ๊ณ„์‚ฐ๋˜์–ด ๋“ค์–ด
1382
1630
  * ์žˆ๋‹ค(`warmSeedFor`). ์”จ์•—์ด ์—†์œผ๋ฉด ์ „๊ณผ ๊ฐ™์ด ๋นˆ ์ฑ„๋กœ ์‹œ์ž‘ํ•œ๋‹ค โ€” ์ง€์–ด๋‚ด์ง€ ์•Š๋Š”๋‹ค.
1383
1631
  */
1384
1632
  const seed = this.recovered[key]?.fold?.reducer;
@@ -1428,10 +1676,19 @@ class TwinEngine {
1428
1676
  * ๋น„๋™๊ธฐ๋ผ ๊ทธ ์‚ฌ์ด์— ์ด๋ฒคํŠธ๊ฐ€ ๋ช‡ ๊ฑด ๋“ค์–ด์™€ ์žˆ์„ ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ทธ๋•Œ ๋’ค๋กœ ๋˜๋Œ๋ฆฌ๋Š” ๊ฒƒ์€ ์ปค๋„์ด ๊ฑฐ์ ˆํ•œ๋‹ค
1429
1677
  * (๊ฒน์น˜๋Š” ๋ฒˆํ˜ธ๋ฅผ ๋ง‰๋Š” ๊ทธ ํŒ์ •์ด๋‹ค). ๊ฑฐ์ ˆ์€ ์‚ผํ‚ค์ง€ ์•Š๊ณ  ๋‚จ๊ธด๋‹ค.
1430
1678
  */
1679
+ /*
1680
+ * ๊ด€๊ณ„๋กœ ๋ฌป์ง€ ์•Š๋Š”๋‹ค โ€” `findOne({ where: { domain: { id } } })` ์€ ๋‘ ๋‹จ๊ณ„ ์งˆ์˜๊ฐ€ ๋˜๊ณ  ์•ˆ์ชฝ์—
1681
+ * ์ƒํ•œ์ด ์—†์–ด ๊ทธ ํŠธ์œˆ์˜ ์ €๋„์„ **์ „๋ถ€ ๋งŒ๋“  ๋’ค** ํ•œ ์ค„์„ ๊ณ ๋ฅธ๋‹ค(ยง`tipOf` ์˜ ์‹ค์ธก). ์—ฌ๊ธฐ๋Š” ๋ถ€ํŒ…
1682
+ * ๊ฒฝ๋กœ์ด๋ฏ€๋กœ ๊ทธ ๋น„์šฉ์ด ๊ธฐ๋™์„ ๋ถ™์žก๋Š”๋‹ค. ์ƒ‰์ธ์˜ ๋ ํ•œ ์ค„๋งŒ ์ฝ๋Š”๋‹ค.
1683
+ */
1431
1684
  (0, shell_1.getRepository)(twin_event_js_1.TwinEvent)
1432
- .findOne({ where: { domain: { id: domainId }, instanceId: id }, order: { revision: 'DESC' } })
1685
+ .createQueryBuilder('e')
1686
+ .select('MAX(e.revision)', 'max')
1687
+ .where('e.domain = :domainId', { domainId })
1688
+ .andWhere('e.instanceId = :instanceId', { instanceId: id })
1689
+ .getRawOne()
1433
1690
  .then(top => {
1434
- const head = top?.revision ?? 0;
1691
+ const head = Number(top?.max) || 0;
1435
1692
  inst.revision = head;
1436
1693
  if (!head || typeof kernel.resumeRevision !== 'function')
1437
1694
  return;
@@ -1455,18 +1712,107 @@ class TwinEngine {
1455
1712
  *
1456
1713
  * โ”€โ”€ ๋„ฃ์€ ์ˆ˜๋ฅผ **๋‹ตํ•œ๋‹ค** (2026-08-20) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1457
1714
  * ํŠธ์œˆ์ด ๋ผ์ด๋ธŒ๋กœ ๋Œ์ง€ ์•Š์œผ๋ฉด ์—ฌ๊ธฐ์„œ ๋ด‰ํˆฌ๋ฅผ ๋ฒ„๋ฆฐ๋‹ค. ๊ทธ๊ฒƒ ์ž์ฒด๋Š” ๋งž๋‹ค(๋„ฃ์„ ์ปค๋„์ด ์—†๋‹ค). ๋ฌธ์ œ๋Š”
1458
- * **์กฐ์šฉํžˆ** ๋ฒ„๋ฆฐ ๊ฒƒ์ด์—ˆ๋‹ค: ํŠธ์œˆ์ด ๋ฉˆ์ถ˜ ๋’ค์—๋„ ํ”ผ๋“œ๋Š” ๋‚จ์•„ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋‚˜๋ฅด๊ณ , ์œ ์ž… ์žฅ๋ถ€๋Š” ๊ทธ๊ฒƒ์„
1715
+ * **์•Œ๋ฆฌ์ง€ ์•Š๊ณ ** ๋ฒ„๋ฆฐ ๊ฒƒ์ด์—ˆ๋‹ค: ํŠธ์œˆ์ด ๋ฉˆ์ถ˜ ๋’ค์—๋„ ํ”ผ๋“œ๋Š” ๋‚จ์•„ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋‚˜๋ฅด๊ณ , ์œ ์ž… ์žฅ๋ถ€๋Š” ๊ทธ๊ฒƒ์„
1459
1716
  * ใ€Œํ†ต๊ณผใ€๋กœ ์…Œ๋‹ค. ํ™”๋ฉด์€ ๋ฉˆ์ถ˜ ํŠธ์œˆ ์˜†์— ใ€Œ150 ํ†ต๊ณผ ยท 100%ใ€๋ผ๊ณ  ์ ์—ˆ๋‹ค โ€” ์‚ฌ์‹ค์ด ์‚ฌ๋ผ์ง€๋Š” ๋™์•ˆ
1460
1717
  * ํ™”๋ฉด์ด ์•ˆ์‹ฌ์‹œํ‚จ ๊ฒƒ์ด๋‹ค.
1461
1718
  *
1462
1719
  * ๊ทธ๋ž˜์„œ **๋„ฃ์€ ์ˆ˜๋ฅผ ๋Œ๋ ค์ค€๋‹ค.** ๋ถ€๋ฅด๋Š” ์ชฝ์ด ์ œ์‹œ ์ˆ˜์™€ ๊ฒฌ์ฃผ์–ด ๋ฒ„๋ ค์ง„ ์ˆ˜๋ฅผ ์žฅ๋ถ€์— ์ ๋Š”๋‹ค. ๋ฐ˜ํ™˜์„
1463
1720
  * ๋ฌด์‹œํ•˜๋Š” ํ˜ธ์ถœ๋ถ€๋Š” ๊ทธ๋Œ€๋กœ ๋™์ž‘ํ•œ๋‹ค(์ „๊ณผ ๊ฐ™๋‹ค).
1464
1721
  */
1465
- static ingestLive(domainId, id, envelopes) {
1722
+ /**
1723
+ * ์œ ์ž… ๊ฒฐ๊ณผ๋ฅผ ์ˆ˜๋กœ ๋‚ธ๋‹ค โ€” ๋ฐ›์€ ์ˆ˜ ยท ์ค‘๋ณต์œผ๋กœ ๋ฒ„๋ฆฐ ์ˆ˜ ยท ๋ณด๋‚ธ ์ˆ˜.
1724
+ *
1725
+ * `ingestLive` ๋Š” ๋ฐ›์€ ์ˆ˜๋งŒ ๋Œ๋ ค์ฃผ๋Š”๋ฐ, ์›นํ›…์œผ๋กœ ๋ฐ›์„ ๋•Œ๋Š” **๋ช‡ ๊ฑด์ด ์ค‘๋ณต์ด์—ˆ๋Š”์ง€๋„ ๋‹ตํ•ด์•ผ** ํ•œ๋‹ค.
1726
+ * ๋ฐ€์–ด ์ฃผ๋Š” ์ชฝ์ด ๊ทธ ์ˆ˜๋ฅผ ๋ณด๊ณ  ์ž๊ธฐ๊ฐ€ ๋˜ํ’€์ด ๋ณด๋‚ด๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๊ณ , ๊ทธ๊ฒƒ์„ ๋ชจ๋ฅด๋ฉด
1727
+ * ์šฐ๋ฆฌ ์‘๋‹ต์ด ใ€Œ์ „๋ถ€ ์ƒˆ ๊ฒƒ์ด์—ˆ๋‹คใ€๋กœ ์ฝํžŒ๋‹ค.
1728
+ */
1729
+ static ingestLiveResult(domainId, id, envelopes, source = 'poll') {
1730
+ const before = this.ingestLedgers[(0, runtime_key_js_1.runtimeKey)(domainId, id)]?.duplicates ?? 0;
1731
+ const offered = envelopes.length;
1732
+ const applied = this.ingestLive(domainId, id, envelopes, source);
1733
+ const after = this.ingestLedgers[(0, runtime_key_js_1.runtimeKey)(domainId, id)]?.duplicates ?? 0;
1734
+ return { offered, applied, duplicates: Math.max(0, after - before) };
1735
+ }
1736
+ /**
1737
+ * **๋งˆ์Šคํ„ฐ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๋Š”๋‹ค** โ€” ๋ณ€ํ•˜์ง€ ์•Š๋Š” ์†์„ฑ์„ ์ƒํƒœ์— ์„ธ์šด๋‹ค. ์ €๋„์— ์ ์ง€ ์•Š๋Š”๋‹ค.
1738
+ *
1739
+ * โ”€โ”€ ์™œ ์ €๋„์— ์ ์ง€ ์•Š๋‚˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1740
+ * ์œ ํ†ต๊ธฐํ•œยท๋กœํŠธ๋ฒˆํ˜ธ ๊ฐ™์€ ๊ฐ’์€ ๊ทธ ๋ฌผ๊ฑด์˜ ์ƒ์•  ๋™์•ˆ ๊ฐ™๋‹ค. ๊ทธ๋ž˜์„œ ใ€Œ๊ทธ๋•Œ๋Š” ์–ผ๋งˆ์˜€๋‚˜ใ€๋ผ๋Š” ๋ฌผ์Œ์ด
1741
+ * ์„ฑ๋ฆฝํ•˜์ง€ ์•Š๋Š”๋‹ค โ€” ์‹œ๊ฐ์ถ•์ด ์—†๋Š” ๊ฐ’์ด๋ฏ€๋กœ ์‚ฌ๊ฑด์ด ์•„๋‹ˆ๋‹ค. ๋˜์„ธ์šธ ๊ทผ๊ฑฐ๋Š” ์ค‘๊ฐ„ ์ €์žฅ๋ณธ์ด ๋“ ๋‹ค
1742
+ * (๋ฆฌ๋“€์„œ๊ฐ€ ๊ทธ ๊ฐ’์„ ์ €์žฅ๋ณธ์— ๋‹ด๋Š”๋‹ค).
1743
+ *
1744
+ * โ”€โ”€ ์ด ๋ฌธ์ด ์—†์–ด์„œ ๋ฌด์—‡์ด ๋‚ฌ๋‚˜ (2026-08-28 ์‹ค์ธก) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1745
+ * ํ‘œ์ค€์€ ๊ทธ ๊ฐ’์„ `ObjectEvent(action=ADD)` ์—๋งŒ ์‹ค์„ ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ์ „๋Ÿ‰์„ ์ฝ๋Š” ์›๋ณธ์ด
1746
+ * ์žฌ๊ณ ๋ฅผ ๋‹ค์‹œ ๋งํ•  ๋•Œ๋งˆ๋‹ค **๋“ค์–ด์˜ค์ง€ ์•Š์€ ๊ฒƒ์„ ใ€Œ๋“ค์–ด์™”๋‹คใ€** ๊ณ  ํ–ˆ๊ณ , ์‚ฌ๊ฑด ๋‹จ์œ„ ๊ฐ’์ด๋ผ ์ž๋ฆฌ๋ณ„๋กœ
1747
+ * ๋ฌถ์„ ์ˆ˜๋„ ์—†์–ด ๋‚ฑ๊ฐœ๋กœ ๋‚˜๊ฐ”๋‹ค โ€” ์žฌ๊ธฐ๋™ ํ•œ ๋ฒˆ์— 2,665๊ฑด์ด ใ€Œ์ฝ์€ ์‹œ๊ฐใ€์œผ๋กœ ์Œ“์˜€๋‹ค.
1748
+ *
1749
+ * ๋Œ๋ ค์ฃผ๋Š” ์ˆ˜๋Š” **์ƒํƒœ์— ๋ฐ˜์˜๋œ ๊ฑด์ˆ˜**๋‹ค. ๋ผ์ด๋ธŒ๋กœ ๋Œ์ง€ ์•Š๋Š” ํŠธ์œˆ์€ 0 ์„ ๋‚ธ๋‹ค โ€” ๊ทธ ์ฐจ์ด๋ฅผ ๋ถ€๋ฅด๋Š”
1750
+ * ์ชฝ์ด ์•Œ์•„์•ผ ํ•œ๋‹ค(๋ฒ„๋ ค์ง„ ๊ฒƒ์„ ํ†ต๊ณผ๋กœ ์„ธ๋ฉด ๋ฉˆ์ถ˜ ํŠธ์œˆ์ด ์ •์ƒ์œผ๋กœ ๋ณด์ธ๋‹ค).
1751
+ *
1752
+ * ์„ค๊ณ„: `operato-twin/design/plans/master-data.md`
1753
+ */
1754
+ static applyMasterData(domainId, id, elements) {
1755
+ if (!elements.length)
1756
+ return 0;
1757
+ const inst = this.instances[(0, runtime_key_js_1.runtimeKey)(domainId, id)];
1758
+ if (inst?.mode !== 'live' || !inst.kernel)
1759
+ return 0;
1760
+ const kernel = inst.kernel;
1761
+ if (typeof kernel.applyMasterData !== 'function') {
1762
+ (0, log_js_1.twinWarn)(`[twin-engine] kernel for "${id}" cannot take master data (no applyMasterData) โ€” ` +
1763
+ 'lot attributes such as expiry will stay empty for this twin.');
1764
+ return 0;
1765
+ }
1766
+ const n = kernel.applyMasterData(elements);
1767
+ /*
1768
+ * **๋Œ์•˜๋‹ค๋Š” ๊ฒƒ์„ ๋กœ๊ทธ๋กœ ๋‚จ๊ธด๋‹ค** (2026-08-28).
1769
+ *
1770
+ * ์ด ๊ฐ’์€ ์ง€๋‚œ ๊ธฐ๋ก์— ์ ํžˆ์ง€ ์•Š์œผ๋ฏ€๋กœ(์‹œ๊ฐ์ถ•์ด ์—†๋‹ค) ์„ธ์ง€ ์•Š์œผ๋ฉด ์ด ํ†ต๋กœ๊ฐ€ ๋Œ์•˜๋Š”์ง€ ์•„๋ฌด ๋ฐ๋„
1771
+ * ๋‚จ์ง€ ์•Š๋Š”๋‹ค. ์ฒ˜์Œ์—๋Š” ์œ ์ž… ์žฅ๋ถ€์— ์ˆ˜๋ฅผ ๋”ํ–ˆ๋Š”๋ฐ **๊ทธ๊ฒƒ์ด ํŽธ๋ฒ•์ด์—ˆ๋‹ค**: ์œ ์ž… ์žฅ๋ถ€๋Š” ใ€Œ์›๋ณธ์—์„œ
1772
+ * ์‚ฌ์‹ค์ด ์–ผ๋งˆ๋‚˜ ๋“ค์–ด์˜ค๊ณ  ๊ฑฐ๋ถ€๋˜๊ณ  ๋ฒ„๋ ค์ง€๋‚˜ใ€๋ฅผ ๋‹ตํ•˜๋Š” ์ž๋ฆฌ์ด๊ณ , ๋งˆ์Šคํ„ฐ๋ฐ์ดํ„ฐ๋Š” ์‚ฌ์‹ค์˜ ํ๋ฆ„์ด ์•„๋‹ˆ๋ผ
1773
+ * ์„ ์–ธ์˜ ๋™๊ธฐ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ˆ˜๋ฅผ ์ฝ๋Š” ํ™”๋ฉด์ด ์—†์—ˆ๋‹ค โ€” ๋‚ด๊ฐ€ ํ™•์ธํ•˜๋ ค๊ณ  ๋งŒ๋“  ๊ฐ’์ด์—ˆ๋‹ค.
1774
+ *
1775
+ * ํ™•์ธ์€ **๋กœ๊ทธ์˜ ์ผ**์ด๋‹ค. ์ •์ƒ ์ƒํƒœ์—์„œ๋Š” ์ด ์ค„์ด ์•ˆ ๋‚˜์˜ค๋Š” ๊ฒƒ์ด ๋งž๋‹ค โ€” ๊ฐ’์ด ๊ทธ๋Œ€๋กœ๋ฉด ์›๋ณธ์ด
1776
+ * ๋‹ค์‹œ ๋งํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋‚˜์˜ค๋ฉด ๊ทธ๋•Œ๊ฐ€ ๋ฌด์–ธ๊ฐ€ ๋ฐ”๋€ ๋•Œ๋‹ค.
1777
+ */
1778
+ if (n > 0)
1779
+ (0, log_js_1.twinLog)(`[twin-engine] "${id}": ๋งˆ์Šคํ„ฐ๋ฐ์ดํ„ฐ ${n}๊ฑด์„ ์ƒํƒœ์— ์„ธ์› ๋‹ค(์ง€๋‚œ ๊ธฐ๋ก์—๋Š” ์ ์ง€ ์•Š๋Š”๋‹ค).`);
1780
+ /* ์ƒํƒœ๊ฐ€ ๋ฐ”๋€Œ์—ˆ์œผ๋‹ˆ ๋‹ค์Œ ์ฐฝ์—์„œ ํ™”๋ฉด์œผ๋กœ ํ˜๋Ÿฌ์•ผ ํ•œ๋‹ค โ€” ๊ด€์ธก ๋ฐ˜์˜๊ณผ ๊ฐ™์€ ๊ทœ์œจ์ด๋‹ค. */
1781
+ inst.dirty = true;
1782
+ this.ensureBroadcastCoalescer();
1783
+ return n;
1784
+ }
1785
+ static ingestLive(domainId, id, envelopes, source = 'poll') {
1466
1786
  const inst = this.instances[(0, runtime_key_js_1.runtimeKey)(domainId, id)];
1467
1787
  if (inst?.mode !== 'live' || !inst.projector)
1468
1788
  return 0;
1469
1789
  const tIngest = performance.now();
1790
+ /*
1791
+ * โ”€โ”€ ๊ฐ™์€ ์‚ฌ์‹ค์„ ๋‘ ๋ฒˆ ๋ฐ›์œผ๋ฉด ํ•œ ๋ฒˆ๋งŒ ๋ฐ˜์˜ํ•œ๋‹ค (2026-08-26) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1792
+ *
1793
+ * ์ด ์ž๋ฆฌ๊ฐ€ ์œ ์ž… ๊ฒฝ๊ณ„๋‹ค โ€” ์ปค๋„ฅํ„ฐ๊ฐ€ ๋ฌผ์–ด์„œ ๋ฐ›์€ ๊ฒƒ ยท ์‹œ๋‚˜๋ฆฌ์˜ค๊ฐ€ ๋„ฃ๋Š” ๊ฒƒ ยท ๋ถ€ํ•˜ ๋„๊ตฌ๊ฐ€ ๋„ฃ๋Š” ๊ฒƒ์ด
1794
+ * ๋ชจ๋‘ ์—ฌ๊ธฐ๋ฅผ ์ง€๋‚œ๋‹ค(์›นํ›…์œผ๋กœ ๋ฐ›๋Š” ๊ธธ์ด ์ƒ๊ธฐ๋ฉด ๊ทธ๊ฒƒ๋„ ์—ฌ๊ธฐ๋กœ ์˜จ๋‹ค). ๊ทธ๋ž˜์„œ ์ค‘๋ณต์„ ์—ฌ๊ธฐ์„œ ํ•œ ๋ฒˆ
1795
+ * ๊ฑฐ๋ฅธ๋‹ค.
1796
+ *
1797
+ * ์ง€๊ธˆ๊นŒ์ง€๋Š” ์ปค๋„ฅํ„ฐ๋งˆ๋‹ค ๊ฐ์ž ๋ง‰๊ณ  ์žˆ์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์žŠ์œผ๋ฉด ํŠธ์œˆ์ด ๊ทธ๋Œ€๋กœ ๋‘ ๋ฒˆ ์…Œ๋‹ค โ€” ํƒœ์–‘๊ด‘
1798
+ * ์ปค๋„ฅํ„ฐ๊ฐ€ ํ•œ ์ฃผ๊ธฐ์— 76๊ฑด์„ ๋˜ํ’€์ด ๋ณด๋ƒˆ๊ณ , ์ปค๋„ฅํ„ฐ๋ฅผ ๊ณ ์ณ์„œ 2๊ฑด์ด ๋๋‹ค. ๋‹ค์Œ ์ปค๋„ฅํ„ฐ๊ฐ€ ๊ฐ™์€ ๊ฒƒ์„
1799
+ * ์žŠ์œผ๋ฉด ๋˜ ๋‚œ๋‹ค.
1800
+ *
1801
+ * ์ ˆ๋Œ€๊ฐ’์œผ๋กœ ๋งํ•˜๋Š” ์‚ฌ์‹ค์€ ๋‘ ๋ฒˆ ๋ฐ˜์˜ํ•ด๋„ ๊ฒฐ๊ณผ๊ฐ€ ๊ฐ™์ง€๋งŒ(์ž”๋Ÿ‰ ยท ์ƒํƒœ ยท ์ ์‚ฐ), ์„ธ๋Š” ๊ฐ’์€ ์•„๋‹ˆ๋‹ค:
1802
+ * ๊ตฌ๊ฐ„์˜ ํ‘œ๋ณธ ์ˆ˜ ยท ํ‰๊ท  ๋ถ€ํ•˜ ยท OEE ์นด์šดํ„ฐ ยท ์œ ์ž… ๊ณ„์ˆ˜. ๊ทธ๋ฆฌ๊ณ  ์ง€๋‚œ ๊ธฐ๋ก์— ๊ฐ™์€ ์‚ฌ์‹ค์ด ๋‘ ์ค„๋กœ
1803
+ * ๋‚จ์œผ๋ฉด, ๋‚˜์ค‘์— ๊ทธ ๊ธฐ๋ก์œผ๋กœ ๋‹ค์‹œ ๊ณ„์‚ฐํ•  ๋•Œ ๋˜ ๋‘ ๋ฒˆ ์„ผ๋‹ค.
1804
+ *
1805
+ * **์ •ํ™•ํžˆ ๊ฐ™์€ ๊ฒƒ๋งŒ ๋ฒ„๋ฆฐ๋‹ค.** ๋Šฆ๊ฒŒ ๋„์ฐฉํ•œ ์˜› ์‚ฌ์‹ค์€ ๋ฒ„๋ฆฌ์ง€ ์•Š๋Š”๋‹ค โ€” ๊ทธ๊ฒƒ์ด ์™”๋‹ค๋Š” ๊ฒƒ๋„ ์‚ฌ์‹ค์ด๊ณ ,
1806
+ * ์ƒํƒœ์— ๋ฐ˜์˜ํ•˜์ง€ ์•Š๋Š” ํŒ๋‹จ์€ ์ปค๋„์ด ์ด๋ฏธ ํ•œ๋‹ค.
1807
+ */
1808
+ const { fresh, duplicates } = (inst.deduper ??= new ingest_dedupe_js_1.FactDeduper()).filter(envelopes);
1809
+ envelopes = fresh;
1810
+ const ledgerKey = (0, runtime_key_js_1.runtimeKey)(domainId, id);
1811
+ const ledger = this.ingestLedgers[ledgerKey] ?? (this.ingestLedgers[ledgerKey] = (0, ingest_health_js_1.newIngestLedger)());
1812
+ if (duplicates)
1813
+ (0, ingest_health_js_1.recordDuplicates)(ledger, duplicates, Date.now());
1814
+ if (envelopes.length)
1815
+ (0, ingest_health_js_1.recordIngestSource)(ledger, source, envelopes.length, Date.now());
1470
1816
  /* ์ธ์ž… ๋ด‰ํˆฌ๋ฅผ ํ‘œ์‹œํ•ด ๋‘๊ณ  ๋„ฃ๋Š”๋‹ค โ€” ์ปค๋„์ด ๊ทธ๊ฒƒ์„ ์žฌ๋ฐฉ์ถœํ•ด๋„ ์ €๋„์— ๋‘ ๋ฒˆ ์ ํžˆ์ง€ ์•Š๊ฒŒ(์œ„ ๊ตฌ๋… ์ฃผ์„). */
1471
1817
  for (const e of envelopes) {
1472
1818
  if (e && typeof e === 'object')
@@ -1491,20 +1837,41 @@ class TwinEngine {
1491
1837
  * ๊ตฌ๊ฐ„ ์„ฑ๊ณผ ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ…์€ **์—†์•ด๋‹ค**(2026-08-06). ์นด๋“œ๊ฐ€ `twinKpi` ๋ฅผ ์ง์ ‘ ๋ฌป๋Š”๋‹ค.
1492
1838
  *
1493
1839
  * ์™œ: ์นด๋“œ๋ฅผ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„(๊ณต๊ฐ„ยทํŠธ์œˆยท๊ตฌ์—ญยท์ž๋ฆฌยท์„ค๋น„)์— ๋ถ™์ด๋ ค๋ฉด ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ…์œผ๋กœ๋Š” ํƒœ๊ทธ๊ฐ€ ํŠธ์œˆ๋‹น 1,200๊ฐœ๊ฐ€ ๋˜๊ณ ,
1494
- * **๋ชจ๋ธ์— ์นด๋“œ๋ฅผ ํ•˜๋‚˜๋„ ์•ˆ ๋†“์•„๋„** 30์ดˆ๋งˆ๋‹ค ํŠธ์œˆ๋งˆ๋‹ค ์ €๋„์„ ์ ‘์—ˆ๋‹ค. ์งˆ์˜๋กœ ๋ฐ”๊พธ๋‹ˆ ๋ณด๊ณ  ์žˆ๋Š” ์นด๋“œ
1840
+ * **๋ชจ๋ธ์— ์นด๋“œ๋ฅผ ํ•˜๋‚˜๋„ ์•ˆ ๋†“์•„๋„** 30์ดˆ๋งˆ๋‹ค ํŠธ์œˆ๋งˆ๋‹ค ์ €๋„์„ ๊ณ„์‚ฐํ–ˆ๋‹ค. ์งˆ์˜๋กœ ๋ฐ”๊พธ๋‹ˆ ๋ณด๊ณ  ์žˆ๋Š” ์นด๋“œ
1495
1841
  * ์ˆ˜๋งŒํผ๋งŒ ๋“ค๊ณ , ๊ฐ™์€ (๋Œ€์ƒยท์ฐฝยท์ถ•) ์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ•˜๋‚˜๋กœ ํ•ฉ์นœ๋‹ค.
1496
1842
  *
1497
1843
  * ๋ค์œผ๋กœ ์งˆ์˜๋งŒ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด ๋‘˜ ์ƒ๊ฒผ๋‹ค โ€” **๊ณผ๊ฑฐ ์‹œ๊ฐ**(`toTime`)๊ณผ **๊ณต๊ฐ„ ๋‹จ์œ„ ํ•ฉ์‚ฐ**(์—ฌ๋Ÿฌ ํŠธ์œˆ์„
1498
- * ํ•œ ๋ฒˆ์— ์ ‘๊ธฐ). ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ… ๋ฃจํ”„๋Š” ํŠธ์œˆ๋ณ„์ด๋ผ ๋‘˜ ๋‹ค ๋ชป ํ–ˆ๋‹ค.
1844
+ * ํ•œ ๋ฒˆ์— ๊ณ„์‚ฐ). ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ… ๋ฃจํ”„๋Š” ํŠธ์œˆ๋ณ„์ด๋ผ ๋‘˜ ๋‹ค ๋ชป ํ–ˆ๋‹ค.
1499
1845
  *
1500
1846
  * ์ถ•์„ ๋‚˜๋ˆ ๋„ ํด๋“œ ๋น„์šฉ์ด ๊ฐ™๋‹ค๋Š” ์‹ค์ธก์ด ๊ทผ๊ฑฐ๋‹ค(`test/kpi-query-bench.test.ts`).
1501
1847
  */
1502
1848
  /**
1503
1849
  * ๋ช‡ ์ฐฝ๋งˆ๋‹ค ํ•œ ๋ฒˆ์€ **์ „๋ถ€** ๋งŒ๋“œ๋‚˜ โ€” ์‚ฌ๊ฑด ์—†์ด ๊ฐ’์ด ๋ฐ”๋€Œ๋Š” ์ž๋ฆฌ์— ๋Œ€ํ•œ ๊ทธ๋ฌผ.
1504
1850
  *
1505
- * 25 ์ฐฝ์ด๋ฉด ๊ธฐ๋ณธ ์ฃผ๊ธฐ์—์„œ 5์ดˆ๋‹ค. ๋ณด์žฅ์ด ์•„๋‹ˆ๋ผ ๊ทธ๋ฌผ์ด๋‹ค(์œ„ `publishEntityData` ์ฃผ์„).
1851
+ * ๋ณด์žฅ์ด ์•„๋‹ˆ๋ผ ๊ทธ๋ฌผ์ด๋‹ค(์œ„ `publishEntityData` ์ฃผ์„). **์„ธ๋Š” ๋‹จ์œ„๋Š” ์ฐฝ์ด์ง€๋งŒ ๋œป์€ ์‹œ๊ฐ„**์ด๋ฏ€๋กœ,
1852
+ * ๊ธฐ๋ณธ ์ฃผ๊ธฐ๋ฅผ 200๋ฐ€๋ฆฌ์ดˆ์—์„œ 1์ดˆ๋กœ ๋Š˜๋ฆฐ ๋‚  ์ด ์ˆ˜๋„ ํ•จ๊ป˜ ์ค„์˜€๋‹ค(25์ฐฝ โ†’ 5์ฐฝ). ๊ทธ๋Ÿฌ์ง€ ์•Š์œผ๋ฉด ๊ทธ๋ฌผ์ด
1853
+ * 5์ดˆ์—์„œ 25์ดˆ๋กœ ๋ฒŒ์–ด์ ธ, ์‹œ๊ฐ๋งŒ์œผ๋กœ ๋ฐ”๋€Œ๋Š” ๊ฐ’(์œ ํšจ ๊ธฐ๊ฐ„ ๋งŒ๋ฃŒ ๊ฐ™์€ ๊ฒƒ)์ด ๊ทธ๋งŒํผ ๋Šฆ๊ฒŒ ๋ณด์ธ๋‹ค.
1854
+ */
1855
+ static { this.FULL_BROADCAST_EVERY = 5; }
1856
+ /**
1857
+ * **ํ•œ ์‹คํ–‰ ์ฐจ๋ก€์— ๋ฐœํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฑด์ˆ˜** โ€” ์ด๋งŒํผ ๋ฐœํ–‰ํ•˜๋ฉด ๋ฃจํ”„๋ฅผ ๋น„์›Œ ์ค€๋‹ค (2026-08-27).
1858
+ *
1859
+ * โ”€โ”€ ์™œ ์ƒํ•œ์ด ํ•„์š”ํ•œ๊ฐ€ (์‹ค์ธก์œผ๋กœ ์„œ๋ฒ„๊ฐ€ ๋‘ ๋ฒˆ ์ฃฝ์—ˆ๋‹ค) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1860
+ * ๊ตฌ๋…์˜ ๋Œ€๊ธฐ์—ด์—๋Š” ๋ฒ„ํผ๊ฐ€ ์—†๋‹ค(`pubsub.subscribe` ๊ฐ€ `new Repeater(fn)` ์„ ๋ฒ„ํผ ์—†์ด ๋งŒ๋“ ๋‹ค).
1861
+ * ๋ฐœํ–‰ํ•˜๋Š” ์ˆœ๊ฐ„์— ใ€Œ๋‹ค์Œ ๊ฐ’์„ ๋‹ฌ๋ผใ€๋Š” ์š”์ฒญ์ด ์ด๋ฏธ ๊ฑธ๋ ค ์žˆ์–ด์•ผ ๋ฐ”๋กœ ์ „๋‹ฌ๋˜๊ณ , ์•„๋‹ˆ๋ฉด ๋Œ€๊ธฐ์—ด์—
1862
+ * ์Œ“์ธ๋‹ค. ์ƒํ•œ์€ 1024 ์ด๊ณ  ๋„˜์œผ๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋‚œ๋‹ค(`@repeaterjs/repeater` ์˜ `MAX_QUEUE_LENGTH`).
1863
+ *
1864
+ * ๊ฐ’์„ ๊บผ๋‚ด๋Š” ์ชฝ์€ ๋น„๋™๊ธฐ๋‹ค. ๊ทธ๋ž˜์„œ **๋™๊ธฐ ๋ฐ˜๋ณต๋ฌธ์ด ๋„๋Š” ์ค‘์—๋Š” ํ•œ ๊ฑด๋„ ๊บผ๋‚ด์ง€ ๋ชปํ•œ๋‹ค.** ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ
1865
+ * ์ง์ ‘ ์žฌ ๋ณธ ๊ฐ’์ด๋‹ค โ€” ์†Œ๋น„์ž๋ฅผ ๋ถ™์—ฌ ๋‘๊ณ  2,000๊ฑด์„ ๋™๊ธฐ๋กœ ๋ฐœํ–‰ํ•˜๋ฉด ์†Œ๋น„์ž๊ฐ€ ๊บผ๋‚ธ ๊ฒƒ์€ 1๊ฑด์ด์—ˆ๊ณ 
1866
+ * ๋‚˜๋จธ์ง€๊ฐ€ ์Œ“์—ฌ ์˜ˆ์™ธ๊ฐ€ ๋‚ฌ๋‹ค.
1867
+ *
1868
+ * ๊ทธ ์˜ˆ์™ธ๋Š” `publish()` ํ˜ธ์ถœ์ž์—๊ฒŒ ์˜ค์ง€ ์•Š๋Š”๋‹ค. `EventTarget` ์ด ๋ฆฌ์Šค๋„ˆ ์˜ˆ์™ธ๋ฅผ uncaughtException ์œผ๋กœ
1869
+ * ๋ณด๋‚ด๋ฏ€๋กœ try/catch ๋กœ๋Š” ์žก์„ ์ˆ˜ ์—†๋‹ค(2026-08-14 ์— ๊ทธ ๊ฐ€๋“œ๋ฅผ ๋ถ™์˜€๋Š”๋ฐ ๊ทธ๋ž˜์„œ ๋ฌด๋ ฅํ–ˆ๋‹ค). ๋ง‰๋Š” ๋ฐฉ๋ฒ•์€
1870
+ * **ํ•œ ์ฐจ๋ก€์— ์ƒํ•œ์„ ๋„˜๊ธฐ์ง€ ์•Š๋Š” ๊ฒƒ** ํ•˜๋‚˜๋‹ค.
1871
+ *
1872
+ * 1024 ๋ณด๋‹ค ๋„‰๋„‰ํžˆ ์ž‘๊ฒŒ ๋‘”๋‹ค โ€” ์†Œ๋น„์ž๊ฐ€ ํ•œ ์ฒญํฌ๋ฅผ ๋‹ค ๊บผ๋‚ด์ง€ ๋ชปํ•ด๋„ ๋‹ค์Œ ์ฒญํฌ๊นŒ์ง€ ์—ฌ์œ ๊ฐ€ ๋‚จ๋Š”๋‹ค.
1506
1873
  */
1507
- static { this.FULL_BROADCAST_EVERY = 25; }
1874
+ static { this.PUBLISH_CHUNK = 200; }
1508
1875
  /** ์ „๋ถ€ ๋งŒ๋“  ํšŸ์ˆ˜ โ€” ๋ฒ”์œ„๋ฅผ ์ขํžˆ์ง€ ๋ชปํ•œ ์ฐฝ์ด ์–ผ๋งˆ๋‚˜ ๋˜๋Š”์ง€ ๊ฐ’์œผ๋กœ ๋‚จ๋Š”๋‹ค. */
1509
1876
  static { this.broadcastFullPasses = 0; }
1510
1877
  /**
@@ -1540,7 +1907,21 @@ class TwinEngine {
1540
1907
  }
1541
1908
  }
1542
1909
  /** ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ… ๋ณ‘ํ•ฉ ์ฃผ๊ธฐ(ms) โ€” ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ…๋ฅ  ์ƒํ•œ. ์ธ์ œ์ŠคํŠธ๊ฐ€ ์•„๋ฌด๋ฆฌ ๋นจ๋ผ๋„ ์ด ์ฃผ๊ธฐ๋กœ๋งŒ ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ…. */
1543
- static { this.BROADCAST_COALESCE_MS = 200; }
1910
+ /*
1911
+ * โ”€โ”€ 200 โ†’ 1000 (2026-08-25 ยท ์‚ฌ์šฉ์ž ๊ฒฐ์ •) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1912
+ * ํŠธ์œˆ **๋‘˜**์—์„œ ์ด๋ฏธ ์ฃผ๊ธฐ๋ฅผ ์Šค์Šค๋กœ ๋Š˜๋ฆฌ๊ณ  ์žˆ์—ˆ๋‹ค(ํ•œ ๋ฒˆ ๋ณด๋‚ด๋Š” ๋ฐ 270๋ฐ€๋ฆฌ์ดˆ). 200๋ฐ€๋ฆฌ์ดˆ๋Š” ์ƒํƒœ๊ฐ€
1913
+ * ์ž‘์„ ๋•Œ์˜ ๊ฐ’์ด๊ณ , ์ง€๊ธˆ ์Šนํ™”ํ‘ธ๋“œ ํ•˜๋‚˜๊ฐ€ ์—”ํ‹ฐํ‹ฐ 17,600๊ฐœ๋‹ค(๋ฌผํ’ˆ 6,494 ยท ์˜ค๋” 4,794 ยท ์ž‘์—… 6,353).
1914
+ *
1915
+ * ํ•œ ๋ฒˆ ๋ณด๋‚ด๋Š” ๊ฐ’์€ ์ƒํƒœ ํฌ๊ธฐ์— ๋น„๋ก€ํ•˜๋ฏ€๋กœ ์ฃผ๊ธฐ๋ฅผ ๋Š˜๋ฆฌ๋Š” ๊ฒƒ์œผ๋กœ ๊ทธ ๊ฐ’์ด ์ค„์ง€๋Š” ์•Š๋Š”๋‹ค. ๋Œ€์‹  **๋‹จ์œ„
1916
+ * ์‹œ๊ฐ„์— ๊ทธ ๊ฐ’์„ ์น˜๋ฅด๋Š” ํšŸ์ˆ˜**๊ฐ€ 5๋ถ„์˜ 1์ด ๋œ๋‹ค. ๊ทธ ์‚ฌ์ด ํ˜ธ์ŠคํŠธ๊ฐ€ HTTPยท๊ตฌ๋…์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.
1917
+ *
1918
+ * ํ™”๋ฉด์ด ๋Šฆ๊ฒŒ ๊ฐฑ์‹ ๋˜๋Š” ๊ฒƒ์€ ์ตœ๋Œ€ 1์ดˆ๋‹ค. ์ตœ์‹ -์ƒํƒœ ์ฑ„๋„์ด๋ผ ๋ฐ€๋ฆฐ ๊ฒƒ์„ ์Œ“์•„ ๋ณด๋‚ด์ง€ ์•Š๊ณ  ๋งˆ์ง€๋ง‰ ํ•˜๋‚˜๋งŒ
1919
+ * ๊ฐ€๋ฏ€๋กœ, ๋Šฆ์–ด์งˆ ๋ฟ ๋‚ด์šฉ์€ ๊ฐ™๋‹ค.
1920
+ *
1921
+ * **๋น„์šฉ์„ ์ค„์ธ ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค.** ์ค„์ด๋Š” ๊ฒƒ์€ ๋ณ€๊ฒฝ๋ถ„๋งŒ ๋งŒ๋“œ๋Š” ์ผ์ด๊ณ (๋ฐ”๋€ ๊ฒƒ๋งŒ ๋‹ค์‹œ ๋งŒ๋“œ๋Š” ์ผ) ๊ทธ๊ฒƒ์€ ๋ณ„
1922
+ * ์ž‘์—…์ด๋‹ค โ€” ๊ณ„ํš: `design/plans/live-broadcast-cost.md`.
1923
+ */
1924
+ static { this.BROADCAST_COALESCE_MS = 1000; }
1544
1925
  /**
1545
1926
  * โ”€โ”€ ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ… ์ฃผ๊ธฐ๋Š” **์žฌ ๋ณธ ๋น„์šฉ์— ๋งž์ถ˜๋‹ค** (2026-08-21 ์‹ค์ธก) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1546
1927
  * ํ•œ ๋ฒˆ์˜ ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ…์€ ์ƒํƒœ ํฌ๊ธฐ์— ๋น„๋ก€ํ•œ๋‹ค(์‹ค์ธก: ๋ฌผํ’ˆ 2,400 ๊ฐœ์ธ ํŠธ์œˆ ํ•˜๋‚˜๊ฐ€ 4.5ms โ€” ์ƒํƒœ ํˆฌ์˜ 1.7ms,
@@ -1554,12 +1935,13 @@ class TwinEngine {
1554
1935
  * ์ด๊ฒƒ์€ ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ… ๋น„์šฉ์„ **์ค„์ด๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค** โ€” ๋น„์šฉ์„ ์ค„์ด๋Š” ๊ฒƒ์€ ๋ณ€๊ฒฝ๋ถ„๋งŒ ๋งŒ๋“œ๋Š” ์ผ์ด๊ณ  ๊ทธ๊ฒƒ์€ ๋ณ„
1555
1936
  * ์ž‘์—…์ด๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ๊ทธ๋•Œ๊นŒ์ง€ ํ˜ธ์ŠคํŠธ๊ฐ€ ๊ตถ์ง€ ์•Š๊ฒŒ ์ƒํ•œ์„ ๋‘”๋‹ค.
1556
1937
  */
1557
- static { this.BROADCAST_MAX_COALESCE_MS = 1000; }
1938
+ /* ๊ธฐ๋ณธ์ด 1์ดˆ๊ฐ€ ๋˜์—ˆ์œผ๋ฏ€๋กœ ๋Š˜๋ฆด ์—ฌ์ง€๋ฅผ ํ•จ๊ป˜ ์˜ฌ๋ฆฐ๋‹ค โ€” ์ƒํ•œ์ด ๊ธฐ๋ณธ๊ณผ ๊ฐ™์œผ๋ฉด ๋Š˜๋ฆด ๊ณณ์ด ์—†๋‹ค. */
1939
+ static { this.BROADCAST_MAX_COALESCE_MS = 2000; }
1558
1940
  /** ์ฃผ๊ธฐ์˜ ๋ช‡ ๋ชซ๊นŒ์ง€ ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ…์— ์จ๋„ ๋˜๋Š”๊ฐ€ โ€” ๋„˜์œผ๋ฉด ์ฃผ๊ธฐ๋ฅผ ๋Š˜๋ฆฐ๋‹ค(์ ˆ๋ฐ˜์ด๋ฉด ๋‚˜๋จธ์ง€ ์ ˆ๋ฐ˜์€ ๋‚จ๊ธด๋‹ค). */
1559
1941
  static { this.BROADCAST_LOAD_RATIO = 0.3; }
1560
1942
  /** ์ง€๊ธˆ ์“ฐ๊ณ  ์žˆ๋Š” ์ฃผ๊ธฐ(ms) โ€” ๊ณ„๊ธฐํŒ์ด ์ด ๊ฐ’์„ ์ฝ๋Š”๋‹ค. ๋Š˜์–ด๋‚œ ์ฑ„๋กœ ์žˆ์œผ๋ฉด ๊ทธ๊ฒƒ์ด ์‚ฌ์‹ค์ด๋‹ค. */
1561
1943
  static { this.broadcastPeriodMs = 200; }
1562
- /** ์ฃผ๊ธฐ๋ฅผ ๋Š˜๋ฆฐ ํšŸ์ˆ˜ โ€” ์กฐ์šฉํžˆ ๋Šฆ์ถ”์ง€ ์•Š๋Š”๋‹ค. */
1944
+ /** ์ฃผ๊ธฐ๋ฅผ ๋Š˜๋ฆฐ ํšŸ์ˆ˜ โ€” ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋Šฆ์ถ”์ง€ ์•Š๋Š”๋‹ค. */
1563
1945
  static { this.broadcastBackoffs = 0; }
1564
1946
  /** live ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ… coalescer โ€” dirty ์ธ live ์ธ์Šคํ„ด์Šค๋งŒ ์ฃผ๊ธฐ์ ์œผ๋กœ 1ํšŒ ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ…(entitySigs ๋กœ ๋ณ€๊ฒฝ ์—”ํ‹ฐํ‹ฐ๋งŒ). */
1565
1947
  static ensureBroadcastCoalescer() {
@@ -1606,7 +1988,31 @@ class TwinEngine {
1606
1988
  * ํ•œ tick ์—์„œ ์ˆ˜๋ฐฑ ๋ฒˆ ๋ฐ˜๋ณต๋˜๋ฉฐ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ๋ง‰์•˜๋‹ค(์œ„ `start()` ์ฃผ์„์˜ 35์ดˆ ํ‹ฑ). ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ…์„ ๋ชจ์œผ๋Š”
1607
1989
  * ๊ทœ์œจ์€ ๋ชจ๋“œ์˜ ์„ฑ์งˆ์ด ์•„๋‹ˆ๋ผ **์ฑ„๋„์˜ ์„ฑ์งˆ**์ด๋‹ค โ€” ์ตœ์‹ -์ƒํƒœ ์ฑ„๋„์ด๋ฉด ์ค‘๊ฐ„ ์ƒํƒœ๋Š” ๋ณด๋‚ผ ๊ฐ’์ด ์—†๋‹ค.
1608
1990
  */
1991
+ /**
1992
+ * ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ… ํ•œ ์ฐฝ โ€” **๊ฒน์ณ ๋Œ์ง€ ์•Š๋Š”๋‹ค** (2026-08-27).
1993
+ *
1994
+ * ๋ฐœํ–‰์ด ์ฒญํฌ๋กœ ๋‚˜๋‰˜์–ด ์‚ฌ์ด์— ๋ฃจํ”„๋ฅผ ๋น„์›Œ ์ฃผ๋ฏ€๋กœ ํ•œ ์ฐฝ์ด ์ฃผ๊ธฐ๋ฅผ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋•Œ ๋‹ค์Œ ํƒ€์ด๋จธ๊ฐ€
1995
+ * ๊ฒน์ณ ๋“ค์–ด์˜ค๋ฉด ๊ฐ™์€ ํƒœ๊ทธ๊ฐ€ ๋‘ ์ˆœ์„œ๋กœ ๋‚˜๊ฐ€๊ณ  ์‹œ๊ทธ๋‹ˆ์ฒ˜ ํ‘œ๊ฐ€ ์—‰ํ‚จ๋‹ค. ๊ทธ๋ž˜์„œ ์•ž ์ฐฝ์ด ๋๋‚  ๋•Œ๊นŒ์ง€
1996
+ * ๋‹ค์Œ ํƒ€์ด๋จธ๋Š” ๊ทธ๋Œ€๋กœ ๋Œ์•„๊ฐ„๋‹ค โ€” **์ฐฝ์„ ๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค.** ๋‹ค์Œ ํƒ€์ด๋จธ๊ฐ€ ์ด์–ด์„œ ํ•œ๋‹ค.
1997
+ *
1998
+ * ๊ฒน์นœ ํšŸ์ˆ˜๋ฅผ ์„ผ๋‹ค. ๊ณ„์† ๊ฒน์น˜๋ฉด ์ฃผ๊ธฐ๋ณด๋‹ค ํ•œ ์ฐฝ์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๊ณ  ์žˆ๋‹ค๋Š” ๋œป์ด๊ณ , ๊ทธ๊ฒƒ์€
1999
+ * `adjustBroadcastPeriod` ๊ฐ€ ์ฃผ๊ธฐ๋ฅผ ์˜ฌ๋ ค ์Šค์Šค๋กœ ํ’€์–ด์•ผ ํ•˜๋Š” ์‚ฌ์‹ค์ด๋‹ค.
2000
+ */
2001
+ static { this.flushInFlight = false; }
2002
+ static { this.flushOverruns = 0; }
1609
2003
  static flushLiveBroadcasts() {
2004
+ if (this.flushInFlight) {
2005
+ this.flushOverruns++;
2006
+ return;
2007
+ }
2008
+ this.flushInFlight = true;
2009
+ void this.flushBroadcastWindow()
2010
+ .catch(err => (0, log_js_1.twinError)('[twin-engine] broadcast window failed', err?.message ?? err))
2011
+ .finally(() => {
2012
+ this.flushInFlight = false;
2013
+ });
2014
+ }
2015
+ static async flushBroadcastWindow() {
1610
2016
  const now = Date.now();
1611
2017
  const flushStart = performance.now();
1612
2018
  for (const inst of Object.values(this.instances)) {
@@ -1645,7 +2051,7 @@ class TwinEngine {
1645
2051
  inst.fullBroadcastCountdown = left;
1646
2052
  }
1647
2053
  // โ‘  ์—”ํ‹ฐํ‹ฐ data(tag) ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ… โ€” ๋ณด๋“œ ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ ๋ Œ๋”.
1648
- this.publishEntityData(inst);
2054
+ await this.publishEntityData(inst);
1649
2055
  if (m) {
1650
2056
  m.broadcastTotal++;
1651
2057
  m._accBroadcast++;
@@ -1693,7 +2099,7 @@ class TwinEngine {
1693
2099
  correlationId: e?.correlationId,
1694
2100
  revision,
1695
2101
  /* **์ด ์‚ฌ์‹ค์ด ์ผ์–ด๋‚œ ๊ณต์žฅ**์„ ํ•จ๊ป˜ ์ฐ๋Š”๋‹ค โ€” ์ด๊ฒƒ์ด ์—†์œผ๋ฉด ๋‚˜์ค‘์— ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ์—ˆ์„ ๋•Œ ์ด ํ–‰์„
1696
- ์ƒˆ ๊ณต์žฅ์— ๋Œ€๊ณ  ์ ‘๊ฒŒ ๋˜๊ณ , ๊ทธ๋•Œ ์—†๋˜ ์„ค๋น„์—์„œ ์ผ์ด ์žˆ์—ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค. */
2102
+ ์ƒˆ ๊ณต์žฅ์— ๋Œ€๊ณ  ๊ณ„์‚ฐํ•˜๊ฒŒ ๋˜๊ณ , ๊ทธ๋•Œ ์—†๋˜ ์„ค๋น„์—์„œ ์ผ์ด ์žˆ์—ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค. */
1697
2103
  ...(structureRev === undefined ? {} : { structureRev }),
1698
2104
  /* ์ปค๋„์€ ISO ๋ฌธ์ž์—ด์„ ์ค€๋‹ค. ์ปฌ๋Ÿผ์€ ๋‚ ์งœ๋‹ค โ€” **์—ฌ๊ธฐ์„œ ์˜ฎ๊ธด๋‹ค.** ๋ฌธ์ž์—ด์„ ๊ทธ๋Œ€๋กœ ๋„ฃ์œผ๋ฉด
1699
2105
  ๋“œ๋ผ์ด๋ฒ„๊ฐ€ ์ œ ํ˜•์‹์œผ๋กœ ์ •๊ทœํ™”ํ•˜์ง€ ์•Š์•„, ๋‚˜์ค‘์— ๋‚ ์งœ๋กœ ๊ฑฐ๋Š” ์งˆ์˜์— ์•ˆ ๊ฑธ๋ฆฐ๋‹ค. */
@@ -1731,12 +2137,12 @@ class TwinEngine {
1731
2137
  * ๋ชจ์•„ ๋‘” ์ €๋„์„ **ํ•œ ๋ฒˆ์—** ์“ด๋‹ค โ€” ๋‘ ๊ตฌ๋™์ด ๊ฐ™์€ ๋ฌธ์„ ์“ด๋‹ค (ยง7.1).
1732
2138
  *
1733
2139
  * โ”€โ”€ ์™œ ํ•œ ํ•จ์ˆ˜์ธ๊ฐ€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1734
- * ์“ฐ๋Š” ์ž๋ฆฌ๊ฐ€ ๋‘˜์ด๋ฉด(์ฃผ๊ธฐ flush ยท ์ •์ง€) ํ•œ์ชฝ๋งŒ ๊ณ ์ณ์ง€๊ณ , ๊ทธ ์–ด๊ธ‹๋‚จ์€ **์‚ฌ์‹ค์ด ์กฐ์šฉํžˆ ์‚ฌ๋ผ์ง€๋Š”**
2140
+ * ์“ฐ๋Š” ์ž๋ฆฌ๊ฐ€ ๋‘˜์ด๋ฉด(์ฃผ๊ธฐ flush ยท ์ •์ง€) ํ•œ์ชฝ๋งŒ ๊ณ ์ณ์ง€๊ณ , ๊ทธ ์–ด๊ธ‹๋‚จ์€ **์‚ฌ์‹ค์ด ์˜ค๋ฅ˜ ์—†์ด ์‚ฌ๋ผ์ง€๋Š”**
1735
2141
  * ๋ชจ์–‘์œผ๋กœ ๋‚˜ํƒ€๋‚œ๋‹ค. ๊ทธ๋ž˜์„œ ํ˜๋ฆฌ๋Š” ๊ทœ์น™์„ ์—ฌ๊ธฐ ํ•œ ๊ณณ์— ๋‘”๋‹ค.
1736
2142
  *
1737
2143
  * โ”€โ”€ ๋ฆฌ๋น„์ „์„ ๋ˆ„๊ฐ€ ๋งค๊ธฐ๋‚˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1738
2144
  * ยท ๋ผ์ด๋ธŒ โ€” ์›์ฒœ์€ ๋ฆฌ๋น„์ „์„ ์ฃผ์ง€ ์•Š์œผ๋ฏ€๋กœ **ํ˜ธ์ŠคํŠธ๊ฐ€** ์ด์–ด ๋ถ™์ธ๋‹ค(์ €๋„ high-water ์—์„œ ์‹œ๋“œ).
1739
- * ยท ์‹œ๋ฎฌ โ€” **์ปค๋„์˜ ๋ฆฌ๋น„์ „**์ด ์‹ค๋ ค ์˜จ๋‹ค(์ €๋„์ด ๊ทธ๊ฒƒ์œผ๋กœ ์ ‘ํžˆ๊ณ  ์‹œ๊ฐ„์—ฌํ–‰์ด ๊ทธ๊ฒƒ์„ ๋”›๋Š”๋‹ค).
2145
+ * ยท ์‹œ๋ฎฌ โ€” **์ปค๋„์˜ ๋ฆฌ๋น„์ „**์ด ์‹ค๋ ค ์˜จ๋‹ค(์ €๋„์ด ๊ทธ ๋ฒˆํ˜ธ๋กœ ์ •๋ ฌ๋˜๊ณ  ์‹œ๊ฐ„์—ฌํ–‰์ด ๊ทธ๊ฒƒ์„ ๋”›๋Š”๋‹ค).
1740
2146
  * ๊ทธ๋ž˜์„œ ๋ฒ„ํผ๋Š” ๋‘ ๋ชจ์–‘์„ ํ•จ๊ป˜ ๋“ ๋‹ค: ๋ด‰ํˆฌ๋งŒ ์žˆ์œผ๋ฉด ๋ผ์ด๋ธŒ, `{ event, revision }` ์ด๋ฉด ์‹œ๋ฎฌ์ด๋‹ค.
1741
2147
  */
1742
2148
  static flushJournal(inst) {
@@ -1761,9 +2167,20 @@ class TwinEngine {
1761
2167
  if (carried.length)
1762
2168
  jobs.push(this.persistCarried(inst.domainId, inst.id, carried));
1763
2169
  if (plain.length && inst.revision != null) {
2170
+ /*
2171
+ * โ”€โ”€ ๋งˆ๊ฐ๋œ ๊ตฌ๊ฐ„ ์‚ฌ์‹ค์€ ํ•œ ๋ฒˆ๋งŒ ์ ๋Š”๋‹ค (2026-08-30) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2172
+ *
2173
+ * ์ด๋ฏธ ์žˆ๋Š” ๊ฒƒ์„ ๊ฑธ๋Ÿฌ๋‚ธ ๋’ค์— ๋ฒˆํ˜ธ๋ฅผ ๋งค๊ธด๋‹ค. ๊ฑธ๋Ÿฌ๋‚ด๊ธฐ ์ „์— ๋งค๊ธฐ๋ฉด **์“ฐ์ง€ ์•Š์€ ๋ฒˆํ˜ธ๊ฐ€ ๋น„๊ณ **,
2174
+ * ๊ทธ ๊ตฌ๋ฉ์ด ์ €๋„์˜ ๋ฆฌ๋น„์ „ ์—ฐ์†์„ฑ์„ ๋Š๋Š”๋‹ค.
2175
+ */
2176
+ /*
2177
+ * ๋ฒˆํ˜ธ๋Š” **๋จผ์ € ์žก๋Š”๋‹ค**(๋™๊ธฐ). ๊ฑธ๋Ÿฌ๋‚ธ ๋’ค์— ์žก์œผ๋ฉด ๋‘ flush ๊ฐ€ ๊ฒน์น  ๋•Œ ๊ฐ™์€ ๋ฒˆํ˜ธ๋ฅผ ๋‘ ๋ฒˆ ์“ด๋‹ค.
2178
+ * ๊ฑธ๋Ÿฌ์ง„ ๋งŒํผ ๋ฒˆํ˜ธ๊ฐ€ ๋น„์ง€๋งŒ, ๊ทธ ๊ตฌ๋ฉ์€ ํ•ด๋กญ์ง€ ์•Š๋‹ค โ€” ๋ฆฌ๋น„์ „์€ ๊ฐ™์€ ์‹œ๊ฐ์„ ๊ฐ€๋ฆฌ๋Š” ๋ฐ๋งŒ ์“ฐ๊ณ 
2179
+ * ์ด์–ด์ง์„ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค(์ €๋„ ๋จธ๋ฆฌ๋Š” ์ตœ๋Œ€๊ฐ’์ด๋‹ค).
2180
+ */
1764
2181
  const start = inst.revision;
1765
2182
  inst.revision = start + plain.length;
1766
- jobs.push(this.persistBatch(inst.domainId, inst.id, plain, start));
2183
+ jobs.push(this.dropAlreadyWritten(inst.domainId, inst.id, plain).then(fresh => fresh.length ? this.persistBatch(inst.domainId, inst.id, fresh, start) : undefined));
1767
2184
  }
1768
2185
  if (!jobs.length)
1769
2186
  return Promise.resolve();
@@ -1790,11 +2207,100 @@ class TwinEngine {
1790
2207
  * ์˜ˆ์ „์—๋Š” ์ด ๊ฒฝ๋กœ๊ฐ€ **๋ธํƒ€๋งˆ๋‹ค ํ•œ ํ–‰์”ฉ** ์ €์žฅํ–ˆ๋‹ค(๊ทธ๋ฆฌ๊ณ  ํ–‰๋งˆ๋‹ค ๊ตฌ์กฐ ๋ฆฌ๋น„์ „์„ ๋ฌผ์—ˆ๋‹ค). ๊ทœ๋ชจ์—์„œ ๊ทธ๊ฒƒ์ด
1791
2208
  * ํ˜ธ์ŠคํŠธ๋ฅผ ๋จน์—ˆ๋‹ค(ยง7.1 ์‹ค์ธก). ์—ฌ๊ธฐ์„œ ๊ตฌ์กฐ ๋ฆฌ๋น„์ „์€ **ํ•œ ๋ฒˆ๋งŒ** ๋ฌป๋Š”๋‹ค.
1792
2209
  */
2210
+ /**
2211
+ * **๋งˆ๊ฐ๋œ ๊ตฌ๊ฐ„ ์‚ฌ์‹ค์€ ํ•œ ๋ฒˆ๋งŒ ์ ๋Š”๋‹ค** โ€” ๊ฐ™์€ ์‚ฌ์‹ค์ด ๋‘ ๋ฒˆ ์˜ค๋ฉด ํ•œ ์‚ฌ์‹ค์ด๋‹ค (2026-08-30).
2212
+ *
2213
+ * โ”€โ”€ ์™œ ์“ฐ๋Š” ์ž๋ฆฌ์—์„œ ๋ง‰๋‚˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2214
+ * ์ปค๋„์ด ๊ทธ ์‚ฌ์‹ค๋“ค์˜ ์ •์ฒด์„ฑ์„ ๋‚ด์šฉ์—์„œ ๋งŒ๋“ ๋‹ค(์ข…๋ฅ˜ยท๋Œ€์ƒยท์‹œ์ž‘ยท๋). ๊ทธ๋Ÿฐ๋ฐ ์ €๋„์˜ ๊ธฐ๋ณธํ‚ค๋Š” ์ƒˆ๋กœ
2215
+ * ๋งŒ๋“œ๋Š” uuid ๋ผ, **id ๊ฐ€ ๊ธ€์ž ํ•˜๋‚˜๊นŒ์ง€ ๊ฐ™์•„๋„ ํ–‰์ด ๋”ฐ๋กœ ์Œ“์˜€๋‹ค.**
2216
+ *
2217
+ * ์‹ค์ธก(2026-08-30): ๋ฐœ์ „ ๋‹จ๊ฐ€๊ฐ€ ํ–‰ 7๊ฐœยท์„œ๋กœ ๋‹ค๋ฅธ id 4๊ฐœ์˜€๋‹ค. 8์›” 27์ผ ๋‹จ๊ฐ€๊ฐ€ ์„ธ ๋ฒŒ ์žˆ์—ˆ๊ณ ,
2218
+ * ๊ทธ ๊ตฌ๊ฐ„์˜ ์ˆ˜์ต์ด ์„ธ ๋ฒˆ ์„ธ์–ด์ง„๋‹ค.
2219
+ *
2220
+ * ์ปค๋„ฅํ„ฐ๊ฐ€ ์ปค์„œ๋กœ ๋ง‰๋Š” ๊ฒƒ์ด ์ฒซ ๋ฐฉ์–ด์„ ์ด๊ณ  ๊ทธ์ชฝ๋„ ๊ณ ์ณค๋‹ค. ๊ทธ๋Ÿฐ๋ฐ **์ปค๋„ฅํ„ฐ๊ฐ€ ํ•œ ๋ฒˆ ์‹ค์ˆ˜ํ•˜๋ฉด ์ €๋„์ด
2221
+ * ๋ถ€ํ’€๊ณ  ๊ทธ ์œ„์˜ ๋ชจ๋“  ํ•ฉ์ด ํ‹€๋ฆฐ๋‹ค.** ์ •์ฒด์„ฑ์€ ์ปค๋„์ด ์„ ์–ธํ•œ ์‚ฌ์‹ค์ด๋ฏ€๋กœ, ์–ด๋А ๋ฌธ์œผ๋กœ ๋“ค์–ด์˜ค๋“ 
2222
+ * ์ง€์ผœ์ ธ์•ผ ํ•œ๋‹ค โ€” ์ฑ„์šฐ๊ธฐ ๊ฒฝ๋กœ์—๋งŒ ์žˆ์œผ๋ฉด ๊ทธ ๋ณด์ฆ์€ ใ€Œ์–ด๋А ๋ฌธ์œผ๋กœ ์™”๋А๋ƒใ€์— ๊ฑธ๋ฆฐ๋‹ค.
2223
+ *
2224
+ * โ”€โ”€ ๊ฐ’์ด ์‹ผ ์ด์œ  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2225
+ * ๋งˆ๊ฐ๋œ ๊ตฌ๊ฐ„ ์‚ฌ์‹ค๋งŒ ๋ณธ๋‹ค(์‹œ์ ์˜ ๊ด€์ธก์€ ๊ทธ๋Œ€๋กœ ์ง€๋‚œ๋‹ค โ€” ๋ฐ˜๋ณต์ด ๋œป์„ ๊ฐ€์ง„๋‹ค). ๊ทธ๊ฒƒ๋“ค์€ ๋“œ๋ฌผ์–ด์„œ
2226
+ * (ํ•œ ์‹œ๊ฐ„์— ํ•œ ์ค„) ํ•œ ๋ฌถ์Œ์— ๋ช‡ ๊ฐœ๋ฟ์ด๊ณ , ๊ทธ ๊ตฌ๊ฐ„์˜ ๊ทธ ์ข…๋ฅ˜๋งŒ ์ƒ‰์ธ์œผ๋กœ ์ฝ๋Š”๋‹ค.
2227
+ */
2228
+ static { this.PERIOD_FACT_TYPES = new Set([
2229
+ 'energy.usage.period',
2230
+ 'energy.generation.period',
2231
+ 'energy.generation.price',
2232
+ 'energy.tariff.basis',
2233
+ 'energy.bill'
2234
+ ]); }
2235
+ static async dropAlreadyWritten(domainId, instanceId, envelopes) {
2236
+ const periodFacts = envelopes.filter(e => this.PERIOD_FACT_TYPES.has(String(e?.eventType)) && typeof e?.eventId === 'string');
2237
+ if (!periodFacts.length)
2238
+ return envelopes;
2239
+ const times = periodFacts.map(e => Date.parse(String(e.eventTime))).filter(n => Number.isFinite(n));
2240
+ if (!times.length)
2241
+ return envelopes;
2242
+ const types = [...new Set(periodFacts.map(e => String(e.eventType)))];
2243
+ const existing = await (0, shell_1.getRepository)(twin_event_js_1.TwinEvent)
2244
+ .createQueryBuilder('e')
2245
+ .select(['e.payload AS "payload"'])
2246
+ .where('e.domain = :domainId', { domainId })
2247
+ .andWhere('e.instanceId = :instanceId', { instanceId })
2248
+ .andWhere('e.eventType IN (:...types)', { types })
2249
+ .andWhere('e.eventTime BETWEEN :fromAt AND :toAt', { fromAt: new Date(Math.min(...times)), toAt: new Date(Math.max(...times)) })
2250
+ .getRawMany()
2251
+ .catch(() => []);
2252
+ const seen = new Set();
2253
+ for (const row of existing) {
2254
+ let p = row.payload;
2255
+ if (typeof p === 'string') {
2256
+ try {
2257
+ p = JSON.parse(p);
2258
+ }
2259
+ catch {
2260
+ p = undefined;
2261
+ }
2262
+ }
2263
+ if (typeof p?.eventId === 'string' && p.eventId)
2264
+ seen.add(p.eventId);
2265
+ }
2266
+ /* ํ•œ ๋ฌถ์Œ ์•ˆ์— ๊ฐ™์€ ๊ฒƒ์ด ๋‘˜ ์žˆ์–ด๋„ ํ•˜๋‚˜๋งŒ ์ ๋Š”๋‹ค. */
2267
+ const kept = [];
2268
+ for (const e of envelopes) {
2269
+ if (!this.PERIOD_FACT_TYPES.has(String(e?.eventType)) || typeof e?.eventId !== 'string') {
2270
+ kept.push(e);
2271
+ continue;
2272
+ }
2273
+ if (seen.has(e.eventId))
2274
+ continue;
2275
+ seen.add(e.eventId);
2276
+ kept.push(e);
2277
+ }
2278
+ return kept;
2279
+ }
1793
2280
  static async persistCarried(domainId, instanceId, items) {
1794
2281
  const repo = (0, shell_1.getRepository)(twin_event_js_1.TwinEvent);
1795
2282
  const structureRev = await this.structureRevOf(domainId, instanceId);
1796
2283
  const rows = items.map(it => this.journalRow(repo, domainId, instanceId, it.event, it.revision, structureRev));
1797
2284
  await this.insertRows(repo, rows);
2285
+ await this.writeSubjects(domainId, instanceId, items.map(it => ({ envelope: it.event, revision: it.revision })));
2286
+ }
2287
+ /**
2288
+ * **๋Œ€์ƒ๋ณ„ ์‚ฌ๊ฑด ๋ชฉ๋ก์— ํ•จ๊ป˜ ์ ๋Š”๋‹ค** โ€” ์กฐํšŒ์šฉ ํŒŒ์ƒ ํ‘œ (2026-08-27).
2289
+ *
2290
+ * ์ €๋„์„ ์“ฐ๋Š” ๋‘ ๋ฌธ(`persistBatch` ยท `persistCarried`) ๋’ค์—์„œ ๋ถ€๋ฅธ๋‹ค. ๋‹ค๋ฅธ ์ž๋ฆฌ์—์„œ ๋‚˜์ค‘์— ์ ์œผ๋ฉด
2291
+ * ใ€Œ์ €๋„์—๋Š” ์žˆ๊ณ  ๊ทธ ํ‘œ์—๋Š” ์—†๋Š” ์‚ฌ๊ฑดใ€์ด ์ƒ๊ธฐ๊ณ , ๊ทธ๊ฒƒ์€ ํ™”๋ฉด์—์„œ ์˜ค๋ฅ˜ ์—†์ด ๋น ์ง„ ์‚ฌ๊ฑด์ด ๋œ๋‹ค.
2292
+ *
2293
+ * **์ €๋„์ด ๋จผ์ €๋‹ค.** ์ด ํ‘œ์— ์ ๋‹ค๊ฐ€ ์‹คํŒจํ•ด๋„ ์ €๋„ ์“ฐ๊ธฐ๋ฅผ ๋˜๋Œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค โ€” ์‚ฌ์‹ค์˜ ์›๋ณธ์€ ์ €๋„์ด๊ณ 
2294
+ * ์ด ํ‘œ๋Š” ๋‹ค์‹œ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ์‹คํŒจ๋Š” ์„ธ์–ด ๋‚จ๊ธด๋‹ค(ยง`subjectWriteFailures`).
2295
+ *
2296
+ * ์„ค๊ณ„: `operato-twin/design/plans/read-models.md`
2297
+ */
2298
+ static async writeSubjects(domainId, instanceId, entries) {
2299
+ if (!entries.length)
2300
+ return;
2301
+ const reg = this.instances[(0, runtime_key_js_1.runtimeKey)(domainId, instanceId)];
2302
+ const itemOf = (0, item_resolver_js_1.itemResolverFor)(domainId, instanceId, reg?.model);
2303
+ await (0, twin_subject_writer_js_1.writeSubjectRows)(domainId, instanceId, entries, itemOf).catch(err => (0, log_js_1.twinWarn)(`[twin-subject] "${instanceId}" ๋Œ€์ƒ ๋ชฉ๋ก ์“ฐ๊ธฐ ์‹คํŒจ โ€” ${err?.message ?? err}`));
1798
2304
  }
1799
2305
  /**
1800
2306
  * ์ €๋„ ํ–‰์„ ๋„ฃ๋Š”๋‹ค โ€” **๋„ฃ๊ธฐ๋งŒ ํ•œ๋‹ค**(2026-08-20).
@@ -1837,6 +2343,7 @@ class TwinEngine {
1837
2343
  const structureRev = await this.structureRevOf(domainId, instanceId);
1838
2344
  const rows = envelopes.map((e, i) => this.journalRow(repo, domainId, instanceId, e, startRevision + i + 1, structureRev));
1839
2345
  await this.insertRows(repo, rows);
2346
+ await this.writeSubjects(domainId, instanceId, envelopes.map((e, i) => ({ envelope: e, revision: startRevision + i + 1 })));
1840
2347
  }
1841
2348
  /** ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ upsert(๋„๋ฉ”์ธ+instanceId ์œ ๋‹ˆํฌ). status ์ธ์ž๋กœ provision(stopped)/start(running) ๊ณต์šฉ. */
1842
2349
  static async register(domainId, instanceId, kind, model, status = 'running', restartPolicy, origin,
@@ -1859,7 +2366,7 @@ class TwinEngine {
1859
2366
  areaId: model?.areaId ?? existing?.areaId,
1860
2367
  /* ์žฌ๊ธฐ๋™ ์ •์ฑ…(ADR-0029 ยง2) โ€” ๋ช…์‹œ๊ฐ’ ์šฐ์„ , ์—†์œผ๋ฉด ๊ธฐ์กด๊ฐ’ ๋ณด์กด(์žฌํ”„๋กœ๋น„์ „์ด ์„ ์–ธ์„ ์ง€์šฐ์ง€ ์•Š๊ฒŒ).
1861
2368
  **๋‘˜ ๋‹ค ์—†์œผ๋ฉด ์˜ค๋ฅ˜๋ฅผ ๋‚ธ๋‹ค**: ์˜ˆ์ „์—๋Š” ์—ฌ๊ธฐ์„œ ๊ธฐ๋ณธ๊ฐ’์„ ๊ฐ์ธํ–ˆ๋Š”๋ฐ, ๊ทธ ๊ธฐ๋ณธ๊ฐ’์ด ใ€Œ์ €๋„ ์ดˆ๊ธฐํ™”ใ€๋ผ์„œ
1862
- ์„ ์–ธ์„ ๋น ๋œจ๋ฆฐ ํ”„๋กœ๋น„์ €๋‹์ด ์กฐ์šฉํžˆ ์ด๋ ฅ์„ ์ง€์šฐ๋Š” ํŠธ์œˆ์„ ๋งŒ๋“ค์—ˆ๋‹ค. ์„ ์–ธ์€ ๋ถ€๋ฅด๋Š” ์ชฝ์˜ ๋ชซ์ด๋‹ค. */
2369
+ ์„ ์–ธ์„ ๋น ๋œจ๋ฆฐ ํ”„๋กœ๋น„์ €๋‹์ด ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์ด๋ ฅ์„ ์ง€์šฐ๋Š” ํŠธ์œˆ์„ ๋งŒ๋“ค์—ˆ๋‹ค. ์„ ์–ธ์€ ๋ถ€๋ฅด๋Š” ์ชฝ์˜ ๋ชซ์ด๋‹ค. */
1863
2370
  restartPolicy: restartPolicy ??
1864
2371
  (0, restart_policy_js_1.readRestartPolicy)(existing?.restartPolicy, `register("${instanceId}") did not declare a restart policy and the stored row has none`),
1865
2372
  /* ์šฉ๋„๋„ ๊ฐ์ธํ•œ๋‹ค. ๋ฒค์น˜๋กœ ๋’ค์ง‘๋Š” ๊ฒƒ์€ `setPurpose` ํ•˜๋‚˜๋ฟ์ด๋ฏ€๋กœ ๊ธฐ์กด๊ฐ’์„ ๋ฐ˜๋“œ์‹œ ๋ณด์กดํ•œ๋‹ค
@@ -1878,6 +2385,23 @@ class TwinEngine {
1878
2385
  }));
1879
2386
  }
1880
2387
  /**
2388
+ * โ”€โ”€ **ํŠธ์œˆ์€ ์„ค๋น„์˜ ๋งˆ์Šคํ„ฐ๊ฐ€ ์•„๋‹ˆ๋‹ค** (์‚ฌ์šฉ์ž ๊ฒฐ์ • 2026-08-30) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2389
+ *
2390
+ * ์ธ์›ยท์„ค๋น„ยท์ž์‚ฐ์˜ ๋งˆ์Šคํ„ฐ๋Š” `@things-factory/ops-master` ๋‹ค. ํŠธ์œˆ์€ ๊ทธ๊ฒƒ์„ **๋‹น๊ฒจ ์™€** ๋ชจ๋ธ์—
2391
+ * ๋ฐ˜์˜ํ•˜๊ณ , ์—ฌ๊ธฐ ๋‚จ๋Š” ๊ฒƒ์€ ๊ทธ ๊ฒฐ๊ณผ์˜ ์บ์‹œ๋‹ค.
2392
+ *
2393
+ * ๊ทธ๋ž˜์„œ ์ด ํ•จ์ˆ˜๊ฐ€ ํ•˜๋Š” ์ผ์— ์„ ์ด ์žˆ๋‹ค.
2394
+ *
2395
+ * ํŠธ์œˆ ์•ˆ์—์„œ ์‚ฌ๋žŒ์ด ๋”ํ•œ ์„ค๋น„ ๊ฐ€์ •ยท์‹œ๋ฎฌ๋ ˆ์ด์…˜์˜ ์‚ฌ์‹ค์ด๋‹ค โ€” ํŠธ์œˆ์ด ๋“ ๋‹ค (์ง€๊ธˆ ์ด ํ•จ์ˆ˜)
2396
+ * ํ˜„์žฅ์— ์‹ค์ œ๋กœ ์žˆ๋Š” ์„ค๋น„ ๋งˆ์Šคํ„ฐ์˜ ์‚ฌ์‹ค์ด๋‹ค โ€” ํŠธ์œˆ์ด ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค
2397
+ *
2398
+ * ์ด ํ•จ์ˆ˜๋Š” **ํŠธ์œˆ ์ œ์–ด ๋ช…๋ น**(`resource.add`) ๋’ค์—๋งŒ ๋ถˆ๋ฆฐ๋‹ค. ๊ด€์ธก์—์„œ ์„ค๋น„๊ฐ€ ์ƒ๊ธฐ๋Š” ๊ฒฝ๋กœ๊ฐ€ ์•„๋‹ˆ๋‹ค.
2399
+ * ๊ด€์ธก์œผ๋กœ ๋ชจ๋ฅด๋Š” ์„ค๋น„๊ฐ€ ๋“ค์–ด์˜ค๋ฉด ๊ทธ๊ฒƒ์€ **๋งˆ์Šคํ„ฐ์— ์—†๋Š” ๊ฒƒ**์ด๊ณ , ํŠธ์œˆ์ด ์กฐ์šฉํžˆ ๋งŒ๋“ค์–ด ์ฃผ๋ฉด ๋‘
2400
+ * ๊ณณ์ด ์„œ๋กœ ๋‹ค๋ฅธ ์„ค๋น„ ๋ชฉ๋ก์„ ๊ฐ–๊ฒŒ ๋œ๋‹ค. ๊ทธ๋•Œ๋Š” ๋งŒ๋“ค์ง€ ๋ง๊ณ  ใ€Œ๋ชจ๋ฅด๋Š” ์„ค๋น„๊ฐ€ ์™”๋‹คใ€๋กœ ์„ธ์–ด์•ผ ํ•œ๋‹ค.
2401
+ *
2402
+ * ๋‹น๊ฒจ ์˜ค๋Š” ๊ธธ์ด ๋ถ™์œผ๋ฉด ์ด ์ฃผ์„์˜ ์œ„์ชฝ ์ ˆ๋ฐ˜๋งŒ ๋‚จ๋Š”๋‹ค.
2403
+ *
2404
+ * โ”€โ”€ ์›๋ž˜ ์„ค๋ช… โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1881
2405
  * ๋ผ์ด๋ธŒ ๊ตฌ์กฐ ๋ณ€์ด(resource.add ๋“ฑ)๋ฅผ ์ €์žฅ๋œ model ์— ๋ฐ˜์˜ โ€” ๋Ÿฐํƒ€์ž„ ์ปค๋„์˜ ๋ฌด๋ฒ„๋ฅผ registry model.equipment ์— ๋™๊ธฐ.
1882
2406
  * ์ด๊ฒŒ ์—†์œผ๋ฉด ๋Ÿฐํƒ€์ž„์—” ์ถ”๊ฐ€๋ผ๋„(์ƒํƒœยท์ €๋„์—” ๋ฐ˜์˜) ์ €์žฅ model ๋Š” ์›๋ณธ์ด๋ผ ํ”„๋กœ๋น„์ €๋‹ ํŽธ์ง‘๊ธฐยท์žฌ๊ธฐ๋™(loadTwinModel)์ด
1883
2407
  * ์ถ”๊ฐ€๋ถ„์„ ์žƒ๋Š”๋‹ค. ๊ธฐ์กด model.equipment ํ•ญ๋ชฉ์€ ๋ณด์กด(homeLocation ์œ ์ง€)ํ•˜๊ณ  ์ƒˆ id ๋งŒ append(์ถ”๊ฐ€ ์‹œ์  location=homeLocation).
@@ -1912,7 +2436,7 @@ class TwinEngine {
1912
2436
  /**
1913
2437
  * ์‹คํ–‰ ์ค‘์ด๋ฉด ๊ตฌ์กฐ๋ฅผ ๊ต์ฒดํ•˜์ง€ ์•Š๋Š”๋‹ค โ€” **ํŒ์ • ๋ฌธ์žฅ์„ ํ•œ ๊ณณ์— ๋‘”๋‹ค.**
1914
2438
  *
1915
- * ์ธ์ œ์ŠคํŠธ๋Š” ์ „๋Ÿ‰ ๊ต์ฒด๋ผ, ์ด๋ฒคํŠธ๋ฅผ ์ ‘๊ณ  ์žˆ๋Š” ์ปค๋„ ๋ฐ‘์—์„œ ๋ฐ”๋‹ฅ์„ ๋ฐ”๊พธ๋Š” ์…ˆ์ด ๋œ๋‹ค. ์ง€๊ธˆ์€ ๊ฑฐ์ ˆ์ด
2439
+ * ์ธ์ œ์ŠคํŠธ๋Š” ์ „๋Ÿ‰ ๊ต์ฒด๋ผ, ์ด๋ฒคํŠธ๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  ์žˆ๋Š” ์ปค๋„ ๋ฐ‘์—์„œ ๋ฐ”๋‹ฅ์„ ๋ฐ”๊พธ๋Š” ์…ˆ์ด ๋œ๋‹ค. ์ง€๊ธˆ์€ ๊ฑฐ์ ˆ์ด
1916
2440
  * ์œ ์ผํ•œ ๋‹ต์ด์ง€๋งŒ ์ตœ์ข…ํ˜•์€ ์•„๋‹ˆ๋‹ค(`adoptStructure` ๋ฐฐ์„ ). ๋‘ ๊ณณ์—์„œ ๊ฐ™์€ ๋ฌธ์žฅ์œผ๋กœ ๊ฑฐ์ ˆํ•ด์•ผ
1917
2441
  * ๋‚˜์ค‘์— ์ด ๊ทœ์น™์„ ๊ฑท์–ด๋‚ผ ๋•Œ ๊ฑท์–ด๋‚ผ ๊ฒƒ์ด ํ•˜๋‚˜๋กœ ๋ณด์ธ๋‹ค.
1918
2442
  */
@@ -1943,7 +2467,7 @@ class TwinEngine {
1943
2467
  * ์„ธ ๊ฐ€์ง€๊ฐ€ **ํ•จ๊ป˜** ์ผ์–ด๋‚˜์•ผ ํ•œ๋‹ค. ํ•˜๋‚˜๋ผ๋„ ๋น ์ง€๋ฉด ์ธต์ด ์–ด๊ธ‹๋‚œ๋‹ค:
1944
2468
  * โ‘  ๋ฆฌ๋น„์ „ โ€” ์ด ์‹œ์  ์ดํ›„์˜ ์ด๋ฒคํŠธ๊ฐ€ ์ƒˆ ๋ฒˆํ˜ธ๋ฅผ ๋‹ฌ๊ณ  ๋‹ค๋‹Œ๋‹ค(์žฌ์ƒ์ด ๋งˆ๋””๋ฅผ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๊ฒŒ)
1945
2469
  * โ‘ก ์ €์žฅ modelยท๊ตฌ์กฐ ํ–‰ โ€” ์กฐํšŒ๊ฐ€ ์ƒˆ ๊ตฌ์กฐ๋ฅผ ๋ณธ๋‹ค
1946
- * โ‘ข ์ปค๋„ โ€” ์ง€๊ธˆ ์ ‘ํžˆ๋Š” ์ด๋ฒคํŠธ๊ฐ€ ์ƒˆ ๊ตฌ์กฐ ์œ„์—์„œ ์ ‘ํžŒ๋‹ค
2470
+ * โ‘ข ์ปค๋„ โ€” ์ง€๊ธˆ ๊ณ„์‚ฐ์— ๋“ค์–ด๊ฐ€๋Š” ์ด๋ฒคํŠธ๊ฐ€ ์ƒˆ ๊ตฌ์กฐ ์œ„์—์„œ ๊ณ„์‚ฐ๋œ๋‹ค
1947
2471
  */
1948
2472
  static async adoptStructure(domainId, instanceId, model, comment, origin) {
1949
2473
  const inst = this.instances[(0, runtime_key_js_1.runtimeKey)(domainId, instanceId)];
@@ -1956,7 +2480,7 @@ class TwinEngine {
1956
2480
  throw new Error(`instance "${instanceId}" is running but not registered โ€” refusing to adopt into an unknown twin`);
1957
2481
  const rev = await this.recordStructure(domainId, instanceId, model, comment);
1958
2482
  await this.register(domainId, instanceId, reg.kind, model, 'running', undefined, origin);
1959
- /* ์ปค๋„์„ **๋งˆ์ง€๋ง‰์—** ์ „ํ™˜ํ•œ๋‹ค โ€” ์ €์žฅ์ด ์‹คํŒจํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ๋งŒ ์ƒˆ ๊ตฌ์กฐ๊ฐ€ ๋˜์–ด, ์žฌ๊ธฐ๋™ํ•˜๋ฉด ์กฐ์šฉํžˆ
2483
+ /* ์ปค๋„์„ **๋งˆ์ง€๋ง‰์—** ์ „ํ™˜ํ•œ๋‹ค โ€” ์ €์žฅ์ด ์‹คํŒจํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ๋งŒ ์ƒˆ ๊ตฌ์กฐ๊ฐ€ ๋˜์–ด, ์žฌ๊ธฐ๋™ํ•˜๋ฉด ์•Œ๋ฆฌ์ง€ ์•Š๊ณ 
1960
2484
  ์˜› ๊ตฌ์กฐ๋กœ ๋Œ์•„๊ฐ„๋‹ค(๊ณ ์น˜๊ธฐ ์–ด๋ ค์šด ์–ด๊ธ‹๋‚จ์ด๋‹ค). */
1961
2485
  const shift = inst.kernel.adoptStructure(model);
1962
2486
  /*
@@ -1973,14 +2497,14 @@ class TwinEngine {
1973
2497
  * ์ค€๋‹ค**(๋„ฃ์€ ๊ฐ’์ด ํ™”๋ฉด์—์„œ ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค). ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€ ์ˆœ๊ฐ„์ด ๊ณง ์บ์‹œ๋ฅผ ๋‹ค์‹œ ๊ทธ๋ฆด ์ˆœ๊ฐ„์ด๋‹ค.
1974
2498
  *
1975
2499
  * ์‹คํŒจ๋Š” ํก์ˆ˜ํ•œ๋‹ค: ํˆฌ์˜์ด ๋ง‰ํ˜€๋„(์˜ˆ: ๋ชจ๋ธ์— ์ค‘๋ณต id) ์ปค๋„์€ ์ด๋ฏธ ์ƒˆ ๊ตฌ์กฐ๋กœ ๋Œ๊ณ  ์žˆ์œผ๋ฏ€๋กœ ๊ทธ ์‚ฌ์‹ค์„
1976
- * ๋˜๋Œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค โ€” ๋‹ค๋งŒ ์กฐ์šฉํžˆ ๋„˜๊ธฐ์ง€ ์•Š๊ณ  ๋งํ•œ๋‹ค.
2500
+ * ๋˜๋Œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค โ€” ๋‹ค๋งŒ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋„˜๊ธฐ์ง€ ์•Š๊ณ  ๋งํ•œ๋‹ค.
1977
2501
  */
1978
2502
  await (0, project_structure_js_1.projectStructure)(domainId, instanceId, model, instanceId).catch((err) => (0, log_js_1.twinWarn)(`[twin-engine] "${instanceId}" adopted a new structure but its projected rows were not refreshed โ€” ${err?.message ?? err}`));
1979
2503
  /*
1980
2504
  * **๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€ ์ˆœ๊ฐ„์ด ์ƒํƒœ๊ฐ€ ๋ฐ”๋€ ์ˆœ๊ฐ„์ด๋‹ค** โ€” ๊ทธ๋Ÿฌ๋‹ˆ ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ…ํ•œ๋‹ค.
1981
2505
  *
1982
2506
  * โ”€โ”€ ๋ฌด์—‡์ด ๋‚ฌ๋‚˜ (2026-08-18) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1983
- * ๊ตฌ์กฐ ์ „ํ™˜์€ ์ปค๋„๋งŒ ๊ฐˆ๊ณ  ์กฐ์šฉํžˆ ๋๋‚ฌ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ํ™”๋ฉด์ด ๋ณด๋Š” ๊ฒƒ ์ƒ๋‹น์ˆ˜๊ฐ€ ๊ตฌ์กฐ์—์„œ ํŒŒ์ƒ๋œ๋‹ค โ€”
2507
+ * ๊ตฌ์กฐ ์ „ํ™˜์€ ์ปค๋„๋งŒ ๊ฐˆ๊ณ  ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋๋‚ฌ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ํ™”๋ฉด์ด ๋ณด๋Š” ๊ฒƒ ์ƒ๋‹น์ˆ˜๊ฐ€ ๊ตฌ์กฐ์—์„œ ํŒŒ์ƒ๋œ๋‹ค โ€”
1984
2508
  * ๊ณ„์•ฝ ๋Œ€๋น„ ํŒ์ •, ์ฃผ๋ชฉ ์‹ ํ˜ธ, ์ž๋ฆฌ ์ƒ‰. ํ˜„์žฅ์ด ๊ณ„์•ฝ์„ ๊ณ ์ณ ์„ ์–ธํ•œ ์ˆœ๊ฐ„ ์กฐ๊ฑด์ด ์„ฑ๋ฆฝํ•˜๋Š”๋ฐ๋„, ์ƒํƒœ
1985
2509
  * ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ…์ด ์—†์–ด์„œ ์ง€๋„ ๋ ˆ์ผ์€ **๋‹ค์Œ ๊ณ„์ธก ํ‘œ๋ณธ์ด ์˜ฌ ๋•Œ๊นŒ์ง€** ์˜› ํ™”๋ฉด์„ ๋“ค๊ณ  ์žˆ์—ˆ๋‹ค(๊ทธ ์‚ฌ์ด ํ—ค๋”
1986
2510
  * ๋ฐฐ์ง€๋Š” 4์ดˆ ํด๋ง์œผ๋กœ ๋จผ์ € ์•Œ์•„, ใ€Œ๋ฐฐ์ง€์—” ์žˆ๊ณ  ๋ชฉ๋ก์—” ์—†๋Š”ใ€ ์–ด๊ธ‹๋‚œ ํ™”๋ฉด์ด ์‹ค์ œ๋กœ ๋ณด์˜€๋‹ค).
@@ -1999,7 +2523,7 @@ class TwinEngine {
1999
2523
  * **์žฌ๊ธฐ๋™ ์ •์ฑ…์€ ์ƒ์„ฑ ์‹œ์ ์˜ ์„ ์–ธ์ด๋‹ค**(ADR-0029 ยง2) โ€” ํŠธ์œˆ์€ ์ •์ฑ… ์—†์ด ์กด์žฌํ•  ์ˆ˜ ์—†๋‹ค.
2000
2524
  *
2001
2525
  * ์˜ˆ์ „์—๋Š” ์ด ์ž๋ฆฌ๊ฐ€ ์—†์—ˆ๊ณ  `register` ๊ฐ€ ๊ธฐ๋ณธ๊ฐ’(`sim-experiment` = ์ €๋„ ์ดˆ๊ธฐํ™”)์„ ๊ฐ์ธํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ
2002
- * ์„ ์–ธ์„ ๋น ๋œจ๋ฆฐ ํ”„๋กœ๋น„์ €๋‹์ด **์žฌ๊ธฐ๋™๋งˆ๋‹ค ์ด๋ ฅ์„ ์ง€์šฐ๋Š” ํŠธ์œˆ**์„ ์กฐ์šฉํžˆ ๋งŒ๋“ค์—ˆ๋‹ค. ์ด์ œ ๋งŒ๋“œ๋Š” ์ชฝ์ด
2526
+ * ์„ ์–ธ์„ ๋น ๋œจ๋ฆฐ ํ”„๋กœ๋น„์ €๋‹์ด **์žฌ๊ธฐ๋™๋งˆ๋‹ค ์ด๋ ฅ์„ ์ง€์šฐ๋Š” ํŠธ์œˆ**์„ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋งŒ๋“ค์—ˆ๋‹ค. ์ด์ œ ๋งŒ๋“œ๋Š” ์ชฝ์ด
2003
2527
  * ๋งํ•ด์•ผ ํ•œ๋‹ค: ์ด ํŠธ์œˆ์ด ์ž๊ธฐ ๊ณผ๊ฑฐ๋ฅผ ์–ด๋–ป๊ฒŒ ๋Œ€ํ•˜๋Š”์ง€๋Š” ๋งŒ๋“œ๋Š” ์‚ฌ๋žŒ์ด ์•„๋Š” ์‚ฌ์‹ค์ด๋‹ค.
2004
2528
  */
2005
2529
  restartPolicy, comment, origin,
@@ -2015,12 +2539,12 @@ class TwinEngine {
2015
2539
  * **๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ์—ญ์‚ฌ๋ฅผ ์ง€์šฐ์ง€ ์•Š๋Š”๋‹ค.**
2016
2540
  *
2017
2541
  * ์˜ˆ์ „์—๋Š” ์„œ๋ช…์ด ๋‹ค๋ฅด๋ฉด ๊ทธ ํŠธ์œˆ์˜ ์ €๋„์„ ํ†ต์งธ๋กœ ์ง€์› ๋‹ค. ์•ˆ ์ง€์šฐ๋ฉด ์˜› ์ด๋ฒคํŠธ๋ฅผ ์ƒˆ ๊ณต์žฅ์— ๋Œ€๊ณ 
2018
- * ์ ‘๊ฒŒ ๋˜์–ด ์ด๋ ฅ์ด ๊ฑฐ์ง“๋ง์„ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค โ€” ๋ถ€์Šค๊ฐ€ ๋‘˜์ด๋˜ ์‹œ์ ˆ์˜ ์‚ฌ์‹ค์„ ์—ฌ์„ฏ ๊ฐœ์งœ๋ฆฌ ๊ณต์žฅ์—
2019
- * ์ ‘์œผ๋ฉด ๊ทธ๋•Œ ์—†๋˜ ๋ถ€์Šค์—์„œ ์ผ์ด ์žˆ์—ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค. ์„ ํƒ์ง€๊ฐ€ **์—ญ์‚ฌ๋ฅผ ์žƒ๊ฑฐ๋‚˜ ๊ฑฐ์ง“๋ง์„
2542
+ * ๊ณ„์‚ฐํ•˜๊ฒŒ ๋˜์–ด ์ด๋ ฅ์ด ๊ฑฐ์ง“๋ง์„ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค โ€” ๋ถ€์Šค๊ฐ€ ๋‘˜์ด๋˜ ์‹œ์ ˆ์˜ ์‚ฌ์‹ค์„ ์—ฌ์„ฏ ๊ฐœ์งœ๋ฆฌ ๊ณต์žฅ์—
2543
+ * ๊ณ„์‚ฐํ•˜๋ฉด ๊ทธ๋•Œ ์—†๋˜ ๋ถ€์Šค์—์„œ ์ผ์ด ์žˆ์—ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค. ์„ ํƒ์ง€๊ฐ€ **์—ญ์‚ฌ๋ฅผ ์žƒ๊ฑฐ๋‚˜ ๊ฑฐ์ง“๋ง์„
2020
2544
  * ํ•˜๊ฑฐ๋‚˜** ๋‘˜๋ฟ์ด์—ˆ๊ณ , ๊ทธ๋ž˜์„œ ๊ณต์žฅ์„ ๊ณ ์น  ๋•Œ๋งˆ๋‹ค ์ด๋ ฅ์„ ๋ฒ„๋ ค์•ผ ํ–ˆ๋‹ค.
2021
2545
  *
2022
2546
  * ์ด์ œ ์…‹์งธ ๊ธธ๋กœ ๊ฐ„๋‹ค: ๋ฐ”๋€ ๊ตฌ์กฐ๋ฅผ **์ƒˆ ๋ฆฌ๋น„์ „**์œผ๋กœ ๋‚จ๊ธฐ๊ณ , ์•ž์œผ๋กœ ์“ฐ์ด๋Š” ์ด๋ฒคํŠธ๊ฐ€ ๊ทธ ๋ฒˆํ˜ธ๋ฅผ
2023
- * ๋‹ฌ๊ณ  ๋‹ค๋‹Œ๋‹ค. ์žฌ์ƒ์€ ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ๋Š” ์ง€์ ์—์„œ ์ „ํ™˜ํ•œ ๋’ค ์ด์–ด ์ ‘๋Š”๋‹ค(`replaySegments`).
2547
+ * ๋‹ฌ๊ณ  ๋‹ค๋‹Œ๋‹ค. ์žฌ์ƒ์€ ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ๋Š” ์ง€์ ์—์„œ ์ „ํ™˜ํ•œ ๋’ค ์ด์–ด์„œ ๊ณ„์‚ฐํ•œ๋‹ค(`replaySegments`).
2024
2548
  */
2025
2549
  await this.recordStructure(domainId, instanceId, model, comment);
2026
2550
  await this.register(domainId, instanceId, kind, model, existing?.status === 'running' ? 'stopped' : existing?.status ?? 'stopped', restartPolicy, origin, meta);
@@ -2172,7 +2696,7 @@ class TwinEngine {
2172
2696
  * ์›œ์Šคํƒ€ํŠธ ์žฌ๋ฃŒ๋ฅผ **์—ฌ๊ธฐ์„œ ํ™•์‹คํžˆ ํ™•๋ณดํ•œ๋‹ค.**
2173
2697
  * `start()` ๋Š” ๋™๊ธฐ๋ผ ์Šค์Šค๋กœ ์บ์‹œ๋ฅผ ์ฝ์„ ์ˆ˜ ์—†์–ด์„œ `recovered` ์— ๋ฏธ๋ฆฌ ๋‹ด๊ฒจ ์žˆ๊ธฐ๋ฅผ ๊ธฐ๋Œ€ํ•˜๋Š”๋ฐ,
2174
2698
  * ๊ทธ๊ฑด `bootstrap()` ์ด ๋จผ์ € ๋Œ์•˜์„ ๋•Œ๋งŒ ์ฐธ์ด๋‹ค. ๋ถ€ํŒ… ์ˆœ์„œ์— ๊ธฐ๋Œ€๋ฉด ์–ด๋–ค ๋‚ ์€ ์žฌ๊ณ ๊ฐ€ ์‚ด์•„๋‚˜๊ณ 
2175
- * ์–ด๋–ค ๋‚ ์€ ์กฐ์šฉํžˆ ๋นˆ ์ฑ„๋กœ ๋œฌ๋‹ค โ€” ์žฌํ˜„๋˜์ง€ ์•Š๋Š” ๊ฒฐํ•จ์ด ๊ฐ€์žฅ ๋‚˜์˜๋‹ค.
2699
+ * ์–ด๋–ค ๋‚ ์€ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋นˆ ์ฑ„๋กœ ๋œฌ๋‹ค โ€” ์žฌํ˜„๋˜์ง€ ์•Š๋Š” ๊ฒฐํ•จ์ด ๊ฐ€์žฅ ๋‚˜์˜๋‹ค.
2176
2700
  * ์ฒดํฌํฌ์ธํŠธ ์บ์‹œ ์šฐ์„ (O(1) + ๋ผ์ด๋ธŒ ํŒŒ์ƒ์ƒํƒœ ๋ณด์กด), ์—†์œผ๋ฉด ์ €๋„ replay ํด๋ฐฑ(๋ถ€ํŒ…๊ณผ ๊ฐ™์€ ์ˆœ์„œ).
2177
2701
  */
2178
2702
  if (!this.recovered[key] && reg.purpose !== 'bench') {
@@ -2201,9 +2725,9 @@ class TwinEngine {
2201
2725
  .getRawOne();
2202
2726
  return this.start(instanceId, domainId,
2203
2727
  /* ์ปค๋„ ์ข…๋ฅ˜ยทํ˜„์‹ค ์„ ์–ธ์€ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์— ๋ฐ˜๋“œ์‹œ ์žˆ๋‹ค(๋‘˜ ๋‹ค NOT NULL). ์˜ˆ์ „์—๋Š” `?? 'wms'` ๋กœ
2204
- ๋ฉ”์› ๋Š”๋ฐ, ๊ทธ๊ฑด YMS/MES ํŠธ์œˆ์„ **์กฐ์šฉํžˆ WMS ๋กœ ๋ถ€ํŒ…**์‹œํ‚ค๋Š” ๊ธธ์ด์—ˆ๋‹ค โ€” ์˜ค๋ฅ˜ ์—†์ด ๋‹ค๋ฅธ ๊ณต์žฅ์ด ๋œฌ๋‹ค. */
2728
+ ๋ฉ”์› ๋Š”๋ฐ, ๊ทธ๊ฑด YMS/MES ํŠธ์œˆ์„ **์•Œ๋ฆฌ์ง€ ์•Š๊ณ  WMS ๋กœ ๋ถ€ํŒ…**์‹œํ‚ค๋Š” ๊ธธ์ด์—ˆ๋‹ค โ€” ์˜ค๋ฅ˜ ์—†์ด ๋‹ค๋ฅธ ๊ณต์žฅ์ด ๋œฌ๋‹ค. */
2205
2729
  reg.kind, reg.model,
2206
- /* ์ €์žฅ๋œ ๊ฐ’์€ ์œ„์—์„œ **์—„๊ฒฉํžˆ** ์ฝ์—ˆ๋‹ค โ€” ๋ชจ๋ฅด๋Š” ๊ฐ’์„ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋ฉ”์šฐ๋ฉด ๊ทธ ํŠธ์œˆ์ด ์กฐ์šฉํžˆ ๋‹ค๋ฅด๊ฒŒ ์žฌ๊ธฐ๋™ํ•œ๋‹ค. */
2730
+ /* ์ €์žฅ๋œ ๊ฐ’์€ ์œ„์—์„œ **์—„๊ฒฉํžˆ** ์ฝ์—ˆ๋‹ค โ€” ๋ชจ๋ฅด๋Š” ๊ฐ’์„ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋ฉ”์šฐ๋ฉด ๊ทธ ํŠธ์œˆ์ด ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋‹ค๋ฅด๊ฒŒ ์žฌ๊ธฐ๋™ํ•œ๋‹ค. */
2207
2731
  policy, reg.purpose, Number(last?.max ?? 0) || 0);
2208
2732
  }
2209
2733
  /**
@@ -2230,7 +2754,7 @@ class TwinEngine {
2230
2754
  * ์žˆ๋Š”๋ฐ ์•„๋ฌด ์ผ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”ใ€ ์ƒํƒœ๊ฐ€ ๋๋‹ค.
2231
2755
  *
2232
2756
  * ์ด๊ฒƒ์€ `start` ๊ฐ€ ์ด๋ฏธ ๋ฐฐ์šด ๊ตํ›ˆ๊ณผ ๊ฐ™์€ ์ž๋ฆฌ๋‹ค: ๋ถ€ํŒ… ์ˆœ์„œ์— ๊ธฐ๋Œ€๋ฉด ์–ด๋–ค ๋‚ ์€ ์ƒํƒœ๊ฐ€ ์‚ด์•„๋‚˜๊ณ 
2233
- * ์–ด๋–ค ๋‚ ์€ ์กฐ์šฉํžˆ ๋นˆ ์ฑ„๋กœ ๋œฌ๋‹ค โ€” **์žฌํ˜„๋˜์ง€ ์•Š๋Š” ๊ฒฐํ•จ์ด ๊ฐ€์žฅ ๋‚˜์˜๋‹ค.** ๊ทธ๋ž˜์„œ ์ˆœ์„œ์— ๊ธฐ๋Œ€์ง€ ์•Š๊ณ 
2757
+ * ์–ด๋–ค ๋‚ ์€ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋นˆ ์ฑ„๋กœ ๋œฌ๋‹ค โ€” **์žฌํ˜„๋˜์ง€ ์•Š๋Š” ๊ฒฐํ•จ์ด ๊ฐ€์žฅ ๋‚˜์˜๋‹ค.** ๊ทธ๋ž˜์„œ ์ˆœ์„œ์— ๊ธฐ๋Œ€์ง€ ์•Š๊ณ 
2234
2758
  * ์ด ์ž๋ฆฌ์—์„œ ํ™•๋ณดํ•œ๋‹ค(์ด๋ฏธ ๋‹ด๊ฒจ ์žˆ์œผ๋ฉด ๊ทธ๊ฒƒ์„ ์“ด๋‹ค).
2235
2759
  */
2236
2760
  const key = (0, runtime_key_js_1.runtimeKey)(domainId, instanceId);
@@ -2430,7 +2954,7 @@ class TwinEngine {
2430
2954
  const agg = new Map();
2431
2955
  for (const r of insts) {
2432
2956
  /* ๋ฒค์น˜ ์ œ์™ธ๋Š” **์„ ์–ธ์œผ๋กœ๋งŒ** ํŒ๋‹จํ•œ๋‹ค. ์˜ˆ์ „์—๋Š” spaceId ์ด๋ฆ„(`bench-`ยท`loadtest*`)์œผ๋กœ๋„ ์ถ”์ธกํ–ˆ๋Š”๋ฐ,
2433
- ๊ทธ๋Ÿฌ๋ฉด ๊ทธ๋ ‡๊ฒŒ ์ด๋ฆ„ ์ง€์€ ์ง„์งœ ์šด์˜ ๊ณต๊ฐ„์ด ๋ชฉ๋ก์—์„œ ์กฐ์šฉํžˆ ์‚ฌ๋ผ์ง„๋‹ค. purpose ๊ฐ€ ์ •๋ณธ์ด๋‹ค. */
2957
+ ๊ทธ๋Ÿฌ๋ฉด ๊ทธ๋ ‡๊ฒŒ ์ด๋ฆ„ ์ง€์€ ์ง„์งœ ์šด์˜ ๊ณต๊ฐ„์ด ๋ชฉ๋ก์—์„œ ์˜ค๋ฅ˜ ์—†์ด ์‚ฌ๋ผ์ง„๋‹ค. purpose ๊ฐ€ ์ •๋ณธ์ด๋‹ค. */
2434
2958
  if (!r.spaceId || r.purpose === 'bench')
2435
2959
  continue;
2436
2960
  const model = r.model ?? {};
@@ -2516,7 +3040,7 @@ class TwinEngine {
2516
3040
  * ๋‹ค์‹œ ๊ทธ๋ฆฌ๋ฉด ๊ทธ๋Œ€๋กœ ์‚ฌ๋ผ์ง„๋‹ค โ€” ๊ธˆ์•ก์ด ์‚ฌ๋ผ์ง€๊ณ , ์‚ฌ์šฉ์ž๋Š” ์ž๊ธฐ๊ฐ€ ๋„ฃ์€ ๊ฐ’์ด ์–ด๋””๋กœ ๊ฐ”๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๋‹ค.
2517
3041
  * ๊ทธ๋ž˜์„œ ๊ฒน์œผ๋กœ ๋ณด๊ด€ํ•œ ์„ ์–ธ์„ ๋งค๋ฒˆ ๋‹ค์‹œ ์–น๋Š”๋‹ค(`engine/local-declarations`).
2518
3042
  *
2519
- * ๋Œ€์ƒ์ด ์‚ฌ๋ผ์กŒ์œผ๋ฉด(๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ์—ˆ๋‹ค) ๊ทธ ์‚ฌ์‹ค์„ ๊ฒฝ๊ณ ๋กœ ๋‚ธ๋‹ค โ€” ๊ฐˆ ๊ณณ ์—†๋Š” ์„ ์–ธ์„ ์กฐ์šฉํžˆ ๋ฒ„๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.
3043
+ * ๋Œ€์ƒ์ด ์‚ฌ๋ผ์กŒ์œผ๋ฉด(๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ์—ˆ๋‹ค) ๊ทธ ์‚ฌ์‹ค์„ ๊ฒฝ๊ณ ๋กœ ๋‚ธ๋‹ค โ€” ๊ฐˆ ๊ณณ ์—†๋Š” ์„ ์–ธ์„ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋ฒ„๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.
2520
3044
  */
2521
3045
  const prior = await (0, shell_1.getRepository)(twin_instance_js_1.TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId: master.source } });
2522
3046
  /*
@@ -2553,7 +3077,7 @@ class TwinEngine {
2553
3077
  }
2554
3078
  }
2555
3079
  const repo = (0, shell_1.getRepository)(twin_space_js_1.TwinSpace);
2556
- /* ํ•ฉ์น  ๊ณต๊ฐ„์„ ๊ณจ๋ž์œผ๋ฉด **์žˆ๋Š”์ง€ ๋จผ์ € ํ™•์ธํ•œ๋‹ค** โ€” ์—†๋Š” ๊ณณ์— ์กฐ์šฉํžˆ ์ƒˆ ๊ณต๊ฐ„์„ ๋งŒ๋“ค๋ฉด ์‚ฌ์šฉ์ž๋Š”
3080
+ /* ํ•ฉ์น  ๊ณต๊ฐ„์„ ๊ณจ๋ž์œผ๋ฉด **์žˆ๋Š”์ง€ ๋จผ์ € ํ™•์ธํ•œ๋‹ค** โ€” ์—†๋Š” ๊ณณ์— ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์ƒˆ ๊ณต๊ฐ„์„ ๋งŒ๋“ค๋ฉด ์‚ฌ์šฉ์ž๋Š”
2557
3081
  ํ•ฉ์ณค๋‹ค๊ณ  ๋ฏฟ๊ณ  ํ™”๋ฉด์€ ๋”ฐ๋กœ ๋…ผ๋‹ค. ํŒ์ • ํ•จ์ˆ˜๊ฐ€ ๊ทธ ๊ฒฝ์šฐ ๊ฑฐ์ ˆํ•œ๋‹ค. */
2558
3082
  const wanted = (into?.spaceId ?? '').trim();
2559
3083
  const wantedExists = wanted
@@ -2564,7 +3088,7 @@ class TwinEngine {
2564
3088
  model.spaceId = spaceId; // ๋ณด๋“œ(์ปค๋„ ์†Œ๋น„)๋„ ๊ฐ™์€ ๊ณต๊ฐ„์„ ๊ฐ€๋ฆฌ์ผœ์•ผ ํ•œ๋‹ค
2565
3089
  const existing = await repo.findOne({ where: { domain: { id: domainId }, spaceId } });
2566
3090
  if (choice.joined) {
2567
- /* ๊ฐ™์€ id ์˜ ๊ตฌ์—ญยท๋žœ๋“œ๋งˆํฌ๋Š” ํ•ฉ์ง‘ํ•ฉ์œผ๋กœ ์ ‘ํžŒ๋‹ค โ€” ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ์„ ๊ณ ๋ฅด์ง€ ์•Š๊ณ  ๋ฌด์—‡์ด
3091
+ /* ๊ฐ™์€ id ์˜ ๊ตฌ์—ญยท๋žœ๋“œ๋งˆํฌ๋Š” ํ•ฉ์ง‘ํ•ฉ์œผ๋กœ ํ•ฉ์ณ์ง„๋‹ค โ€” ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ์„ ๊ณ ๋ฅด์ง€ ์•Š๊ณ  ๋ฌด์—‡์ด
2568
3092
  ํ•ฉ์ณ์กŒ๋Š”์ง€ ๋งํ•œ๋‹ค(์ •๋‹นํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์œผ๋ฏ€๋กœ ๋ง‰์ง€ ์•Š๋Š”๋‹ค). */
2569
3093
  }
2570
3094
  /*
@@ -2589,7 +3113,7 @@ class TwinEngine {
2589
3113
  * ๋Œ€ํ‘œ ํ‘œํ˜„ ํฌ์ธํ„ฐ๋Š” **ํ‘œํ˜„์„ ์ €์žฅํ•œ ๋’ค** ์‹ค์ œ ํ–‰ id ๋กœ ์ฑ„์šด๋‹ค(์•„๋ž˜). ์—ฌ๊ธฐ์„œ ๋งˆ์Šคํ„ฐ์˜ ๋…ผ๋ฆฌ
2590
3114
  * id(`r-map` ๋”ฐ์œ„)๋ฅผ ๋„ฃ์œผ๋ฉด ํ‘œํ˜„ ํ–‰์€ ์ƒ์„ฑ uuid ๋ฅผ ๋ฐ›์œผ๋ฏ€๋กœ ํฌ์ธํ„ฐ๊ฐ€ **์ฒ˜์Œ๋ถ€ํ„ฐ ํ—ˆ๊ณต์„
2591
3115
  * ๊ฐ€๋ฆฌํ‚จ๋‹ค** โ€” ์‹ค์ œ๋กœ 13๊ฐœ ํ˜„์žฅ์ด ๊ทธ ์ƒํƒœ์˜€๊ณ  ์ •ํ•ฉ์„ฑ ์ ๊ฒ€์ด ์žก์•˜๋‹ค(2026-08-13). ํ™”๋ฉด์€
2592
- * ํ–‰์˜ `isPrimary` ๋กœ โ˜…๋ฅผ ๊ทธ๋ ค์„œ ์ฆ์ƒ์ด ๋ณด์ด์ง€ ์•Š์•˜๋‹ค(์‚ฌ์‹ค์ด ๋‘ ๋ฒŒ์ด๋ฉด ์ด๋ ‡๊ฒŒ ์กฐ์šฉํ•˜๋‹ค).
3116
+ * ํ–‰์˜ `isPrimary` ๋กœ โ˜…๋ฅผ ๊ทธ๋ ค์„œ ์ฆ์ƒ์ด ๋ณด์ด์ง€ ์•Š์•˜๋‹ค(์‚ฌ์‹ค์ด ๋‘ ๋ฒŒ์ด๋ฉด ์ด๋ ‡๊ฒŒ ๋“ค์–ด์˜จ ๊ฒƒ์ด ์—†๋‹ค).
2593
3117
  */
2594
3118
  primaryRepresentationId: existing?.primaryRepresentationId ?? null
2595
3119
  }));
@@ -2650,7 +3174,7 @@ class TwinEngine {
2650
3174
  }
2651
3175
  if (running) {
2652
3176
  /* ์‹คํ–‰ ์ค‘์ธ ๋ฏธ๋Ÿฌ โ€” ๋ฉˆ์ถ”์ง€ ์•Š๊ณ  ์ „ํ™˜ํ•œ๋‹ค. ๋ฌด์—‡์ด ์‚ฌ๋ผ์กŒ๋Š”์ง€๋Š” **๊ฒฝ๊ณ ๋กœ ๋งํ•œ๋‹ค**
2653
- (๋ฉˆ์ท„๋‹ค ์„ธ์šฐ๋Š” ๋งˆ๋””๊ฐ€ ์—†์œผ๋ฏ€๋กœ, ๋งํ•˜์ง€ ์•Š์œผ๋ฉด ์ž๋ฆฌ ํ•˜๋‚˜๊ฐ€ ์กฐ์šฉํžˆ ์—†์–ด์ง„๋‹ค). */
3177
+ (๋ฉˆ์ท„๋‹ค ์„ธ์šฐ๋Š” ๋งˆ๋””๊ฐ€ ์—†์œผ๋ฏ€๋กœ, ๋งํ•˜์ง€ ์•Š์œผ๋ฉด ์ž๋ฆฌ ํ•˜๋‚˜๊ฐ€ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์—†์–ด์ง„๋‹ค). */
2654
3178
  const shift = await this.adoptStructure(domainId, master.source, model, undefined, master.origin);
2655
3179
  warnings.push((0, reference_master_js_3.structureAdopted)(shift.rev, shift));
2656
3180
  }
@@ -2667,7 +3191,7 @@ class TwinEngine {
2667
3191
  *
2668
3192
  * ์ธ์ œ์ŠคํŠธ๊ฐ€ ์œ ์ผํ•œ ์“ฐ๊ธฐ ๊ฒฝ๋กœ๋‹ค. ์—ฌ๊ธฐ์„œ ์‹คํŒจํ•ด๋„ ์ธ์ œ์ŠคํŠธ ์ž์ฒด๋Š” ์„ฑ๊ณต์œผ๋กœ ๋‘”๋‹ค โ€”
2669
3193
  * ํ–‰์€ **์›๋ณธ์—์„œ ์–ธ์ œ๋“  ๋‹ค์‹œ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋Š” ์บ์‹œ**์ด๊ณ , ํŠธ์œˆ ์ž์ฒด๋Š” model ๋กœ ์ด๋ฏธ ๋™์ž‘ํ•œ๋‹ค.
2670
- * ๋‹ค๋งŒ **์กฐ์šฉํžˆ ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค**: ๋ชป ์ด์€ ์ฐธ์กฐ๋Š” ์ธ์ œ์ŠคํŠธ ๊ฒฝ๊ณ ๋กœ ์˜ฌ๋ผ๊ฐ„๋‹ค.
3194
+ * ๋‹ค๋งŒ **์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค**: ๋ชป ์ด์€ ์ฐธ์กฐ๋Š” ์ธ์ œ์ŠคํŠธ ๊ฒฝ๊ณ ๋กœ ์˜ฌ๋ผ๊ฐ„๋‹ค.
2671
3195
  */
2672
3196
  try {
2673
3197
  const projected = await (0, project_structure_js_1.projectStructure)(domainId, master.source, model, master.source);
@@ -2681,7 +3205,7 @@ class TwinEngine {
2681
3205
  */
2682
3206
  if (model?.utcOffsetMinutes === undefined) {
2683
3207
  for (const e of (model?.equipment ?? [])) {
2684
- const hasProfile = (e?.properties ?? []).some(p => String(p?.id) === twin_kernel_1.EMS_PROPERTY.genDailyProfile && String(p?.value ?? '').trim());
3208
+ const hasProfile = (e?.properties ?? []).some(p => String(p?.id) === ops_contract_1.EMS_PROPERTY.genDailyProfile && String(p?.value ?? '').trim());
2685
3209
  if (hasProfile)
2686
3210
  warnings.push((0, reference_master_js_3.generationWithoutTimeZone)(String(model?.spaceId ?? ''), String(e.id)));
2687
3211
  }
@@ -2728,6 +3252,19 @@ class TwinEngine {
2728
3252
  * ๋ชป ๋ณด๋‚ธ ๊ฒƒ์€ **์‚ฌ์‹ค๋กœ ๋‚จ๊ธด๋‹ค**(ํšŸ์ˆ˜๋ฅผ ์„ธ๊ณ  ์ฐฝ๋งˆ๋‹ค ํ•œ ์ค„ ๋‚จ๊ธด๋‹ค). ์‚ผํ‚ค๋ฉด ใ€Œ๋ณด๋ƒˆ๋Š”๋ฐ ํ™”๋ฉด์ด ๋‚ก์•˜๋‹คใ€๊ฐ€
2729
3253
  * ๋˜๊ณ , ๊ทธ๊ฑด ๊ฐ€์žฅ ์ฐพ๊ธฐ ์–ด๋ ค์šด ๋ถ€๋ฅ˜๋‹ค.
2730
3254
  */
3255
+ /**
3256
+ * ๋ธŒ๋กœ๋“œ์บ์ŠคํŒ… ํ•œ ๊ฑด โ€” **์ด ๊ฐ€๋“œ๊ฐ€ ๋Œ€๊ธฐ์—ด ๋„˜์นจ์„ ๋ง‰์ง€๋Š” ๋ชปํ•œ๋‹ค** (2026-08-27 ์ธก์ •์œผ๋กœ ๋ฐ”๋กœ์žก์Œ).
3257
+ *
3258
+ * 2026-08-14 ์— ์ด ํ•จ์ˆ˜๋ฅผ ใ€Œ๋ฐฉ์†ก์ด ํ˜ธ์ŠคํŠธ๋ฅผ ์ฃฝ์ด์ง€ ๋ชปํ•˜๊ฒŒใ€๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋ถ™์˜€๋‹ค. ๊ทธ๊ฒƒ์ด ์‚ฌ์‹ค์ด ์•„๋‹ˆ๋‹ค.
3259
+ * ๋Œ€๊ธฐ์—ด์ด ๋„˜์น  ๋•Œ ๋‚˜๋Š” ์˜ˆ์™ธ๋Š” `EventTarget` ์ด ๋ถ€๋ฅธ ๋ฆฌ์Šค๋„ˆ ์•ˆ์—์„œ ๋‚œ๋‹ค. ๋Ÿฐํƒ€์ž„์€ ๋ฆฌ์Šค๋„ˆ ์˜ˆ์™ธ๋ฅผ
3260
+ * ํ˜ธ์ถœ์ž์—๊ฒŒ ์ฃผ์ง€ ์•Š๊ณ  uncaughtException ์œผ๋กœ ๋ณด๋‚ธ๋‹ค. ๊ทธ๋ž˜์„œ ์ด try/catch ์—๋Š” ์žก์„ ๊ฒƒ์ด ์—†๋‹ค.
3261
+ * ์‹ค์ œ๋กœ ๊ทธ ๋’ค 3์ฃผ ๋™์•ˆ ๋ง‰ํžŒ ์ค„ ์•Œ๊ณ  ์žˆ์—ˆ๊ณ , 2026-08-27 10:24 ์— ๊ฐ™์€ ์˜ˆ์™ธ๋กœ ๋‹ค์‹œ ์ฃฝ์—ˆ๋‹ค.
3262
+ *
3263
+ * ๋„˜์นจ์„ ๋ง‰๋Š” ๊ฒƒ์€ **ํ•œ ์‹คํ–‰ ์ฐจ๋ก€์— ๋ฐœํ–‰ํ•˜๋Š” ์ˆ˜๋ฅผ ์ƒํ•œ ์•„๋ž˜๋กœ ๋‘๋Š” ๊ฒƒ**์ด๋‹ค(ยง`PUBLISH_CHUNK`).
3264
+ *
3265
+ * ๊ทธ๋ž˜๋„ ์ด ๊ฐ€๋“œ๋ฅผ ๋‚จ๊ธฐ๋Š” ์ด์œ ๋Š” ๋‘˜์ด๋‹ค. `publish` ๊ฐ€ ์ง์ ‘ ๋˜์ง€๋Š” ๊ฒฝ์šฐ(์ง๋ ฌํ™” ์‹คํŒจ ๋“ฑ)๋ฅผ ์žก๊ณ ,
3266
+ * ๋ชป ๋ณด๋‚ธ ํƒœ๊ทธ์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ๋˜๋Œ๋ฆฐ๋‹ค โ€” ๋‚จ๊ฒจ ๋‘๋ฉด ๊ทธ ํƒœ๊ทธ๊ฐ€ ์˜ค๋ฅ˜ ์—†์ด ์˜์›ํžˆ ๋‚ก์€ ๊ฐ’์„ ๋ณด์ธ๋‹ค.
3267
+ */
2731
3268
  static publishGuarded(channel, payload, what) {
2732
3269
  try {
2733
3270
  shell_1.pubsub.publish(channel, payload);
@@ -2745,7 +3282,7 @@ class TwinEngine {
2745
3282
  return false;
2746
3283
  }
2747
3284
  }
2748
- static publishEntityData(inst) {
3285
+ static async publishEntityData(inst) {
2749
3286
  const domain = inst.domain;
2750
3287
  if (!domain)
2751
3288
  return;
@@ -2778,7 +3315,7 @@ class TwinEngine {
2778
3315
  * ๋ฌธ์ž์—ด๋กœ ๋ฐ”๊พผ ๋’ค ใ€Œ๊ฐ™๋‹คใ€๋ฅผ ํ™•์ธํ•˜๊ณ  ๋ฒ„๋ ธ๋‹ค.
2779
3316
  *
2780
3317
  * ๋ฒ”์œ„๋Š” ์ด ์ฐฝ์— ๋“ค์–ด์˜จ ์‚ฌ๊ฑด์—์„œ ๋ชจ์•˜๋‹ค(`touchedItemKeys`). **๋งํ•  ์ˆ˜ ์—†๋Š” ์‚ฌ๊ฑด์ด ํ•˜๋‚˜๋ผ๋„ ์žˆ์œผ๋ฉด
2781
- * ๋ฒ”์œ„๋Š” ์—†๊ณ  ์ „๋ถ€ ๋งŒ๋“ ๋‹ค** โ€” ๋‚ฏ์„  ์–ดํœ˜๊ฐ€ ์˜ค๋ฉด ์กฐ์šฉํžˆ ๋น ๋œจ๋ฆฌ๋Š” ๋Œ€์‹  ๋น„์‹ธ๊ฒŒ ์•ˆ์ „ํ•œ ์ชฝ์œผ๋กœ ๋–จ์–ด์ง„๋‹ค.
3318
+ * ๋ฒ”์œ„๋Š” ์—†๊ณ  ์ „๋ถ€ ๋งŒ๋“ ๋‹ค** โ€” ๋‚ฏ์„  ์–ดํœ˜๊ฐ€ ์˜ค๋ฉด ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋น ๋œจ๋ฆฌ๋Š” ๋Œ€์‹  ๋น„์‹ธ๊ฒŒ ์•ˆ์ „ํ•œ ์ชฝ์œผ๋กœ ๋–จ์–ด์ง„๋‹ค.
2782
3319
  *
2783
3320
  * ๊ทธ๋ฆฌ๊ณ  ์ฃผ๊ธฐ๋งˆ๋‹ค ํ•œ ๋ฒˆ์€ **๋ฌด์กฐ๊ฑด ์ „๋ถ€** ๋งŒ๋“ ๋‹ค(`FULL_BROADCAST_EVERY`). ์ปค๋„์ด ์‚ฌ๊ฑด ์—†์ด ๋ฌผํ’ˆ์„
2784
3321
  * ๋ฐ”๊พธ๋Š” ์ž๋ฆฌ๊ฐ€ ์ƒ๊ธฐ๋ฉด ๊ทธ ๊ฐ’์ด ํ™”๋ฉด์— ๋‚จ์„ ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ทธ ์ฐฝ์„ ๋ช‡ ์ดˆ๋กœ ๋ฌถ๋Š” ๊ทธ๋ฌผ์ด๋‹ค. **๋ณด์žฅ์ด ์•„๋‹ˆ๋ผ
@@ -2794,6 +3331,22 @@ class TwinEngine {
2794
3331
  inst.fullBroadcastDue = false;
2795
3332
  (0, load_meter_js_1.recordPhase)(load, 'deltas', performance.now() - tDelta);
2796
3333
  const tPub = performance.now();
3334
+ /*
3335
+ * โ”€โ”€ **ํ•œ ์‹คํ–‰ ์ฐจ๋ก€์— ์ƒํ•œ๊นŒ์ง€๋งŒ ๋ฐœํ–‰ํ•œ๋‹ค** (2026-08-27 ์‹ค์ธก์œผ๋กœ ๊ณ ์นจ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3336
+ *
3337
+ * ์ด ๋ฐ˜๋ณต๋ฌธ์ด ๋™๊ธฐ์˜€๋‹ค. ๊ตฌ๋…์˜ ๋Œ€๊ธฐ์—ด์—๋Š” ๋ฒ„ํผ๊ฐ€ ์—†๊ณ , ๋ฐœํ–‰ํ•˜๋Š” ์ˆœ๊ฐ„์— ใ€Œ๋‹ค์Œ ๊ฐ’์„ ๋‹ฌ๋ผใ€๋Š” ์š”์ฒญ์ด
3338
+ * ๊ฑธ๋ ค ์žˆ์ง€ ์•Š์œผ๋ฉด ์Œ“์ธ๋‹ค. ๊ฐ’์„ ๊บผ๋‚ด๋Š” ์ชฝ์€ ๋น„๋™๊ธฐ๋ผ์„œ **๋™๊ธฐ ๋ฐ˜๋ณต๋ฌธ์ด ๋„๋Š” ์ค‘์—๋Š” ํ•œ ๊ฑด๋„ ๊บผ๋‚ด์ง€
3339
+ * ๋ชปํ•œ๋‹ค.** ๊ทธ๋ž˜์„œ N ๊ฑด์„ ๋ฐœํ–‰ํ•˜๋ฉด ์ฒซ ๊ฑด๋งŒ ์ „๋‹ฌ๋˜๊ณ  ๋‚˜๋จธ์ง€๊ฐ€ ์Œ“์˜€๊ณ , 1024 ๋ฅผ ๋„˜๋Š” ์ˆœ๊ฐ„ ์˜ˆ์™ธ๊ฐ€ ๋‚˜์„œ
3340
+ * ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋‚ด๋ ค๊ฐ”๋‹ค(2026-08-27 10:24 ยท 2026-08-14 ์—๋„ ๊ฐ™์€ ์˜ˆ์™ธ).
3341
+ *
3342
+ * `PUBLISH_CHUNK` ๋งˆ๋‹ค ๋ฃจํ”„๋ฅผ ๋น„์›Œ ์ค€๋‹ค. ๊ทธ ํ‹ˆ์— ์†Œ๋น„์ž๊ฐ€ ๋Œ€๊ธฐ์—ด์„ ๊บผ๋‚ด ๊ฐ„๋‹ค.
3343
+ *
3344
+ * **๊ฑด๋„ˆ๋›ฐ์ง€ ์•Š๋Š”๋‹ค.** ์ด ์ฐฝ์˜ ๋ฐœํ–‰์€ ๋๊นŒ์ง€ ํ•œ๋‹ค โ€” ์ค‘๊ฐ„์— ๊ทธ๋งŒ๋‘๋ฉด ๊ทธ ํƒœ๊ทธ๊ฐ€ ๋‚ก์€ ๊ฐ’์œผ๋กœ ๋‚จ๋Š”๋‹ค.
3345
+ * ์ด ํ•จ์ˆ˜๊ฐ€ ์ฃผ๊ธฐ๋ณด๋‹ค ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋ฉด ๋‹ค์Œ ํƒ€์ด๋จธ๋Š” ๊ทธ๋Œ€๋กœ ๋Œ์•„๊ฐ€๊ณ (ยง`flushLiveBroadcasts`),
3346
+ * `adjustBroadcastPeriod` ๊ฐ€ ์ฃผ๊ธฐ๋ฅผ ์˜ฌ๋ฆฐ๋‹ค.
3347
+ */
3348
+ let published = 0;
3349
+ let sinceYield = 0;
2797
3350
  for (const { tag, data } of deltas) {
2798
3351
  seen.add(tag);
2799
3352
  const sig = JSON.stringify(data);
@@ -2806,8 +3359,26 @@ class TwinEngine {
2806
3359
  */
2807
3360
  if (!this.publishGuarded('data', { data: { domain, tag, data } }, `data:${inst.id}`))
2808
3361
  sigs.delete(tag);
3362
+ published++;
3363
+ if (++sinceYield >= this.PUBLISH_CHUNK) {
3364
+ sinceYield = 0;
3365
+ await new Promise(resolve => setImmediate(resolve));
3366
+ }
2809
3367
  }
2810
3368
  (0, load_meter_js_1.recordPhase)(load, 'publish', performance.now() - tPub);
3369
+ /*
3370
+ * โ”€โ”€ **๋ช‡ ๊ฑด์„ ๋ฐœํ–‰ํ–ˆ๋Š”์ง€ ๋‚จ๊ธด๋‹ค** (2026-08-27) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3371
+ *
3372
+ * 10:24 ์— ์„œ๋ฒ„๊ฐ€ ์ฃฝ์—ˆ์„ ๋•Œ ๊ทธ ์ฐฝ์ด ๋ช‡ ๊ฑด์„ ๋ฐœํ–‰ํ–ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์—†์—ˆ๋‹ค. ๊ทธ ์ˆ˜๊ฐ€ ์—†์œผ๋ฉด ใ€Œ๋Œ€๊ธฐ์—ด์ด
3373
+ * ๋„˜์ณค๋‹คใ€๋Š” ์‚ฌ์‹ค๋งŒ ์žˆ๊ณ  ๋ฌด์—‡์ด ๋„˜๊ฒผ๋Š”์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์—†์–ด, ์›์ธ์„ ์ถ”์ธก์œผ๋กœ ๊ณ ๋ฅด๊ฒŒ ๋œ๋‹ค.
3374
+ *
3375
+ * ์ƒํ•œ์„ ๋„˜๊ธด ์ฐฝ๋งŒ ๋‚จ๊ธด๋‹ค โ€” ๋งค ์ฐฝ์„ ๋‚จ๊ธฐ๋ฉด ๋กœ๊ทธ๊ฐ€ ๊ทธ ์ž์ฒด๋กœ ๋ถ€ํ•˜๊ฐ€ ๋œ๋‹ค.
3376
+ */
3377
+ inst.publishedTotal = (inst.publishedTotal ?? 0) + published;
3378
+ if (published >= this.PUBLISH_CHUNK) {
3379
+ (0, log_js_1.twinLog)(`[twin-engine] "${inst.id}" ์ด๋ฒˆ ์ฐฝ์—์„œ ${published}๊ฑด ๋ฐœํ–‰ ` +
3380
+ `(์ „์ฒด ${deltas.length}๊ฑด ์ค‘ ยท ${full ? '์ „๋Ÿ‰' : '์ขํž˜'} ยท ์ฒญํฌ ${this.PUBLISH_CHUNK})`);
3381
+ }
2811
3382
  /*
2812
3383
  * ์‚ฌ๋ผ์ง„ ์—”ํ‹ฐํ‹ฐ์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์ •๋ฆฌ(๋งต ๋ฌดํ•œ ์„ฑ์žฅ ๋ฐฉ์ง€) โ€” **์ „๋ถ€ ๋งŒ๋“  ์ฐฝ์—์„œ๋งŒ.**
2813
3384
  * ๋ฒ”์œ„๋ฅผ ์ขํžŒ ์ฐฝ์˜ `seen` ์—๋Š” ๋งŒ๋“ค์ง€ ์•Š์€ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์—†์œผ๋ฏ€๋กœ, ๊ทธ๋•Œ ์ •๋ฆฌํ•˜๋ฉด ์‚ด์•„ ์žˆ๋Š” ํƒœ๊ทธ์˜
@@ -2846,18 +3417,18 @@ class TwinEngine {
2846
3417
  return this.snapshot(domainId, instanceId);
2847
3418
  }
2848
3419
  /*
2849
- * **๋์— ์žˆ๋Š” ์Šค๋ƒ…์ƒท์ด๋ฉด ์ ‘์ง€ ์•Š๋Š”๋‹ค.**
3420
+ * **๋์— ์žˆ๋Š” ์Šค๋ƒ…์ƒท์ด๋ฉด ๊ณ„์‚ฐํ•˜์ง€ ์•Š๋Š”๋‹ค.**
2850
3421
  *
2851
- * ๋ฉˆ์ถ˜ ํŠธ์œˆ์„ ์กฐํšŒํ•  ๋•Œ๋งˆ๋‹ค ์ €๋„์„ ์ „๋Ÿ‰ ์ ‘๊ณ  ์žˆ์—ˆ๋‹ค โ€” ํŠธ์œˆ์„ ํ•˜๋‚˜๋„ ์•ˆ ๋Œ๋ ค๋„ ๋ชจ๋ธ ์กฐํšŒ๊ฐ€
3422
+ * ๋ฉˆ์ถ˜ ํŠธ์œˆ์„ ์กฐํšŒํ•  ๋•Œ๋งˆ๋‹ค ์ €๋„์„ ์ „๋Ÿ‰ ๊ณ„์‚ฐํ•˜๊ณ  ์žˆ์—ˆ๋‹ค โ€” ํŠธ์œˆ์„ ํ•˜๋‚˜๋„ ์•ˆ ๋Œ๋ ค๋„ ๋ชจ๋ธ ์กฐํšŒ๊ฐ€
2852
3423
  * 4.6~7.7์ดˆ์˜€๋˜ ์ด์œ ๋‹ค(crew-probe 23,731๊ฑด). ์Šค๋ƒ…์ƒท์€ ๊ฐ™์€ ํด๋“œ์˜ ๊ฒฐ๊ณผ์ด๋ฏ€๋กœ, ๊ทธ ๋’ค๋กœ ์ด๋ฒคํŠธ๋„
2853
- * ๊ตฌ์กฐ ๋ณ€๊ฒฝ๋„ ์—†๋‹ค๋ฉด **๋‹ค์‹œ ์ ‘์–ด ๋ด์•ผ ๊ฐ™์€ ๊ฐ’**์ด๋‹ค.
3424
+ * ๊ตฌ์กฐ ๋ณ€๊ฒฝ๋„ ์—†๋‹ค๋ฉด **๋‹ค์‹œ ๊ณ„์‚ฐํ•ด ๋ด์•ผ ๊ฐ™์€ ๊ฐ’**์ด๋‹ค.
2854
3425
  *
2855
- * ์“ฐ์ง€ ์•Š๋Š” ์กฐ๊ฑด์„ ์ข๊ฒŒ ์žก๋Š”๋‹ค: ์ง€๊ธˆ์„ ๋ฌผ์—ˆ์„ ๋•Œ๋งŒ(์‹œ๊ฐ„์—ฌํ–‰์€ ๊ทธ ์‹œ์ ๊นŒ์ง€ ์ ‘์–ด์•ผ ํ•œ๋‹ค), ๋ฆฌ๋น„์ „์ด
2856
- * ๋๊ณผ ๊ฐ™์„ ๋•Œ๋งŒ, ๊ตฌ์กฐ ๋ฆฌ๋น„์ „๊นŒ์ง€ ๊ฐ™์„ ๋•Œ๋งŒ. ํ•˜๋‚˜๋ผ๋„ ์–ด๊ธ‹๋‚˜๋ฉด ์ ‘๋Š”๋‹ค โ€” ์บ์‹œ๊ฐ€ ์‚ฌ์‹ค์„ ์ด๊ธฐ์ง€ ์•Š๋Š”๋‹ค.
3426
+ * ์“ฐ์ง€ ์•Š๋Š” ์กฐ๊ฑด์„ ์ข๊ฒŒ ์žก๋Š”๋‹ค: ์ง€๊ธˆ์„ ๋ฌผ์—ˆ์„ ๋•Œ๋งŒ(์‹œ๊ฐ„์—ฌํ–‰์€ ๊ทธ ์‹œ์ ๊นŒ์ง€ ๊ณ„์‚ฐํ•ด์•ผ ํ•œ๋‹ค), ๋ฆฌ๋น„์ „์ด
3427
+ * ๋๊ณผ ๊ฐ™์„ ๋•Œ๋งŒ, ๊ตฌ์กฐ ๋ฆฌ๋น„์ „๊นŒ์ง€ ๊ฐ™์„ ๋•Œ๋งŒ. ํ•˜๋‚˜๋ผ๋„ ์–ด๊ธ‹๋‚˜๋ฉด ๊ณ„์‚ฐํ•œ๋‹ค โ€” ์บ์‹œ๊ฐ€ ์‚ฌ์‹ค์„ ์ด๊ธฐ์ง€ ์•Š๋Š”๋‹ค.
2857
3428
  */
2858
3429
  const asOfNowRead = untilRevision == null && untilTime == null;
2859
3430
  const tip = asOfNowRead ? await this.tipOf(domainId, instanceId).catch(() => null) : null;
2860
- /* ํ•œ ๋ฒˆ๋งŒ ์ฝ๋Š”๋‹ค โ€” ๋์— ์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ ์“ฐ๊ณ , ์•„๋‹ˆ๋ฉด ์•„๋ž˜์—์„œ **์ด์–ด ์ ‘๋Š” ์”จ์•—**์œผ๋กœ ์“ด๋‹ค. */
3431
+ /* ํ•œ ๋ฒˆ๋งŒ ์ฝ๋Š”๋‹ค โ€” ๋์— ์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ ์“ฐ๊ณ , ์•„๋‹ˆ๋ฉด ์•„๋ž˜์—์„œ **์ด์–ด ๊ณ„์‚ฐํ•˜๋Š” ์”จ์•—**์œผ๋กœ ์“ด๋‹ค. */
2861
3432
  const cachedForResume = asOfNowRead && tip ? await this.loadSnapshot(domainId, instanceId).catch(() => null) : null;
2862
3433
  if (cachedForResume) {
2863
3434
  const state = (0, warm_start_js_1.unwrapState)(cachedForResume.state);
@@ -2868,22 +3439,22 @@ class TwinEngine {
2868
3439
  if (!reg?.model)
2869
3440
  throw new Error(`twin instance "${instanceId}" not registered (no model to replay)`);
2870
3441
  /*
2871
- * โ”€โ”€ ์žฌ๊ฐœ์ ์—์„œ **์ด์–ด ์ ‘๋Š”๋‹ค** (2026-08-18) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3442
+ * โ”€โ”€ ์žฌ๊ฐœ์ ์—์„œ **์ด์–ด์„œ ๊ณ„์‚ฐํ•œ๋‹ค** (2026-08-18) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2872
3443
  *
2873
- * ์šฐ๋ฆฌ๋Š” ์ด๋ฏธ ์ ‘์€ ๊ฒฐ๊ณผ๋ฅผ ๋‚จ๊ธฐ๊ณ  ์žˆ๋‹ค(`saveFoldedSnapshot`). ๊ทธ ์ง€์ ์˜ **์žฌ๊ฐœ์ **(๋ฆฌ๋“€์„œ ๋‚ด๋ถ€ ์ƒํƒœ
2874
- * ์ „๋ถ€ + ๊ฐ€๋™ ๋ˆ„์ ๊ธฐ)์ด ํ•จ๊ป˜ ์žˆ์œผ๋ฉด, ๊ทธ ๋’ค์— ์ผ์–ด๋‚œ ๊ฒƒ๋งŒ ์ ‘์–ด๋„ ๊ฐ™์€ ๋‹ต์ด ๋‚˜์˜จ๋‹ค โ€” ๊ทธ ๋™์น˜๋Š”
2875
- * ์ปค๋„ ์‹œํ—˜์ด ์ฆ๋ช…ํ•œ๋‹ค(0๋ถ€ํ„ฐ ์ ‘๊ธฐ == ์žฌ๊ฐœ์  + ๊ผฌ๋ฆฌ).
3444
+ * ์šฐ๋ฆฌ๋Š” ์ด๋ฏธ ๊ณ„์‚ฐํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋‚จ๊ธฐ๊ณ  ์žˆ๋‹ค(`saveFoldedSnapshot`). ๊ทธ ์ง€์ ์˜ **์žฌ๊ฐœ์ **(๋ฆฌ๋“€์„œ ๋‚ด๋ถ€ ์ƒํƒœ
3445
+ * ์ „๋ถ€ + ๊ฐ€๋™ ๋ˆ„์ ๊ธฐ)์ด ํ•จ๊ป˜ ์žˆ์œผ๋ฉด, ๊ทธ ๋’ค์— ์ผ์–ด๋‚œ ๊ฒƒ๋งŒ ๊ณ„์‚ฐํ•ด๋„ ๊ฐ™์€ ๋‹ต์ด ๋‚˜์˜จ๋‹ค โ€” ๊ทธ ๋™์น˜๋Š”
3446
+ * ์ปค๋„ ์‹œํ—˜์ด ์ฆ๋ช…ํ•œ๋‹ค(0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•˜๊ธฐ == ์žฌ๊ฐœ์  + ๊ผฌ๋ฆฌ).
2876
3447
  *
2877
3448
  * ์“ฐ๋Š” ์กฐ๊ฑด์„ ์ข๊ฒŒ ์žก๋Š”๋‹ค: **์ง€๊ธˆ์„ ๋ฌผ์—ˆ์„ ๋•Œ๋งŒ**(์‹œ๊ฐ„์—ฌํ–‰์€ ๋ชฉํ‘œ ์ด์ „ ์žฌ๊ฐœ์ ์ด ํ•„์š”ํ•œ๋ฐ ์ง€๊ธˆ์€
2878
3449
  * ์ตœ์‹  ํ•˜๋‚˜๋งŒ ๋‚จ๊ธด๋‹ค โ€” ์‚ฌ์Šฌ์€ ๋‹ค์Œ ๋‹จ๊ณ„๋‹ค), ๊ตฌ์กฐ๊ฐ€ ๊ทธ๋Œ€๋กœ์ผ ๋•Œ๋งŒ(๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๊ทธ ๊ฒฝ๊ณ„์—์„œ ๊ฐˆ๋ผ
2879
- * ์ ‘์–ด์•ผ ํ•œ๋‹ค), ์žฌ๊ฐœ์ ์ด ์ €๋„ ๋๋ณด๋‹ค ์•ž์„ค ๋•Œ๋งŒ. ํ•˜๋‚˜๋ผ๋„ ์–ด๊ธ‹๋‚˜๋ฉด 0๋ถ€ํ„ฐ ์ ‘๋Š”๋‹ค โ€” **์บ์‹œ๊ฐ€ ์‚ฌ์‹ค์„
3450
+ * ๊ณ„์‚ฐํ•ด์•ผ ํ•œ๋‹ค), ์žฌ๊ฐœ์ ์ด ์ €๋„ ๋๋ณด๋‹ค ์•ž์„ค ๋•Œ๋งŒ. ํ•˜๋‚˜๋ผ๋„ ์–ด๊ธ‹๋‚˜๋ฉด 0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•œ๋‹ค โ€” **์บ์‹œ๊ฐ€ ์‚ฌ์‹ค์„
2880
3451
  * ์ด๊ธฐ์ง€ ์•Š๋Š”๋‹ค.**
2881
3452
  */
2882
3453
  /*
2883
3454
  * ์”จ์•—์€ ๋‘˜ ์ค‘ ํ•˜๋‚˜๋‹ค: **์ง€๊ธˆ**์„ ๋ฌผ์œผ๋ฉด ์ตœ์‹  ์žฌ๊ฐœ์ , **๊ณผ๊ฑฐ**๋ฅผ ๋ฌผ์œผ๋ฉด ์‚ฌ์Šฌ์—์„œ ๋ชฉํ‘œ ์ง์ „ ์ง€์ .
2884
3455
  *
2885
- * ๊ณผ๊ฑฐ ์”จ์•—์€ ๊ตฌ์กฐ๊ฐ€ ํ•œ ๋ฒˆ๋„ ๋ฐ”๋€Œ์ง€ ์•Š์€ ํŠธ์œˆ์—์„œ๋งŒ ์“ด๋‹ค โ€” ๊ตฌ์กฐ๊ฐ€ ๊ฐˆ๋ฆฐ ์ €๋„์€ ๋งˆ๋””๋งˆ๋‹ค ๊ฐˆ์•„ ์ ‘์–ด์•ผ
2886
- * ํ•˜๊ณ , ๋งˆ๋””๋ฅผ ๊ฑด๋„ˆ๋›ด ์”จ์•—์€ ๊ทธ ๊ฒฝ๊ณ„์˜ ํŒ์ •์„ ์žƒ๋Š”๋‹ค(๊ทธ๋•Œ๋Š” 0๋ถ€ํ„ฐ ์ ‘๋Š” ๊ฒƒ์ด ์˜ณ๋‹ค).
3456
+ * ๊ณผ๊ฑฐ ์”จ์•—์€ ๊ตฌ์กฐ๊ฐ€ ํ•œ ๋ฒˆ๋„ ๋ฐ”๋€Œ์ง€ ์•Š์€ ํŠธ์œˆ์—์„œ๋งŒ ์“ด๋‹ค โ€” ๊ตฌ์กฐ๊ฐ€ ๊ฐˆ๋ฆฐ ์ €๋„์€ ๋งˆ๋””๋งˆ๋‹ค ๊ฐˆ์•„ ๊ณ„์‚ฐํ•ด์•ผ
3457
+ * ํ•˜๊ณ , ๋งˆ๋””๋ฅผ ๊ฑด๋„ˆ๋›ด ์”จ์•—์€ ๊ทธ ๊ฒฝ๊ณ„์˜ ํŒ์ •์„ ์žƒ๋Š”๋‹ค(๊ทธ๋•Œ๋Š” 0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•˜๋Š” ๊ฒƒ์ด ์˜ณ๋‹ค).
2887
3458
  */
2888
3459
  const structureCount = asOfNowRead
2889
3460
  ? 0
@@ -2911,10 +3482,10 @@ class TwinEngine {
2911
3482
  const cutoffMs = untilTime != null ? Date.parse(untilTime) : NaN;
2912
3483
  const useTime = untilTime != null && !Number.isNaN(cutoffMs);
2913
3484
  const baseWhere = { domain: { id: domainId }, instanceId };
2914
- /* ์ด์–ด ์ ‘์„ ๋•Œ๋Š” **๊ทธ ๋’ค๋งŒ** ์ฝ๋Š”๋‹ค โ€” ์žฌ๊ฐœ์ ๊นŒ์ง€์˜ ์‚ฌ์‹ค์€ ์ด๋ฏธ ์”จ์•— ์•ˆ์— ์žˆ๋‹ค. */
3485
+ /* ์ด์–ด ๊ณ„์‚ฐํ•  ๋•Œ๋Š” **๊ทธ ๋’ค๋งŒ** ์ฝ๋Š”๋‹ค โ€” ์žฌ๊ฐœ์ ๊นŒ์ง€์˜ ์‚ฌ์‹ค์€ ์ด๋ฏธ ์”จ์•— ์•ˆ์— ์žˆ๋‹ค. */
2915
3486
  const from = resume ? (resume.revision ?? 0) : undefined;
2916
3487
  /*
2917
- * journal-fold: ์žฌ์ƒ์€ ์‚ฌ์‹ค์„ ํ•˜๋‚˜์”ฉ ์ ‘๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ๊ทธ ๊ตฌ๊ฐ„์˜ ํ–‰์ด ํ•„์š”ํ•˜๋‹ค. ์ปค์„œ(`from`)์™€ ์‹œ๊ฐ
3488
+ * journal-fold: ์žฌ์ƒ์€ ์‚ฌ์‹ค์„ ํ•˜๋‚˜์”ฉ ๊ณ„์‚ฐํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ๊ทธ ๊ตฌ๊ฐ„์˜ ํ–‰์ด ํ•„์š”ํ•˜๋‹ค. ์ปค์„œ(`from`)์™€ ์‹œ๊ฐ
2918
3489
  * ์ƒํ•œ์ด ๊ตฌ๊ฐ„์„ ์ž๋ฅด๊ณ , ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ์•ž์ชฝ์„ ์”จ์•—์œผ๋กœ ๋Œ€์‹ ํ•œ๋‹ค โ€” ํ‘œ ์ „์ฒด๋ฅผ ์ฝ์ง€ ์•Š๋Š”๋‹ค.
2919
3490
  */
2920
3491
  const rows = await (0, shell_1.getRepository)(twin_event_js_1.TwinEvent).find({
@@ -2940,14 +3511,14 @@ class TwinEngine {
2940
3511
  });
2941
3512
  const wanted = rows;
2942
3513
  /*
2943
- * **๊ทธ๋•Œ์˜ ๊ณต์žฅ์œผ๋กœ ์ ‘๋Š”๋‹ค.**
3514
+ * **๊ทธ๋•Œ์˜ ๊ณต์žฅ์œผ๋กœ ๊ณ„์‚ฐํ•œ๋‹ค.**
2944
3515
  *
2945
- * ์˜ˆ์ „์—๋Š” ๋ชจ๋“  ์ด๋ฒคํŠธ๋ฅผ ์ง€๊ธˆ ๋“ฑ๋ก๋œ ๋ณด๋“œ ํ•˜๋‚˜๋กœ ์ ‘์—ˆ๋‹ค. ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€ ์  ์—†์œผ๋ฉด ๋งž์ง€๋งŒ, ๋ฐ”๋€
2946
- * ๋’ค์—๋Š” ์˜› ์‚ฌ์‹ค์„ ์ƒˆ ๊ณต์žฅ์— ๋Œ€๊ณ  ์ ‘๊ฒŒ ๋˜์–ด โ€” ๊ทธ๋•Œ ์—†๋˜ ์„ค๋น„์—์„œ ์ผ์ด ์žˆ์—ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค.
3516
+ * ์˜ˆ์ „์—๋Š” ๋ชจ๋“  ์ด๋ฒคํŠธ๋ฅผ ์ง€๊ธˆ ๋“ฑ๋ก๋œ ๋ณด๋“œ ํ•˜๋‚˜๋กœ ๊ณ„์‚ฐํ–ˆ๋‹ค. ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€ ์  ์—†์œผ๋ฉด ๋งž์ง€๋งŒ, ๋ฐ”๋€
3517
+ * ๋’ค์—๋Š” ์˜› ์‚ฌ์‹ค์„ ์ƒˆ ๊ณต์žฅ์— ๋Œ€๊ณ  ๊ณ„์‚ฐํ•˜๊ฒŒ ๋˜์–ด โ€” ๊ทธ๋•Œ ์—†๋˜ ์„ค๋น„์—์„œ ์ผ์ด ์žˆ์—ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค.
2947
3518
  * (๊ทธ๋ž˜์„œ ์˜ˆ์ „ ํ”„๋กœ๋น„์ €๋‹์€ ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์ €๋„์„ ์•„์˜ˆ ์ง€์› ๋‹ค. ์—ญ์‚ฌ๋ฅผ ์žƒ๊ฑฐ๋‚˜ ๊ฑฐ์ง“๋ง์„ ํ•˜๊ฑฐ๋‚˜.)
2948
3519
  *
2949
- * ์ด์ œ ์ด๋ฒคํŠธ๊ฐ€ ์ž๊ธฐ ๊ตฌ์กฐ๋ฅผ ๋‹ฌ๊ณ  ์˜ค๋ฏ€๋กœ, ๋ฆฌ๋น„์ „์ด ๋ฐ”๋€Œ๋Š” ์ง€์ ์—์„œ ๊ตฌ์กฐ๋ฅผ ๊ฐˆ์•„ํƒ€๋ฉฐ ์ด์–ด ์ ‘๋Š”๋‹ค.
2950
- * ๋ฆฌ๋น„์ „์ด ํ•˜๋‚˜๋ฟ์ด๊ฑฐ๋‚˜(๋Œ€๋‹ค์ˆ˜) ์•„์˜ˆ ์—†์œผ๋ฉด(๊ตฌ์กฐ ์ด๋ ฅ ์ด์ „) ์˜ˆ์ „๊ณผ ๋˜‘๊ฐ™์ด ํ•œ ๋ฒˆ์— ์ ‘๋Š”๋‹ค.
3520
+ * ์ด์ œ ์ด๋ฒคํŠธ๊ฐ€ ์ž๊ธฐ ๊ตฌ์กฐ๋ฅผ ๋‹ฌ๊ณ  ์˜ค๋ฏ€๋กœ, ๋ฆฌ๋น„์ „์ด ๋ฐ”๋€Œ๋Š” ์ง€์ ์—์„œ ๊ตฌ์กฐ๋ฅผ ๊ฐˆ์•„ํƒ€๋ฉฐ ์ด์–ด์„œ ๊ณ„์‚ฐํ•œ๋‹ค.
3521
+ * ๋ฆฌ๋น„์ „์ด ํ•˜๋‚˜๋ฟ์ด๊ฑฐ๋‚˜(๋Œ€๋‹ค์ˆ˜) ์•„์˜ˆ ์—†์œผ๋ฉด(๊ตฌ์กฐ ์ด๋ ฅ ์ด์ „) ์˜ˆ์ „๊ณผ ๋˜‘๊ฐ™์ด ํ•œ ๋ฒˆ์— ๊ณ„์‚ฐํ•œ๋‹ค.
2951
3522
  */
2952
3523
  const structures = await (0, shell_1.getRepository)(twin_structure_js_1.TwinStructure).find({
2953
3524
  where: { domain: { id: domainId }, instanceId },
@@ -2973,7 +3544,7 @@ class TwinEngine {
2973
3544
  * **๊ฐ€์žฅ ์ƒˆ ๊ตฌ์กฐ๊ฐ€ ์ง€๊ธˆ์˜ ๊ณต์žฅ์ด๋‹ค** โ€” ๊ทธ ์•„๋ž˜์—์„œ ์•„์ง ์•„๋ฌด ์ผ๋„ ์—†์—ˆ๋”๋ผ๋„.
2974
3545
  *
2975
3546
  * ์žฌํ”„๋กœ๋น„์ €๋‹ ์งํ›„๊ฐ€ ์ •ํ™•ํžˆ ๊ทธ ์ƒํƒœ๋‹ค: ๋ถ€์Šค๋ฅผ ๋„ท ๋Š˜๋ ธ๋Š”๋ฐ ์ƒˆ ์ด๋ฒคํŠธ๋Š” ์•„์ง ํ•˜๋‚˜๋„ ์—†๋‹ค. ์ด๋•Œ
2976
- * ๋งˆ์ง€๋ง‰ ์ด๋ฒคํŠธ์˜ ๊ตฌ์กฐ๋กœ๋งŒ ์ ‘์œผ๋ฉด ํ™”๋ฉด์€ **์˜› ๊ณต์žฅ**์„ ๋ณด์—ฌ์ค€๋‹ค โ€” ๋ฐฉ๊ธˆ ๋Š˜๋ฆฐ ๊ฒƒ์ด ์•ˆ ๋ณด์ธ๋‹ค.
3547
+ * ๋งˆ์ง€๋ง‰ ์ด๋ฒคํŠธ์˜ ๊ตฌ์กฐ๋กœ๋งŒ ๊ณ„์‚ฐํ•˜๋ฉด ํ™”๋ฉด์€ **์˜› ๊ณต์žฅ**์„ ๋ณด์—ฌ์ค€๋‹ค โ€” ๋ฐฉ๊ธˆ ๋Š˜๋ฆฐ ๊ฒƒ์ด ์•ˆ ๋ณด์ธ๋‹ค.
2977
3548
  * ๊ทธ๋ž˜์„œ ๋งˆ๋””์˜ ๋์— ์ง€๊ธˆ ๊ตฌ์กฐ๋ฅผ ํ•œ ๋ฒˆ ๋” ์–น๋Š”๋‹ค(์ด๋ฒคํŠธ ์—†๋Š” ๋งˆ๋””).
2978
3549
  *
2979
3550
  * **๋‹ค๋งŒ "์ง€๊ธˆ" ์„ ๋ฌผ์—ˆ์„ ๋•Œ๋งŒ์ด๋‹ค.** ๊ณผ๊ฑฐ ์‹œ์ ์„ ๋ฌผ์—ˆ๋Š”๋ฐ ์ตœ์‹  ๊ตฌ์กฐ๋ฅผ ์–น์œผ๋ฉด, ๊ทธ ์‹œ์ ์— ์—†๋˜
@@ -2984,9 +3555,9 @@ class TwinEngine {
2984
3555
  if (asOfNow && newest && segments[segments.length - 1]?.model !== newest)
2985
3556
  segments.push({ model: newest, events: [] });
2986
3557
  /*
2987
- * ์ ‘์€ ์ƒํƒœ์— **์ฃผ์˜ ์‹ ํ˜ธ์™€ ์‹œ๊ฐ์„ ์ฑ„์šด๋‹ค.**
3558
+ * ๊ณ„์‚ฐํ•œ ์ƒํƒœ์— **์ฃผ์˜ ์‹ ํ˜ธ์™€ ์‹œ๊ฐ์„ ์ฑ„์šด๋‹ค.**
2988
3559
  *
2989
- * ๋ผ์ด๋ธŒยท์‹œ๋ฎฌ์€ ์ปค๋„์ด ์‹ ํ˜ธ๋ฅผ ์Šค์Šค๋กœ ๋‚ด์ง€๋งŒ, ์ €๋„์„ ์ ‘๋Š” ์ด ๊ฒฝ๋กœ๋Š” ํ”„๋กœ์ ํ„ฐ ์ƒํƒœ๋งŒ ๋‚ธ๋‹ค โ€” ์‹ ํ˜ธ๋„
3560
+ * ๋ผ์ด๋ธŒยท์‹œ๋ฎฌ์€ ์ปค๋„์ด ์‹ ํ˜ธ๋ฅผ ์Šค์Šค๋กœ ๋‚ด์ง€๋งŒ, ์ €๋„์„ ๊ณ„์‚ฐํ•˜๋Š” ์ด ๊ฒฝ๋กœ๋Š” ํ”„๋กœ์ ํ„ฐ ์ƒํƒœ๋งŒ ๋‚ธ๋‹ค โ€” ์‹ ํ˜ธ๋„
2990
3561
  * `nowTime` ๋„ ์—†๋‹ค. ๊ทธ๋ž˜์„œ **๊ณผ๊ฑฐ๋ฅผ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜๋ฉด ์ฃผ์˜ ๋ ˆ์ผ์ด ํ…… ๋น„์—ˆ๊ณ **(์ง€๋„๋Š” `snap.attentions` ๋ฅผ
2991
3562
  * ์ฝ๋Š”๋‹ค), ๊ธฐ๋™๋ผ ์žˆ์ง€ ์•Š์€ ํŠธ์œˆ์„ ๋ณด๋Š” ํ™”๋ฉด๋„ ๊ฐ™์•˜๋‹ค. ์‹ ํ˜ธ๋Š” ์ƒํƒœ์—์„œ ๊ณ„์‚ฐ๋˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ
2992
3563
  * ์—ฌ๊ธฐ์„œ ๊ฐ™์€ ๊ณต์‹(`deriveAttentions`)์œผ๋กœ ์ฑ„์šฐ๋ฉด ๋œ๋‹ค โ€” ๋‘ ๋ฒŒ์„ ๋‘์ง€ ์•Š๋Š”๋‹ค.
@@ -3000,7 +3571,7 @@ class TwinEngine {
3000
3571
  * **๊ฐ€๋™ ์ด๋ ฅ๋„ ๋˜์‚ด๋ฆฐ๋‹ค** โ€” ๋‹ค์‹œ ๊ณ„์‚ฐํ•œ ํ™”๋ฉด์— ์„ค๋น„ ๊ณ„์ธก์ด ๋น„์–ด ์žˆ๋˜ ๊ฒƒ.
3001
3572
  *
3002
3573
  * OEE ๋Š” ์› ์‹œ์Šคํ…œ์ด ๋ˆ„์ ์„ ๋ณด๋‚ด ์ฃผ์ง€ ์•Š์•„ ํ˜ธ์ŠคํŠธ๊ฐ€ ์ƒํƒœ ์ „์ด๋ฅผ ์ ๋ถ„ํ•ด ๋งŒ๋“ ๋‹ค(๊ทธ๋ž˜์„œ ์ปค๋„์ด ์•„๋‹ˆ๋ผ
3003
- * ์—ฌ๊ธฐ ์žˆ๋‹ค). ๊ทธ๋Ÿฐ๋ฐ ๊ทธ ๋ˆ„์ ๊ธฐ๋Š” **๋ผ์ด๋ธŒ์—์„œ๋งŒ** ๋Œ์•˜๊ณ , ์ €๋„์„ ์ ‘๋Š” ๊ฒฝ๋กœ๋Š” ๊ทธ ๊ณ„์‚ฐ์„ ํ•˜์ง€ ์•Š์•˜๋‹ค โ€”
3574
+ * ์—ฌ๊ธฐ ์žˆ๋‹ค). ๊ทธ๋Ÿฐ๋ฐ ๊ทธ ๋ˆ„์ ๊ธฐ๋Š” **๋ผ์ด๋ธŒ์—์„œ๋งŒ** ๋Œ์•˜๊ณ , ์ €๋„์„ ๊ณ„์‚ฐํ•˜๋Š” ๊ฒฝ๋กœ๋Š” ๊ทธ ๊ณ„์‚ฐ์„ ํ•˜์ง€ ์•Š์•˜๋‹ค โ€”
3004
3575
  * ๊ณผ๊ฑฐ๋ฅผ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜๋ฉด ๋ชจ๋“  ์„ค๋น„๊ฐ€ "๊ฐ€๋™ ์ด๋ ฅ์ด ์ „ํ˜€ ์—†์Œ" ์œผ๋กœ ๋ณด์˜€๋‹ค.
3005
3576
  *
3006
3577
  * ์—†๋Š” ๊ฒƒ์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹ˆ๋ผ ๊ณ„์‚ฐ์ด๋‹ค: ์ž…๋ ฅ(`equipment.status` ์ „์ดยท`quality.output`)์€ ์ €๋„์— ๋‹ค
@@ -3010,7 +3581,7 @@ class TwinEngine {
3010
3581
  * ๊ฐ€๋™ ์ด๋ ฅ์ด ๋‚˜์˜จ๋‹ค(๊ทธ ๋’ค์— ์ผ์–ด๋‚œ ๊ณ ์žฅ์ด ๊ณผ๊ฑฐ ํ™”๋ฉด์— ์„ž์ด์ง€ ์•Š๋Š”๋‹ค).
3011
3582
  */
3012
3583
  const oee = new oee_accumulator_js_1.OeeAccumulator();
3013
- /* ์žฌ๊ฐœ์ ์ด ์žˆ์œผ๋ฉด ๊ทธ ์œ„์— ๊ผฌ๋ฆฌ๋งŒ ์–น๋Š”๋‹ค โ€” ์—†์œผ๋ฉด ๊ผฌ๋ฆฌ๋ถ„๋งŒ ์„ธ์–ด ๊ฐ€์šฉ๋ฅ ์ด ์กฐ์šฉํžˆ ์ž‘์•„์ง„๋‹ค. */
3584
+ /* ์žฌ๊ฐœ์ ์ด ์žˆ์œผ๋ฉด ๊ทธ ์œ„์— ๊ผฌ๋ฆฌ๋งŒ ์–น๋Š”๋‹ค โ€” ์—†์œผ๋ฉด ๊ผฌ๋ฆฌ๋ถ„๋งŒ ์„ธ์–ด ๊ฐ€์šฉ๋ฅ ์ด ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์ž‘์•„์ง„๋‹ค. */
3014
3585
  if (resume?.fold?.oee)
3015
3586
  oee.restore(resume.fold.oee);
3016
3587
  for (const r of wanted) {
@@ -3031,20 +3602,20 @@ class TwinEngine {
3031
3602
  const withMetrics = (0, oee_accumulator_js_1.withLiveOee)(based, oee, Number.isFinite(nowMs) ? nowMs : undefined);
3032
3603
  return (0, live_attentions_js_1.withLiveAttentions)(withMetrics);
3033
3604
  };
3034
- /* ์ ‘์€ ๊ฒฐ๊ณผ๋ฅผ ๋‚จ๊ธด๋‹ค โ€” **์ง€๊ธˆ์„ ๋ฌผ์—ˆ์„ ๋•Œ๋งŒ**(์‹œ๊ฐ„์—ฌํ–‰ ๊ฒฐ๊ณผ๋ฅผ "์ง€๊ธˆ" ์œผ๋กœ ์ €์žฅํ•˜๋ฉด ๊ฑฐ์ง“์ด ๋œ๋‹ค). */
3605
+ /* ๊ณ„์‚ฐํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋‚จ๊ธด๋‹ค โ€” **์ง€๊ธˆ์„ ๋ฌผ์—ˆ์„ ๋•Œ๋งŒ**(์‹œ๊ฐ„์—ฌํ–‰ ๊ฒฐ๊ณผ๋ฅผ "์ง€๊ธˆ" ์œผ๋กœ ์ €์žฅํ•˜๋ฉด ๊ฑฐ์ง“์ด ๋œ๋‹ค). */
3035
3606
  /*
3036
- * ์ ‘์€ ์ง€์ ์˜ ๋ฆฌ๋น„์ „ โ€” ์‚ฌ์Šฌ์— ์ ์„ ์ด๋ฆ„์ด๋‹ค. ๊ผฌ๋ฆฌ๋ฅผ ์ ‘์—ˆ์œผ๋ฉด ๊ทธ ๊ผฌ๋ฆฌ์˜ ๋, ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ์ฝ์—ˆ์œผ๋ฉด
3607
+ * ๊ณ„์‚ฐํ•œ ์ง€์ ์˜ ๋ฆฌ๋น„์ „ โ€” ์‚ฌ์Šฌ์— ์ ์„ ์ด๋ฆ„์ด๋‹ค. ๊ผฌ๋ฆฌ๋ฅผ ๊ณ„์‚ฐํ–ˆ์œผ๋ฉด ๊ทธ ๊ผฌ๋ฆฌ์˜ ๋, ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ์ฝ์—ˆ์œผ๋ฉด
3037
3608
  * ์”จ์•—์˜ ์ž๋ฆฌ ๊ทธ๋Œ€๋กœ๋‹ค(๋ชจ๋ฅด๋ฉด 0).
3038
3609
  */
3039
3610
  const foldedTo = rows.length ? (rows[rows.length - 1]?.revision ?? 0) : (from ?? 0);
3040
3611
  const keep = (st, fold) => {
3041
- /* ์žฌ๊ฐœ์ ์„ ํ•จ๊ป˜ ๋‚จ๊ธด๋‹ค โ€” ์ƒํƒœ๋งŒ ๋‚จ๊ธฐ๋ฉด ๋‹ค์Œ ๋ฒˆ์— ๋˜ 0๋ถ€ํ„ฐ ์ ‘์–ด์•ผ ํ•œ๋‹ค(๊ทธ๊ฒƒ์ด ์ด ์ž‘์—…์˜ ์š”์ ์ด๋‹ค).
3612
+ /* ์žฌ๊ฐœ์ ์„ ํ•จ๊ป˜ ๋‚จ๊ธด๋‹ค โ€” ์ƒํƒœ๋งŒ ๋‚จ๊ธฐ๋ฉด ๋‹ค์Œ ๋ฒˆ์— ๋˜ 0๋ถ€ํ„ฐ ๊ณ„์‚ฐํ•ด์•ผ ํ•œ๋‹ค(๊ทธ๊ฒƒ์ด ์ด ์ž‘์—…์˜ ์š”์ ์ด๋‹ค).
3042
3613
  **๊ตฌ์กฐ๊ฐ€ ๊ฐˆ๋ฆฐ ํด๋“œ์—๋Š” ์žฌ๊ฐœ์ ์„ ๋ถ™์ด์ง€ ์•Š๋Š”๋‹ค**: ๋งˆ๋””๋ฅผ ๊ฑด๋„ˆ๋›ด ์”จ์•—์€ ๊ทธ ๊ฒฝ๊ณ„์˜ ํŒ์ •์„ ์žƒ๋Š”๋‹ค. */
3043
3614
  if (asOfNowRead && tip && st) {
3044
3615
  void this.saveFoldedSnapshot(domainId, instanceId, { revision: tip.revision, state: st, structureRev: tip.structureRev, fold });
3045
3616
  }
3046
3617
  /*
3047
- * ์‚ฌ์Šฌ์—๋Š” **๊ณผ๊ฑฐ๋ฅผ ์ ‘์—ˆ์„ ๋•Œ๋„** ํ•œ ์ง€์ ์„ ๋‚จ๊ธด๋‹ค (2026-08-18 ์‹ค์ธก์œผ๋กœ ๊ณ ์นจ).
3618
+ * ์‚ฌ์Šฌ์—๋Š” **๊ณผ๊ฑฐ๋ฅผ ๊ณ„์‚ฐํ–ˆ์„ ๋•Œ๋„** ํ•œ ์ง€์ ์„ ๋‚จ๊ธด๋‹ค (2026-08-18 ์‹ค์ธก์œผ๋กœ ๊ณ ์นจ).
3048
3619
  *
3049
3620
  * ์ฒ˜์Œ์—๋Š” ใ€Œ์ง€๊ธˆ ์ฝ๊ธฐใ€์—์„œ๋งŒ ๋‚จ๊ฒผ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋„๋Š” ํŠธ์œˆ์˜ ์ง€๊ธˆ ์ฝ๊ธฐ๋Š” ์ปค๋„ ์Šค๋ƒ…์ƒท์œผ๋กœ ์ฆ‰์‹œ ๋‹ตํ•˜๊ณ 
3050
3621
  * ํด๋“œ์— ๋‹ฟ์ง€ ์•Š๋Š”๋‹ค โ€” ๊ทธ๋ž˜์„œ ์‚ฌ์Šฌ์ด **์˜์›ํžˆ ๋น„์–ด ์žˆ์—ˆ๋‹ค**(์‹ค์ธก: ์ง€์  0๊ฐœ, ์‹œ๊ฐ„์—ฌํ–‰ 3.7s ๊ทธ๋Œ€๋กœ).
@@ -3065,26 +3636,26 @@ class TwinEngine {
3065
3636
  return st;
3066
3637
  };
3067
3638
  /*
3068
- * ์”จ์•—์ด ์žˆ์œผ๋ฉด **์ด์–ด ์ ‘๋Š”๋‹ค** โ€” ํ•œ ๊ตฌ์กฐ ์•ˆ์—์„œ๋งŒ(๊ตฌ์กฐ๊ฐ€ ๊ฐˆ๋ฆฌ๋ฉด ์•„๋ž˜ ๋งˆ๋”” ๊ฒฝ๋กœ๊ฐ€ ๋งก๋Š”๋‹ค).
3069
- * ์ƒˆ ์žฌ๊ฐœ์ ๋„ ํ•จ๊ป˜ ๋‚จ๊ธด๋‹ค: ๋‹ค์Œ ๋ฒˆ์— ๋˜ ๊ผฌ๋ฆฌ๋งŒ ์ ‘์„ ์ˆ˜ ์žˆ์–ด์•ผ ์ด ์ง€๋ฆ„๊ธธ์ด ๊ณ„์† ์‚ฐ๋‹ค.
3639
+ * ์”จ์•—์ด ์žˆ์œผ๋ฉด **์ด์–ด์„œ ๊ณ„์‚ฐํ•œ๋‹ค** โ€” ํ•œ ๊ตฌ์กฐ ์•ˆ์—์„œ๋งŒ(๊ตฌ์กฐ๊ฐ€ ๊ฐˆ๋ฆฌ๋ฉด ์•„๋ž˜ ๋งˆ๋”” ๊ฒฝ๋กœ๊ฐ€ ๋งก๋Š”๋‹ค).
3640
+ * ์ƒˆ ์žฌ๊ฐœ์ ๋„ ํ•จ๊ป˜ ๋‚จ๊ธด๋‹ค: ๋‹ค์Œ ๋ฒˆ์— ๋˜ ๊ผฌ๋ฆฌ๋งŒ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ์–ด์•ผ ์ด ์ง€๋ฆ„๊ธธ์ด ๊ณ„์† ์‚ฐ๋‹ค.
3070
3641
  */
3071
3642
  if (resume?.fold?.reducer && segments.length <= 1) {
3072
3643
  const model = (segments[0]?.model ?? (asOfNow && newest) ?? reg.model);
3073
- const out = replayFrom(model, resume.fold.reducer, segments[0]?.events ?? []);
3644
+ const out = replayFrom(model, resume.fold.reducer, segments[0]?.events ?? [], replayOptions(reg, domainId));
3074
3645
  return keep(withNow(out.state), { reducer: out.checkpoint, oee: oee.serialize() });
3075
3646
  }
3076
3647
  if (!segments.length) {
3077
3648
  const model = ((asOfNow && newest) || reg.model);
3078
- const out = replayWithCheckpoint(model, []);
3649
+ const out = replayWithCheckpoint(model, [], replayOptions(reg, domainId));
3079
3650
  return keep(withNow(out.state), { reducer: out.checkpoint, oee: oee.serialize() });
3080
3651
  }
3081
3652
  if (segments.length === 1) {
3082
- const out = replayWithCheckpoint(segments[0].model, segments[0].events);
3653
+ const out = replayWithCheckpoint(segments[0].model, segments[0].events, replayOptions(reg, domainId));
3083
3654
  return keep(withNow(out.state), { reducer: out.checkpoint, oee: oee.serialize() });
3084
3655
  }
3085
3656
  /* ์ปค๋„ ๊ณ„์•ฝ๋„ `model` ์ด๋‹ค(0.6.14) โ€” ๊ฒฝ๊ณ„์—์„œ ์–ดํœ˜๋ฅผ ๋˜๋Œ๋ ค ๋‹ด๋˜ ๋ธŒ๋ฆฟ์ง€๊ฐ€ ์‚ฌ๋ผ์กŒ๋‹ค. */
3086
- const { state, shifts } = replaySegments(segments);
3087
- /* ๊ฒฝ๊ณ„์—์„œ ์‚ฌ๋ผ์ง„ ๊ฒƒ์„ ์กฐ์šฉํžˆ ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค โ€” ์ˆ˜๊ฐ€ ์ค„์–ด๋“  ์ด์œ ๋ฅผ ์–ด๋”˜๊ฐ€์—๋Š” ๋‚จ๊ฒจ์•ผ ํ•œ๋‹ค. */
3657
+ const { state, shifts } = replaySegments(segments, replayOptions(reg, domainId));
3658
+ /* ๊ฒฝ๊ณ„์—์„œ ์‚ฌ๋ผ์ง„ ๊ฒƒ์„ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค โ€” ์ˆ˜๊ฐ€ ์ค„์–ด๋“  ์ด์œ ๋ฅผ ์–ด๋”˜๊ฐ€์—๋Š” ๋‚จ๊ฒจ์•ผ ํ•œ๋‹ค. */
3088
3659
  for (const sh of shifts)
3089
3660
  if (sh.equipmentDropped || sh.locationsDropped || sh.personsDropped || sh.assetsDropped)
3090
3661
  console.info(`[twin-engine] "${instanceId}" replay crossed a structure change โ€” dropped ${sh.equipmentDropped} equipment, ${sh.locationsDropped} locations, ${sh.personsDropped} persons, ${sh.assetsDropped} assets that no longer exist.`);
@@ -3172,10 +3743,10 @@ class TwinEngine {
3172
3743
  /*
3173
3744
  * **๋„๊ธฐ ์ „์— ๋‚จ๊ธด๋‹ค** โ€” ์ง€๊ธˆ์ด ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์ง„์‹ค์ธ ๋งˆ์ง€๋ง‰ ์ˆœ๊ฐ„์ด๋‹ค.
3174
3745
  *
3175
- * ๋‚จ๊ธฐ์ง€ ์•Š์œผ๋ฉด ๋‹ค์Œ ์กฐํšŒ๊ฐ€ ์ €๋„์„ ์ „๋Ÿ‰ ๋‹ค์‹œ ์ ‘๋Š”๋‹ค(๊ทธ ๊ฐ’์€ ์–ด์ฐจํ”ผ ๋ฐฉ๊ธˆ ๋ฉ”๋ชจ๋ฆฌ์— ์žˆ๋˜ ๊ฒƒ์ด๋‹ค).
3176
- * ์ฒดํฌํฌ์ธํŠธ ๋ฃจํ”„๊ฐ€ 20์ดˆ๋งˆ๋‹ค ๋œจ์ง€๋งŒ ๊ทธ ์‚ฌ์ด์— ๋ฉˆ์ถ”๋ฉด ๊ทธ ๊ตฌ๊ฐ„์ด ํ†ต์งธ๋กœ ๋‹ค์‹œ ์ ‘ํžŒ๋‹ค.
3746
+ * ๋‚จ๊ธฐ์ง€ ์•Š์œผ๋ฉด ๋‹ค์Œ ์กฐํšŒ๊ฐ€ ์ €๋„์„ ์ „๋Ÿ‰ ๋‹ค์‹œ ๊ณ„์‚ฐํ•œ๋‹ค(๊ทธ ๊ฐ’์€ ์–ด์ฐจํ”ผ ๋ฐฉ๊ธˆ ๋ฉ”๋ชจ๋ฆฌ์— ์žˆ๋˜ ๊ฒƒ์ด๋‹ค).
3747
+ * ์ฒดํฌํฌ์ธํŠธ ๋ฃจํ”„๊ฐ€ 20์ดˆ๋งˆ๋‹ค ๋œจ์ง€๋งŒ ๊ทธ ์‚ฌ์ด์— ๋ฉˆ์ถ”๋ฉด ๊ทธ ๊ตฌ๊ฐ„์ด ํ†ต์งธ๋กœ ๋‹ค์‹œ ๊ณ„์‚ฐ๋œ๋‹ค.
3177
3748
  */
3178
- await this.persistSnapshot(domainId, id).catch(err => (0, log_js_1.twinError)(`[twin-engine] snapshot on stop fail "${id}"`, err?.message ?? err));
3749
+ await this.persistSnapshot(domainId, id, true).catch(err => (0, log_js_1.twinError)(`[twin-engine] snapshot on stop fail "${id}"`, err?.message ?? err));
3179
3750
  /*
3180
3751
  * **๋ชจ์•„ ๋‘” ์ €๋„๋„ ์ง€๊ธˆ ํ˜๋ฆฐ๋‹ค** โ€” ์ฃผ๊ธฐ flush ์‚ฌ์ด(์ตœ๋Œ€ `BROADCAST_COALESCE_MS`)์— ๋ฉˆ์ถ”๋ฉด ๊ทธ ๊ตฌ๊ฐ„์˜
3181
3752
  * ์‚ฌ์‹ค์ด ์‚ฌ๋ผ์ง„๋‹ค. ์Šค๋ƒ…์ƒท๋งŒ ๋‚จ๊ธฐ๋ฉด ์ƒํƒœ๋Š” ์‚ด์•„๋„ **์™œ ๊ทธ๋ ‡๊ฒŒ ๋๋Š”์ง€**๊ฐ€ ๋นˆ๋‹ค(์ €๋„์ด ๋‹ตํ•˜๋Š” ๊ฒƒ์ด๋‹ค).
@@ -3204,7 +3775,7 @@ class TwinEngine {
3204
3775
  * ์„ ์–ธ์ด ๋ชจ๋“  ํ…Œ๋„ŒํŠธ๋ฅผ ๋ฉˆ์ถ˜ ์…ˆ์ด๋‹ค. ๋ฌธ ์•ž์—์„œ ๋ง‰๋Š” ๊ฒƒ์ด 1์ฐจ ๋ฐฉ๋ฒฝ์ด๊ณ (`validateScenario`),
3205
3776
  * ์ด๊ฒƒ์ด 2์ฐจ ๋ฐฉ๋ฒฝ์ด๋‹ค.
3206
3777
  *
3207
- * **์กฐ์šฉํžˆ ์‚ผํ‚ค์ง€ ์•Š๋Š”๋‹ค.** ์‹คํ–‰์„ ๋ฉˆ์ถ”๊ณ  ๊ทธ ์‚ฌ์‹ค์„ ๋‚จ๊ธด๋‹ค โ€” ์˜ˆ์™ธ๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ๊ณ„์† tick ํ•˜๋ฉด
3778
+ * **์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์‚ผํ‚ค์ง€ ์•Š๋Š”๋‹ค.** ์‹คํ–‰์„ ๋ฉˆ์ถ”๊ณ  ๊ทธ ์‚ฌ์‹ค์„ ๋‚จ๊ธด๋‹ค โ€” ์˜ˆ์™ธ๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ๊ณ„์† tick ํ•˜๋ฉด
3208
3779
  * ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋งค ์ฃผ๊ธฐ ์Ÿ์•„์ง€๊ณ , ๊ทธ ํŠธ์œˆ์€ "๋„๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๋ฉด์„œ" ์•„๋ฌด๊ฒƒ๋„ ์ง„ํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค.
3209
3780
  */
3210
3781
  /**
@@ -3334,7 +3905,7 @@ class TwinEngine {
3334
3905
  * ๋ฏธ๋Ÿฌ โ€” ๊ด€์ธก ์ปค๋„๋กœ ๋ณด๋‚ด๊ณ , **์ปค๋งจ๋“œ๊ฐ€ ๋‚ธ ์‚ฌ์‹ค์„ ์ €๋„ ํ์— ์‹ค์–ด** ์ฝ”์–ผ๋ ˆ์„œ๊ฐ€ ๋ฒˆํ˜ธ๋ฅผ ๋ถ€์—ฌํ•˜๊ฒŒ ํ•œ๋‹ค.
3335
3906
  * ๋ฏธ๋Ÿฌ์—๋Š” State ๊ตฌ๋… ๋ฐฐ๊ด€์ด ์—†์–ด(์‹œ๋ฎฌ์€ ๊ทธ ๊ฒฝ๋กœ๋กœ ์ €๋„๋ง) ์ปค๋„ ๋ฐฉ์ถœ์ด ์•„๋ฌด ๋ฐ๋„ ๋‹ฟ์ง€ ์•Š๋Š”๋‹ค.
3336
3907
  * ์—ฌ๊ธฐ์„œ DB ๋ฅผ ๋”ฐ๋กœ ์ฝ์–ด ๋ฒˆํ˜ธ๋ฅผ ๋งค๊ธฐ๋ฉด ์ธ๋ฉ”๋ชจ๋ฆฌ ์นด์šดํ„ฐ์™€ ์–ด๊ธ‹๋‚˜ ๋ฆฌ๋น„์ „์ด ๊ฒน์นœ๋‹ค(๊ฒน์นจ์€ ์˜ค๋ฅ˜๋ฅผ
3337
- * ๋‚ด์ง€ ์•Š๊ณ  ์žฌ์ƒ ์ˆœ์„œ๋งŒ ์กฐ์šฉํžˆ ๋’ค์„ž๋Š”๋‹ค).
3908
+ * ๋‚ด์ง€ ์•Š๊ณ  ์žฌ์ƒ ์ˆœ์„œ๋งŒ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋’ค์„ž๋Š”๋‹ค).
3338
3909
  */
3339
3910
  const kernel = inst.kernel;
3340
3911
  const emitted = [];
@@ -3419,7 +3990,7 @@ class TwinEngine {
3419
3990
  const snap = kernel?.getSnapshot?.() ?? this.snapshot(domainId, id);
3420
3991
  return { instanceId: id, attentions: snap?.attentions ?? [] };
3421
3992
  });
3422
- /* ๋ชจ์œผ๋Š” ๊ทœ์น™(ํƒœ๊น…ยท๊ธ‰ํ•œ ์ˆœ์„œยท์ž๋ฆฌ ์ƒ‰)์€ ์ˆœ์ˆ˜ ํ•จ์ˆ˜๊ฐ€ ๋“ค๊ณ  ์žˆ๋‹ค โ€” ์—ฌ๊ธฐ์„œ ์†์œผ๋กœ ์ ‘์ง€ ์•Š๋Š”๋‹ค. */
3993
+ /* ๋ชจ์œผ๋Š” ๊ทœ์น™(ํƒœ๊น…ยท๊ธ‰ํ•œ ์ˆœ์„œยท์ž๋ฆฌ ์ƒ‰)์€ ์ˆœ์ˆ˜ ํ•จ์ˆ˜๊ฐ€ ๋“ค๊ณ  ์žˆ๋‹ค โ€” ์—ฌ๊ธฐ์„œ ์†์œผ๋กœ ๊ณ„์‚ฐํ•˜์ง€ ์•Š๋Š”๋‹ค. */
3423
3994
  return (0, attention_digest_js_1.mergeLensAttentions)(lenses, limit);
3424
3995
  }
3425
3996
  /** ๋ผ์ด๋ธŒ ์ปค๋„(ForecastTwin) โ€” forecast/divergence ์˜ˆ์ธก ์—ฐ์‚ฐ์šฉ. */
@@ -3533,6 +4104,51 @@ class TwinEngine {
3533
4104
  * `offered` ๋Š” **์ œ์‹œ๋œ ๋ ˆ์ฝ”๋“œ ์ˆ˜**๋‹ค(๊ฑฐ๋ถ€ ์—ฌ๋ถ€ ๋ฌด๊ด€). ํ†ต๊ณผ์œจ์„ ์„œ๋กœ ๋‹ค๋ฅธ ๋‘ ๊ณ„์ˆ˜๊ธฐ์—์„œ ๋‚˜๋ˆ  ๊ณ„์‚ฐํ•˜๋ฉด
3534
4105
  * ๋ถ„๋ชจ์™€ ๋ถ„์ž๊ฐ€ ๋‹ค๋ฅธ ๊ฒƒ์„ ์„ธ๊ฒŒ ๋˜๋ฏ€๋กœ, ํ•œ์ž๋ฆฌ์—์„œ ๋ณธ ์ˆ˜๋ฅผ ๊ทธ๋Œ€๋กœ ๋„˜๊ธด๋‹ค.
3535
4106
  */
4107
+ /**
4108
+ * ์ด ํŠธ์œˆ์˜ ์ €๋„์ด ์ง€๊ธˆ ๋ช‡ ๋ฒˆ๊นŒ์ง€ ์ ํ˜”๋‚˜ โ€” **์ ‘๊ทผ์ž๋กœ ๋‚ธ๋‹ค.**
4109
+ *
4110
+ * ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋ฅผ ๋ฐ–์—์„œ ์ง์ ‘ ์ƒ‰์ธํ•˜์ง€ ์•Š๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ์ด ์ €์žฅ์†Œ์˜ ๊ทœ์œจ์ด๋‹ค(ยง`engine-registry-access`).
4111
+ * ์ง€๋‚œ ๊ธฐ๋ก์„ ์ฑ„์šฐ๋Š” ๊ธธ์ด ๋‹ค์Œ ๋ฒˆํ˜ธ๋ฅผ ์•Œ์•„์•ผ ํ•ด์„œ ์—ด์—ˆ๋‹ค.
4112
+ *
4113
+ * ํŠธ์œˆ์ด ๋„๋Š” ์ค‘์ด ์•„๋‹ˆ๋ฉด 0 ์ด๋‹ค โ€” ๊ทธ๋•Œ๋Š” ์ €๋„๋งŒ ์žˆ๊ณ  ์ด์–ด ๋ถ™์ผ ๋ฒˆํ˜ธ๋ฅผ ์ปค๋„์ด ๋“ค๊ณ  ์žˆ์ง€ ์•Š๋‹ค.
4114
+ */
4115
+ /**
4116
+ * ๋งˆ๊ฐ๋œ ๊ตฌ๊ฐ„ ์‚ฌ์‹ค์˜ **์ด๋ฆ„์„ ์ง€์„ ๋•Œ ์“ธ ๋ฒ”์œ„์™€ ์„ ์–ธ** โ€” ์œ ์ž… ๋ฌธ์— ๋„˜๊ธด๋‹ค.
4117
+ *
4118
+ * โ”€โ”€ ์™œ ํ˜ธ์ŠคํŠธ๊ฐ€ ์ฃผ๋‚˜ (2026-08-30) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
4119
+ * ์ปค๋„์€ `new Kernel(domainId, โ€ฆ)` ์œผ๋กœ ์„œ๋ฏ€๋กœ **์ž๊ธฐ๊ฐ€ ์–ด๋А ํŠธ์œˆ์ธ์ง€ ๋ชจ๋ฅธ๋‹ค.** ๊ทธ๋Ÿฐ๋ฐ ์„ค๋น„ ๋ฒˆํ˜ธ๋Š”
4120
+ * ํŠธ์œˆ ์•ˆ์—์„œ๋งŒ ํ†ตํ•˜๋Š” ์ด๋ฆ„ํ‘œ๋ผ, ํ•œ ๋„๋ฉ”์ธ์— ํ˜„์žฅ์ด ๋‘˜์ด๋ฉด ์„œ๋กœ ๋‹ค๋ฅธ ์„ค๋น„์˜ ๊ฐ™์€ ๋‚ ์ด ํ•œ ์‚ฌ์‹ค์ด
4121
+ * ๋œ๋‹ค. ์‹ค์ธก์œผ๋กœ ๊ทธ ์ผ์ด ๋‚ฌ๋‹ค โ€” ๋‘ ๋ฐœ์ „์†Œ์˜ `002` ๊ฐ€ ๊ฒน์ณ, ์—ฌ๋Ÿฌ ํ˜„์žฅ์„ ํ•จ๊ป˜ ๊ณ„์‚ฐํ•˜๋Š” ์„ฑ๊ณผ ํ™”๋ฉด์—์„œ
4122
+ * ํ•œ์ชฝ ๋ฐœ์ „๋Ÿ‰์ด ์‚ฌ๋ผ์ง„๋‹ค.
4123
+ *
4124
+ * ์„ ์–ธ๋œ ์ •์ฒด์„ฑ์€ **์ปค๋„์ด ๋‹ตํ•œ๋‹ค**(ยง`FlowEngine.equipmentIdentity`) โ€” ํŒ์ •์„ ๋‘ ๊ณณ์— ๋‘์ง€ ์•Š๋Š”๋‹ค.
4125
+ * ํŠธ์œˆ์ด ๋Œ๊ณ  ์žˆ์ง€ ์•Š์œผ๋ฉด ์„ ์–ธ์„ ๋ฌผ์„ ๊ณณ์ด ์—†์œผ๋ฏ€๋กœ ๋ฒ”์œ„๋งŒ ๋„˜๊ธด๋‹ค(๊ทธ๋•Œ๋„ ๊ฒน์น˜์ง€๋Š” ์•Š๋Š”๋‹ค).
4126
+ */
4127
+ static factScope(domainId, instanceId) {
4128
+ const inst = this.instances[(0, runtime_key_js_1.runtimeKey)(domainId, instanceId)];
4129
+ const kernel = inst?.kernel;
4130
+ if (typeof kernel?.equipmentIdentity !== 'function')
4131
+ return { scopeId: instanceId };
4132
+ return {
4133
+ scopeId: instanceId,
4134
+ identityOf: (kind, localId) => (kind === 'equipment' ? kernel.equipmentIdentity(localId) : undefined)
4135
+ };
4136
+ }
4137
+ static journalHead(domainId, instanceId) {
4138
+ const inst = this.instances[(0, runtime_key_js_1.runtimeKey)(domainId, instanceId)];
4139
+ return Number(inst?.revision ?? 0);
4140
+ }
4141
+ /**
4142
+ * ์ €๋„์— ๊ทธ๋งŒํผ ์ ์—ˆ์œผ๋‹ˆ ๋ฒˆํ˜ธ๋ฅผ ๋ฐ€์–ด ์ค€๋‹ค.
4143
+ *
4144
+ * ๋ฐ€์–ด ์ฃผ์ง€ ์•Š์œผ๋ฉด ๋‹ค์Œ์— ๋ผ์ด๋ธŒ๊ฐ€ **๊ฐ™์€ ๋ฒˆํ˜ธ๋ฅผ ๋‹ค์‹œ ์“ด๋‹ค** โ€” ๊ฐ™์€ ์‹œ๊ฐ์˜ ๋‘ ์‚ฌ์‹ค์„ ๊ฐ€๋ฆด ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค.
4145
+ */
4146
+ static advanceJournalHead(domainId, instanceId, by) {
4147
+ const inst = this.instances[(0, runtime_key_js_1.runtimeKey)(domainId, instanceId)];
4148
+ if (!inst || !(by > 0))
4149
+ return;
4150
+ inst.revision = Number(inst.revision ?? 0) + by;
4151
+ }
3536
4152
  static recordIngestResult(domainId, instanceId, offered, rejected, nowMs = Date.now(),
3537
4153
  /**
3538
4154
  * ๋งคํ•‘์€ ํ†ต๊ณผํ–ˆ๋Š”๋ฐ **์ปค๋„์ด ๋ฐ›์ง€ ์•Š์€** ์ˆ˜(`ingestLive` ์˜ ๋ฐ˜ํ™˜๊ณผ ๊ฒฌ์ฃผ์–ด ์–ป๋Š”๋‹ค).
@@ -3548,22 +4164,32 @@ class TwinEngine {
3548
4164
  * **์›๋ณธ์— ๋‹ฟ์ง€ ๋ชปํ–ˆ๋‹ค**๋ฅผ ์ ๋Š”๋‹ค โ€” ใ€Œ๋ฐ›์€ ๊ฒƒ์ด ์—†๋‹คใ€์™€ ๊ฐ€๋ฅธ๋‹ค(ยง`recordReadFailure`).
3549
4165
  *
3550
4166
  * ์ด ๋ฌธ์ด ์—†๋˜ ๋™์•ˆ ์‹ค ์›๋ณธ์ด ๋Š๊ฒจ๋„ ํŠธ์œˆ์˜ ์กฐํšŒ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ์— ๊ทธ ์‚ฌ์‹ค์ด ์—†์—ˆ๋‹ค. ํ™”๋ฉด์ด ๋ณผ ์ˆ˜ ์žˆ๋Š”
3551
- * ๊ฒƒ์€ ใ€Œ์ƒˆ ์‚ฌ์‹ค์ด ์—†๋‹คใ€๋ฟ์ด์—ˆ๊ณ  ๊ทธ๊ฒƒ์€ ใ€Œ์›๋ณธ์ด ์กฐ์šฉํ•˜๋‹คใ€์™€ ๊ตฌ๋ณ„๋˜์ง€ ์•Š๋Š”๋‹ค โ€” ์‹ค์ฆ ์ค‘์— ์›๋ณธ์ด
4167
+ * ๊ฒƒ์€ ใ€Œ์ƒˆ ์‚ฌ์‹ค์ด ์—†๋‹คใ€๋ฟ์ด์—ˆ๊ณ  ๊ทธ๊ฒƒ์€ ใ€Œ์—ฐ๊ฒฐ๋œ ์‹œ์Šคํ…œ์—์„œ ๋“ค์–ด์˜จ ๊ฒƒ์ด ์—†๋‹คใ€์™€ ๊ตฌ๋ณ„๋˜์ง€ ์•Š๋Š”๋‹ค โ€” ์‹ค์ฆ ์ค‘์— ์›๋ณธ์ด
3552
4168
  * ๋Š๊ธฐ๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์›์ธ์„ ์ฐพ์„ ์ˆ˜ ์—†๋‹ค.
3553
4169
  *
3554
4170
  * ๋กœ๊ทธ๋กœ๋Š” ๋งํ•˜๊ณ  ์žˆ์—ˆ๋‹ค(์–ด๋Œ‘ํ„ฐ๊ฐ€ ์žฌ์‹œ๋„๋ฅผ ๊ฒฝ๊ณ ํ•œ๋‹ค). ๊ทธ๋Ÿฌ๋‚˜ **๋กœ๊ทธ๋Š” ์‚ฌ๋žŒ์ด ๋ณผ ๋•Œ๋งŒ ๊ฐ’์ด ์žˆ๋‹ค** โ€”
3555
4171
  * ํ™”๋ฉด์ด ๋งํ•˜๋ ค๋ฉด ์ƒํƒœ์— ์žˆ์–ด์•ผ ํ•œ๋‹ค.
3556
4172
  */
3557
- static recordIngestReadFailure(domainId, instanceId, reason, nowMs = Date.now(), stream) {
4173
+ static recordIngestReadFailure(domainId, instanceId, reason, nowMs = Date.now(), stream, nextRetryMs) {
4174
+ const key = (0, runtime_key_js_1.runtimeKey)(domainId, instanceId);
4175
+ const ledger = this.ingestLedgers[key] ?? (this.ingestLedgers[key] = (0, ingest_health_js_1.newIngestLedger)());
4176
+ (0, ingest_health_js_1.recordReadFailure)(ledger, reason, nowMs, stream, nextRetryMs);
4177
+ }
4178
+ /**
4179
+ * **๋Š๊ฒผ๋‹ค๊ฐ€ ๋Œ์•„์™”๋‹ค** โ€” ์‹คํŒจ ๊ธฐ๋ก์„ ์ง€์šฐ๊ณ  ๊ทธ ์‚ฌ์‹ค์„ ๋‚จ๊ธด๋‹ค (2026-08-27).
4180
+ *
4181
+ * `clearIngestReadFailure` ์™€ ๋‚˜๋ˆ„์–ด ๋‘”๋‹ค: ์ง€์šฐ๊ธฐ๋งŒ ํ•˜๋ฉด ใ€Œ๋Š๊ธด ์ ์ด ์žˆ์—ˆ๋‹คใ€๊ฐ€ ํ™”๋ฉด์—์„œ ์‚ฌ๋ผ์ง„๋‹ค.
4182
+ */
4183
+ static recordIngestRecovered(domainId, instanceId, afterFailures, downMs, nowMs = Date.now()) {
3558
4184
  const key = (0, runtime_key_js_1.runtimeKey)(domainId, instanceId);
3559
4185
  const ledger = this.ingestLedgers[key] ?? (this.ingestLedgers[key] = (0, ingest_health_js_1.newIngestLedger)());
3560
- (0, ingest_health_js_1.recordReadFailure)(ledger, reason, nowMs, stream);
4186
+ (0, ingest_health_js_1.recordRecovered)(ledger, afterFailures, downMs, nowMs);
3561
4187
  }
3562
4188
  /**
3563
4189
  * ์ฝ๊ธฐ๊ฐ€ ์„ฑ๊ณตํ–ˆ๋‹ค โ€” ๋‹จ์ ˆ ๊ธฐ๋ก์„ ์ง€์šด๋‹ค.
3564
4190
  *
3565
4191
  * **๋นˆ ์ฝ๊ธฐ๋„ ์„ฑ๊ณต์ด๋‹ค.** ์›๋ณธ์ด ใ€Œ์ƒˆ ๊ฒƒ์ด ์—†๋‹คใ€๊ณ  ๋‹ตํ•œ ๊ฒƒ์€ ๋‹ฟ์•˜๋‹ค๋Š” ๋œป์ด๋ฏ€๋กœ, ๊ทธ๋•Œ๋„ ๋ถ€๋ฅธ๋‹ค.
3566
- * ๊ทธ ๋‘˜์„ ๊ฐ™๊ฒŒ ๋‘๋ฉด ์กฐ์šฉํ•œ ์›๋ณธ์ด ๋Š๊ธด ์›๋ณธ์œผ๋กœ ๋ณด์ธ๋‹ค.
4192
+ * ๊ทธ ๋‘˜์„ ๊ฐ™๊ฒŒ ๋‘๋ฉด ๋“ค์–ด์˜จ ๊ฒƒ์ด ์—†๋Š” ์—ฐ๊ฒฐ์ด ๋Š๊ธด ์›๋ณธ์œผ๋กœ ๋ณด์ธ๋‹ค.
3567
4193
  */
3568
4194
  static clearIngestReadFailure(domainId, instanceId) {
3569
4195
  const ledger = this.ingestLedgers[(0, runtime_key_js_1.runtimeKey)(domainId, instanceId)];
@@ -3588,7 +4214,7 @@ class TwinEngine {
3588
4214
  * **์›๋ณธ์— ์žˆ๋Š”๋ฐ ์„ธ์šฐ์ง€ ์•Š์€ ๊ฒƒ์„ ์ ๋Š”๋‹ค** โ€” ์ด์œ ์™€ ์ˆ˜(ยง`IngestLedger.withheld`).
3589
4215
  *
3590
4216
  * ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์ฃผ๊ธฐ๋งˆ๋‹ค ๋ถˆ๋Ÿฌ๋„ ๋œ๋‹ค: ์žฅ๋ถ€๊ฐ€ ์ด์œ ๋กœ ๋ฌถ์–ด **๋งˆ์ง€๋ง‰ ์ˆ˜๋กœ ๋ฎ๋Š”๋‹ค**(๋ˆ„์ ํ•˜์ง€ ์•Š๋Š”๋‹ค).
3591
- * `count: 0` ์€ ใ€Œ๊ทธ ์ด์œ ๊ฐ€ ํ’€๋ ธ๋‹คใ€๋กœ ๊ทธ ์ค„์„ ์ง€์šด๋‹ค โ€” ์กฐ์šฉํžˆ ๊ทธ์น˜๋ฉด ๋‚ก์€ ์ˆ˜๊ฐ€ ๋‚จ๋Š”๋‹ค.
4217
+ * `count: 0` ์€ ใ€Œ๊ทธ ์ด์œ ๊ฐ€ ํ’€๋ ธ๋‹คใ€๋กœ ๊ทธ ์ค„์„ ์ง€์šด๋‹ค โ€” ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๊ทธ์น˜๋ฉด ๋‚ก์€ ์ˆ˜๊ฐ€ ๋‚จ๋Š”๋‹ค.
3592
4218
  */
3593
4219
  static recordIngestWithheld(domainId, instanceId, reason, count, nowMs = Date.now()) {
3594
4220
  const key = (0, runtime_key_js_1.runtimeKey)(domainId, instanceId);
@@ -3693,7 +4319,7 @@ class TwinEngine {
3693
4319
  /**
3694
4320
  * ๋„๋Š” ์ธ์Šคํ„ด์Šค ์ „์ฒด์˜ ๊ณ„์ธก(๋ชจ๋‹ˆํ„ฐ ๋Œ€์‹œ๋ณด๋“œ์šฉ) โ€” **์‹œ๋ฎฌ๊ณผ ๋ฏธ๋Ÿฌ๋ฅผ ํ•จ๊ป˜**.
3695
4321
  *
3696
- * ์˜ˆ์ „์—๋Š” ์‹œ๋ฎฌ์— ๊ณ„๊ธฐ๊ฐ€ ์—†์–ด ์ด ๋ชฉ๋ก์—์„œ ์กฐ์šฉํžˆ ๋น ์กŒ๋‹ค(๊ณ„๊ธฐ๊ฐ€ `null` ์ด๋ผ ๊ฑธ๋Ÿฌ์กŒ๋‹ค). ๋„๋Š” ํŠธ์œˆ
4322
+ * ์˜ˆ์ „์—๋Š” ์‹œ๋ฎฌ์— ๊ณ„๊ธฐ๊ฐ€ ์—†์–ด ์ด ๋ชฉ๋ก์—์„œ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋น ์กŒ๋‹ค(๊ณ„๊ธฐ๊ฐ€ `null` ์ด๋ผ ๊ฑธ๋Ÿฌ์กŒ๋‹ค). ๋„๋Š” ํŠธ์œˆ
3697
4323
  * ๋Œ€๋ถ€๋ถ„์ด ์‹œ๋ฎฌ์ธ ์„œ๋ฒ„์—์„œ ๊ทธ ๋ชฉ๋ก์€ ใ€Œ๋ถ€ํ•˜๊ฐ€ ๊ฑฐ์˜ ์—†๋‹คใ€๋กœ ๋ณด์˜€๋‹ค.
3698
4324
  */
3699
4325
  static async allMetrics(domainId) {
@@ -3750,10 +4376,10 @@ class TwinEngine {
3750
4376
  return null;
3751
4377
  const cutoff = untilTime != null ? Date.parse(untilTime) : Infinity;
3752
4378
  /*
3753
- * journal-fold: ์˜ค๋”์˜ ๋งˆ์ง€๋ง‰ ์ƒํƒœ๋Š” ๊ทธ ์˜ค๋”์— ์ผ์–ด๋‚œ ์‚ฌ์‹ค๋“ค์„ ์ ‘์–ด์•ผ ๋‚˜์˜จ๋‹ค.
4379
+ * journal-fold: ์˜ค๋”์˜ ๋งˆ์ง€๋ง‰ ์ƒํƒœ๋Š” ๊ทธ ์˜ค๋”์— ์ผ์–ด๋‚œ ์‚ฌ์‹ค๋“ค์„ ๊ณ„์‚ฐํ•ด์•ผ ๋‚˜์˜จ๋‹ค.
3754
4380
  *
3755
4381
  * **์—†์–ด์งˆ ์กฐ๊ฑด** โ€” ์ง€๊ธˆ ์ด ์ฝ๊ธฐ์—๋Š” ์ƒํ•œ์ด ์—†๊ณ  `eventType` ์— ์ธ๋ฑ์Šค๋„ ์—†๋‹ค. ๊ทธ๋ž˜์„œ ์ €๋„์ด ํฐ
3756
- * ํŠธ์œˆ์—์„œ๋Š” ์ด ํ•œ ๋ฒˆ์ด ๊ทธ ํŠธ์œˆ์˜ ์ €๋„ ์ „์ฒด ์ฃผ์‚ฌ๋‹ค. ์˜ค๋” ์ƒํƒœ๋ฅผ ๋”ฐ๋กœ ์ ‘์–ด ๋‘” ํ‘œ(ํˆฌ์˜)๋ฅผ ๋‘๊ฑฐ๋‚˜,
4382
+ * ํŠธ์œˆ์—์„œ๋Š” ์ด ํ•œ ๋ฒˆ์ด ๊ทธ ํŠธ์œˆ์˜ ์ €๋„ ์ „์ฒด ์ฃผ์‚ฌ๋‹ค. ์˜ค๋” ์ƒํƒœ๋ฅผ ๋”ฐ๋กœ ๊ณ„์‚ฐํ•ด ๋‘” ํ‘œ(ํˆฌ์˜)๋ฅผ ๋‘๊ฑฐ๋‚˜,
3757
4383
  * `(domain, instanceId, eventType, revision)` ์ธ๋ฑ์Šค๋ฅผ ๋ถ™์ด๊ณ  ์ปค์„œ๋ฅผ ๋„ฃ์œผ๋ฉด ์ด ์˜ˆ์™ธ๋Š” ์‚ฌ๋ผ์ง„๋‹ค.
3758
4384
  * ์‹œ๊ฐ ์ƒํ•œ(`cutoff`)์„ SQL ๋กœ ๋‚ด๋ ค๋„ ์ ˆ๋ฐ˜์€ ์ค€๋‹ค.
3759
4385
  */