@things-factory/headless-twin 10.0.8 → 10.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/dist-server/engine/attention-digest.d.ts +52 -0
  2. package/dist-server/engine/attention-digest.js +76 -0
  3. package/dist-server/engine/attention-digest.js.map +1 -0
  4. package/dist-server/engine/canonical-ingest.d.ts +2 -2
  5. package/dist-server/engine/canonical-ingest.js +24 -2
  6. package/dist-server/engine/canonical-ingest.js.map +1 -1
  7. package/dist-server/engine/command-routing.d.ts +33 -0
  8. package/dist-server/engine/command-routing.js +53 -0
  9. package/dist-server/engine/command-routing.js.map +1 -0
  10. package/dist-server/engine/energy-topology.d.ts +86 -0
  11. package/dist-server/engine/energy-topology.js +144 -0
  12. package/dist-server/engine/energy-topology.js.map +1 -0
  13. package/dist-server/engine/index.d.ts +11 -0
  14. package/dist-server/engine/index.js +17 -0
  15. package/dist-server/engine/index.js.map +1 -1
  16. package/dist-server/engine/kpi-baseline.d.ts +78 -0
  17. package/dist-server/engine/kpi-baseline.js +123 -0
  18. package/dist-server/engine/kpi-baseline.js.map +1 -0
  19. package/dist-server/engine/kpi-fold.d.ts +137 -1
  20. package/dist-server/engine/kpi-fold.js +225 -0
  21. package/dist-server/engine/kpi-fold.js.map +1 -1
  22. package/dist-server/engine/kpi-query.d.ts +32 -1
  23. package/dist-server/engine/kpi-query.js +262 -20
  24. package/dist-server/engine/kpi-query.js.map +1 -1
  25. package/dist-server/engine/kpi-target.d.ts +17 -0
  26. package/dist-server/engine/kpi-target.js +24 -2
  27. package/dist-server/engine/kpi-target.js.map +1 -1
  28. package/dist-server/engine/live-attentions.d.ts +1 -0
  29. package/dist-server/engine/live-attentions.js +7 -1
  30. package/dist-server/engine/live-attentions.js.map +1 -1
  31. package/dist-server/engine/live-feed-registry.d.ts +25 -0
  32. package/dist-server/engine/live-feed-registry.js +51 -0
  33. package/dist-server/engine/live-feed-registry.js.map +1 -0
  34. package/dist-server/engine/load-meter.d.ts +181 -0
  35. package/dist-server/engine/load-meter.js +267 -0
  36. package/dist-server/engine/load-meter.js.map +1 -0
  37. package/dist-server/engine/local-declarations.d.ts +262 -0
  38. package/dist-server/engine/local-declarations.js +528 -0
  39. package/dist-server/engine/local-declarations.js.map +1 -0
  40. package/dist-server/engine/model-basis.d.ts +39 -6
  41. package/dist-server/engine/model-basis.js +69 -9
  42. package/dist-server/engine/model-basis.js.map +1 -1
  43. package/dist-server/engine/model-vocabulary.d.ts +17 -0
  44. package/dist-server/engine/model-vocabulary.js +81 -0
  45. package/dist-server/engine/model-vocabulary.js.map +1 -0
  46. package/dist-server/engine/oee-accumulator.d.ts +28 -0
  47. package/dist-server/engine/oee-accumulator.js +26 -1
  48. package/dist-server/engine/oee-accumulator.js.map +1 -1
  49. package/dist-server/engine/operation-basis.d.ts +32 -0
  50. package/dist-server/engine/operation-basis.js +87 -0
  51. package/dist-server/engine/operation-basis.js.map +1 -0
  52. package/dist-server/engine/property-effects.d.ts +30 -0
  53. package/dist-server/engine/property-effects.js +192 -0
  54. package/dist-server/engine/property-effects.js.map +1 -0
  55. package/dist-server/engine/runtime-key.d.ts +15 -0
  56. package/dist-server/engine/runtime-key.js +64 -0
  57. package/dist-server/engine/runtime-key.js.map +1 -0
  58. package/dist-server/engine/spec-coverage.js +3 -3
  59. package/dist-server/engine/spec-coverage.js.map +1 -1
  60. package/dist-server/engine/state-axes.d.ts +21 -0
  61. package/dist-server/engine/state-axes.js +59 -0
  62. package/dist-server/engine/state-axes.js.map +1 -0
  63. package/dist-server/engine/structure-diff.js +1 -1
  64. package/dist-server/engine/structure-diff.js.map +1 -1
  65. package/dist-server/engine/travel-estimator.d.ts +9 -2
  66. package/dist-server/engine/travel-estimator.js +11 -6
  67. package/dist-server/engine/travel-estimator.js.map +1 -1
  68. package/dist-server/engine/twin-engine.d.ts +418 -57
  69. package/dist-server/engine/twin-engine.js +1599 -306
  70. package/dist-server/engine/twin-engine.js.map +1 -1
  71. package/dist-server/engine/warm-start.d.ts +93 -13
  72. package/dist-server/engine/warm-start.js +114 -14
  73. package/dist-server/engine/warm-start.js.map +1 -1
  74. package/dist-server/index.d.ts +1 -0
  75. package/dist-server/index.js +17 -11
  76. package/dist-server/index.js.map +1 -1
  77. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.d.ts +5 -0
  78. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +48 -0
  79. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -0
  80. package/dist-server/migrations/index.d.ts +2 -0
  81. package/dist-server/migrations/index.js +10 -0
  82. package/dist-server/migrations/index.js.map +1 -0
  83. package/dist-server/service/index.d.ts +3 -2
  84. package/dist-server/service/index.js +25 -17
  85. package/dist-server/service/index.js.map +1 -1
  86. package/dist-server/service/reference/discovery-result.d.ts +34 -0
  87. package/dist-server/service/reference/discovery-result.js +84 -0
  88. package/dist-server/service/reference/discovery-result.js.map +1 -0
  89. package/dist-server/service/reference/ingest-space.d.ts +53 -0
  90. package/dist-server/service/reference/ingest-space.js +79 -0
  91. package/dist-server/service/reference/ingest-space.js.map +1 -0
  92. package/dist-server/service/reference/knob-defaults.d.ts +20 -0
  93. package/dist-server/service/reference/knob-defaults.js +59 -0
  94. package/dist-server/service/reference/knob-defaults.js.map +1 -0
  95. package/dist-server/service/reference/reference-live.d.ts +23 -0
  96. package/dist-server/service/reference/reference-live.js +123 -6
  97. package/dist-server/service/reference/reference-live.js.map +1 -1
  98. package/dist-server/service/reference/reference-master.d.ts +227 -11
  99. package/dist-server/service/reference/reference-master.js +206 -25
  100. package/dist-server/service/reference/reference-master.js.map +1 -1
  101. package/dist-server/service/reference/reference-resolver.d.ts +33 -3
  102. package/dist-server/service/reference/reference-resolver.js +290 -23
  103. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  104. package/dist-server/service/reference/template-registry.d.ts +19 -3
  105. package/dist-server/service/reference/template-registry.js.map +1 -1
  106. package/dist-server/service/twin-attention/twin-attention-query.d.ts +8 -1
  107. package/dist-server/service/twin-attention/twin-attention-query.js +39 -8
  108. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  109. package/dist-server/service/twin-audit/command-audit.d.ts +37 -0
  110. package/dist-server/service/twin-audit/command-audit.js +53 -0
  111. package/dist-server/service/twin-audit/command-audit.js.map +1 -0
  112. package/dist-server/service/twin-audit/index.d.ts +4 -0
  113. package/dist-server/service/twin-audit/index.js +8 -0
  114. package/dist-server/service/twin-audit/index.js.map +1 -0
  115. package/dist-server/service/twin-audit/twin-audit-event.d.ts +24 -0
  116. package/dist-server/service/twin-audit/twin-audit-event.js +125 -0
  117. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -0
  118. package/dist-server/service/twin-audit/twin-audit-query.d.ts +4 -0
  119. package/dist-server/service/twin-audit/twin-audit-query.js +76 -0
  120. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -0
  121. package/dist-server/service/twin-control/twin-control-mutation.d.ts +2 -0
  122. package/dist-server/service/twin-control/twin-control-mutation.js +50 -11
  123. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  124. package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
  125. package/dist-server/service/twin-event/twin-event-keys.js +1 -1
  126. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  127. package/dist-server/service/twin-event/twin-event.d.ts +14 -5
  128. package/dist-server/service/twin-event/twin-event.js +46 -14
  129. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  130. package/dist-server/service/twin-forecast/forecast-metrics.d.ts +13 -0
  131. package/dist-server/service/twin-forecast/forecast-metrics.js +57 -0
  132. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -0
  133. package/dist-server/service/twin-forecast/forecast-tuning.d.ts +12 -0
  134. package/dist-server/service/twin-forecast/forecast-tuning.js +44 -0
  135. package/dist-server/service/twin-forecast/forecast-tuning.js.map +1 -0
  136. package/dist-server/service/twin-forecast/gap-analytics.d.ts +28 -2
  137. package/dist-server/service/twin-forecast/gap-analytics.js +43 -17
  138. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  139. package/dist-server/service/twin-forecast/twin-forecast-query.js +219 -39
  140. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  141. package/dist-server/service/twin-instance/twin-instance.d.ts +38 -5
  142. package/dist-server/service/twin-instance/twin-instance.js +66 -18
  143. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  144. package/dist-server/service/twin-journal/twin-journal-query.d.ts +11 -4
  145. package/dist-server/service/twin-journal/twin-journal-query.js +95 -15
  146. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  147. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +11 -5
  148. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +46 -27
  149. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  150. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  151. package/dist-server/service/twin-metrics/twin-metrics-query.js +63 -1
  152. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  153. package/dist-server/service/twin-model/epcis-coverage.d.ts +14 -0
  154. package/dist-server/service/twin-model/epcis-coverage.js +174 -0
  155. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -0
  156. package/dist-server/service/twin-model/iec61850-coverage.d.ts +14 -0
  157. package/dist-server/service/twin-model/iec61850-coverage.js +98 -0
  158. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -0
  159. package/dist-server/service/twin-model/index.d.ts +13 -0
  160. package/dist-server/service/twin-model/index.js +29 -0
  161. package/dist-server/service/twin-model/index.js.map +1 -0
  162. package/dist-server/service/twin-model/isa95-coverage.d.ts +48 -0
  163. package/dist-server/service/twin-model/isa95-coverage.js +155 -0
  164. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -0
  165. package/dist-server/service/twin-model/project-structure.d.ts +16 -0
  166. package/dist-server/service/twin-model/project-structure.js +173 -0
  167. package/dist-server/service/twin-model/project-structure.js.map +1 -0
  168. package/dist-server/service/twin-model/standard-coverage.d.ts +9 -0
  169. package/dist-server/service/twin-model/standard-coverage.js +37 -0
  170. package/dist-server/service/twin-model/standard-coverage.js.map +1 -0
  171. package/dist-server/service/twin-model/twin-equipment.d.ts +64 -0
  172. package/dist-server/service/twin-model/twin-equipment.js +134 -0
  173. package/dist-server/service/twin-model/twin-equipment.js.map +1 -0
  174. package/dist-server/service/twin-model/twin-lineage-query.d.ts +3 -0
  175. package/dist-server/service/twin-model/twin-lineage-query.js +206 -0
  176. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -0
  177. package/dist-server/service/twin-model/twin-location.d.ts +58 -0
  178. package/dist-server/service/twin-model/twin-location.js +130 -0
  179. package/dist-server/service/twin-model/twin-location.js.map +1 -0
  180. package/dist-server/service/twin-model/twin-model-item-query.d.ts +141 -0
  181. package/dist-server/service/twin-model/twin-model-item-query.js +852 -0
  182. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -0
  183. package/dist-server/service/twin-model/twin-model-mutation.d.ts +57 -0
  184. package/dist-server/service/twin-model/twin-model-mutation.js +490 -0
  185. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -0
  186. package/dist-server/service/twin-model/twin-model-query.d.ts +43 -0
  187. package/dist-server/service/twin-model/twin-model-query.js +551 -0
  188. package/dist-server/service/twin-model/twin-model-query.js.map +1 -0
  189. package/dist-server/service/twin-model/twin-model-tree-query.d.ts +3 -0
  190. package/dist-server/service/twin-model/twin-model-tree-query.js +158 -0
  191. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -0
  192. package/dist-server/service/twin-model/twin-operation.d.ts +61 -0
  193. package/dist-server/service/twin-model/twin-operation.js +137 -0
  194. package/dist-server/service/twin-model/twin-operation.js.map +1 -0
  195. package/dist-server/service/twin-space/move-space.d.ts +65 -0
  196. package/dist-server/service/twin-space/move-space.js +166 -0
  197. package/dist-server/service/twin-space/move-space.js.map +1 -0
  198. package/dist-server/service/twin-space/space-integrity.d.ts +97 -0
  199. package/dist-server/service/twin-space/space-integrity.js +182 -0
  200. package/dist-server/service/twin-space/space-integrity.js.map +1 -0
  201. package/dist-server/service/twin-space/twin-space-area.js +4 -4
  202. package/dist-server/service/twin-space/twin-space-area.js.map +1 -1
  203. package/dist-server/service/twin-space/twin-space-representation.js +6 -6
  204. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  205. package/dist-server/service/twin-space/twin-space-resolver.d.ts +63 -1
  206. package/dist-server/service/twin-space/twin-space-resolver.js +365 -18
  207. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  208. package/dist-server/service/twin-space/twin-space.d.ts +17 -1
  209. package/dist-server/service/twin-space/twin-space.js +15 -5
  210. package/dist-server/service/twin-space/twin-space.js.map +1 -1
  211. package/dist-server/service/twin-state/twin-state-subscription.js +1 -1
  212. package/dist-server/service/twin-state/twin-state-subscription.js.map +1 -1
  213. package/dist-server/service/twin-structure/twin-structure.d.ts +1 -1
  214. package/dist-server/service/twin-structure/twin-structure.js +8 -7
  215. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  216. package/dist-server/service/twin-target/twin-target-resolver.js +21 -4
  217. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
  218. package/dist-shared/axis-read.d.ts +12 -0
  219. package/dist-shared/axis-read.js +32 -0
  220. package/dist-shared/axis-read.js.map +1 -0
  221. package/dist-shared/entity-delta.d.ts +29 -0
  222. package/dist-shared/entity-delta.js +287 -0
  223. package/dist-shared/entity-delta.js.map +1 -0
  224. package/dist-shared/kpi-broadcast.d.ts +4 -0
  225. package/dist-shared/kpi-broadcast.js +16 -0
  226. package/dist-shared/kpi-broadcast.js.map +1 -0
  227. package/dist-shared/twin-level.d.ts +23 -0
  228. package/dist-shared/twin-level.js +52 -0
  229. package/dist-shared/twin-level.js.map +1 -0
  230. package/package.json +14 -12
  231. package/server/engine/attention-digest.ts +102 -0
  232. package/server/engine/canonical-ingest.ts +36 -4
  233. package/server/engine/command-routing.ts +67 -0
  234. package/server/engine/energy-topology.ts +189 -0
  235. package/server/engine/index.ts +17 -0
  236. package/server/engine/kpi-baseline.ts +202 -0
  237. package/server/engine/kpi-fold.ts +389 -1
  238. package/server/engine/kpi-query.ts +295 -22
  239. package/server/engine/kpi-target.ts +24 -2
  240. package/server/engine/live-attentions.ts +7 -2
  241. package/server/engine/live-feed-registry.ts +58 -0
  242. package/server/engine/load-meter.ts +384 -0
  243. package/server/engine/local-declarations.ts +700 -0
  244. package/server/engine/model-basis.ts +78 -10
  245. package/server/engine/model-vocabulary.ts +82 -0
  246. package/server/engine/oee-accumulator.ts +34 -1
  247. package/server/engine/operation-basis.ts +100 -0
  248. package/server/engine/property-effects.ts +199 -0
  249. package/server/engine/runtime-key.ts +58 -0
  250. package/server/engine/spec-coverage.ts +3 -3
  251. package/server/engine/state-axes.ts +60 -0
  252. package/server/engine/structure-diff.ts +1 -1
  253. package/server/engine/travel-estimator.ts +11 -6
  254. package/server/engine/twin-engine.ts +1710 -297
  255. package/server/engine/warm-start.ts +207 -22
  256. package/server/index.ts +13 -10
  257. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +43 -0
  258. package/server/migrations/index.ts +7 -0
  259. package/server/service/index.ts +9 -1
  260. package/server/service/reference/discovery-result.ts +95 -0
  261. package/server/service/reference/ingest-space.ts +104 -0
  262. package/server/service/reference/knob-defaults.ts +59 -0
  263. package/server/service/reference/reference-live.ts +121 -6
  264. package/server/service/reference/reference-master.ts +362 -35
  265. package/server/service/reference/reference-resolver.ts +307 -23
  266. package/server/service/reference/template-registry.ts +20 -5
  267. package/server/service/twin-attention/twin-attention-query.ts +43 -6
  268. package/server/service/twin-audit/command-audit.ts +81 -0
  269. package/server/service/twin-audit/index.ts +5 -0
  270. package/server/service/twin-audit/twin-audit-event.ts +112 -0
  271. package/server/service/twin-audit/twin-audit-query.ts +72 -0
  272. package/server/service/twin-control/twin-control-mutation.ts +53 -13
  273. package/server/service/twin-event/twin-event-keys.ts +1 -1
  274. package/server/service/twin-event/twin-event.ts +52 -15
  275. package/server/service/twin-forecast/forecast-metrics.ts +60 -0
  276. package/server/service/twin-forecast/forecast-tuning.ts +40 -0
  277. package/server/service/twin-forecast/gap-analytics.ts +48 -9
  278. package/server/service/twin-forecast/twin-forecast-query.ts +211 -38
  279. package/server/service/twin-instance/twin-instance.ts +127 -22
  280. package/server/service/twin-journal/twin-journal-query.ts +108 -14
  281. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +40 -23
  282. package/server/service/twin-metrics/twin-metrics-query.ts +60 -3
  283. package/server/service/twin-model/epcis-coverage.ts +195 -0
  284. package/server/service/twin-model/iec61850-coverage.ts +117 -0
  285. package/server/service/twin-model/index.ts +25 -0
  286. package/server/service/twin-model/isa95-coverage.ts +190 -0
  287. package/server/service/twin-model/project-structure.ts +199 -0
  288. package/server/service/twin-model/standard-coverage.ts +35 -0
  289. package/server/service/twin-model/twin-equipment.ts +150 -0
  290. package/server/service/twin-model/twin-lineage-query.ts +193 -0
  291. package/server/service/twin-model/twin-location.ts +140 -0
  292. package/server/service/twin-model/twin-model-item-query.ts +848 -0
  293. package/server/service/twin-model/twin-model-mutation.ts +503 -0
  294. package/server/service/twin-model/twin-model-query.ts +537 -0
  295. package/server/service/twin-model/twin-model-tree-query.ts +180 -0
  296. package/server/service/twin-model/twin-operation.ts +150 -0
  297. package/server/service/twin-space/move-space.ts +262 -0
  298. package/server/service/twin-space/space-integrity.ts +293 -0
  299. package/server/service/twin-space/twin-space-area.ts +4 -4
  300. package/server/service/twin-space/twin-space-representation.ts +6 -6
  301. package/server/service/twin-space/twin-space-resolver.ts +365 -22
  302. package/server/service/twin-space/twin-space.ts +40 -6
  303. package/server/service/twin-state/twin-state-subscription.ts +1 -1
  304. package/server/service/twin-structure/twin-structure.ts +10 -7
  305. package/server/service/twin-target/twin-target-resolver.ts +22 -4
  306. package/shared/axis-read.ts +28 -0
  307. package/shared/entity-delta.ts +286 -0
  308. package/shared/kpi-broadcast.ts +13 -0
  309. package/shared/twin-level.ts +48 -0
  310. package/test/adopt-structure-live.test.ts +133 -0
  311. package/test/attention-digest.test.ts +135 -0
  312. package/test/axis-read.test.ts +87 -0
  313. package/test/boot-resume.test.ts +205 -0
  314. package/test/canonical-ingest-vocabularies.test.ts +118 -0
  315. package/test/capability-mapping.test.ts +5 -5
  316. package/test/command-routing.test.ts +61 -0
  317. package/test/declaration-reaches-model.test.ts +178 -0
  318. package/test/discovery-result.test.ts +75 -0
  319. package/test/energy-topology.test.ts +113 -0
  320. package/test/entity-delta.test.ts +195 -25
  321. package/test/event-time-column.test.ts +67 -0
  322. package/test/forecast-metrics.test.ts +71 -0
  323. package/test/forecast-tuning.test.ts +42 -0
  324. package/test/gap-analytics.test.ts +34 -7
  325. package/test/ingest-bench.test.ts +9 -9
  326. package/test/ingest-running-guard.test.ts +136 -0
  327. package/test/ingest-space.test.ts +78 -0
  328. package/test/instance-cache-lifecycle.test.ts +110 -0
  329. package/test/kernel-kind-guard.test.ts +96 -0
  330. package/test/knob-defaults.test.ts +72 -0
  331. package/test/kpi-baseline-db.test.ts +216 -0
  332. package/test/kpi-baseline.test.ts +196 -0
  333. package/test/kpi-fold.test.ts +393 -2
  334. package/test/kpi-query-bench.test.ts +130 -0
  335. package/test/lineage-survives-restart.test.ts +203 -0
  336. package/test/live-feed-registry.test.ts +67 -0
  337. package/test/live-kernel-facts.test.ts +161 -0
  338. package/test/live-mirror-parity.test.ts +54 -14
  339. package/test/load-meter.test.ts +314 -0
  340. package/test/local-declarations.test.ts +554 -0
  341. package/test/master-to-twin.test.ts +128 -36
  342. package/test/model-basis.test.ts +56 -1
  343. package/test/model-vocabulary.test.ts +114 -0
  344. package/test/move-space.test.ts +149 -0
  345. package/test/mutation-gate.test.ts +122 -0
  346. package/test/oee-accumulator.test.ts +90 -7
  347. package/test/operation-basis.test.ts +100 -0
  348. package/test/operations-capability-db.test.ts +165 -0
  349. package/test/project-structure-db.test.ts +226 -0
  350. package/test/projection-reaches-screen.test.ts +272 -0
  351. package/test/property-effects.test.ts +96 -0
  352. package/test/registry-key-guard.test.ts +80 -0
  353. package/test/resync-origin-site.test.ts +77 -0
  354. package/test/runtime-key.test.ts +66 -0
  355. package/test/scale-twin-bench.test.ts +7 -7
  356. package/test/snapshot-freshness.test.ts +60 -0
  357. package/test/space-integrity.test.ts +223 -0
  358. package/test/standard-coverage.test.ts +94 -0
  359. package/test/state-axes.test.ts +74 -0
  360. package/test/streamline-e2e.test.ts +16 -16
  361. package/test/structure-revision-db.test.ts +16 -15
  362. package/test/tenant-registry-db.test.ts +149 -0
  363. package/test/twin-audit.test.ts +83 -0
  364. package/test/twin-model-item-db.test.ts +275 -0
  365. package/test/twin-model-tree-db.test.ts +176 -0
  366. package/test/twin-origin-resync.test.ts +114 -0
  367. package/test/warm-start-seam.test.ts +140 -0
  368. package/test/warm-start.test.ts +223 -4
  369. package/tsconfig.json +6 -1
  370. package/tsconfig.shared.json +23 -0
  371. package/tsconfig.shared.tsbuildinfo +1 -0
  372. package/tsconfig.tsbuildinfo +1 -0
  373. package/dist-server/engine/entity-delta.d.ts +0 -19
  374. package/dist-server/engine/entity-delta.js +0 -161
  375. package/dist-server/engine/entity-delta.js.map +0 -1
  376. package/dist-server/service/twin-event/backfill-keys.d.ts +0 -11
  377. package/dist-server/service/twin-event/backfill-keys.js +0 -63
  378. package/dist-server/service/twin-event/backfill-keys.js.map +0 -1
  379. package/dist-server/tsconfig.tsbuildinfo +0 -1
  380. package/server/engine/entity-delta.ts +0 -169
  381. package/server/service/twin-event/backfill-keys.ts +0 -72
