@things-factory/headless-twin 10.0.8 → 10.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/dist-server/engine/attention-digest.d.ts +52 -0
  2. package/dist-server/engine/attention-digest.js +76 -0
  3. package/dist-server/engine/attention-digest.js.map +1 -0
  4. package/dist-server/engine/canonical-ingest.d.ts +2 -2
  5. package/dist-server/engine/canonical-ingest.js +24 -2
  6. package/dist-server/engine/canonical-ingest.js.map +1 -1
  7. package/dist-server/engine/command-routing.d.ts +33 -0
  8. package/dist-server/engine/command-routing.js +53 -0
  9. package/dist-server/engine/command-routing.js.map +1 -0
  10. package/dist-server/engine/energy-topology.d.ts +86 -0
  11. package/dist-server/engine/energy-topology.js +144 -0
  12. package/dist-server/engine/energy-topology.js.map +1 -0
  13. package/dist-server/engine/index.d.ts +11 -0
  14. package/dist-server/engine/index.js +17 -0
  15. package/dist-server/engine/index.js.map +1 -1
  16. package/dist-server/engine/kpi-baseline.d.ts +78 -0
  17. package/dist-server/engine/kpi-baseline.js +123 -0
  18. package/dist-server/engine/kpi-baseline.js.map +1 -0
  19. package/dist-server/engine/kpi-fold.d.ts +137 -1
  20. package/dist-server/engine/kpi-fold.js +225 -0
  21. package/dist-server/engine/kpi-fold.js.map +1 -1
  22. package/dist-server/engine/kpi-query.d.ts +32 -1
  23. package/dist-server/engine/kpi-query.js +262 -20
  24. package/dist-server/engine/kpi-query.js.map +1 -1
  25. package/dist-server/engine/kpi-target.d.ts +17 -0
  26. package/dist-server/engine/kpi-target.js +24 -2
  27. package/dist-server/engine/kpi-target.js.map +1 -1
  28. package/dist-server/engine/live-attentions.d.ts +1 -0
  29. package/dist-server/engine/live-attentions.js +7 -1
  30. package/dist-server/engine/live-attentions.js.map +1 -1
  31. package/dist-server/engine/live-feed-registry.d.ts +25 -0
  32. package/dist-server/engine/live-feed-registry.js +51 -0
  33. package/dist-server/engine/live-feed-registry.js.map +1 -0
  34. package/dist-server/engine/load-meter.d.ts +181 -0
  35. package/dist-server/engine/load-meter.js +267 -0
  36. package/dist-server/engine/load-meter.js.map +1 -0
  37. package/dist-server/engine/local-declarations.d.ts +262 -0
  38. package/dist-server/engine/local-declarations.js +528 -0
  39. package/dist-server/engine/local-declarations.js.map +1 -0
  40. package/dist-server/engine/model-basis.d.ts +39 -6
  41. package/dist-server/engine/model-basis.js +69 -9
  42. package/dist-server/engine/model-basis.js.map +1 -1
  43. package/dist-server/engine/model-vocabulary.d.ts +17 -0
  44. package/dist-server/engine/model-vocabulary.js +81 -0
  45. package/dist-server/engine/model-vocabulary.js.map +1 -0
  46. package/dist-server/engine/oee-accumulator.d.ts +28 -0
  47. package/dist-server/engine/oee-accumulator.js +26 -1
  48. package/dist-server/engine/oee-accumulator.js.map +1 -1
  49. package/dist-server/engine/operation-basis.d.ts +32 -0
  50. package/dist-server/engine/operation-basis.js +87 -0
  51. package/dist-server/engine/operation-basis.js.map +1 -0
  52. package/dist-server/engine/property-effects.d.ts +30 -0
  53. package/dist-server/engine/property-effects.js +192 -0
  54. package/dist-server/engine/property-effects.js.map +1 -0
  55. package/dist-server/engine/runtime-key.d.ts +15 -0
  56. package/dist-server/engine/runtime-key.js +64 -0
  57. package/dist-server/engine/runtime-key.js.map +1 -0
  58. package/dist-server/engine/spec-coverage.js +3 -3
  59. package/dist-server/engine/spec-coverage.js.map +1 -1
  60. package/dist-server/engine/state-axes.d.ts +21 -0
  61. package/dist-server/engine/state-axes.js +59 -0
  62. package/dist-server/engine/state-axes.js.map +1 -0
  63. package/dist-server/engine/structure-diff.js +1 -1
  64. package/dist-server/engine/structure-diff.js.map +1 -1
  65. package/dist-server/engine/travel-estimator.d.ts +9 -2
  66. package/dist-server/engine/travel-estimator.js +11 -6
  67. package/dist-server/engine/travel-estimator.js.map +1 -1
  68. package/dist-server/engine/twin-engine.d.ts +418 -57
  69. package/dist-server/engine/twin-engine.js +1599 -306
  70. package/dist-server/engine/twin-engine.js.map +1 -1
  71. package/dist-server/engine/warm-start.d.ts +93 -13
  72. package/dist-server/engine/warm-start.js +114 -14
  73. package/dist-server/engine/warm-start.js.map +1 -1
  74. package/dist-server/index.d.ts +1 -0
  75. package/dist-server/index.js +17 -11
  76. package/dist-server/index.js.map +1 -1
  77. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.d.ts +5 -0
  78. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +48 -0
  79. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -0
  80. package/dist-server/migrations/index.d.ts +2 -0
  81. package/dist-server/migrations/index.js +10 -0
  82. package/dist-server/migrations/index.js.map +1 -0
  83. package/dist-server/service/index.d.ts +3 -2
  84. package/dist-server/service/index.js +25 -17
  85. package/dist-server/service/index.js.map +1 -1
  86. package/dist-server/service/reference/discovery-result.d.ts +34 -0
  87. package/dist-server/service/reference/discovery-result.js +84 -0
  88. package/dist-server/service/reference/discovery-result.js.map +1 -0
  89. package/dist-server/service/reference/ingest-space.d.ts +53 -0
  90. package/dist-server/service/reference/ingest-space.js +79 -0
  91. package/dist-server/service/reference/ingest-space.js.map +1 -0
  92. package/dist-server/service/reference/knob-defaults.d.ts +20 -0
  93. package/dist-server/service/reference/knob-defaults.js +59 -0
  94. package/dist-server/service/reference/knob-defaults.js.map +1 -0
  95. package/dist-server/service/reference/reference-live.d.ts +23 -0
  96. package/dist-server/service/reference/reference-live.js +123 -6
  97. package/dist-server/service/reference/reference-live.js.map +1 -1
  98. package/dist-server/service/reference/reference-master.d.ts +227 -11
  99. package/dist-server/service/reference/reference-master.js +206 -25
  100. package/dist-server/service/reference/reference-master.js.map +1 -1
  101. package/dist-server/service/reference/reference-resolver.d.ts +33 -3
  102. package/dist-server/service/reference/reference-resolver.js +290 -23
  103. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  104. package/dist-server/service/reference/template-registry.d.ts +19 -3
  105. package/dist-server/service/reference/template-registry.js.map +1 -1
  106. package/dist-server/service/twin-attention/twin-attention-query.d.ts +8 -1
  107. package/dist-server/service/twin-attention/twin-attention-query.js +39 -8
  108. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  109. package/dist-server/service/twin-audit/command-audit.d.ts +37 -0
  110. package/dist-server/service/twin-audit/command-audit.js +53 -0
  111. package/dist-server/service/twin-audit/command-audit.js.map +1 -0
  112. package/dist-server/service/twin-audit/index.d.ts +4 -0
  113. package/dist-server/service/twin-audit/index.js +8 -0
  114. package/dist-server/service/twin-audit/index.js.map +1 -0
  115. package/dist-server/service/twin-audit/twin-audit-event.d.ts +24 -0
  116. package/dist-server/service/twin-audit/twin-audit-event.js +125 -0
  117. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -0
  118. package/dist-server/service/twin-audit/twin-audit-query.d.ts +4 -0
  119. package/dist-server/service/twin-audit/twin-audit-query.js +76 -0
  120. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -0
  121. package/dist-server/service/twin-control/twin-control-mutation.d.ts +2 -0
  122. package/dist-server/service/twin-control/twin-control-mutation.js +50 -11
  123. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  124. package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
  125. package/dist-server/service/twin-event/twin-event-keys.js +1 -1
  126. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  127. package/dist-server/service/twin-event/twin-event.d.ts +14 -5
  128. package/dist-server/service/twin-event/twin-event.js +46 -14
  129. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  130. package/dist-server/service/twin-forecast/forecast-metrics.d.ts +13 -0
  131. package/dist-server/service/twin-forecast/forecast-metrics.js +57 -0
  132. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -0
  133. package/dist-server/service/twin-forecast/forecast-tuning.d.ts +12 -0
  134. package/dist-server/service/twin-forecast/forecast-tuning.js +44 -0
  135. package/dist-server/service/twin-forecast/forecast-tuning.js.map +1 -0
  136. package/dist-server/service/twin-forecast/gap-analytics.d.ts +28 -2
  137. package/dist-server/service/twin-forecast/gap-analytics.js +43 -17
  138. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  139. package/dist-server/service/twin-forecast/twin-forecast-query.js +219 -39
  140. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  141. package/dist-server/service/twin-instance/twin-instance.d.ts +38 -5
  142. package/dist-server/service/twin-instance/twin-instance.js +66 -18
  143. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  144. package/dist-server/service/twin-journal/twin-journal-query.d.ts +11 -4
  145. package/dist-server/service/twin-journal/twin-journal-query.js +95 -15
  146. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  147. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +11 -5
  148. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +46 -27
  149. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  150. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  151. package/dist-server/service/twin-metrics/twin-metrics-query.js +63 -1
  152. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  153. package/dist-server/service/twin-model/epcis-coverage.d.ts +14 -0
  154. package/dist-server/service/twin-model/epcis-coverage.js +174 -0
  155. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -0
  156. package/dist-server/service/twin-model/iec61850-coverage.d.ts +14 -0
  157. package/dist-server/service/twin-model/iec61850-coverage.js +98 -0
  158. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -0
  159. package/dist-server/service/twin-model/index.d.ts +13 -0
  160. package/dist-server/service/twin-model/index.js +29 -0
  161. package/dist-server/service/twin-model/index.js.map +1 -0
  162. package/dist-server/service/twin-model/isa95-coverage.d.ts +48 -0
  163. package/dist-server/service/twin-model/isa95-coverage.js +155 -0
  164. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -0
  165. package/dist-server/service/twin-model/project-structure.d.ts +16 -0
  166. package/dist-server/service/twin-model/project-structure.js +173 -0
  167. package/dist-server/service/twin-model/project-structure.js.map +1 -0
  168. package/dist-server/service/twin-model/standard-coverage.d.ts +9 -0
  169. package/dist-server/service/twin-model/standard-coverage.js +37 -0
  170. package/dist-server/service/twin-model/standard-coverage.js.map +1 -0
  171. package/dist-server/service/twin-model/twin-equipment.d.ts +64 -0
  172. package/dist-server/service/twin-model/twin-equipment.js +134 -0
  173. package/dist-server/service/twin-model/twin-equipment.js.map +1 -0
  174. package/dist-server/service/twin-model/twin-lineage-query.d.ts +3 -0
  175. package/dist-server/service/twin-model/twin-lineage-query.js +206 -0
  176. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -0
  177. package/dist-server/service/twin-model/twin-location.d.ts +58 -0
  178. package/dist-server/service/twin-model/twin-location.js +130 -0
  179. package/dist-server/service/twin-model/twin-location.js.map +1 -0
  180. package/dist-server/service/twin-model/twin-model-item-query.d.ts +141 -0
  181. package/dist-server/service/twin-model/twin-model-item-query.js +852 -0
  182. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -0
  183. package/dist-server/service/twin-model/twin-model-mutation.d.ts +57 -0
  184. package/dist-server/service/twin-model/twin-model-mutation.js +490 -0
  185. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -0
  186. package/dist-server/service/twin-model/twin-model-query.d.ts +43 -0
  187. package/dist-server/service/twin-model/twin-model-query.js +551 -0
  188. package/dist-server/service/twin-model/twin-model-query.js.map +1 -0
  189. package/dist-server/service/twin-model/twin-model-tree-query.d.ts +3 -0
  190. package/dist-server/service/twin-model/twin-model-tree-query.js +158 -0
  191. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -0
  192. package/dist-server/service/twin-model/twin-operation.d.ts +61 -0
  193. package/dist-server/service/twin-model/twin-operation.js +137 -0
  194. package/dist-server/service/twin-model/twin-operation.js.map +1 -0
  195. package/dist-server/service/twin-space/move-space.d.ts +65 -0
  196. package/dist-server/service/twin-space/move-space.js +166 -0
  197. package/dist-server/service/twin-space/move-space.js.map +1 -0
  198. package/dist-server/service/twin-space/space-integrity.d.ts +97 -0
  199. package/dist-server/service/twin-space/space-integrity.js +182 -0
  200. package/dist-server/service/twin-space/space-integrity.js.map +1 -0
  201. package/dist-server/service/twin-space/twin-space-area.js +4 -4
  202. package/dist-server/service/twin-space/twin-space-area.js.map +1 -1
  203. package/dist-server/service/twin-space/twin-space-representation.js +6 -6
  204. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  205. package/dist-server/service/twin-space/twin-space-resolver.d.ts +63 -1
  206. package/dist-server/service/twin-space/twin-space-resolver.js +365 -18
  207. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  208. package/dist-server/service/twin-space/twin-space.d.ts +17 -1
  209. package/dist-server/service/twin-space/twin-space.js +15 -5
  210. package/dist-server/service/twin-space/twin-space.js.map +1 -1
  211. package/dist-server/service/twin-state/twin-state-subscription.js +1 -1
  212. package/dist-server/service/twin-state/twin-state-subscription.js.map +1 -1
  213. package/dist-server/service/twin-structure/twin-structure.d.ts +1 -1
  214. package/dist-server/service/twin-structure/twin-structure.js +8 -7
  215. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  216. package/dist-server/service/twin-target/twin-target-resolver.js +21 -4
  217. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
  218. package/dist-shared/axis-read.d.ts +12 -0
  219. package/dist-shared/axis-read.js +32 -0
  220. package/dist-shared/axis-read.js.map +1 -0
  221. package/dist-shared/entity-delta.d.ts +29 -0
  222. package/dist-shared/entity-delta.js +287 -0
  223. package/dist-shared/entity-delta.js.map +1 -0
  224. package/dist-shared/kpi-broadcast.d.ts +4 -0
  225. package/dist-shared/kpi-broadcast.js +16 -0
  226. package/dist-shared/kpi-broadcast.js.map +1 -0
  227. package/dist-shared/twin-level.d.ts +23 -0
  228. package/dist-shared/twin-level.js +52 -0
  229. package/dist-shared/twin-level.js.map +1 -0
  230. package/package.json +14 -12
  231. package/server/engine/attention-digest.ts +102 -0
  232. package/server/engine/canonical-ingest.ts +36 -4
  233. package/server/engine/command-routing.ts +67 -0
  234. package/server/engine/energy-topology.ts +189 -0
  235. package/server/engine/index.ts +17 -0
  236. package/server/engine/kpi-baseline.ts +202 -0
  237. package/server/engine/kpi-fold.ts +389 -1
  238. package/server/engine/kpi-query.ts +295 -22
  239. package/server/engine/kpi-target.ts +24 -2
  240. package/server/engine/live-attentions.ts +7 -2
  241. package/server/engine/live-feed-registry.ts +58 -0
  242. package/server/engine/load-meter.ts +384 -0
  243. package/server/engine/local-declarations.ts +700 -0
  244. package/server/engine/model-basis.ts +78 -10
  245. package/server/engine/model-vocabulary.ts +82 -0
  246. package/server/engine/oee-accumulator.ts +34 -1
  247. package/server/engine/operation-basis.ts +100 -0
  248. package/server/engine/property-effects.ts +199 -0
  249. package/server/engine/runtime-key.ts +58 -0
  250. package/server/engine/spec-coverage.ts +3 -3
  251. package/server/engine/state-axes.ts +60 -0
  252. package/server/engine/structure-diff.ts +1 -1
  253. package/server/engine/travel-estimator.ts +11 -6
  254. package/server/engine/twin-engine.ts +1710 -297
  255. package/server/engine/warm-start.ts +207 -22
  256. package/server/index.ts +13 -10
  257. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +43 -0
  258. package/server/migrations/index.ts +7 -0
  259. package/server/service/index.ts +9 -1
  260. package/server/service/reference/discovery-result.ts +95 -0
  261. package/server/service/reference/ingest-space.ts +104 -0
  262. package/server/service/reference/knob-defaults.ts +59 -0
  263. package/server/service/reference/reference-live.ts +121 -6
  264. package/server/service/reference/reference-master.ts +362 -35
  265. package/server/service/reference/reference-resolver.ts +307 -23
  266. package/server/service/reference/template-registry.ts +20 -5
  267. package/server/service/twin-attention/twin-attention-query.ts +43 -6
  268. package/server/service/twin-audit/command-audit.ts +81 -0
  269. package/server/service/twin-audit/index.ts +5 -0
  270. package/server/service/twin-audit/twin-audit-event.ts +112 -0
  271. package/server/service/twin-audit/twin-audit-query.ts +72 -0
  272. package/server/service/twin-control/twin-control-mutation.ts +53 -13
  273. package/server/service/twin-event/twin-event-keys.ts +1 -1
  274. package/server/service/twin-event/twin-event.ts +52 -15
  275. package/server/service/twin-forecast/forecast-metrics.ts +60 -0
  276. package/server/service/twin-forecast/forecast-tuning.ts +40 -0
  277. package/server/service/twin-forecast/gap-analytics.ts +48 -9
  278. package/server/service/twin-forecast/twin-forecast-query.ts +211 -38
  279. package/server/service/twin-instance/twin-instance.ts +127 -22
  280. package/server/service/twin-journal/twin-journal-query.ts +108 -14
  281. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +40 -23
  282. package/server/service/twin-metrics/twin-metrics-query.ts +60 -3
  283. package/server/service/twin-model/epcis-coverage.ts +195 -0
  284. package/server/service/twin-model/iec61850-coverage.ts +117 -0
  285. package/server/service/twin-model/index.ts +25 -0
  286. package/server/service/twin-model/isa95-coverage.ts +190 -0
  287. package/server/service/twin-model/project-structure.ts +199 -0
  288. package/server/service/twin-model/standard-coverage.ts +35 -0
  289. package/server/service/twin-model/twin-equipment.ts +150 -0
  290. package/server/service/twin-model/twin-lineage-query.ts +193 -0
  291. package/server/service/twin-model/twin-location.ts +140 -0
  292. package/server/service/twin-model/twin-model-item-query.ts +848 -0
  293. package/server/service/twin-model/twin-model-mutation.ts +503 -0
  294. package/server/service/twin-model/twin-model-query.ts +537 -0
  295. package/server/service/twin-model/twin-model-tree-query.ts +180 -0
  296. package/server/service/twin-model/twin-operation.ts +150 -0
  297. package/server/service/twin-space/move-space.ts +262 -0
  298. package/server/service/twin-space/space-integrity.ts +293 -0
  299. package/server/service/twin-space/twin-space-area.ts +4 -4
  300. package/server/service/twin-space/twin-space-representation.ts +6 -6
  301. package/server/service/twin-space/twin-space-resolver.ts +365 -22
  302. package/server/service/twin-space/twin-space.ts +40 -6
  303. package/server/service/twin-state/twin-state-subscription.ts +1 -1
  304. package/server/service/twin-structure/twin-structure.ts +10 -7
  305. package/server/service/twin-target/twin-target-resolver.ts +22 -4
  306. package/shared/axis-read.ts +28 -0
  307. package/shared/entity-delta.ts +286 -0
  308. package/shared/kpi-broadcast.ts +13 -0
  309. package/shared/twin-level.ts +48 -0
  310. package/test/adopt-structure-live.test.ts +133 -0
  311. package/test/attention-digest.test.ts +135 -0
  312. package/test/axis-read.test.ts +87 -0
  313. package/test/boot-resume.test.ts +205 -0
  314. package/test/canonical-ingest-vocabularies.test.ts +118 -0
  315. package/test/capability-mapping.test.ts +5 -5
  316. package/test/command-routing.test.ts +61 -0
  317. package/test/declaration-reaches-model.test.ts +178 -0
  318. package/test/discovery-result.test.ts +75 -0
  319. package/test/energy-topology.test.ts +113 -0
  320. package/test/entity-delta.test.ts +195 -25
  321. package/test/event-time-column.test.ts +67 -0
  322. package/test/forecast-metrics.test.ts +71 -0
  323. package/test/forecast-tuning.test.ts +42 -0
  324. package/test/gap-analytics.test.ts +34 -7
  325. package/test/ingest-bench.test.ts +9 -9
  326. package/test/ingest-running-guard.test.ts +136 -0
  327. package/test/ingest-space.test.ts +78 -0
  328. package/test/instance-cache-lifecycle.test.ts +110 -0
  329. package/test/kernel-kind-guard.test.ts +96 -0
  330. package/test/knob-defaults.test.ts +72 -0
  331. package/test/kpi-baseline-db.test.ts +216 -0
  332. package/test/kpi-baseline.test.ts +196 -0
  333. package/test/kpi-fold.test.ts +393 -2
  334. package/test/kpi-query-bench.test.ts +130 -0
  335. package/test/lineage-survives-restart.test.ts +203 -0
  336. package/test/live-feed-registry.test.ts +67 -0
  337. package/test/live-kernel-facts.test.ts +161 -0
  338. package/test/live-mirror-parity.test.ts +54 -14
  339. package/test/load-meter.test.ts +314 -0
  340. package/test/local-declarations.test.ts +554 -0
  341. package/test/master-to-twin.test.ts +128 -36
  342. package/test/model-basis.test.ts +56 -1
  343. package/test/model-vocabulary.test.ts +114 -0
  344. package/test/move-space.test.ts +149 -0
  345. package/test/mutation-gate.test.ts +122 -0
  346. package/test/oee-accumulator.test.ts +90 -7
  347. package/test/operation-basis.test.ts +100 -0
  348. package/test/operations-capability-db.test.ts +165 -0
  349. package/test/project-structure-db.test.ts +226 -0
  350. package/test/projection-reaches-screen.test.ts +272 -0
  351. package/test/property-effects.test.ts +96 -0
  352. package/test/registry-key-guard.test.ts +80 -0
  353. package/test/resync-origin-site.test.ts +77 -0
  354. package/test/runtime-key.test.ts +66 -0
  355. package/test/scale-twin-bench.test.ts +7 -7
  356. package/test/snapshot-freshness.test.ts +60 -0
  357. package/test/space-integrity.test.ts +223 -0
  358. package/test/standard-coverage.test.ts +94 -0
  359. package/test/state-axes.test.ts +74 -0
  360. package/test/streamline-e2e.test.ts +16 -16
  361. package/test/structure-revision-db.test.ts +16 -15
  362. package/test/tenant-registry-db.test.ts +149 -0
  363. package/test/twin-audit.test.ts +83 -0
  364. package/test/twin-model-item-db.test.ts +275 -0
  365. package/test/twin-model-tree-db.test.ts +176 -0
  366. package/test/twin-origin-resync.test.ts +114 -0
  367. package/test/warm-start-seam.test.ts +140 -0
  368. package/test/warm-start.test.ts +223 -4
  369. package/tsconfig.json +6 -1
  370. package/tsconfig.shared.json +23 -0
  371. package/tsconfig.shared.tsbuildinfo +1 -0
  372. package/tsconfig.tsbuildinfo +1 -0
  373. package/dist-server/engine/entity-delta.d.ts +0 -19
  374. package/dist-server/engine/entity-delta.js +0 -161
  375. package/dist-server/engine/entity-delta.js.map +0 -1
  376. package/dist-server/service/twin-event/backfill-keys.d.ts +0 -11
  377. package/dist-server/service/twin-event/backfill-keys.js +0 -63
  378. package/dist-server/service/twin-event/backfill-keys.js.map +0 -1
  379. package/dist-server/tsconfig.tsbuildinfo +0 -1
  380. package/server/engine/entity-delta.ts +0 -169
  381. package/server/service/twin-event/backfill-keys.ts +0 -72