@@ -1,53 +1,238 @@
1
1
  /*
2
- * 웜스타트 판정 — **순수**. "기동하는 커널에 직전 상태를 심을 것인가, 심는다면 무엇을" 만 정한다.
2
+ * 웜스타트 판정 — **순수**. "기동하는 커널에 직전 상태를 주입할 것인가, 주입한다면 무엇을" 만 정한다.
3
3
  * 실제 주입(hydrateObserved 호출)과 로그는 엔진이 한다.
4
4
  *
5
5
  * ── 왜 떼어냈나 ─────────────────────────────────────────────────────────────
6
- * 이 판정이 틀리면 증상이 정반대 두 방향으로 나온다: 심어야 할 때 안 심으면 **있는 재고가 0 으로**
6
+ * 이 판정이 틀리면 증상이 정반대 두 방향으로 나온다: 주입해야 할 때 안 주입하면 **있는 재고가 0 으로**
7
7
  * 보이고(2026-07-31 hatiolab-wms: 저널에 입고 540·출고 94 인데 재고 화면이 비어 있었다), 심지
8
- * 말아야 할 벤치에 심으면 **용량 측정이 오염된다.** 둘 다 조용히 틀리는 종류라 규칙을 고정한다.
8
+ * 말아야 할 벤치에 주입하면 **용량 측정이 오염된다.** 둘 다 조용히 틀리는 종류라 규칙을 고정한다.
9
+ *
10
+ * ── 오더를 버리던 전제가 낡았다 (2026-08-05) ────────────────────────────────
11
+ * 예전 주석은 "스냅샷의 오더에는 progress 밖에 없어서 역산하면 없는 숫자를 지어내는 셈이므로
12
+ * 넘기지 않는다" 였다. 그때는 맞았다. 그러나 커널 `OrderState` 가 **원값을 되찾은 뒤**
13
+ * (`requested`·`fulfilled`·`lines`) 이 판정만 그대로 남아, 재기동하면 재고는 맞는데 **진행 중
14
+ * 주문이 통째로 사라진** 화면이 나왔다. 오류는 나지 않는다 — 그래서 오래 남아 있었다.
15
+ *
16
+ * 두 스냅샷 생산 경로가 모두 원값을 담는다: 시뮬은 커널 `snapshot()`, 라이브(미러)는
17
+ * `StateProjector` 다. 사람·자산·작업도 같은 스냅샷에 있고 커널이 심을 수 있는데 넘기지 않고 있었다.
18
+ * 그래서 씨앗을 **관측 스냅샷 전체**로 넓힌다.
19
+ *
20
+ * 다만 넓히면서 **조용한 누락을 만들지 않는다.** 원값이 없는 오더는 커널이 걸러 내므로(지어내지
21
+ * 않는다), 그 수를 여기서 세어 호출부가 말할 수 있게 한다. 세지 않으면 "주문이 없다" 와 "주문을
22
+ * 못 심었다" 가 화면에서 같아진다.
9
23
  */
10
24
 
11
- /** 커널에 심을 관측 상태 — 구조가 아니라 "무엇이 어디에 얼마나". */
25
+ /**
26
+ * 커널에 심을 관측 상태 — 구조가 아니라 "무엇이 어디에 얼마나".
27
+ *
28
+ * 커널 `hydrateObserved` 가 받는 스냅샷 모양을 그대로 따른다. 여기서 골라 담지 않는 이유: 무엇을
29
+ * 심을 수 있는지는 커널이 알고, 그 규칙(고아 작업 제외·이행 완료 오더 제외 등)도 커널에 있다.
30
+ * 호스트가 미리 골라내면 규칙이 두 곳으로 갈린다.
31
+ */
12
32
  export interface ObservedSeed {
13
33
  locations: unknown[]
14
34
  items: unknown[]
15
35
  equipment: unknown[]
36
+ persons?: unknown[]
37
+ assets?: unknown[]
38
+ tasks?: unknown[]
39
+ orders?: unknown[]
40
+ /** 확인해 둔 주목 신호 id — 상태에서 파생되지 않아 반드시 함께 넘긴다. */
41
+ acked?: string[]
42
+ /**
43
+ * 주목 신호가 **처음 성립한 시각** — 확인과 같은 성질이다(계산으로 되살릴 수 없다).
44
+ *
45
+ * 넘기지 않으면 재기동 뒤 세 시간째 지속된 조건이 「0초째」로 되살아난다. 방금 찬 자리와 세 시간째
46
+ * 막힌 자리는 할 일이 다르므로, 그 값이 거짓이면 화면이 사람을 잘못된 순서로 움직인다.
47
+ */
48
+ attentionSince?: { id: string; since: string }[]
49
+ }
50
+
51
+ /** 심은 것의 내역 — 로그와 테스트가 같은 숫자를 본다. */
52
+ export interface WarmStartCounts {
53
+ itemCount: number
54
+ equipmentCount: number
55
+ personCount: number
56
+ assetCount: number
57
+ taskCount: number
58
+ /** 원값(requested)이 있어 커널이 실제로 심을 수 있는 오더. */
59
+ orderCount: number
60
+ /** 이어받은 확인 처리 수 — 잃으면 확인해 둔 신호가 다시 빨개진다. */
61
+ ackedCount: number
62
+ /** 이어받은 시작 시각 수 — 잃으면 지속된 조건이 모두 「방금」이 된다. */
63
+ attentionSinceCount: number
64
+ /**
65
+ * 원값이 없어 심지 못하는 오더 — **0 이 아니면 반드시 말한다.**
66
+ * progress 만 있는 오더는 남은 수량을 알 수 없어 재계획의 근거가 되지 못한다. 지어내지 않고 뺀다.
67
+ */
68
+ ordersWithoutDemand: number
16
69
  }