@@ -0,0 +1,136 @@
1
+ /*
2
+ * ★ 가동 중 인제스트 — **거절할 것이면 아무것도 쓰기 전에 거절한다.**
3
+ *
4
+ * 가동 중 재프로비저닝은 `provision` 이 막는다. 그런데 그 호출은 `ingestMaster` 의 **맨 끝**이었고,
5
+ * 그때까지 공간·표현·구역(TwinArea)은 이미 저장된 뒤였다. 사용자 화면에는 "인제스트 실패" 가
6
+ * 뜨는데 실제로는 절반이 적용된 상태다 — **실패라고 말하면서 데이터를 바꾸는 것**이 가장 나쁘다.
7
+ *
8
+ * 이 검사는 그 경계 하나를 지킨다: 거절된 인제스트는 **흔적을 남기지 않는다.**
9
+ *
10
+ * 순수 함수로는 확인되지 않는다. 부분 쓰기는 정의상 "여러 표에 걸쳐 무엇이 남았나" 이므로 실
11
+ * DataSource 가 있어야 한다(멀티DB 규약상 sqlite 가 5개 드라이버 공통의 최소선이다).
12
+ *
13
+ * 이 제약 자체는 최종형이 아니다 — 커널에는 가동 중 구조 교체(`adoptStructure`)가 이미 있고
14
+ * 재생은 리비전 경계마다 그것을 쓴다. 라이브에 배선되면 이 거절은 사라지고 이 검사도 함께 간다.
15
+ */
16
+ import { test, before, after } from 'node:test'
17
+ import assert from 'node:assert/strict'
18
+ import { createRequire } from 'node:module'
19
+ import type { DataSource } from 'typeorm'
20
+
21
+ const req = createRequire(import.meta.url)
22
+ const { NamingStrategy, addDataSource, entities: shellEntities, Domain } = req('@things-factory/shell')
23
+ const { entities: authEntities } = req('@things-factory/auth-base')
24
+ const { entities: twinEntities } = req('../dist-server/service/index.js')
25
+ const { TwinEngine } = req('../dist-server/engine/index.js')
26
+ const { TwinInstance } = req('../dist-server/service/twin-instance/twin-instance.js')
27
+ const { TwinStructure } = req('../dist-server/service/twin-structure/twin-structure.js')
28
+ const { TwinArea } = req('../dist-server/service/twin-space/twin-area.js')
29
+ const { TwinSpace } = req('../dist-server/service/twin-space/twin-space.js')
30
+ const { TwinLocation } = req('../dist-server/service/twin-model/twin-location.js')
31
+ const { DataSource: TypeOrmDataSource } = req('typeorm')
32
+
33
+ let ds: DataSource
34
+ let domainId: string
35
+
36
+ const INSTANCE = 'guard-probe'
37
+
38
+ /** WMS 마스터 — `bins` 로 규모를, `siteName`·구역 이름으로 "무엇이 바뀌려 했나" 를 다이얼한다. */
39
+ const master = (bins: number, siteName: string, areaName: string): any => ({
40
+ source: INSTANCE,
41
+ system: 'wms',
42
+ siteName,
43
+ space: { width: 60, depth: 30, unit: 'm' },
44
+ locations: [
45
+ { id: 'a-dc', type: 'warehouse', role: 'area', name: areaName },
46
+ { id: 'dock-recv', type: 'dock', role: 'location', name: '입고 도크', capacity: 100 },
47
+ ...Array.from({ length: bins }, (_, i) => ({
48
+ id: `bin-${i + 1}`,
49
+ type: 'storage',
50
+ role: 'location',
51
+ name: `빈${i + 1}`,
52
+ parentId: 'a-dc',
53
+ capacity: 20
54
+ }))
55
+ ],
56
+ equipment: [{ id: 'fk-1', kind: 'forklift', homeLocationId: 'dock-recv' }]
57
+ })
58
+
59
+ /** 지금 DB 에 남아 있는 모든 흔적 — 인제스트가 건드리는 표를 한 줌으로 뜬다. */
60
+ async function trace() {
61
+ const inst = await ds.getRepository(TwinInstance).findOne({ where: { instanceId: INSTANCE } })
62
+ const space = await ds.getRepository(TwinSpace).findOne({ where: { spaceId: `${INSTANCE}-site` } })
63
+ const areas = await ds.getRepository(TwinArea).find({ order: { areaId: 'ASC' } })
64
+ const revs = await ds.getRepository(TwinStructure).find({ order: { rev: 'ASC' } })
65
+ return {
66
+ boardLocations: (inst?.model?.locations ?? []).length,
67
+ spaceName: space?.name,
68
+ areaNames: areas.map((a: any) => a.name),
69
+ revs: revs.map((r: any) => r.rev),
70
+ rows: await ds.getRepository(TwinLocation).count()
71
+ }
72
+ }
73
+
74
+ before(async () => {
75
+ ds = new TypeOrmDataSource({
76
+ type: 'sqlite',
77
+ database: ':memory:',
78
+ synchronize: true,
79
+ dropSchema: true,
80
+ namingStrategy: new NamingStrategy(),
81
+ entities: [...shellEntities, ...authEntities, ...twinEntities],
82
+ logging: false
83
+ })
84
+ await ds.initialize()
85
+ addDataSource('default', ds)
86
+ const d = await ds.getRepository(Domain).save({ name: 'guard-test', subdomain: 'guard-test' } as any)
87
+ domainId = d.id
88
+
89
+ await TwinEngine.ingestMaster(domainId, master(3, '서울 창고', '보관 구역'))
90
+ })
91
+
92
+ after(async () => {
93
+ await TwinEngine.stop(domainId, INSTANCE).catch(() => {})
94
+ await ds?.destroy()
95
+ })
96
+
97
+ test('멈춰 있으면 재인제스트가 통한다 — 막는 것은 가동 중일 때뿐이다', async () => {
98
+ await TwinEngine.ingestMaster(domainId, master(5, '서울 창고', '보관 구역'))
99
+ const t = await trace()
100
+ assert.equal(t.boardLocations, 6, '자리 6개(도크 1 + 빈 5)로 갱신된다')
101
+ assert.deepEqual(t.revs, [1, 2], '구조가 달라졌으니 리비전이 하나 늘어난다')
102
+ assert.equal(t.rows, 6, '표도 함께 다시 그려진다')
103
+ })
104
+
105
+ test('가동 중이면 거절하고 — **아무것도 바꾸지 않는다**', async () => {
106
+ await TwinEngine.startFromRegistry(domainId, INSTANCE, 'sim-experiment')
107
+ const before = await trace()
108
+
109
+ /*
110
+ * 바뀔 것을 **일부러 전부** 흔든다. 구조(빈 5→9)뿐 아니라 공간 이름·구역 이름까지 —
111
+ * 부분 쓰기는 앞쪽(공간·구역)에서 일어났으므로, 구조만 확인하면 예전 결함을 못 잡는다.
112
+ */
113
+ await assert.rejects(
114
+ () => TwinEngine.ingestMaster(domainId, master(9, '부산 창고', '다른 구역')),
115
+ /is running/,
116
+ '가동 중에는 거절해야 한다'
117
+ )
118
+
119
+ assert.deepEqual(await trace(), before, '거절된 인제스트는 어느 표에도 흔적을 남기지 않는다')
120
+ })
121
+
122
+ test('멈추면 다시 통한다 — 거절은 잠금이 아니라 순서다', async () => {
123
+ await TwinEngine.stop(domainId, INSTANCE)
124
+ await TwinEngine.ingestMaster(domainId, master(9, '부산 창고', '다른 구역'))
125
+
126
+ const t = await trace()
127
+ assert.equal(t.boardLocations, 10, '아까 거절당한 그 구조가 이제 들어간다')
128
+ assert.deepEqual(t.revs, [1, 2, 3])
129
+ assert.equal(t.rows, 10)
130
+ /*
131
+ * 공간 이름은 **안 바뀐다** — 이미 있는 공간의 이름은 인제스트가 덮지 않는다(공유 공간에서
132
+ * 먼저 쓴 값이 이긴다). 거절 검사가 "안 바뀌었다" 를 확인할 때 이 성질에 기대지 않도록,
133
+ * 여기서 그것이 별개의 규칙임을 못 박아 둔다.
134
+ */
135
+ assert.equal(t.spaceName, '서울 창고', '기존 공간 이름은 재인제스트가 덮지 않는다(별개 규칙)')
136
+ })
@@ -0,0 +1,78 @@
1
+ /*
2
+ * 인제스트 공간 판정 — "이 현장에 더한다" 가 조용히 빗나가지 않게.
3
+ *
4
+ * 엔진 본체는 DB·TypeORM 을 물고 있어 단위테스트로 부를 수 없다. 그래서 **결정만** 순수 모듈로
5
+ * 떼어 놓고 여기서 본다(엔진은 공간 존재 여부를 조회해 넘기는 일만 한다).
6
+ */
7
+ import { test } from 'node:test'
8
+ import assert from 'node:assert/strict'
9
+
10
+ import { resolveIngestSpace, refuseImportSpace, collidingIds, INGEST_SPACE_NOT_FOUND } from '../server/service/reference/ingest-space.ts'
11
+
12
+ test('고르지 않으면 마스터가 파생한 공간으로 — 지금까지의 거동을 바꾸지 않는다', () => {
13
+ for (const req of [undefined, null, '', ' ']) {
14
+ const c = resolveIngestSpace('hatiolab-wms-site', req as any, false)
15
+ assert.deepEqual(c, { spaceId: 'hatiolab-wms-site', joined: false }, `요청=${JSON.stringify(req)}`)
16
+ }
17
+ })
18
+
19
+ test('기존 현장을 고르면 그 공간으로 들어가고 합치기로 표시된다', () => {
20
+ const c = resolveIngestSpace('trailer-yard-new-site', 'rosarito-site', true)
21
+ assert.deepEqual(c, { spaceId: 'rosarito-site', joined: true })
22
+ })
23
+
24
+ test('마스터가 이미 그 공간이면 합치기가 아니다 — 같은 공간의 재인제스트다', () => {
25
+ const c = resolveIngestSpace('rosarito-site', 'rosarito-site', true)
26
+ assert.deepEqual(c, { spaceId: 'rosarito-site', joined: false })
27
+ })
28
+
29
+ test('없는 현장은 거절한다 — 조용히 새 공간을 만들지 않는다', () => {
30
+ /* 여기서 새 공간을 만들어 주면 사용자는 합쳤다고 믿고 화면은 따로 논다. 그때는 이미 양쪽에
31
+ 이벤트가 쌓여 있어 손으로 합치는 것이 훨씬 비싸다. */
32
+ assert.throws(
33
+ () => resolveIngestSpace('a-site', 'typo-site', false),
34
+ (e: any) => e.code === INGEST_SPACE_NOT_FOUND && /typo-site/.test(e.message)
35
+ )
36
+ })
37
+
38
+ test('앞뒤 공백은 다듬는다 — 사용자가 붙여 넣은 id 로 거절당하지 않게', () => {
39
+ const c = resolveIngestSpace('a-site', ' rosarito-site ', true)
40
+ assert.equal(c.spaceId, 'rosarito-site')
41
+ })
42
+
43
+ test('같은 id 를 쓰는 것들을 찾아낸다 — 합쳐지는 것을 모르고 지나가지 않게', () => {
44
+ const existing = [{ id: 'a-yard' }, { id: 'a-dock' }]
45
+ const incoming = [{ id: 'a-yard' }, { id: 'a-line' }, { id: 'a-dock' }]
46
+ assert.deepEqual(collidingIds(existing, incoming), ['a-dock', 'a-yard'], '정렬된 교집합')
47
+ assert.deepEqual(collidingIds(existing, [{ id: 'a-line' }]), [], '겹치지 않으면 빈 배열')
48
+ assert.deepEqual(collidingIds(undefined, undefined), [], '없는 쪽은 빈 것으로 본다')
49
+ assert.deepEqual(collidingIds([{}, { id: '' }], [{ id: '' }]), [], 'id 없는 항목은 세지 않는다')
50
+ })
51
+
52
+ /* ── 실 시스템 import 의 현장 선택 (2026-08-13) ───────────────────────────── */
53
+
54
+ test('현장을 고르지 않으면 사이트가 몇이든 진행한다 — 지금까지의 거동', () => {
55
+ for (const n of [0, 1, 5, 40]) {
56
+ assert.equal(refuseImportSpace(n, undefined), undefined)
57
+ assert.equal(refuseImportSpace(n, ''), undefined)
58
+ }
59
+ })
60
+
61
+ test('사이트 하나면 그 현장에 넣는다 — 렌즈를 더하는 정상 경로', () => {
62
+ assert.equal(refuseImportSpace(1, 'rosarito-site'), undefined)
63
+ })
64
+
65
+ test('사이트 여럿을 한 현장에 넣는 것은 거절한다 — 서로 다른 현장이 조용히 합쳐진다', () => {
66
+ const r = refuseImportSpace(3, 'rosarito-site')
67
+ assert.ok(r, '거절해야 한다')
68
+ assert.equal(r.code, 'space-with-multiple-sites')
69
+ assert.equal(r.params?.count, 3)
70
+ assert.equal(r.params?.spaceId, 'rosarito-site')
71
+ assert.match(r.message, /pick one site/)
72
+ })
73
+
74
+ test('거절 사유는 코드로 낸다 — 화면이 다섯 언어로 번역할 수 있게', () => {
75
+ const r = refuseImportSpace(2, ' rosarito-site ')
76
+ assert.equal(r?.code, 'space-with-multiple-sites')
77
+ assert.equal(r?.params?.spaceId, 'rosarito-site', '앞뒤 공백은 다듬어 낸다')
78
+ })
@@ -0,0 +1,110 @@
1
+ /*
2
+ * ★ 인스턴스별 캐시는 **트윈과 함께 사라진다.**
3
+ *
4
+ * ── 왜 이 검사가 필요한가 ─────────────────────────────────────────────────────
5
+ * 엔진은 트윈마다 메모리에 몇 가지를 들고 있다(실측 추정기·구조 리비전·복구 표식). 지움은 저널과
6
+ * 등록부까지 갔는데 **캐시는 남았다** — 삭제된 트윈의 항목이 서버 재기동까지 살아 있었다. 항목 하나가
7
+ * 하루치 KPI 그룹을 물고 있으므로, 트윈을 만들고 지우는 일이 반복되는 현장에서는 그것이 곧 누수다.
8
+ *
9
+ * TTL 은 이것을 막지 못한다: TTL 은 **값이 낡는 것**을 막을 뿐 **없어진 주인**을 지우지 않는다.
10
+ *
11
+ * 그래서 규율은 둘이다.
12
+ * ① 트윈이 사라지면 그 트윈에 대한 메모리를 **전부** 잊는다(정확한 쪽).
13
+ * ② 우리 API 를 지나지 않고 사라지는 길(도메인째 삭제·DB 직접 조작)에는 **상한**이 막는다(방어).
14
+ *
15
+ * 사설(private) 필드를 들여다보는 검사인 것을 안다. 그런데 여기서 지켜야 하는 것은 겉으로 드러나는
16
+ * 값이 아니라 **메모리가 자라지 않는다**는 사실이고, 그것은 안을 봐야 볼 수 있다.
17
+ */
18
+ import { test, before, after } from 'node:test'
19
+ import assert from 'node:assert/strict'
20
+ import { createRequire } from 'node:module'
21
+ import type { DataSource } from 'typeorm'
22
+
23
+ const req = createRequire(import.meta.url)
24
+ const { NamingStrategy, addDataSource, entities: shellEntities, Domain } = req('@things-factory/shell')
25
+ const { entities: authEntities } = req('@things-factory/auth-base')
26
+ const { entities: twinEntities } = req('../dist-server/service/index.js')
27
+ const { TwinEngine } = req('../dist-server/engine/index.js')
28
+ const { DataSource: TypeOrmDataSource } = req('typeorm')
29
+
30
+ let ds: DataSource
31
+ let domainId: string
32
+
33
+ const INSTANCE = 'cache-probe'
34
+
35
+ const master = (): any => ({
36
+ source: INSTANCE,
37
+ system: 'wms',
38
+ siteName: '캐시 창고',
39
+ space: { spaceId: 'cache-site', width: 40, depth: 30, unit: 'm' },
40
+ locations: [
41
+ { id: 'dock', type: 'dock', role: 'location', name: '도크', capacity: 50 },
42
+ { id: 'bin-1', type: 'storage', role: 'location', name: '빈1', capacity: 20 }
43
+ ],
44
+ equipment: [{ id: 'fk-1', kind: 'forklift', homeLocationId: 'dock' }]
45
+ })
46
+
47
+ /** 캐시 안을 본다 — 겉으로는 안 보이는 사실을 지키는 검사이므로. */
48
+ const caches = () => ({
49
+ measured: (TwinEngine as any).measuredCache as Map<string, unknown>,
50
+ structureRev: (TwinEngine as any).structureRevCache as Record<string, number>,
51
+ recovered: (TwinEngine as any).recovered as Record<string, unknown>
52
+ })
53
+ const keyOf = (id: string) => `${domainId}:${id}`
54
+
55
+ before(async () => {
56
+ ds = new TypeOrmDataSource({
57
+ type: 'sqlite',
58
+ database: ':memory:',
59
+ synchronize: true,
60
+ dropSchema: true,
61
+ namingStrategy: new NamingStrategy(),
62
+ entities: [...shellEntities, ...authEntities, ...twinEntities],
63
+ logging: false
64
+ })
65
+ await ds.initialize()
66
+ addDataSource('default', ds)
67
+ const d = await ds.getRepository(Domain).save({ name: 'cache-test', subdomain: 'cache-test' } as any)
68
+ domainId = d.id
69
+ })
70
+
71
+ after(async () => {
72
+ await TwinEngine.stop(domainId, INSTANCE).catch(() => {})
73
+ await ds?.destroy()
74
+ })
75
+
76
+ test('지운 트윈의 캐시는 남지 않는다 — TTL 은 없어진 주인을 지우지 않는다', async () => {
77
+ await TwinEngine.ingestMaster(domainId, master())
78
+ /* 캐시를 실제로 채운다 — 손으로 넣으면 "채우는 경로" 가 바뀌어도 검사가 통과한다. */
79
+ await TwinEngine.structureRevOf(domainId, INSTANCE)
80
+ const { measured, structureRev } = caches()
81
+ assert.ok(keyOf(INSTANCE) in structureRev, '구조 리비전이 캐시에 올라와야 이 검사가 뜻이 있다')
82
+
83
+ await TwinEngine.remove(domainId, INSTANCE)
84
+
85
+ const after = caches()
86
+ assert.equal(keyOf(INSTANCE) in after.structureRev, false, '지운 트윈의 구조 리비전이 남았다')
87
+ assert.equal(after.measured.has(keyOf(INSTANCE)), false, '지운 트윈의 실측 캐시가 남았다')
88
+ assert.equal(keyOf(INSTANCE) in after.recovered, false, '지운 트윈의 복구 표식이 남았다')
89
+ assert.equal(measured, after.measured, '캐시 객체를 갈아치우는 것이 아니라 항목을 지운다')
90
+ })
91
+
92
+ test('상한을 넘으면 가장 오래 손대지 않은 것부터 버린다 — 주인 없이 사라진 트윈까지 막는다', async () => {
93
+ const { measured } = caches()
94
+ measured.clear()
95
+ const MAX = (TwinEngine as any).MEASURED_MAX as number
96
+ assert.ok(MAX > 0, '상한이 선언되어 있어야 방어가 성립한다')
97
+
98
+ /*
99
+ * 상한 + 2 개를 넣고, 넣는 경로(`measuredEstimator`)를 그대로 쓴다. 트윈이 등록되어 있지 않으면
100
+ * KPI 조회가 빈 값을 내고 캐시에는 `undefined` 가 들어간다 — **그 자체도 캐시할 값이다**(없다는
101
+ * 사실을 알고 있는 것과 매번 다시 묻는 것은 다르다).
102
+ */
103
+ const call = (TwinEngine as any).measuredEstimator.bind(TwinEngine)
104
+ for (let i = 0; i < MAX + 2; i++) await call(domainId, `ghost-${i}`)
105
+
106
+ assert.ok(measured.size <= MAX, `상한을 넘어 자랐다(${measured.size} > ${MAX})`)
107
+ assert.equal(measured.has(`${domainId}:ghost-0`), false, '가장 오래된 항목이 남아 있다 — 버릴 순서가 어긋났다')
108
+ assert.ok(measured.has(`${domainId}:ghost-${MAX + 1}`), '가장 최근 항목은 남아야 한다')
109
+ measured.clear()
110
+ })
@@ -0,0 +1,96 @@
1
+ /*
2
+ * 커널 선택 가드 — **모르는 종류로는 뜨지 않는다.**
3
+ *
4
+ * ── 무엇이 있었나 (2026-08-08) ───────────────────────────────────────────────
5
+ * 커널을 고르는 자리 셋이 모두 이랬다:
6
+ *
7
+ * const Kernel = KERNELS[reg.kind ?? 'wms'] ?? WmsKernel
8
+ *
9
+ * 폴백이 둘 겹쳐 있었다. ①은 컬럼이 비었을 때, ②는 표에 없는 값일 때. 그리고 `kind` 는
10
+ * 검증 없는 자유 문자열이다(`@Arg('kind') kind: string` — enum 도 화이트리스트도 없다).
11
+ * 그래서 오타 하나·대소문자 하나로 **야드/생산 트윈이 조용히 창고 커널로 돌았다.**
12
+ * 오류가 없으니 화면에는 트윈이 정상으로 보이고, 안에서 도는 규칙만 다른 도메인의 것이다.
13
+ * 예측 경로가 가장 나빴다 — 야드의 미래를 창고 규칙으로 굴려 놓고 숫자만 뜬다.
14
+ *
15
+ * ── 왜 검사로 남기나 ────────────────────────────────────────────────────────
16
+ * 자리를 고치는 것만으로는 부족하다. 커널 종류가 늘 때(다음은 TMS 든 무엇이든) 새 자리가
17
+ * 생기고, `?? WmsKernel` 은 "안전한 기본값" 처럼 보여서 다시 자란다. 실제로 세 자리에
18
+ * 똑같이 자라 있었다. 틀린 공장을 조용히 띄우는 것보다 뜨지 않는 편이 낫다.
19
+ *
20
+ * 이 검사가 막는 것은 **표를 밖에서 직접 색인하는 것**이다. 종류→커널 해소는 `kernelFor`
21
+ * 하나만 한다. 새 자리를 쓸 때 이 검사가 걸리면, 폴백을 붙이지 말고 `kernelFor` 를 부를 것.
22
+ */
23
+ import { test } from 'node:test'
24
+ import assert from 'node:assert/strict'
25
+ import { readdirSync, readFileSync, statSync } from 'node:fs'
26
+ import { join } from 'node:path'
27
+
28
+ const ENGINE = new URL('../server/engine/twin-engine.ts', import.meta.url).pathname
29
+ const MUTATION = new URL('../server/service/twin-lifecycle/twin-lifecycle-mutation.ts', import.meta.url).pathname
30
+
31
+ /*
32
+ * 종류 폴백은 **한 패키지에 갇혀 있지 않다.** 처음에 twin-engine 두 자리만 고쳤더니, 라이브 미러를
33
+ * 띄우는 호출자 넷(operato-twin 의 벤치·부하시험·프리셋 프로비저닝, headless 의 reference-live)이
34
+ * `det.kind ?? 'wms'` 로 **엔진이 던지기 전에 먼저 이기고** 있었다. 커넥터 spec 도 마찬가지였다.
35
+ * 그래서 두 패키지의 서버 코드를 함께 훑는다.
36
+ */
37
+ const ROOTS = [new URL('../server', import.meta.url).pathname, new URL('../../operato-twin/server', import.meta.url).pathname]
38
+
39
+ /**
40
+ * 코드만 본다 — 주석은 **과거를 설명하느라 옛 코드를 그대로 인용한다**(바로 위 머리말이 그렇다).
41
+ * 줄 단위로 `*`·`//` 만 걸러내면 여러 줄 주석의 둘째 줄부터 새어 나오므로 블록을 통째로 지운다.
42
+ */
43
+ function codeOf(path: string): string[] {
44
+ return readFileSync(path, 'utf-8')
45
+ .replace(/\/\*[\s\S]*?\*\//g, '') // 블록 주석
46
+ .split('\n')
47
+ .map(l => l.replace(/\/\/.*$/, '')) // 줄 주석
48
+ .filter(l => l.trim())
49
+ }
50
+
51
+ test('커널 폴백이 없다 — 모르는 종류는 창고로 떨어지지 않는다', () => {
52
+ const offenders = codeOf(ENGINE).filter(l => /\?\?\s*WmsKernel|\|\|\s*WmsKernel/.test(l))
53
+ assert.deepEqual(offenders, [], '커널 선택에 폴백이 다시 생겼다 — kernelFor 로 던지게 할 것')
54
+ })
55
+
56
+ test('종류→커널 해소는 kernelFor 한 곳만 한다', () => {
57
+ /* 표를 직접 색인하는 줄. 정의부(`const KERNELS: Record…`)와 kernelFor 내부는 제외. */
58
+ const indexing = codeOf(ENGINE).filter(l => /KERNELS\[/.test(l) && !/const K = KERNELS\[kind\]/.test(l))
59
+ assert.deepEqual(indexing, [], 'KERNELS 를 밖에서 색인하고 있다 — kernelFor(kind) 를 쓸 것')
60
+ })
61
+
62
+ test('kernelFor 는 모르는 종류에 던진다 — 조용히 고르지 않는다', () => {
63
+ const src = readFileSync(ENGINE, 'utf-8')
64
+ const fn = src.slice(src.indexOf('function kernelFor'))
65
+ assert.match(fn.slice(0, 300), /throw new Error/, 'kernelFor 가 던지지 않으면 이 계약이 무너진다')
66
+ })
67
+
68
+ test("board 를 주는 기동은 kind 를 요구한다 — `kind ?? 'wms'` 로 메우지 않는다", () => {
69
+ const offenders = codeOf(MUTATION).filter(l => /kind\s*\?\?\s*['"]/.test(l))
70
+ assert.deepEqual(offenders, [], '종류를 빠뜨린 프로비저닝이 다시 창고로 기동한다')
71
+ })
72
+
73
+ /** 두 패키지의 서버 .ts 전부. */
74
+ function serverFiles(): string[] {
75
+ const out: string[] = []
76
+ const walk = (dir: string) => {
77
+ for (const e of readdirSync(dir)) {
78
+ const p = join(dir, e)
79
+ if (statSync(p).isDirectory()) walk(p)
80
+ else if (p.endsWith('.ts') && !p.endsWith('.d.ts')) out.push(p)
81
+ }
82
+ }
83
+ for (const r of ROOTS) walk(r)
84
+ return out
85
+ }
86
+
87
+ test('어느 서버 코드도 커널 종류를 리터럴로 메우지 않는다 — 호출자가 엔진의 검증을 앞질렀다', () => {
88
+ /* 예시 JSON 안의 `system: 'wms'` 는 필드를 보여 주는 것이라 대상이 아니다 — 폴백 연산자만 본다. */
89
+ const offenders: string[] = []
90
+ for (const f of serverFiles()) {
91
+ codeOf(f).forEach(l => {
92
+ if (/(\?\?|\|\|)\s*['"](wms|yms|mes)['"]/.test(l)) offenders.push(`${f.split('/packages/')[1]}: ${l.trim().slice(0, 90)}`)
93
+ })
94
+ }
95
+ assert.deepEqual(offenders, [], '종류 폴백이 다시 생겼다 — 선언이 없으면 메우지 말고 던질 것')
96
+ })
@@ -0,0 +1,72 @@
1
+ /*
2
+ * 노브 기본값 — **조용히 아무것도 안 하는 트윈**이 만들어지지 않게.
3
+ *
4
+ * 기본값을 아는 곳은 노브 스키마(계약)인데 그것을 적용하는 곳이 위자드뿐이었다. 그래서 화면을 거치지
5
+ * 않는 호출자가 파라미터를 일부만 주면 템플릿 안에서 발생률이 0 이 되고, 이벤트를 하나도 만들지 않는
6
+ * 트윈이 아무 말 없이 생겼다(실제로 밟았고 템플릿이 깨진 것으로 오진했다).
7
+ */
8
+ import { test } from 'node:test'
9
+ import assert from 'node:assert/strict'
10
+
11
+ import { applyKnobDefaults, filledKnobKeys } from '../server/service/reference/knob-defaults.ts'
12
+
13
+ const KNOBS = [
14
+ { key: 'id', default: 'wms-new' },
15
+ { key: 'inboundRate', default: 40 },
16
+ { key: 'outboundRate', default: 30 },
17
+ { key: 'geo', default: { lat: 37.5, lon: 127.0 } },
18
+ { key: 'noDefault' } // 기본값을 선언하지 않은 노브
19
+ ]
20
+
21
+ test('빠진 노브를 기본값으로 채운다 — 부분 파라미터 호출이 죽은 트윈을 만들지 않게', () => {
22
+ const out = applyKnobDefaults(KNOBS, { id: 'mine' })
23
+ assert.equal(out.id, 'mine', '준 값은 그대로')
24
+ assert.equal(out.inboundRate, 40)
25
+ assert.equal(out.outboundRate, 30)
26
+ assert.deepEqual(out.geo, { lat: 37.5, lon: 127.0 })
27
+ assert.ok(!('noDefault' in out), '기본값 없는 노브는 만들어 내지 않는다')
28
+ })
29
+
30
+ test('0 은 덮지 않는다 — "자극 없이 구조만" 이라는 선언을 뒤집지 않게', () => {
31
+ /* falsy 로 판정해 덮으면 발생률 0(도착 없음)이 40 으로 바뀐다. 없는 것과 0 은 다르다. */
32
+ const out = applyKnobDefaults(KNOBS, { inboundRate: 0, outboundRate: 0 })
33
+ assert.equal(out.inboundRate, 0)
34
+ assert.equal(out.outboundRate, 0)
35
+ })
36
+
37
+ test('빈 문자열·false 도 뜻 있는 값이라 덮지 않는다', () => {
38
+ const out = applyKnobDefaults([{ key: 'name', default: 'New site' }, { key: 'flag', default: true }], { name: '', flag: false })
39
+ assert.equal(out.name, '')
40
+ assert.equal(out.flag, false)
41
+ })
42
+
43
+ test('null 은 "주지 않음" 으로 본다 — JSON·GraphQL 경로가 그렇게 보낸다', () => {
44
+ const out = applyKnobDefaults(KNOBS, { inboundRate: null as any })
45
+ assert.equal(out.inboundRate, 40)
46
+ })
47
+
48
+ test('선언 밖 파라미터는 손대지 않는다 — 템플릿의 폴백 경로를 끊지 않게', () => {
49
+ /* 템플릿은 `geo` 대신 `lat`/`lon` 을 읽는 폴백을 가진다. 모르는 키를 걷어내면 그 경로가 끊긴다. */
50
+ const out = applyKnobDefaults(KNOBS, { lat: 1, lon: 2 })
51
+ assert.equal(out.lat, 1)
52
+ assert.equal(out.lon, 2)
53
+ })
54
+
55
+ test('입력을 바꾸지 않는다 — 호출자의 객체가 몰래 변하면 추적이 어렵다', () => {
56
+ const given = { id: 'mine' }
57
+ applyKnobDefaults(KNOBS, given)
58
+ assert.deepEqual(given, { id: 'mine' })
59
+ })
60
+
61
+ test('노브가 없거나 파라미터가 없어도 무너지지 않는다', () => {
62
+ assert.deepEqual(applyKnobDefaults(null, { a: 1 }), { a: 1 })
63
+ assert.deepEqual(applyKnobDefaults(KNOBS, null).inboundRate, 40)
64
+ assert.deepEqual(applyKnobDefaults(undefined, undefined), {})
65
+ })
66
+
67
+ test('무엇을 채웠는지 알린다 — 준 적 없는 값으로 만들어진 것을 밝히게', () => {
68
+ assert.deepEqual(filledKnobKeys(KNOBS, { id: 'mine', inboundRate: 0 }), ['outboundRate', 'geo'],
69
+ '준 것과 0 은 채운 목록에 없다')
70
+ assert.deepEqual(filledKnobKeys(KNOBS, null), ['id', 'inboundRate', 'outboundRate', 'geo'])
71
+ assert.deepEqual(filledKnobKeys(KNOBS, { id: 'a', inboundRate: 1, outboundRate: 2, geo: {} }), [], '다 주면 빈 목록')
72
+ })