17
70
 
18
71
  export type WarmStartPlan =
19
- | { action: 'hydrate'; seed: ObservedSeed; itemCount: number; equipmentCount: number }
20
- /** 벤치 트윈 — 새 시작에서 용량을 재는 게 목적이라 현재 상태를 심으면 측정이 오염된다. */
72
+ | ({ action: 'hydrate'; seed: ObservedSeed } & WarmStartCounts)
73
+ /** 벤치 트윈 — 새 시작에서 용량을 재는 게 목적이라 현재 상태를 주입하면 측정이 오염된다. */
21
74
  | { action: 'skip'; reason: 'bench' }
22
75
  /** 심을 상태가 없다 — 처음 만든 트윈이거나 저널·체크포인트가 비었다. 정상이다. */
23
76
  | { action: 'skip'; reason: 'no-state' }
24
77
  /** 커널이 관측 주입을 지원하지 않는다 — 구조만으로 시작하므로 보유량은 0 으로 읽힌다(알려야 한다). */
25
78
  | { action: 'skip'; reason: 'unsupported' }
26
79
 
80
+ /** 스냅샷이 들고 올 수 있는 관측 축 — 어느 것이든 있으면 주입할 가치가 있다. */
81
+ interface ObservedState {
82
+ locations?: unknown[]
83
+ items?: unknown[]
84
+ equipment?: unknown[]
85
+ persons?: unknown[]
86
+ assets?: unknown[]
87
+ tasks?: unknown[]
88
+ orders?: unknown[]
89
+ acked?: string[]
90
+ attentionSince?: { id: string; since: string }[]
91
+ }
92
+
93
+ /** 관측 상태가 실려 있는지 — 축 하나라도 있으면 상태다(봉투에는 없다). */
94
+ const looksLikeState = (x: any): boolean =>
95
+ !!x && typeof x === 'object' && ['locations', 'items', 'equipment', 'orders', 'tasks', 'persons', 'assets', 'acked'].some(k => k in x)
96
+
97
+ /**
98
+ * 봉투를 벗긴다 — **체크포인트가 겹포장돼 복원이 조용히 죽던 자리.**
99
+ *
100
+ * `TwinEngine.snapshot()` 은 라이브에서는 상태를, 시뮬에서는 `runtime.resync()` 봉투(`{revision,state}`)를
101
+ * 돌려준다. 그 봉투를 다시 `{revision, state}` 로 감싸 캐시에 넣었으므로, 꺼낸 `state` 는 축이 하나도
102
+ * 없는 껍데기였다 → `planWarmStart` 가 "심을 것이 없다" 로 판정하고 **조용히** 넘어갔다. 저널에 수천 건이
103
+ * 있고 캐시도 최신인데 트윈이 빈 채로 떴다(오류도 로그도 없다).
104
+ *
105
+ * 반환 모양을 통일하는 것이 뿌리 수정이지만, 그 값은 State 구독 페이로드로도 나가므로 클라이언트 계약을
106
+ * 건드린다. 그래서 **복원 경로에서만** 벗긴다. 겹겹이 싸여 있어도 벗긴다(과거에 저장된 값이 남아 있다).
107
+ */
108
+ export function unwrapState(x: any, depth = 4): any {
109
+ if (!x || typeof x !== 'object') return x
110
+ if (looksLikeState(x)) return x
111
+ if (depth > 0 && x.state && typeof x.state === 'object') return unwrapState(x.state, depth - 1)
112
+ return x
113
+ }
114
+
115
+ const arr = (v: unknown[] | undefined): unknown[] => (Array.isArray(v) ? v : [])
116
+
117
+ /** 원값이 있는 오더인가 — 커널 `hydrateObserved` 의 판정(`requested !== undefined`)과 같은 기준. */
118
+ const hasDemand = (o: unknown): boolean =>
119
+ !!o && typeof o === 'object' && (o as { requested?: unknown }).requested !== undefined
120
+
27
121
  /**
28
122
  * 무엇을 할지 정한다.
29
123
  *
30
- * **오더는 의도적으로 심지 않는다.** 커널의 관측 주입은 오더에 requested/fulfilled/lines 요구하는데
31
- * 스냅샷의 오더에는 `progress` 밖에 없다. progress 에서 역산하면 없는 숫자를 지어내는 것이므로
32
- * 넘기지 않는다 — 재고·노드·무버만 복원되고 진행 중 오더는 비어서 시작하는 편이 정직하다.
124
+ * 오더·작업·사람·자산도 함께 주입한다. 골라내는 규칙은 커널이 들고 있고(이행 완료 오더, 고아 작업),
125
+ * 여기서는 **심을 없는 오더의 수만 센다** 호출부가 사실을 말할 수 있어야 하기 때문이다.
33
126
  */
34
- export function planWarmStart(
35
- state: { locations?: unknown[]; items?: unknown[]; equipment?: unknown[] } | null | undefined,
36
- purpose: string | undefined,
37
- canHydrate: boolean
38
- ): WarmStartPlan {
39
- /* 벤치 판정이 먼저다 — 상태가 있든 없든 벤치에는 심지 않는다는 사실이 바뀌지 않는다. */
127
+ export function planWarmStart(state: ObservedState | null | undefined, purpose: string | undefined, canHydrate: boolean): WarmStartPlan {
128
+ /* 벤치 판정이 먼저다 상태가 있든 없든 벤치에는 주입하지 않는다는 사실이 바뀌지 않는다. */
40
129
  if (purpose === 'bench') return { action: 'skip', reason: 'bench' }
41
130
  if (!state) return { action: 'skip', reason: 'no-state' }
131
+ /* 봉투가 들어오면 스스로 벗는다 — 호출부가 한 곳이라도 잊으면 그 경로가 조용히 죽는다(실제로 그랬다). */
132
+ state = unwrapState(state)
42
133
 
43
- const locations = state.locations ?? []
44
- const items = state.items ?? []
45
- const equipment = state.equipment ?? []
46
- /* 비었으면 심을 것이 없다 — 빈 주입으로 로그만 남기지 않는다. */
47
- if (locations.length === 0 && items.length === 0 && equipment.length === 0) return { action: 'skip', reason: 'no-state' }
134
+ const locations = arr(state.locations)
135
+ const items = arr(state.items)
136
+ const equipment = arr(state.equipment)
137
+ const persons = arr(state.persons)
138
+ const assets = arr(state.assets)
139
+ const tasks = arr(state.tasks)
140
+ const orders = arr(state.orders)
141
+ /* 확인 처리는 문자열 id 목록이다 — 배열이 아니면 없는 것으로 본다(지어내지 않는다). */
142
+ const acked = Array.isArray(state.acked) ? state.acked.filter((x): x is string => typeof x === 'string') : []
143
+ /* 시작 시각은 {id, since} 쌍 목록이다 — 모양이 아니면 없는 것으로 본다(지어낸 시각을 심으면 화면이
144
+ 「세 시간째」라고 거짓을 말한다. 모르면 그 신호는 이어붙는 순간이 새 시작이다). */
145
+ const attentionSince = Array.isArray(state.attentionSince)
146
+ ? state.attentionSince.filter((x: any): x is { id: string; since: string } => !!x && typeof x.id === 'string' && typeof x.since === 'string')
147
+ : []
48
148
 
49
- /* 지원 여부는 마지막에 본다 — 심을 있는데 심는 상황이라야 경고할 값어치가 있다. */
149
+ /* 어느 축도 없으면 심을 것이 없다 주입으로 로그만 남기지 않는다. */
150
+ if (!locations.length && !items.length && !equipment.length && !persons.length && !assets.length && !tasks.length && !orders.length && !acked.length && !attentionSince.length) {
151
+ return { action: 'skip', reason: 'no-state' }
152
+ }
153
+
154
+ /* 지원 여부는 마지막에 본다 — 주입할 것이 있는데 주입하지 못하는 상황이라야 경고할 가치가 있다. */
50
155
  if (!canHydrate) return { action: 'skip', reason: 'unsupported' }
51
156
 
52
- return { action: 'hydrate', seed: { locations, items, equipment }, itemCount: items.length, equipmentCount: equipment.length }
157
+ const seedable = orders.filter(hasDemand)
158
+
159
+ return {
160
+ action: 'hydrate',
161
+ seed: {
162
+ locations,
163
+ items,
164
+ equipment,
165
+ ...(persons.length ? { persons } : {}),
166
+ ...(assets.length ? { assets } : {}),
167
+ ...(tasks.length ? { tasks } : {}),
168
+ /* 원값 없는 오더는 넘기지 않는다 — 커널도 걸러 내지만, 넘기지 않으면 세어 둔 수와 어긋날 일이 없다. */
169
+ ...(seedable.length ? { orders: seedable } : {}),
170
+ /* 확인 처리는 조건이 사라진 id 가 섞여 있어도 해롭지 않다 — 그 신호가 없으면 표시할 대상이 없다. */
171
+ ...(acked.length ? { acked } : {}),
172
+ /* 조건이 사라진 id 가 섞여 있어도 해롭지 않다 — 커널이 그 신호가 없으면 함께 지운다. */
173
+ ...(attentionSince.length ? { attentionSince } : {})
174
+ },
175
+ itemCount: items.length,
176
+ equipmentCount: equipment.length,
177
+ personCount: persons.length,
178
+ assetCount: assets.length,
179
+ taskCount: tasks.length,
180
+ orderCount: seedable.length,
181
+ ordersWithoutDemand: orders.length - seedable.length,
182
+ ackedCount: acked.length,
183
+ attentionSinceCount: attentionSince.length
184
+ }
185
+ }
186
+
187
+ /*
188
+ * ── 미러(관측 구동) 재기동의 연속성 ─────────────────────────────────────────
189
+ *
190
+ * 미러의 진실은 원천이다. 그래서 재고·위치·설비 상태는 심지 않는다 — 다음 계측·다음 마스터 재읽기가
191
+ * 곧 정정하므로, 심으면 **떠난 물건이 되살아나는** 위험만 남는다(그래서 `startLive` 는 오랫동안 아무것도
192
+ * 심지 않았다).
193
+ *
194
+ * 그런데 원천이 **애초에 다시 말해 주지 않는 축**이 있다. 우리가 접어 만든 것들이다:
195
+ * · 확인(ack)·조건이 처음 성립한 시각 — 사람이 한 일과 우리가 기억한 시각
196
+ * · 에너지의 열린 구간 누적·적산 기준점·관측 이후 최대 — SCADA 는 「이번 15분에 지금까지 얼마」를 모른다
197
+ *
198
+ * 잃으면 **오류 없이 값이 작아진다**(실측 2026-08-18: 재기동 뒤 그 구간의 전력량·피크가 작아지고, 세
199
+ * 시간째 지속된 경보가 「0초째」가 됐다). 그래서 축을 갈라 되풀어 주지 않는 것만 이어받는다.
200
+ *
201
+ * 무엇을 이어받을지는 여기서 고르고, **어떻게 흡수할지는 커널이 정한다**(`hydrateContinuity`) — 도메인
202
+ * 규칙이 호스트로 새면 트윈마다 다른 답이 된다.
203
+ */
204
+ export interface ContinuitySeed {
205
+ acked?: string[]
206
+ attentionSince?: { id: string; since: string }[]
207
+ energy?: unknown
208
+ }
209
+
210
+ export interface ContinuityPlan {
211
+ seed: ContinuitySeed
212
+ /** 무엇을 이어받았나 — 로그와 시험이 같은 숫자를 본다(0 이면 이어받을 것이 없었다는 사실이다). */
213
+ ackedCount: number
214
+ attentionSinceCount: number
215
+ /** 에너지 연속성을 이어받았나 — 열린 구간이 있으면 그 구간의 요금 근거가 살아난다. */
216
+ energy: boolean
217
+ }
218
+
219
+ /** 미러가 이어받을 축만 골라낸다 — 관측 축(재고·위치·설비)은 일부러 넣지 않는다. */
220
+ export function planLiveContinuity(raw: unknown): ContinuityPlan {
221
+ const state = raw ? (unwrapState(raw as any) as any) : undefined
222
+ const acked = Array.isArray(state?.acked) ? state.acked.filter((x: any): x is string => typeof x === 'string') : []
223
+ const attentionSince = Array.isArray(state?.attentionSince)
224
+ ? state.attentionSince.filter((x: any): x is { id: string; since: string } => !!x && typeof x.id === 'string' && typeof x.since === 'string')
225
+ : []
226
+ /* 에너지는 커널이 자기 모양을 안다 — 호스트는 「있나」만 본다(골라 담으면 규칙이 두 곳으로 갈린다). */
227
+ const energy = state?.energy && typeof state.energy === 'object' ? state.energy : undefined
228
+ return {
229
+ seed: {
230
+ ...(acked.length ? { acked } : {}),
231
+ ...(attentionSince.length ? { attentionSince } : {}),
232
+ ...(energy ? { energy } : {})
233
+ },
234
+ ackedCount: acked.length,
235
+ attentionSinceCount: attentionSince.length,
236
+ energy: !!energy
237
+ }
53
238
  }
package/server/index.ts CHANGED
@@ -1,25 +1,28 @@
1
1
  export * from './engine/index.js'
2
2
  export * from './service/index.js'
3
+ /* 스키마 이력 — 프레임워크가 `migrations` export 를 모아 실행한다(`run migration`). */
4
+ export { migrations } from './migrations/index.js'
3
5
 
4
6
  import './routes.js'
5
7
 
6
8
  import { TwinEngine } from './engine/index.js'
7
- import { backfillTwinEventKeys } from './service/twin-event/backfill-keys.js'
9
+ import { resumeReferenceLiveFeeds } from './service/reference/reference-live.js'
8
10
 
9
- /* 모듈 부팅 — 영속 인스턴스 복구 훅(향후). 지금은 명시 start(mutation/부팅 설정)로 인스턴스 생성. */
11
+ /*
12
+ * 모듈 부팅 — **도는 트윈을 되살린다.**
13
+ *
14
+ * 두 층이 순서대로다: ① 엔진이 등록부의 `running` 행을 선언된 `realityMode` 그대로 되살리고
15
+ * (`bootstrap`), ② 레퍼런스 계층이 미러 트윈의 커넥터 피드를 다시 붙인다. 순서가 뒤집히면 피드가
16
+ * 아직 없는 커널에 계측을 붓게 된다.
17
+ *
18
+ * 예전에는 ①도 ②도 없었다 — 재기동 한 번에 등록부만 「도는 중」이라 말하는 트윈이 남았다.
19
+ */
10
20
  process.on('bootstrap-module-start' as any, async ({ app, config, client }: any) => {
11
21
  try {
12
22
  await TwinEngine.bootstrap()
23
+ await resumeReferenceLiveFeeds()
13
24
  console.log('Headless Twin host has started.')
14
25
  } catch (ex) {
15
26
  console.error('Headless Twin host failed to start.', ex)
16
27
  }
17
-
18
- /*
19
- * 승격 검색 키 백필 — 컬럼이 생기기 전에 쌓인 저널을 채운다. 멱등·재개 가능이라 매 기동 불러도
20
- * 채울 게 없으면 조각 한 번 읽고 끝난다(로그도 남기지 않는다).
21
- * 기동을 막지 않는다 — 저널이 크면 오래 걸릴 수 있고, 그동안 트윈은 정상 동작해야 한다.
22
- * 실패해도 서비스는 계속된다: 못 채운 만큼 **과거 이력 검색이 덜 나올 뿐**이므로 조용히 삼키지 않고 알린다.
23
- */
24
- backfillTwinEventKeys().catch(ex => console.error('twin-event key backfill failed — search over older journal rows will be incomplete.', ex))
25
28
  })
@@ -0,0 +1,43 @@
1
+ import { MigrationInterface, QueryRunner } from 'typeorm'
2
+
3
+ /**
4
+ * `twin_instances.model` → `twin_instances.model`.
5
+ *
6
+ * ── 왜 개명하나 ────────────────────────────────────────────────────────────────
7
+ * 이 컬럼이 담는 것은 커널이 `TwinModelDef` 라 부르는 **트윈 모델**이다. `board` 는 저장 위치의 옛
8
+ * 이름이고, 이 앱에는 **진짜 board 가 따로 있다**(things-scene 보드 — 공간 표현의 `boardId`). 같은
9
+ * 낱말이 두 가지를 뜻하니 읽는 사람이 매번 어느 쪽인지 판단해야 했고, 코드와 주석이 "board(마스터
10
+ * 청사진)" 처럼 번역을 달아 설명해 왔다. 커널이 이미 옳은 이름을 갖고 있으므로 발명할 이름도 없다.
11
+ *
12
+ * ── 데이터를 잃지 않는다 ──────────────────────────────────────────────────────
13
+ * `renameColumn` 은 값을 그대로 둔 채 이름만 바꾼다. 지우고 다시 만들면(엔티티 변경 + synchronize)
14
+ * 기존 트윈의 모델이 통째로 사라지고, 원천이 없는 트윈은 되살릴 방법이 없다.
15
+ *
16
+ * `renameColumn` API 만 쓰므로 지원 대상 5개 드라이버가 각자의 DDL 로 처리한다(raw SQL 금지 규약).
17
+ *
18
+ * ── 멱등 ──────────────────────────────────────────────────────────────────────
19
+ * 이미 `model` 이 있으면(엔티티 선언 + synchronize 가 먼저 지나간 경우) 아무것도 하지 않는다.
20
+ * 그 경우 옛 컬럼이 남아 있을 수 있는데 **지우지 않는다** — 값이 든 컬럼을 마이그레이션이 조용히
21
+ * 버리면 안 된다. 남은 컬럼의 처분은 사람이 확인한 뒤의 일이다.
22
+ */
23
+ const TABLE_NAME = 'twin_instances'
24
+ const OLD = 'board'
25
+ const NEW = 'model'
26
+
27
+ export class RenameTwinInstanceBoardToModel1786000000000 implements MigrationInterface {
28
+ public async up(queryRunner: QueryRunner): Promise<void> {
29
+ const table = await queryRunner.getTable(TABLE_NAME)
30
+ if (!table) return
31
+ const has = (name: string) => table.columns.some(c => c.name === name)
32
+ if (has(NEW) || !has(OLD)) return
33
+ await queryRunner.renameColumn(table, OLD, NEW)
34
+ }
35
+
36
+ public async down(queryRunner: QueryRunner): Promise<void> {
37
+ const table = await queryRunner.getTable(TABLE_NAME)
38
+ if (!table) return
39
+ const has = (name: string) => table.columns.some(c => c.name === name)
40
+ if (has(OLD) || !has(NEW)) return
41
+ await queryRunner.renameColumn(table, NEW, OLD)
42
+ }
43
+ }
@@ -0,0 +1,7 @@
1
+ /*
2
+ * 마이그레이션 묶음 — 프레임워크 규약(`@things-factory/shell` · `auth-base` 와 같은 방식).
3
+ * 파일마다 하나씩 export 하고 여기서 모아 `server/index.ts` 가 내보낸다.
4
+ */
5
+ import { RenameTwinInstanceBoardToModel1786000000000 } from './1786000000000-RenameTwinInstanceBoardToModel.js'
6
+
7
+ export const migrations = [RenameTwinInstanceBoardToModel1786000000000]
@@ -1,5 +1,6 @@
1
1
  /* EXPORT ENTITY TYPES */
2
2
  export * from './twin-event/twin-event.js'
3
+ export * from './twin-audit/twin-audit-event.js'
3
4
  export * from './twin-instance/twin-instance.js'
4
5
  export * from './twin-space/twin-space.js'
5
6
  export * from './twin-space/twin-space-representation.js'
@@ -18,6 +19,7 @@ export * from './twin-lifecycle/domain-catalog.js'
18
19
 
19
20
  /* IMPORT ENTITIES AND RESOLVERS */
20
21
  import { entities as TwinEventEntities } from './twin-event/index.js'
22
+ import { entities as TwinAuditEntities, resolvers as TwinAuditResolvers } from './twin-audit/index.js'
21
23
  import { entities as TwinInstanceEntities } from './twin-instance/index.js'
22
24
  import { entities as TwinStructureEntities } from './twin-structure/index.js'
23
25
  import { entities as TwinSpaceEntities, resolvers as TwinSpaceResolvers } from './twin-space/index.js'
@@ -25,6 +27,7 @@ import { entities as TwinTargetEntities, resolvers as TwinTargetResolvers } from
25
27
  import { entities as TwinReferenceEntities, resolvers as TwinReferenceResolvers } from './reference/index.js'
26
28
  import { resolvers as TwinStateResolvers } from './twin-state/index.js'
27
29
  import { resolvers as TwinControlResolvers } from './twin-control/index.js'
30
+ import { entities as TwinModelEntities, resolvers as TwinModelResolvers } from './twin-model/index.js'
28
31
  import { resolvers as TwinLifecycleResolvers } from './twin-lifecycle/index.js'
29
32
  import { resolvers as TwinJournalResolvers } from './twin-journal/index.js'
30
33
  import { resolvers as TwinForecastResolvers } from './twin-forecast/index.js'
@@ -34,11 +37,14 @@ import { resolvers as TwinMetricsResolvers } from './twin-metrics/index.js'
34
37
  export const entities = [
35
38
  /* ENTITIES */
36
39
  ...TwinEventEntities,
40
+ ...TwinAuditEntities,
37
41
  ...TwinInstanceEntities,
38
42
  ...TwinStructureEntities,
39
43
  ...TwinSpaceEntities,
40
44
  ...TwinTargetEntities,
41
- ...TwinReferenceEntities
45
+ ...TwinReferenceEntities,
46
+ /* 구조 투영 — model JSON 에서 풀어낸 표준 엔티티(ADR-0032). 원본에서 온 캐시. */
47
+ ...TwinModelEntities
42
48
  ]
43
49
 
44
50
  export const subscribers = []
@@ -51,9 +57,11 @@ export const schema = {
51
57
  ...TwinControlResolvers,
52
58
  ...TwinLifecycleResolvers,
53
59
  ...TwinJournalResolvers,
60
+ ...TwinAuditResolvers,
54
61
  ...TwinForecastResolvers,
55
62
  ...TwinAttentionResolvers,
56
63
  ...TwinMetricsResolvers,
64
+ ...TwinModelResolvers,
57
65
  ...TwinSpaceResolvers,
58
66
  ...TwinTargetResolvers,
59
67
  ...TwinReferenceResolvers
@@ -0,0 +1,95 @@
1
+ /*
2
+ * 사이트 발견 결과 — **"못 찾았다" 와 "없다" 를 구분한다.** 순수.
3
+ *
4
+ * ── 왜 있나 (2026-08-06) ────────────────────────────────────────────────────
5
+ * 실 시스템을 연결해 "거기 있는 것" 으로 트윈을 만드는 경로에서, 발견이 실패하면 화면이
6
+ * **"사이트를 찾지 못했습니다"** 라고만 말했다. 네 가지가 그 한 문장으로 접혀 있었다.
7
+ *
8
+ * ① 그 레퍼런스가 등록돼 있지 않다 (`!ref → []`)
9
+ * ② 어댑터 종류를 모른다 (`!adapter → []`)
10
+ * ③ 연결·조회가 실패했다 (`catch { return [] }`)
11
+ * ④ 정말로 사이트가 없다 (빈 배열)
12
+ *
13
+ * ①②③ 은 **고칠 수 있는 문제**이고 ④ 는 사실이다. 그런데 화면이 같은 말을 하므로 사용자는 자격이
14
+ * 틀렸는지, 주소가 틀렸는지, 정말 공장이 없는지 알 수 없다 — 실 시스템 연결에서 가장 자주 막히는
15
+ * 자리인데 거기서 아무 단서도 주지 않았다.
16
+ *
17
+ * ── 왜 문장이 아니라 코드인가 ───────────────────────────────────────────────
18
+ * 이 프로젝트가 이미 쓰는 규약을 따른다(주의 신호·커맨드 거절·인제스트 경고와 같은 모양):
19
+ * `code`(언어 중립) + `params`(문장에 끼울 값) + `message`(영어 canonical, 로그·폴백).
20
+ * 화면은 `twin.connect.discover.<code>` 로 사람 말을 만든다.
21
+ *
22
+ * ── 원 시스템의 오류 문장을 감추지 않는다 ────────────────────────────────────
23
+ * 어댑터가 준 사유(자격 거부·타임아웃·404)는 **그 시스템만 아는 사실**이라 우리가 다시 쓸 수 없다.
24
+ * `detail` 로 그대로 실어 보내 화면이 보조 문장으로 보인다 — 원문이 있으면 사용자가 자기 관리자에게
25
+ * 물어볼 수 있고, 없으면 우리 짐작만 남는다.
26
+ */
27
+
28
+ /** 발견이 왜 아무것도 주지 못했나 — 언어 중립 코드. */
29
+ export type DiscoveryErrorCode = 'reference-not-found' | 'adapter-unknown' | 'discovery-failed' | 'discovery-timeout'
30
+
31
+ export interface DiscoveryError {
32
+ code: DiscoveryErrorCode
33
+ params?: Record<string, string | number>
34
+ /** 영어 canonical — 로그에 남고, 번역이 없으면 그대로 보인다. */
35
+ message: string
36
+ }
37
+
38
+ export interface DiscoveryResult<S = unknown> {
39
+ /** 발견된 사이트들. 실패했으면 빈 배열이고 `error` 가 이유를 말한다. */
40
+ sites: S[]
41
+ /** 실패 사유. **없으면 성공이다** — 빈 배열 + 사유 없음 = "연결은 됐지만 사이트가 없다". */
42
+ error?: DiscoveryError
43
+ }
44
+
45
+ /** 등록된 레퍼런스가 없다 — 목록이 낡았거나 다른 테넌트의 것이다. */
46
+ export const referenceNotFound = (source: string): DiscoveryError => ({
47
+ code: 'reference-not-found',
48
+ params: { source },
49
+ message: `reference "${source}" is not registered in this tenant`
50
+ })
51
+
52
+ /** 어댑터 종류를 모른다 — 배포에 그 커넥터가 없다(패키지 미설치·오타). */
53
+ export const adapterUnknown = (adapterType: string, source: string): DiscoveryError => ({
54
+ code: 'adapter-unknown',
55
+ params: { adapterType, source },
56
+ message: `no connector of type "${adapterType}" is available for reference "${source}"`
57
+ })
58
+
59
+ /** 연결·조회가 실패했다 — 원 시스템이 준 사유를 그대로 싣는다. */
60
+ export const discoveryFailed = (source: string, detail: string): DiscoveryError => ({
61
+ code: 'discovery-failed',
62
+ params: { source, detail },
63
+ message: `could not read sites from "${source}": ${detail}`
64
+ })
65
+
66
+ /** 기다려도 답이 없었다 — 무한히 도는 화면보다 사유가 낫다. */
67
+ export const discoveryTimedOut = (source: string, seconds: number): DiscoveryError => ({
68
+ code: 'discovery-timeout',
69
+ params: { source, seconds },
70
+ message: `"${source}" did not answer within ${seconds}s`
71
+ })
72
+
73
+ /**
74
+ * 어댑터가 준 것을 결과로 접는다.
75
+ *
76
+ * **사이트가 없는 것을 실패로 만들지 않는다** — 연결이 되고 조회가 성공했는데 공장이 하나도 없는
77
+ * 시스템은 있을 수 있다(권한이 좁거나 아직 등록되지 않은 경우). 그것은 사실이고, 화면은 "없다" 고
78
+ * 말해야 한다. 실패로 합치면 사용자가 있지도 않은 문제를 찾는다.
79
+ */
80
+ export function discovered<S>(sites: readonly S[] | undefined | null): DiscoveryResult<S> {
81
+ return { sites: Array.isArray(sites) ? [...sites] : [] }
82
+ }
83
+
84
+ /** 실패를 결과로 접는다 — 사이트는 비어 있다(반쪽 목록을 성공처럼 보이게 하지 않는다). */
85
+ export function failed<S>(error: DiscoveryError): DiscoveryResult<S> {
86
+ return { sites: [], error }
87
+ }
88
+
89
+ /** 어댑터가 던진 것에서 사람이 읽을 사유를 뽑는다 — 형태를 가정하지 않는다. */
90
+ export function detailOf(e: unknown): string {
91
+ if (e instanceof Error && e.message) return e.message
92
+ if (typeof e === 'string' && e) return e
93
+ /* 무엇이 왔는지 모르면 그렇게 말한다 — 빈 문장으로 "이유 없음" 처럼 보이게 하지 않는다. */
94
+ return 'the connector failed without a message'
95
+ }
@@ -0,0 +1,104 @@
1
+ /*
2
+ * 인제스트가 어느 공간으로 들어갈지 정한다 — **순수**.
3
+ *
4
+ * ── 왜 있나 (2026-08-05) ────────────────────────────────────────────────────
5
+ * 한 현실(공장·물류센터)을 여러 렌즈가 비춘다: 창고(WMS)·공정(MES)·야드(YMS) 가 같은 부지에 있다.
6
+ * 엔진은 이미 그것을 지원한다 — 같은 `spaceId` 로 들어오면 공간 content 를 병합하고 기존 값을 이긴다.
7
+ *
8
+ * 그런데 **만드는 흐름에 그 선택이 없었다.** 템플릿은 `space.spaceId` 를 정하지 않으므로 항상
9
+ * `<source>-site` 로 파생돼, 새 트윈은 늘 새 현장처럼 생겼다. 데이터로는 가능한 일이 저작으로는
10
+ * 불가능했다.
11
+ *
12
+ * ── 없는 공간에 조용히 새 공간을 만들지 않는다 ────────────────────────────────
13
+ * "이 현장에 더한다" 는 요청이 오타·낡은 목록으로 빗나갔을 때 새 공간을 만들어 주면, 사용자는
14
+ * 합쳤다고 믿고 화면에서는 따로 놀게 된다 — 나중에 두 현장을 손으로 합쳐야 하고, 그때는 이미
15
+ * 이벤트가 양쪽에 쌓여 있다. 그래서 **모르는 공간이면 거절한다.**
16
+ *
17
+ * ── 같은 id 를 쓰는 구역은 조용히 합쳐지므로 알린다 ──────────────────────────
18
+ * 공간 content 의 구역·랜드마크는 id 로 합집합을 만든다(`unionById`). 두 렌즈가 우연히 같은 구역
19
+ * id(`a-yard` 따위)를 쓰면 서로 다른 구역이 하나로 접힌다. 막을 일은 아니다(같은 현장의 같은 구역을
20
+ * 가리키는 정당한 경우가 많다) — 다만 **모르고 지나가지 않게** 경고로 돌려준다.
21
+ */
22
+
23
+ /** 공간 판정 결과. */
24
+ export interface IngestSpaceChoice {
25
+ /** 실제로 쓸 공간 id. */
26
+ spaceId: string
27
+ /** 사용자가 기존 현장에 합치기를 골랐나(마스터가 파생한 공간과 다른 곳으로 들어가나). */
28
+ joined: boolean
29
+ }
30
+
31
+ /** 거절 사유 — 호출측이 문장을 만들 때 쓴다(언어 중립 코드). */
32
+ export const INGEST_SPACE_NOT_FOUND = 'space-not-found'
33
+
34
+ /**
35
+ * 요청받은 공간과 마스터가 파생한 공간 중 무엇으로 들어갈지 정한다.
36
+ *
37
+ * @param masterSpaceId 마스터에서 파생된 공간 id(요청이 없을 때의 기본).
38
+ * @param requested 사용자가 고른 기존 공간 id. 없으면 빈 값/undefined.
39
+ * @param requestedExists 그 공간이 이 테넌트에 실제로 있나(호출측이 조회해 넘긴다).
40
+ * @throws 요청한 공간이 없으면 — 조용히 새로 만들지 않는다.
41
+ */
42
+ export function resolveIngestSpace(masterSpaceId: string, requested: string | undefined | null, requestedExists: boolean): IngestSpaceChoice {
43
+ const want = (requested ?? '').trim()
44
+ if (!want) return { spaceId: masterSpaceId, joined: false }
45
+ if (!requestedExists) {
46
+ const e = new Error(`space "${want}" not found — cannot co-locate into a space that does not exist`)
47
+ ;(e as any).code = INGEST_SPACE_NOT_FOUND
48
+ throw e
49
+ }
50
+ /* 마스터가 이미 그 공간을 가리키면 합치기가 아니다 — 같은 공간의 재인제스트다. */
51
+ return { spaceId: want, joined: want !== masterSpaceId }
52
+ }
53
+
54
+ /**
55
+ * 실 시스템 import 가 현장을 고를 때의 거절 판정 — **순수**.
56
+ *
57
+ * 레퍼런스 하나는 사이트를 여럿 발견할 수 있고(`discoverSites`), 그 사이트마다 트윈이 하나씩 생긴다.
58
+ * 그런데 "이 현장에 더한다" 는 **한 곳**을 가리키는 말이다. 사이트 여럿을 한 공간에 넣으면 서로 다른
59
+ * 현장의 구역·랜드마크가 id 로 합쳐지고(`unionById`), 그 사실을 알아차릴 때는 이미 이벤트가 양쪽에
60
+ * 쌓여 있다 — 손으로 되돌릴 수 없는 종류다. 그래서 **묻지 않고 합치지 않고, 거절한다.**
61
+ *
62
+ * 공간을 고르지 않으면 지금까지의 거동 그대로다: 사이트마다 마스터가 파생한 공간으로 들어간다.
63
+ */
64
+ export type ImportSpaceRefusalCode = 'space-with-multiple-sites'
65
+
66
+ export interface ImportSpaceRefusal {
67
+ code: ImportSpaceRefusalCode
68
+ params?: Record<string, string | number>
69
+ /** 영어 canonical — 로그에 남고, 번역이 없으면 그대로 보인다(발견 오류와 같은 규약). */
70
+ message: string
71
+ }
72
+
73
+ /**
74
+ * @param siteCount 실제로 만들 사이트 수(필터 적용 후).
75
+ * @param requested 사용자가 고른 기존 공간 id. 없으면 판정할 것이 없다.
76
+ * @returns 거절 사유. 없으면 진행해도 된다.
77
+ */
78
+ export function refuseImportSpace(siteCount: number, requested?: string | null): ImportSpaceRefusal | undefined {
79
+ const want = (requested ?? '').trim()
80
+ if (!want || siteCount <= 1) return undefined
81
+ return {
82
+ code: 'space-with-multiple-sites',
83
+ params: { spaceId: want, count: siteCount },
84
+ message: `cannot co-locate ${siteCount} sites into space "${want}" — pick one site, or omit the space so each site gets its own`
85
+ }
86
+ }
87
+
88
+ /** 구역·랜드마크처럼 id 로 합쳐지는 것들. */
89
+ interface Identified {
90
+ id?: string
91
+ }
92
+
93
+ /**
94
+ * 합치는 쪽과 기존 쪽이 같은 id 를 쓰는 것들을 찾아 경고 문장을 만든다.
95
+ *
96
+ * 정당한 경우(같은 현장의 같은 구역)와 사고(서로 다른 구역이 우연히 같은 이름)를 기계가 가릴 수는
97
+ * 없다. 구분할 수 없는 것을 고르지 않고, **무엇이 합쳐졌는지 말한다.**
98
+ */
99
+ export function collidingIds(existing: readonly Identified[] | undefined | null, incoming: readonly Identified[] | undefined | null): string[] {
100
+ const have = new Set((existing ?? []).map(x => x?.id).filter(Boolean) as string[])
101
+ const hits = new Set<string>()
102
+ for (const x of incoming ?? []) if (x?.id && have.has(x.id)) hits.add(x.id)
103
+ return [...hits].sort()
104
+ }