@things-factory/headless-twin 10.0.15 → 10.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (513) hide show
  1. package/dist-server/engine/attention-digest.d.ts +4 -4
  2. package/dist-server/engine/attention-digest.js +7 -7
  3. package/dist-server/engine/attention-digest.js.map +1 -1
  4. package/dist-server/engine/canonical-ingest.d.ts +18 -82
  5. package/dist-server/engine/canonical-ingest.js +183 -18
  6. package/dist-server/engine/canonical-ingest.js.map +1 -1
  7. package/dist-server/engine/command-routing.js +1 -1
  8. package/dist-server/engine/command-routing.js.map +1 -1
  9. package/dist-server/engine/declared-stimulus.js +1 -1
  10. package/dist-server/engine/declared-stimulus.js.map +1 -1
  11. package/dist-server/engine/energy-topology.js +4 -4
  12. package/dist-server/engine/energy-topology.js.map +1 -1
  13. package/dist-server/engine/ingest-dedupe.d.ts +38 -0
  14. package/dist-server/engine/ingest-dedupe.js +145 -0
  15. package/dist-server/engine/ingest-dedupe.js.map +1 -0
  16. package/dist-server/engine/ingest-health.d.ts +90 -10
  17. package/dist-server/engine/ingest-health.js +125 -16
  18. package/dist-server/engine/ingest-health.js.map +1 -1
  19. package/dist-server/engine/integration-probes.d.ts +3 -3
  20. package/dist-server/engine/integration-probes.js +6 -6
  21. package/dist-server/engine/integration-probes.js.map +1 -1
  22. package/dist-server/engine/kpi-baseline.js +4 -4
  23. package/dist-server/engine/kpi-baseline.js.map +1 -1
  24. package/dist-server/engine/kpi-fold.d.ts +212 -14
  25. package/dist-server/engine/kpi-fold.js +374 -21
  26. package/dist-server/engine/kpi-fold.js.map +1 -1
  27. package/dist-server/engine/kpi-query.d.ts +11 -11
  28. package/dist-server/engine/kpi-query.js +281 -36
  29. package/dist-server/engine/kpi-query.js.map +1 -1
  30. package/dist-server/engine/live-feed-registry.d.ts +1 -1
  31. package/dist-server/engine/live-feed-registry.js +1 -1
  32. package/dist-server/engine/live-feed-registry.js.map +1 -1
  33. package/dist-server/engine/load-meter.d.ts +1 -1
  34. package/dist-server/engine/load-meter.js.map +1 -1
  35. package/dist-server/engine/local-declarations.d.ts +7 -7
  36. package/dist-server/engine/local-declarations.js +15 -14
  37. package/dist-server/engine/local-declarations.js.map +1 -1
  38. package/dist-server/engine/log.d.ts +1 -1
  39. package/dist-server/engine/log.js +1 -1
  40. package/dist-server/engine/log.js.map +1 -1
  41. package/dist-server/engine/measured-estimator.js +1 -1
  42. package/dist-server/engine/measured-estimator.js.map +1 -1
  43. package/dist-server/engine/measured-yield.js +1 -1
  44. package/dist-server/engine/measured-yield.js.map +1 -1
  45. package/dist-server/engine/model-basis.d.ts +1 -1
  46. package/dist-server/engine/model-basis.js +1 -1
  47. package/dist-server/engine/model-basis.js.map +1 -1
  48. package/dist-server/engine/model-gap.d.ts +3 -3
  49. package/dist-server/engine/model-gap.js +1 -1
  50. package/dist-server/engine/model-gap.js.map +1 -1
  51. package/dist-server/engine/model-vocabulary.js +3 -3
  52. package/dist-server/engine/model-vocabulary.js.map +1 -1
  53. package/dist-server/engine/oee-accumulator.d.ts +6 -5
  54. package/dist-server/engine/oee-accumulator.js +6 -5
  55. package/dist-server/engine/oee-accumulator.js.map +1 -1
  56. package/dist-server/engine/property-effects.js +2 -2
  57. package/dist-server/engine/property-effects.js.map +1 -1
  58. package/dist-server/engine/read-time.d.ts +22 -0
  59. package/dist-server/engine/read-time.js +81 -0
  60. package/dist-server/engine/read-time.js.map +1 -0
  61. package/dist-server/engine/restart-policy.js +1 -1
  62. package/dist-server/engine/restart-policy.js.map +1 -1
  63. package/dist-server/engine/runtime-key.js +1 -1
  64. package/dist-server/engine/runtime-key.js.map +1 -1
  65. package/dist-server/engine/spec-coverage.d.ts +2 -2
  66. package/dist-server/engine/spec-coverage.js +1 -1
  67. package/dist-server/engine/spec-coverage.js.map +1 -1
  68. package/dist-server/engine/stage-path.d.ts +4 -4
  69. package/dist-server/engine/stage-path.js +1 -1
  70. package/dist-server/engine/stage-path.js.map +1 -1
  71. package/dist-server/engine/state-axes.d.ts +1 -1
  72. package/dist-server/engine/state-axes.js +2 -2
  73. package/dist-server/engine/state-axes.js.map +1 -1
  74. package/dist-server/engine/travel-estimator.d.ts +2 -2
  75. package/dist-server/engine/travel-estimator.js +1 -1
  76. package/dist-server/engine/travel-estimator.js.map +1 -1
  77. package/dist-server/engine/twin-engine.d.ts +307 -54
  78. package/dist-server/engine/twin-engine.js +822 -196
  79. package/dist-server/engine/twin-engine.js.map +1 -1
  80. package/dist-server/engine/warm-start.d.ts +2 -2
  81. package/dist-server/engine/warm-start.js +5 -5
  82. package/dist-server/engine/warm-start.js.map +1 -1
  83. package/dist-server/index.js +1 -1
  84. package/dist-server/index.js.map +1 -1
  85. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +1 -1
  86. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -1
  87. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +3 -3
  88. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -1
  89. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +2 -2
  90. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -1
  91. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.d.ts +5 -0
  92. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js +54 -0
  93. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +1 -0
  94. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.d.ts +5 -0
  95. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js +44 -0
  96. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +1 -0
  97. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.d.ts +6 -0
  98. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js +84 -0
  99. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +1 -0
  100. package/dist-server/migrations/index.js +7 -1
  101. package/dist-server/migrations/index.js.map +1 -1
  102. package/dist-server/routes.d.ts +1 -0
  103. package/dist-server/routes.js +69 -5
  104. package/dist-server/routes.js.map +1 -1
  105. package/dist-server/service/index.d.ts +2 -2
  106. package/dist-server/service/index.js +30 -24
  107. package/dist-server/service/index.js.map +1 -1
  108. package/dist-server/service/reference/control-routing.d.ts +1 -1
  109. package/dist-server/service/reference/control-routing.js +1 -1
  110. package/dist-server/service/reference/control-routing.js.map +1 -1
  111. package/dist-server/service/reference/discovery-result.d.ts +2 -2
  112. package/dist-server/service/reference/discovery-result.js +3 -3
  113. package/dist-server/service/reference/discovery-result.js.map +1 -1
  114. package/dist-server/service/reference/hook-contract.d.ts +43 -0
  115. package/dist-server/service/reference/hook-contract.js +59 -0
  116. package/dist-server/service/reference/hook-contract.js.map +1 -0
  117. package/dist-server/service/reference/ingest-space.d.ts +1 -1
  118. package/dist-server/service/reference/ingest-space.js +4 -4
  119. package/dist-server/service/reference/ingest-space.js.map +1 -1
  120. package/dist-server/service/reference/knob-defaults.d.ts +1 -1
  121. package/dist-server/service/reference/knob-defaults.js +4 -4
  122. package/dist-server/service/reference/knob-defaults.js.map +1 -1
  123. package/dist-server/service/reference/reference-adapter.d.ts +116 -9
  124. package/dist-server/service/reference/reference-adapter.js +27 -0
  125. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  126. package/dist-server/service/reference/reference-hook.d.ts +20 -0
  127. package/dist-server/service/reference/reference-hook.js +133 -0
  128. package/dist-server/service/reference/reference-hook.js.map +1 -0
  129. package/dist-server/service/reference/reference-live.d.ts +2 -2
  130. package/dist-server/service/reference/reference-live.js +76 -13
  131. package/dist-server/service/reference/reference-live.js.map +1 -1
  132. package/dist-server/service/reference/reference-master.d.ts +25 -16
  133. package/dist-server/service/reference/reference-master.js +35 -13
  134. package/dist-server/service/reference/reference-master.js.map +1 -1
  135. package/dist-server/service/reference/reference-resolver.d.ts +1 -1
  136. package/dist-server/service/reference/reference-resolver.js +12 -12
  137. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  138. package/dist-server/service/reference/template-registry.d.ts +1 -1
  139. package/dist-server/service/reference/template-registry.js.map +1 -1
  140. package/dist-server/service/reference/twin-reference.d.ts +3 -3
  141. package/dist-server/service/reference/twin-reference.js.map +1 -1
  142. package/dist-server/service/twin-attention/twin-attention-query.d.ts +1 -1
  143. package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
  144. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  145. package/dist-server/service/twin-audit/command-audit.d.ts +1 -1
  146. package/dist-server/service/twin-audit/command-audit.js.map +1 -1
  147. package/dist-server/service/twin-audit/twin-audit-event.d.ts +1 -1
  148. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
  149. package/dist-server/service/twin-audit/twin-audit-query.js +1 -1
  150. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -1
  151. package/dist-server/service/twin-backfill/backfill-period-facts.d.ts +24 -0
  152. package/dist-server/service/twin-backfill/backfill-period-facts.js +96 -0
  153. package/dist-server/service/twin-backfill/backfill-period-facts.js.map +1 -0
  154. package/dist-server/service/twin-backfill/backfill-shape.d.ts +34 -0
  155. package/dist-server/service/twin-backfill/backfill-shape.js +75 -0
  156. package/dist-server/service/twin-backfill/backfill-shape.js.map +1 -0
  157. package/dist-server/service/twin-backfill/index.d.ts +2 -0
  158. package/dist-server/service/twin-backfill/index.js +6 -0
  159. package/dist-server/service/twin-backfill/index.js.map +1 -0
  160. package/dist-server/service/twin-backfill/twin-backfill-resolver.d.ts +10 -0
  161. package/dist-server/service/twin-backfill/twin-backfill-resolver.js +44 -0
  162. package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +1 -0
  163. package/dist-server/service/twin-control/twin-control-mutation.js +3 -3
  164. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  165. package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
  166. package/dist-server/service/twin-event/twin-event-keys.js +2 -2
  167. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  168. package/dist-server/service/twin-event/twin-event-type.d.ts +1 -0
  169. package/dist-server/service/twin-event/twin-event-type.js +8 -1
  170. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  171. package/dist-server/service/twin-event/twin-event.d.ts +2 -2
  172. package/dist-server/service/twin-event/twin-event.js +32 -4
  173. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  174. package/dist-server/service/twin-forecast/forecast-metrics.js +1 -1
  175. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -1
  176. package/dist-server/service/twin-forecast/gap-analytics.d.ts +1 -1
  177. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  178. package/dist-server/service/twin-forecast/twin-forecast-query.js +29 -11
  179. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  180. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +1 -1
  181. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +1 -1
  182. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -1
  183. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +1 -1
  184. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -1
  185. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  186. package/dist-server/service/twin-journal/event-type-split-shape.d.ts +50 -0
  187. package/dist-server/service/twin-journal/event-type-split-shape.js +111 -0
  188. package/dist-server/service/twin-journal/event-type-split-shape.js.map +1 -0
  189. package/dist-server/service/twin-journal/event-type-split.d.ts +15 -0
  190. package/dist-server/service/twin-journal/event-type-split.js +102 -0
  191. package/dist-server/service/twin-journal/event-type-split.js.map +1 -0
  192. package/dist-server/service/twin-journal/journal-order.d.ts +15 -0
  193. package/dist-server/service/twin-journal/journal-order.js +17 -0
  194. package/dist-server/service/twin-journal/journal-order.js.map +1 -0
  195. package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -5
  196. package/dist-server/service/twin-journal/twin-journal-query.js +232 -21
  197. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  198. package/dist-server/service/twin-lifecycle/domain-catalog.js +2 -1
  199. package/dist-server/service/twin-lifecycle/domain-catalog.js.map +1 -1
  200. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +27 -1
  201. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +59 -3
  202. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  203. package/dist-server/service/twin-metrics/twin-metrics-query.js +14 -1
  204. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  205. package/dist-server/service/twin-model/axis-journal-evidence.js +1 -1
  206. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -1
  207. package/dist-server/service/twin-model/epcis-coverage.js +34 -3
  208. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  209. package/dist-server/service/twin-model/iec61850-coverage.js +1 -1
  210. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  211. package/dist-server/service/twin-model/isa95-coverage.d.ts +12 -9
  212. package/dist-server/service/twin-model/isa95-coverage.js +33 -9
  213. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  214. package/dist-server/service/twin-model/item-ref.js +2 -2
  215. package/dist-server/service/twin-model/item-ref.js.map +1 -1
  216. package/dist-server/service/twin-model/name-index.d.ts +1 -1
  217. package/dist-server/service/twin-model/name-index.js +2 -2
  218. package/dist-server/service/twin-model/name-index.js.map +1 -1
  219. package/dist-server/service/twin-model/project-structure.d.ts +1 -1
  220. package/dist-server/service/twin-model/project-structure.js +7 -6
  221. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  222. package/dist-server/service/twin-model/twin-equipment.d.ts +2 -2
  223. package/dist-server/service/twin-model/twin-equipment.js +3 -3
  224. package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
  225. package/dist-server/service/twin-model/twin-lineage-query.js +16 -5
  226. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  227. package/dist-server/service/twin-model/twin-location.d.ts +1 -1
  228. package/dist-server/service/twin-model/twin-location.js.map +1 -1
  229. package/dist-server/service/twin-model/twin-model-item-query.d.ts +3 -3
  230. package/dist-server/service/twin-model/twin-model-item-query.js +15 -15
  231. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  232. package/dist-server/service/twin-model/twin-model-mutation.d.ts +2 -2
  233. package/dist-server/service/twin-model/twin-model-mutation.js +4 -4
  234. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -1
  235. package/dist-server/service/twin-model/twin-model-query.js +51 -30
  236. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  237. package/dist-server/service/twin-model/twin-model-tree-query.js +23 -1
  238. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  239. package/dist-server/service/twin-model/twin-operation.d.ts +1 -1
  240. package/dist-server/service/twin-model/twin-operation.js +1 -1
  241. package/dist-server/service/twin-model/twin-operation.js.map +1 -1
  242. package/dist-server/service/twin-space/move-space.js +2 -2
  243. package/dist-server/service/twin-space/move-space.js.map +1 -1
  244. package/dist-server/service/twin-space/representation-role.d.ts +2 -0
  245. package/dist-server/service/twin-space/representation-role.js +18 -0
  246. package/dist-server/service/twin-space/representation-role.js.map +1 -0
  247. package/dist-server/service/twin-space/space-integrity.d.ts +1 -1
  248. package/dist-server/service/twin-space/space-integrity.js.map +1 -1
  249. package/dist-server/service/twin-space/twin-space-representation.d.ts +2 -0
  250. package/dist-server/service/twin-space/twin-space-representation.js +16 -0
  251. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  252. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  253. package/dist-server/service/twin-space/twin-space-resolver.js +37 -8
  254. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  255. package/dist-server/service/twin-structure/twin-structure.js +3 -3
  256. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  257. package/dist-server/service/twin-subject/event-subjects.d.ts +43 -0
  258. package/dist-server/service/twin-subject/event-subjects.js +137 -0
  259. package/dist-server/service/twin-subject/event-subjects.js.map +1 -0
  260. package/dist-server/service/twin-subject/index.d.ts +9 -0
  261. package/dist-server/service/twin-subject/index.js +16 -0
  262. package/dist-server/service/twin-subject/index.js.map +1 -0
  263. package/dist-server/service/twin-subject/item-resolver.d.ts +10 -0
  264. package/dist-server/service/twin-subject/item-resolver.js +93 -0
  265. package/dist-server/service/twin-subject/item-resolver.js.map +1 -0
  266. package/dist-server/service/twin-subject/subject-query-shape.d.ts +22 -0
  267. package/dist-server/service/twin-subject/subject-query-shape.js +88 -0
  268. package/dist-server/service/twin-subject/subject-query-shape.js.map +1 -0
  269. package/dist-server/service/twin-subject/twin-subject-backfill.d.ts +23 -0
  270. package/dist-server/service/twin-subject/twin-subject-backfill.js +159 -0
  271. package/dist-server/service/twin-subject/twin-subject-backfill.js.map +1 -0
  272. package/dist-server/service/twin-subject/twin-subject-event.d.ts +14 -0
  273. package/dist-server/service/twin-subject/twin-subject-event.js +111 -0
  274. package/dist-server/service/twin-subject/twin-subject-event.js.map +1 -0
  275. package/dist-server/service/twin-subject/twin-subject-fast-path.d.ts +39 -0
  276. package/dist-server/service/twin-subject/twin-subject-fast-path.js +295 -0
  277. package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +1 -0
  278. package/dist-server/service/twin-subject/twin-subject-query.d.ts +27 -0
  279. package/dist-server/service/twin-subject/twin-subject-query.js +232 -0
  280. package/dist-server/service/twin-subject/twin-subject-query.js.map +1 -0
  281. package/dist-server/service/twin-subject/twin-subject-writer.d.ts +27 -0
  282. package/dist-server/service/twin-subject/twin-subject-writer.js +166 -0
  283. package/dist-server/service/twin-subject/twin-subject-writer.js.map +1 -0
  284. package/dist-server/service/twin-target/twin-target-resolver.js +1 -1
  285. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
  286. package/dist-shared/entity-delta.d.ts +1 -1
  287. package/dist-shared/entity-delta.js +4 -4
  288. package/dist-shared/entity-delta.js.map +1 -1
  289. package/dist-shared/touched-items.js +2 -2
  290. package/dist-shared/touched-items.js.map +1 -1
  291. package/dist-shared/twin-level.js +1 -1
  292. package/dist-shared/twin-level.js.map +1 -1
  293. package/package.json +7 -6
  294. package/server/engine/attention-digest.ts +9 -9
  295. package/server/engine/canonical-ingest.ts +202 -87
  296. package/server/engine/command-routing.ts +1 -1
  297. package/server/engine/declared-stimulus.ts +1 -1
  298. package/server/engine/energy-topology.ts +2 -2
  299. package/server/engine/ingest-dedupe.ts +149 -0
  300. package/server/engine/ingest-health.ts +171 -22
  301. package/server/engine/integration-probes.ts +7 -7
  302. package/server/engine/kpi-baseline.ts +4 -4
  303. package/server/engine/kpi-fold.ts +592 -41
  304. package/server/engine/kpi-query.ts +307 -48
  305. package/server/engine/live-feed-registry.ts +1 -1
  306. package/server/engine/load-meter.ts +1 -1
  307. package/server/engine/local-declarations.ts +15 -14
  308. package/server/engine/log.ts +1 -1
  309. package/server/engine/measured-estimator.ts +1 -1
  310. package/server/engine/measured-yield.ts +1 -1
  311. package/server/engine/model-basis.ts +2 -2
  312. package/server/engine/model-gap.ts +3 -3
  313. package/server/engine/model-vocabulary.ts +2 -2
  314. package/server/engine/oee-accumulator.ts +8 -6
  315. package/server/engine/property-effects.ts +2 -2
  316. package/server/engine/read-time.ts +77 -0
  317. package/server/engine/restart-policy.ts +1 -1
  318. package/server/engine/runtime-key.ts +1 -1
  319. package/server/engine/spec-coverage.ts +3 -3
  320. package/server/engine/stage-path.ts +5 -5
  321. package/server/engine/state-axes.ts +2 -2
  322. package/server/engine/travel-estimator.ts +2 -2
  323. package/server/engine/twin-engine.ts +906 -205
  324. package/server/engine/warm-start.ts +6 -6
  325. package/server/index.ts +1 -1
  326. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +1 -1
  327. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +3 -3
  328. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +2 -2
  329. package/server/migrations/1786300000000-IndexEventTypeByTime.ts +48 -0
  330. package/server/migrations/1786400000000-IndexEventCreatedAt.ts +38 -0
  331. package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +93 -0
  332. package/server/migrations/index.ts +7 -1
  333. package/server/routes.ts +73 -5
  334. package/server/service/index.ts +7 -1
  335. package/server/service/reference/control-routing.ts +1 -1
  336. package/server/service/reference/discovery-result.ts +3 -3
  337. package/server/service/reference/hook-contract.ts +103 -0
  338. package/server/service/reference/ingest-space.ts +4 -4
  339. package/server/service/reference/knob-defaults.ts +4 -4
  340. package/server/service/reference/reference-adapter.ts +145 -10
  341. package/server/service/reference/reference-hook.ts +145 -0
  342. package/server/service/reference/reference-live.ts +87 -15
  343. package/server/service/reference/reference-master.ts +51 -20
  344. package/server/service/reference/reference-resolver.ts +11 -11
  345. package/server/service/reference/template-registry.ts +1 -1
  346. package/server/service/reference/twin-reference.ts +3 -3
  347. package/server/service/twin-attention/twin-attention-query.ts +1 -1
  348. package/server/service/twin-audit/command-audit.ts +1 -1
  349. package/server/service/twin-audit/twin-audit-event.ts +2 -2
  350. package/server/service/twin-audit/twin-audit-query.ts +1 -1
  351. package/server/service/twin-backfill/backfill-period-facts.ts +168 -0
  352. package/server/service/twin-backfill/backfill-shape.ts +86 -0
  353. package/server/service/twin-backfill/index.ts +3 -0
  354. package/server/service/twin-backfill/twin-backfill-resolver.ts +35 -0
  355. package/server/service/twin-control/twin-control-mutation.ts +2 -2
  356. package/server/service/twin-event/twin-event-keys.ts +2 -2
  357. package/server/service/twin-event/twin-event-type.ts +18 -1
  358. package/server/service/twin-event/twin-event.ts +32 -6
  359. package/server/service/twin-forecast/forecast-metrics.ts +1 -1
  360. package/server/service/twin-forecast/gap-analytics.ts +1 -1
  361. package/server/service/twin-forecast/twin-forecast-query.ts +30 -11
  362. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +1 -1
  363. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +1 -1
  364. package/server/service/twin-instance/twin-instance.ts +4 -4
  365. package/server/service/twin-journal/event-type-split-shape.ts +112 -0
  366. package/server/service/twin-journal/event-type-split.ts +144 -0
  367. package/server/service/twin-journal/journal-order.ts +20 -0
  368. package/server/service/twin-journal/twin-journal-query.ts +224 -20
  369. package/server/service/twin-lifecycle/domain-catalog.ts +2 -1
  370. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +58 -4
  371. package/server/service/twin-metrics/twin-metrics-query.ts +14 -1
  372. package/server/service/twin-model/axis-journal-evidence.ts +1 -1
  373. package/server/service/twin-model/epcis-coverage.ts +34 -3
  374. package/server/service/twin-model/iec61850-coverage.ts +1 -1
  375. package/server/service/twin-model/isa95-coverage.ts +43 -16
  376. package/server/service/twin-model/item-ref.ts +2 -2
  377. package/server/service/twin-model/name-index.ts +2 -2
  378. package/server/service/twin-model/project-structure.ts +7 -6
  379. package/server/service/twin-model/twin-equipment.ts +16 -4
  380. package/server/service/twin-model/twin-lineage-query.ts +16 -5
  381. package/server/service/twin-model/twin-location.ts +1 -1
  382. package/server/service/twin-model/twin-model-item-query.ts +15 -15
  383. package/server/service/twin-model/twin-model-mutation.ts +4 -4
  384. package/server/service/twin-model/twin-model-query.ts +49 -28
  385. package/server/service/twin-model/twin-model-tree-query.ts +26 -2
  386. package/server/service/twin-model/twin-operation.ts +2 -2
  387. package/server/service/twin-space/move-space.ts +3 -3
  388. package/server/service/twin-space/representation-role.ts +15 -0
  389. package/server/service/twin-space/space-integrity.ts +1 -1
  390. package/server/service/twin-space/twin-space-representation.ts +37 -0
  391. package/server/service/twin-space/twin-space-resolver.ts +40 -8
  392. package/server/service/twin-structure/twin-structure.ts +3 -3
  393. package/server/service/twin-subject/event-subjects.ts +155 -0
  394. package/server/service/twin-subject/index.ts +10 -0
  395. package/server/service/twin-subject/item-resolver.ts +92 -0
  396. package/server/service/twin-subject/subject-query-shape.ts +84 -0
  397. package/server/service/twin-subject/twin-subject-backfill.ts +196 -0
  398. package/server/service/twin-subject/twin-subject-event.ts +101 -0
  399. package/server/service/twin-subject/twin-subject-fast-path.ts +346 -0
  400. package/server/service/twin-subject/twin-subject-query.ts +210 -0
  401. package/server/service/twin-subject/twin-subject-writer.ts +189 -0
  402. package/server/service/twin-target/twin-target-resolver.ts +1 -1
  403. package/shared/entity-delta.ts +4 -4
  404. package/shared/touched-items.ts +2 -2
  405. package/shared/twin-level.ts +1 -1
  406. package/test/adopt-structure-live.test.ts +3 -3
  407. package/test/aggregate-not-findone.test.ts +112 -0
  408. package/test/attention-digest.test.ts +6 -6
  409. package/test/axis-read.test.ts +6 -5
  410. package/test/backfill-shape.test.ts +89 -0
  411. package/test/boot-resume.test.ts +20 -7
  412. package/test/broadcast-cost-baseline.test.ts +2 -1
  413. package/test/broadcast-period.test.ts +2 -2
  414. package/test/broadcast-queue-overflow.test.ts +244 -0
  415. package/test/canonical-ingest-vocabularies.test.ts +259 -8
  416. package/test/canonical-quantity-door.test.ts +2 -2
  417. package/test/capability-mapping.test.ts +1 -1
  418. package/test/checkpoint-refuses-empty.test.ts +2 -2
  419. package/test/connector-capability-declaration.test.ts +88 -0
  420. package/test/contract-layer-guard.test.ts +48 -0
  421. package/test/control-capability.test.ts +1 -1
  422. package/test/cursor-stall-not-read-failure.test.ts +2 -2
  423. package/test/declaration-reaches-model.test.ts +7 -5
  424. package/test/declared-stimulus.test.ts +4 -4
  425. package/test/discovery-result.test.ts +2 -2
  426. package/test/entity-delta.test.ts +4 -4
  427. package/test/equipment-identity-reaches.test.ts +57 -0
  428. package/test/event-subjects.test.ts +147 -0
  429. package/test/event-time-column.test.ts +3 -3
  430. package/test/event-type-split.test.ts +155 -0
  431. package/test/fact-scope-wiring.test.ts +96 -0
  432. package/test/fold-gap-speaks.test.ts +53 -0
  433. package/test/forecast-metrics.test.ts +1 -1
  434. package/test/forecast-tuning.test.ts +1 -1
  435. package/test/generated-not-counted.test.ts +99 -0
  436. package/test/ingest-bench.test.ts +2 -1
  437. package/test/ingest-health-engine.test.ts +2 -2
  438. package/test/ingest-health-wiring.test.ts +7 -5
  439. package/test/ingest-health.test.ts +6 -6
  440. package/test/ingest-history.test.ts +2 -2
  441. package/test/ingest-idempotent.test.ts +132 -0
  442. package/test/ingest-space.test.ts +3 -3
  443. package/test/ingest-wiring-guard.test.ts +197 -0
  444. package/test/integration-probes.test.ts +3 -3
  445. package/test/integration-runner.test.ts +1 -1
  446. package/test/item-ref.test.ts +1 -1
  447. package/test/journal-order-axis.test.ts +80 -0
  448. package/test/journal-read-discipline.test.ts +16 -6
  449. package/test/journal-retention.test.ts +171 -29
  450. package/test/journal-sort-axis.test.ts +1 -1
  451. package/test/journal-write-door.test.ts +3 -3
  452. package/test/journal-write-trend.test.ts +1 -1
  453. package/test/kernel-kind-guard.test.ts +3 -3
  454. package/test/knob-defaults.test.ts +1 -1
  455. package/test/kpi-baseline-db.test.ts +2 -2
  456. package/test/kpi-fold.test.ts +266 -14
  457. package/test/kpi-query-bench.test.ts +3 -3
  458. package/test/live-cursor-wiring.test.ts +6 -6
  459. package/test/live-kernel-facts.test.ts +11 -11
  460. package/test/live-mirror-parity.test.ts +9 -6
  461. package/test/load-meter.test.ts +1 -1
  462. package/test/local-declarations.test.ts +13 -12
  463. package/test/log-stamp.test.ts +1 -1
  464. package/test/master-to-twin.test.ts +2 -2
  465. package/test/measured-yield.test.ts +2 -2
  466. package/test/merge-streams.test.ts +70 -0
  467. package/test/mirror-resumes-from-checkpoint.test.ts +13 -13
  468. package/test/model-basis.test.ts +2 -2
  469. package/test/model-gap.test.ts +4 -4
  470. package/test/model-vocabulary.test.ts +2 -2
  471. package/test/move-space.test.ts +1 -1
  472. package/test/oee-accumulator.test.ts +20 -5
  473. package/test/operational-vocabulary.test.ts +2 -2
  474. package/test/project-structure-db.test.ts +2 -2
  475. package/test/projection-reaches-screen.test.ts +7 -2
  476. package/test/projection-reads-declared.test.ts +109 -0
  477. package/test/property-effects.test.ts +10 -6
  478. package/test/read-failure-ledger.test.ts +106 -0
  479. package/test/read-failure-visible.test.ts +7 -7
  480. package/test/read-time.test.ts +118 -0
  481. package/test/reference-grounding.test.ts +1 -1
  482. package/test/reference-hook.test.ts +86 -0
  483. package/test/registry-key-guard.test.ts +1 -1
  484. package/test/rename-twin.test.ts +81 -0
  485. package/test/restart-policy.test.ts +3 -3
  486. package/test/resync-origin-site.test.ts +3 -3
  487. package/test/revision-axis.test.ts +14 -8
  488. package/test/runtime-key.test.ts +3 -3
  489. package/test/scale-twin-bench.test.ts +2 -1
  490. package/test/snapshot-freshness.test.ts +4 -4
  491. package/test/source-outcome-audit.test.ts +1 -1
  492. package/test/space-integrity.test.ts +1 -1
  493. package/test/space-representation.test.ts +66 -0
  494. package/test/space-scope-includes-stopped.test.ts +55 -0
  495. package/test/spec-coverage.test.ts +1 -1
  496. package/test/standard-coverage.test.ts +19 -21
  497. package/test/status-tally.test.ts +1 -1
  498. package/test/structure-revision-db.test.ts +7 -7
  499. package/test/subject-fast-path.test.ts +148 -0
  500. package/test/subject-read-model.test.ts +175 -0
  501. package/test/touched-items.test.ts +2 -2
  502. package/test/twin-audit.test.ts +1 -1
  503. package/test/twin-event-keys.test.ts +1 -1
  504. package/test/twin-model-item-db.test.ts +5 -5
  505. package/test/twin-model-tree-db.test.ts +35 -2
  506. package/test/twin-origin-resync.test.ts +1 -1
  507. package/test/vocabulary-guard.test.ts +1 -1
  508. package/test/warm-start-seam.test.ts +1 -1
  509. package/test/warm-start.test.ts +6 -6
  510. package/test/withheld-door.test.ts +2 -2
  511. package/test/yield-loop.test.ts +4 -4
  512. package/tsconfig.shared.tsbuildinfo +1 -1
  513. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,88 @@
1
+ /*
2
+ * ★ 구현했는데 선언하지 않은 능력을 아무도 말해 주지 않았다.
3
+ *
4
+ * ── 무엇이 났나 (2026-08-25) ────────────────────────────────────────────────
5
+ * 능력 판정은 **한 방향만** 본다. 선언했는데 구현이 없는 것은 걸러 내고, 구현했는데 선언이 없는 것은
6
+ * 들어온 것이 없다. 그래서 태양광 발전소를 붙이던 사람이 이렇게 막혔다.
7
+ *
8
+ * 커넥터가 openLiveFeed 를 구현했다 · capabilities: ['live'] 를 잊었다
9
+ * → 연결 화면이 「이 원본은 라이브를 못 낸다」로 읽는다
10
+ * → 재기동 정책을 정할 수 없어 트윈 생성이 거절된다
11
+ * → 화면에 보이는 것은 「재기동 정책이 없습니다」뿐이다
12
+ *
13
+ * 원인을 찾는 데 코드를 뒤져야 했다. 지금 저장소에 같은 상태인 커넥터가 일곱이다.
14
+ *
15
+ * ── 막지 않고 알린다 ────────────────────────────────────────────────────────
16
+ * 등록에서 던지면 커넥터 하나 때문에 호스트가 못 뜬다. 만들다 만 커넥터를 들고 있는 사람이 아무것도
17
+ * 못 하게 된다. 이 어긋남은 고칠 수 있는 것이지 위험한 것이 아니다.
18
+ */
19
+ import { test } from 'node:test'
20
+ import assert from 'node:assert/strict'
21
+
22
+ /* `twinWarn` 은 `console.warn` 으로 나간다 — 모듈을 바꿔치지 않고 그 자리에서 받는다. */
23
+ const warnings: string[] = []
24
+ const originalWarn = console.warn
25
+ console.warn = (...args: unknown[]) => {
26
+ warnings.push(args.join(' '))
27
+ }
28
+ process.on('exit', () => (console.warn = originalWarn))
29
+
30
+ const { registerAdapterType, capabilitiesOf } = await import('../server/service/reference/reference-adapter.js')
31
+
32
+ const adapter = (over: Record<string, unknown> = {}) =>
33
+ ({
34
+ type: `probe-${Math.round(performance.now() * 1000)}-${warnings.length}`,
35
+ async fetchMaster() {
36
+ return {} as any
37
+ },
38
+ ...over
39
+ }) as any
40
+
41
+ test('★ 라이브를 구현하고 선언하지 않으면 말해 준다 — 이것이 없어서 화면이 다른 말을 했다', () => {
42
+ const before = warnings.length
43
+ const a = adapter({ openLiveFeed: () => () => {} })
44
+ registerAdapterType(a)
45
+ const said = warnings.slice(before).join('\n')
46
+ assert.ok(said.includes(a.type), '어느 커넥터인지 말한다')
47
+ assert.ok(said.includes('live'), '무엇이 빠졌는지 말한다')
48
+ assert.ok(said.includes('capabilities'), '무엇을 하면 되는지 말한다')
49
+ /* 그리고 지금 화면이 어떻게 읽는지도 말한다 — 사람이 겪은 증상과 이어져야 원인으로 읽힌다. */
50
+ assert.ok(/live twin cannot be created/.test(said))
51
+ })
52
+
53
+ test('★ 조치를 구현하고 선언하지 않아도 말해 준다 — 같은 규율이다', () => {
54
+ const before = warnings.length
55
+ const a = adapter({ control: async () => ({ ok: true }) })
56
+ registerAdapterType(a)
57
+ assert.ok(warnings.slice(before).join('\n').includes('control'))
58
+ })
59
+
60
+ test('둘 다 어긋나면 둘 다 말한다 — 하나만 말하면 고친 뒤 또 막힌다', () => {
61
+ const before = warnings.length
62
+ const a = adapter({ openLiveFeed: () => () => {}, control: async () => ({ ok: true }) })
63
+ registerAdapterType(a)
64
+ const said = warnings.slice(before).join('\n')
65
+ assert.ok(said.includes('live') && said.includes('control'))
66
+ })
67
+
68
+ test('선언과 구현이 맞으면 아무 말도 하지 않는다 — 소음이 되면 아무도 읽지 않는다', () => {
69
+ const before = warnings.length
70
+ registerAdapterType(adapter({ openLiveFeed: () => () => {}, capabilities: ['live'] }))
71
+ registerAdapterType(adapter({}))
72
+ assert.equal(warnings.length, before)
73
+ })
74
+
75
+ test('선언했는데 구현이 없는 쪽은 판정이 이미 걸러 낸다 — 여기서 두 번 말하지 않는다', () => {
76
+ const before = warnings.length
77
+ const a = adapter({ capabilities: ['live'] })
78
+ registerAdapterType(a)
79
+ assert.equal(warnings.length, before, '등록은 들어온 것이 없다')
80
+ assert.deepEqual(capabilitiesOf(a), [], '능력 판정이 구현 없는 선언을 걸러 낸다')
81
+ })
82
+
83
+ test('★ 등록을 막지 않는다 — 커넥터 하나 때문에 호스트가 못 뜨면 안 된다', () => {
84
+ const a = adapter({ openLiveFeed: () => () => {} })
85
+ assert.doesNotThrow(() => registerAdapterType(a))
86
+ /* 등록은 되었고, 다만 그 능력은 선언되지 않았으므로 판정에서 빠진다 — 그 사실이 경고와 짝이다. */
87
+ assert.deepEqual(capabilitiesOf(a), [])
88
+ })
@@ -0,0 +1,48 @@
1
+ /*
2
+ * **층이 이름만 남지 않게 한다** — 계약은 계약 패키지에서 온다.
3
+ *
4
+ * ── 왜 있나 (2026-08-30) ──────────────────────────────────────────────────
5
+ * 어휘와 판별 함수를 `@operato/ops-contract` 로 갈라냈다. 사실을 만드는 쪽(MES · WMS · 커넥터)이
6
+ * 엔진을 의존하지 않게 하기 위해서다.
7
+ *
8
+ * ── 처음에는 커널이 계약을 다시 내보냈다 ──────────────────────────────────
9
+ * 이유는 「이 저장소를 한 줄도 안 고쳐도 되게」였다. 그것은 **관리 편의**이지 구조의 근거가 아니다.
10
+ * 그렇게 두면 같은 이름을 두 경로로 가져올 수 있고, 새 코드가 편한 쪽을 쓰면 계약이 커널 뒤에 숨는다.
11
+ *
12
+ * 그때는 이 파일에 **세는 시험**이 있었다 — 커널 경로로 가져오는 자리가 늘지 않는지 보는 것이다.
13
+ * 세는 시험은 층이 깨졌다는 것을 인정하고 빚을 재는 도구다. 재export 를 걷으면서 그 시험도 없앴다.
14
+ *
15
+ * 지금 남은 것은 **사실을 확인하는 것**뿐이다. 커널이 계약을 내보내지 않으므로 잘못된 경로는 아예
16
+ * 컴파일되지 않는다.
17
+ */
18
+ import { test } from 'node:test'
19
+ import assert from 'node:assert/strict'
20
+
21
+ import * as contract from '@operato/ops-contract'
22
+ import * as kernel from '@operato/twin-kernel'
23
+
24
+ test('★ 계약 패키지가 어휘를 내보낸다', () => {
25
+ const names = Object.keys(contract)
26
+ assert.ok(names.length > 100, `계약이 내보내는 이름이 ${names.length}개뿐이다 — 설치가 잘못됐다`)
27
+ for (const must of ['isEnergyRecord', 'ENERGY_EVENT', 'effectivityAt', 'ingestMasterData', 'procedureViolations']) {
28
+ assert.ok(names.includes(must), `${must} 가 계약에 없다`)
29
+ }
30
+ })
31
+
32
+ test('★ 커널은 계약을 다시 내보내지 않는다 — 내보내면 어휘가 엔진 뒤에 숨는다', () => {
33
+ const contractNames = new Set(Object.keys(contract))
34
+ const leaked = Object.keys(kernel).filter(n => contractNames.has(n))
35
+ assert.deepEqual(
36
+ leaked,
37
+ [],
38
+ `커널이 계약 이름을 다시 내보낸다:\n${leaked.slice(0, 20).join(', ')}\n` +
39
+ '두 경로로 가져올 수 있게 되면 새 코드가 편한 쪽을 쓰고, 사실을 만들기만 하는 쪽이 엔진을 의존하게 된다.'
40
+ )
41
+ })
42
+
43
+ test('커널은 엔진을 내보낸다 — 걷어내다가 필요한 것까지 걷지 않았는지 본다', () => {
44
+ const names = Object.keys(kernel)
45
+ for (const must of ['EmsKernel', 'MesKernel', 'WmsKernel', 'YmsKernel', 'StateProjector']) {
46
+ assert.ok(names.includes(must), `${must} 가 커널에 없다`)
47
+ }
48
+ })
@@ -12,7 +12,7 @@
12
12
  * 이 시험이 지키는 것:
13
13
  * ① 선언과 구현이 **함께** 있어야 능력이다 — 선언만 있으면 화면이 거짓말을 하고, 구현만 있으면 닫아 둔
14
14
  * 문을 여는 것이다(선언이 권위다).
15
- * ② 능력 없는 원본은 **사유 코드로** 거절한다(조용한 실패 금지).
15
+ * ② 능력 없는 원본은 **사유 코드로** 거절한다(들어온 것이 없는 실패 금지).
16
16
  * ③ 없는 기능을 있는 것처럼 답하지 않는다(내장 시뮬은 아직 재씨앗을 못 한다).
17
17
  */
18
18
  import { test } from 'node:test'
@@ -76,9 +76,9 @@ test('「몇 번·언제부터」를 정체 쪽도 든다 — 한 번 밀린 것
76
76
 
77
77
  test('뷰로 나간다 — **유입이 없는 갈래에서도**', () => {
78
78
  /*
79
- * ★ 여기가 가장 조용해지기 쉬운 자리다. 커서가 막히면 유입이 0이므로 장부가 「빈 장부」 갈래를
79
+ * ★ 여기가 아무 신호도 나지 않기 가장 쉬운 자리다. 커서가 막히면 유입이 0이므로 장부가 「빈 장부」 갈래를
80
80
  * 지나고, 그 갈래에서 이 값을 떨어뜨리면 **정체가 실제로 일어나는 바로 그 상황에서** 침묵한다.
81
- * `unhandled` 를 스냅샷이 떨어뜨려 정합성 검사가 영원히 조용했던 것과 같은 부류다.
81
+ * `unhandled` 를 스냅샷이 떨어뜨려 정합성 검사가 영원히 아무 신호도 내지 않았던 것과 같은 부류다.
82
82
  */
83
83
  const l = newIngestLedger()
84
84
  recordCursorStall(l, 'one timestamp holds more rows than a page', T0, 'trace')
@@ -5,7 +5,7 @@
5
5
  * 커널 쪽에는 같은 이름의 하네스가 있다(kernel/test/declaration-in-state: 선언 ⊆ 상태). 그런데 사실이
6
6
  * 새던 자리는 그 앞에도 있었다: **레퍼런스 마스터 → 커널 모델** 이라는 문 하나(`masterToTwin`).
7
7
  *
8
- * 여기서 새면 아무도 모른다. 원본은 말했고, 화면은 조용히 id 를 보인다. 실제로 그렇게 잃었다 —
8
+ * 여기서 새면 아무도 모른다. 원본은 말했고, 화면은 알리지 않고 id 를 보인다. 실제로 그렇게 잃었다 —
9
9
  * 자리의 이름(화면이 `cut-station` 을 찍었다), 사람의 이름, GS1 참조, 자원 속성, 시험 명세 참조,
10
10
  * 사람·설비의 시험 결과. 하루 사이에 여섯 번이고, 전부 **한 줄에서 필드 하나가 빠진** 것이었다.
11
11
  *
@@ -21,7 +21,7 @@
21
21
  * 셋 중 어느 것도 아니면 실패한다 — 새 필드를 추가한 사람이 그때 분류해야 한다. 분류를 강제하는 것이
22
22
  * 이 검사의 전부다: 잊는 것이 기본값이면 잊는다.
23
23
  *
24
- * 필드 목록은 **소스에서 읽는다**(손으로 적지 않는다). 손으로 적으면 새 필드가 조용히 빠지고, 그것이
24
+ * 필드 목록은 **소스에서 읽는다**(손으로 적지 않는다). 손으로 적으면 새 필드가 알리지 않고 빠지고, 그것이
25
25
  * 바로 이 검사가 막으려는 실패다.
26
26
  */
27
27
  import { test } from 'node:test'
@@ -70,7 +70,7 @@ const ELSEWHERE: Record<string, string> = {
70
70
  footprint: '좌표는 `model.layout`(중심점)과 구역 기하로 간다 — 자리 줄에 실리는 값이 아니다.'
71
71
  }
72
72
 
73
- /** 모든 필드에 값을 실은 원본 — 값을 모르는 필드가 나오면 이 하네스는 실패한다(조용히 건너뛰지 않는다). */
73
+ /** 모든 필드에 값을 실은 원본 — 값을 모르는 필드가 나오면 이 하네스는 실패한다(알리지 않고 건너뛰지 않는다). */
74
74
  const VALUE: Record<string, unknown> = {
75
75
  id: '__ID__',
76
76
  type: 'cut-station',
@@ -86,6 +86,8 @@ const VALUE: Record<string, unknown> = {
86
86
  level: 'WorkCenter',
87
87
  footprint: { x: 1, y: 2, w: 3, h: 4 },
88
88
  gs1Id: 'urn:epc:id:sgln:0614141.00001.0',
89
+ /* 설비의 정체성 — **GS1 키가 아닌 것이 정상이다**(실측: 인버터 하드웨어 식별자). */
90
+ identity: 'ES100000007d8d061d-1',
89
91
  homeLocationId: 'cut-station',
90
92
  mtbfMs: 600000,
91
93
  mttrMs: 60000,
@@ -122,7 +124,7 @@ function build(c: Case): any {
122
124
  assert.ok(
123
125
  f in VALUE,
124
126
  `픽스처가 "${c.iface}.${f}" 에 실을 값을 모른다 — VALUE 에 하나 적어라.\n` +
125
- '조용히 건너뛰면 그 필드는 아무도 안 보는 필드가 되고, 그것이 이 하네스가 막으려는 실패다.'
127
+ '알리지 않고 건너뛰면 그 필드는 아무도 안 보는 필드가 되고, 그것이 이 하네스가 막으려는 실패다.'
126
128
  )
127
129
  o[f] = f === 'id' ? c.id : VALUE[f]
128
130
  }
@@ -171,7 +173,7 @@ for (const c of CASES) {
171
173
  { lost, mangled },
172
174
  { lost: [], mangled: [] },
173
175
  `${c.iface}: 잃은 사실=[${lost}] · 옮기다 값이 바뀐 사실=[${mangled}]\n` +
174
- '원본이 말한 것을 문에서 버리면 아무도 모른다 — 화면은 조용히 id 를 보인다.\n' +
176
+ '원본이 말한 것을 문에서 버리면 아무도 모른다 — 화면은 알리지 않고 id 를 보인다.\n' +
175
177
  '일부러 다른 길로 보내는 것이면 ELSEWHERE 에 그 길을 적어라(이름만 바뀌는 것은 RENAMED).'
176
178
  )
177
179
  })
@@ -12,7 +12,7 @@
12
12
  * 첫 조각(`openLiveFeed`)이 같은 선언을 그대로 읽는다.
13
13
  *
14
14
  * 이 시험이 지키는 것:
15
- * ① 선언이 없으면 조용히 지난다(정적 구조만 반영하는 트윈이 정상이다).
15
+ * ① 선언이 없으면 알리지 않고 지난다(정적 구조만 반영하는 트윈이 정상이다).
16
16
  * ② **미러에는 싣지 않는다** — 외부 실물이 진실인 트윈에 도착을 지어 주면 그것은 현실이 아니다.
17
17
  * ③ 검사를 통과하지 못한 선언은 **태우지 않고** 이유를 남긴다(잘못된 선언 하나가 서버를 내린 이력이 있다).
18
18
  * ④ 검사기가 없으면 싣지 않는다 — 검사할 수 없는 선언을 태우는 것이 가장 비싼 선택이다.
@@ -30,7 +30,7 @@ const ok = () => ({ ok: true as const })
30
30
  const bad = () => ({ ok: false as const, errorCode: 'scenario-sku-mix-required' })
31
31
  const sim = { hasScenarioEngine: true, mode: 'sim' as const }
32
32
 
33
- test('선언이 없으면 조용히 지난다 — 구조만 반영하는 트윈이 정상이다', () => {
33
+ test('선언이 없으면 알리지 않고 지난다 — 구조만 반영하는 트윈이 정상이다', () => {
34
34
  assert.deepEqual(planStimulus(undefined, sim, ok), { action: 'skip', reason: 'none' })
35
35
  assert.deepEqual(planStimulus({ endpoint: 'x' }, sim, ok), { action: 'skip', reason: 'none' })
36
36
  /* 시나리오 자리에 객체가 아닌 것이 오면 선언이 아니다(문자열을 시나리오로 읽지 않는다). */
@@ -42,7 +42,7 @@ test('관측으로 도는 트윈에는 싣지 않는다 — 현실을 지어내
42
42
  assert.deepEqual(plan, { action: 'skip', reason: 'observed' })
43
43
  })
44
44
 
45
- test('시나리오 엔진이 없으면 싣지 않는다 — 조용히 아무 일도 없게 두지 않는다', () => {
45
+ test('시나리오 엔진이 없으면 싣지 않는다 — 알리지 않고 아무 일도 없게 두지 않는다', () => {
46
46
  const plan = planStimulus({ scenario }, { hasScenarioEngine: false, mode: 'sim' }, ok)
47
47
  assert.deepEqual(plan, { action: 'skip', reason: 'no-engine' })
48
48
  })
@@ -73,7 +73,7 @@ test('선언을 얹으면서 접속 정보를 지우지 않는다', () => {
73
73
  /*
74
74
  * ── 배선 (소스로 지킨다) ────────────────────────────────────────────────────
75
75
  * 판정은 순수 함수가 하지만, 그것을 **기동 때 부르지 않으면** 아무 일도 일어나지 않는다. 그리고 못 실은
76
- * 이유는 말해야 한다 — 「선언했는데 안 실렸다」가 조용하면 사용자는 트윈이 고장 난 줄로 읽는다.
76
+ * 이유는 말해야 한다 — 「선언했는데 안 실렸다」를 말하지 않으면 사용자는 트윈이 고장 난 줄로 읽는다.
77
77
  */
78
78
  test('두 기동 경로에서 부른다 — 시뮬은 싣고, 미러는 그 사실을 말한다', () => {
79
79
  const src = readFileSync(fileURLToPath(new URL('../server/engine/twin-engine.ts', import.meta.url)), 'utf-8')
@@ -2,7 +2,7 @@
2
2
  * 사이트 발견 결과 — **"못 찾았다" 를 "없다" 로 말하지 않게.**
3
3
  *
4
4
  * 실 시스템을 연결해 트윈을 만드는 경로에서, 발견이 실패하면 화면이 "사이트를 찾지 못했습니다" 라고만
5
- * 말했다. 레퍼런스 미등록·어댑터 미상·조회 실패·정말 0건이 그 한 문장으로 접혀 있었다 — 자격이
5
+ * 말했다. 레퍼런스 미등록·어댑터 미상·조회 실패·정말 0건이 그 한 문장에 섞여 있었다 — 자격이
6
6
  * 틀렸는지 주소가 틀렸는지 알 수 없었다.
7
7
  */
8
8
  import { test } from 'node:test'
@@ -50,7 +50,7 @@ test('네 가지 사유가 서로 구별된다 — 이것이 이 작업의 요
50
50
  discoveryTimedOut('s', 15).code
51
51
  ]
52
52
  assert.deepEqual(codes, ['reference-not-found', 'adapter-unknown', 'discovery-failed', 'discovery-timeout'])
53
- assert.equal(new Set(codes).size, 4, '넷이 한 코드로 접히지 않는다')
53
+ assert.equal(new Set(codes).size, 4, '넷이 한 코드로 합쳐지지 않는다')
54
54
  })
55
55
 
56
56
  test('원 시스템이 준 사유를 감추지 않는다 — 사용자가 관리자에게 물어볼 수 있게', () => {
@@ -3,10 +3,10 @@
3
3
  *
4
4
  * 지키는 것(2026-08-01 재편):
5
5
  * ① **레퍼런스가 아는 것은 전부 나간다** — 필드를 손으로 골라 담지 않는다. 화이트리스트였을 때
6
- * 노드 parentId·작업 전량·물품 전량이 조용히 누락됐다. 계약에 속성이 생기면 자동으로 도달해야 한다.
6
+ * 노드 parentId·작업 전량·물품 전량이 오류 없이 빠졌다. 계약에 속성이 생기면 자동으로 도달해야 한다.
7
7
  * ② **표식은 도메인 낱말을 훔치지 않는다** — 엔티티 종류 표식은 `entity`, `kind` 는 도메인 것
8
8
  * (무버 cutter · 작업 putaway · 오더 workorder).
9
- * ③ **조용히 자르지 않는다** — 물품 집약이 상한에 걸리면 total·truncated 로 밝힌다.
9
+ * ③ **알리지 않고 자르지 않는다** — 물품 집약이 상한에 걸리면 total·truncated 로 밝힌다.
10
10
  */
11
11
  import { test } from 'node:test'
12
12
  import assert from 'node:assert/strict'
@@ -111,7 +111,7 @@ test('물품: 개별(태그=epc) + 집약으로 나간다 — 수량·소속 팔
111
111
  assert.equal(agg.truncated, undefined, '상한 안이면 truncated 없음')
112
112
  })
113
113
 
114
- test('물품 집약: 상한을 넘으면 잘린 사실을 밝힌다 — 조용한 절단 금지', () => {
114
+ test('물품 집약: 상한을 넘으면 잘린 사실을 밝힌다 — 들어온 것이 없는 절단 금지', () => {
115
115
  const items = Array.from({ length: ITEMS_AGGREGATE_LIMIT + 7 }, (_, i) => ({ epc: `e${i}`, location: 'rack-1' }))
116
116
  const d = buildEntityDeltas({ items }, 't1')
117
117
  const agg = byTag(d, itemsTag('t1')).data
@@ -236,7 +236,7 @@ test('수요 구간·피크·계약은 집약 태그로 나간다 — 개체의
236
236
  assert.equal(agg.data.peakSince.kW, 4200)
237
237
  /* 하루치를 매 브로드캐스팅에 실으면 채널이 그것으로 찬다 — 최근 것만 내고 전체 수를 함께 낸다. */
238
238
  assert.equal(agg.data.recent.length, 8)
239
- assert.equal(agg.data.closedTotal, 137, '자른 사실을 조용히 두지 않는다')
239
+ assert.equal(agg.data.closedTotal, 137, '자른 사실을 알리지 않고 두지 않는다')
240
240
  })
241
241
 
242
242
  test('에너지가 없는 트윈은 에너지 태그를 내지 않는다 — 없는 것을 만들지 않는다', () => {
@@ -0,0 +1,57 @@
1
+ /*
2
+ * **선언한 설비 정체성이 투영까지 닿는가** — 선언에서 화면까지 걸어 본다.
3
+ *
4
+ * ── 무엇이 있었나 (2026-08-30) ────────────────────────────────────────────
5
+ * 투영이 `m.gs1Id` 를 읽고 있었는데 **선언에 그런 필드가 없었다.** 그래서 그 칸은 일곱 대 모두 영원히
6
+ * 비어 있었다. 오류는 나지 않았다 — `?? null` 이 늘 `null` 을 냈을 뿐이다.
7
+ *
8
+ * 값은 한 칸 옆에 있었다. 커넥터가 인버터의 하드웨어 식별자를 속성으로 이미 보내고 있었고
9
+ * (`properties.deviceId = ES100000007d8d061d-1`), 정체성 칸도 있었는데 **그 사이에 길이 없었다.**
10
+ *
11
+ * 이 저장소에서 가장 자주 나는 결함 부류다. 그래서 필드를 여는 것으로 끝내지 않고 **선언 → 투영**을
12
+ * 실제로 걸어 본다. 필드만 있고 길이 없으면 여기서 빨갛게 난다.
13
+ */
14
+ import { test } from 'node:test'
15
+ import assert from 'node:assert/strict'
16
+ import { readFileSync } from 'node:fs'
17
+ import { fileURLToPath } from 'node:url'
18
+ import { dirname, join } from 'node:path'
19
+
20
+ import type { TwinModelDef } from '@operato/ops-contract'
21
+
22
+ const here = dirname(fileURLToPath(import.meta.url))
23
+ const src = (p: string) => readFileSync(join(here, '..', 'server', p), 'utf-8')
24
+
25
+ test('★ 선언에 정체성 자리가 있다', () => {
26
+ /* 타입이 받아들이면 선언에 자리가 있는 것이다 — 없으면 이 파일이 컴파일되지 않는다. */
27
+ const model: TwinModelDef = {
28
+ locations: [{ id: 'incoming', type: 'incoming' }],
29
+ equipment: [{ id: '001', kind: 'pv-array', homeLocation: 'incoming', identity: 'ES100000007d8d061d-1' }]
30
+ } as any
31
+ assert.equal((model.equipment?.[0] as any).identity, 'ES100000007d8d061d-1')
32
+ })
33
+
34
+ test('★ 투영이 그 자리를 읽는다 — 선언에 없는 이름을 읽으면 칸이 영원히 빈다', () => {
35
+ const projection = src('service/twin-model/project-structure.ts')
36
+ assert.match(
37
+ projection,
38
+ /identity:\s*\(m as any\)\.identity/,
39
+ '설비 투영이 선언의 identity 를 읽어야 한다. 다른 이름을 읽으면 그 칸은 늘 null 이다.'
40
+ )
41
+ assert.doesNotMatch(
42
+ projection,
43
+ /gs1Id:\s*m\.gs1Id/,
44
+ '설비 선언에 gs1Id 는 없다 — 읽으면 늘 null 이 된다(2026-08-30 실측: 일곱 대 모두 비어 있었다)'
45
+ )
46
+ })
47
+
48
+ test('★ 저장 자리의 이름이 선언과 같다 — 두 벌이면 한쪽만 고쳐진다', () => {
49
+ const entity = src('service/twin-model/twin-equipment.ts')
50
+ assert.match(entity, /identity\?: string/, '설비 행에 identity 칸이 있어야 한다')
51
+ assert.doesNotMatch(entity, /gs1Id\?: string/, '설비에서는 gs1Id 를 쓰지 않는다 — 정체성이 GS1 키가 아닌 것이 정상이다')
52
+ })
53
+
54
+ test('자리(location)의 gs1Id 는 그대로 둔다 — 그쪽은 실제로 SGLN 이 온다', () => {
55
+ const entity = src('service/twin-model/twin-location.ts')
56
+ assert.match(entity, /gs1Id\?: string/, '자리는 GS1 키를 받는다 — 설비와 같은 이유로 지우면 안 된다')
57
+ })
@@ -0,0 +1,147 @@
1
+ /*
2
+ * ★ 사건 하나가 무엇을 가리키는지 뽑는다 — 360 이력이 64초인 원인을 없애는 첫 조각.
3
+ *
4
+ * ── 왜 이 모양인가 (2026-08-27 실측) ────────────────────────────────────────
5
+ * 「이 품목의 이력」 조회가 64초다. 저장된 식별자에 품목과 로트가 한 문자열로 붙어 있어서, 품목만으로는
6
+ * 찾을 방법이 없다.
7
+ *
8
+ * https://seunghwa.net/class/product-M0000618-lot-L2604100071
9
+ *
10
+ * 문자열을 자르는 규칙을 여기에 넣지 않는다 — 어느 부호가 로트를 가리키는지 연결된 시스템마다 다르고,
11
+ * chef 의 `-lot-` 을 굳히면 이 파일이 그 시스템 위에 서게 된다. 대신 대상마다 한 줄을 적고, 품목이
12
+ * 무엇인지는 그 트윈의 선언과 견주어 부르는 쪽이 답한다.
13
+ */
14
+ import { test } from 'node:test'
15
+ import assert from 'node:assert/strict'
16
+ import { subjectsOf, SUBJECT_KINDS } from '../server/service/twin-subject/event-subjects.js'
17
+
18
+ const objectEvent = (over: any = {}) => ({
19
+ eventType: 'epcis.ObjectEvent',
20
+ eventTime: '2026-08-27T05:00:00.000Z',
21
+ data: {
22
+ epcList: ['https://seunghwa.net/class/product-M0000438-lot-L2403090386'],
23
+ bizLocation: { id: 'M2R0001' },
24
+ action: 'OBSERVE',
25
+ ...over
26
+ }
27
+ })
28
+
29
+ const find = (subjects: any[], kind: string) => subjects.filter(s => s.kind === kind).map(s => s.id)
30
+
31
+ test('★ 품목과 로트를 두 줄로 적는다 — 문자열을 자르지 않고도 품목으로 찾을 수 있다', () => {
32
+ const subjects = subjectsOf(objectEvent(), {
33
+ /* 선언과 견주어 품목을 찾는 일은 부르는 쪽이 한다 — 그 트윈의 선언을 아는 것이 그쪽이다. */
34
+ itemOf: epc => (epc.includes('product-M0000438') ? 'M0000438' : null)
35
+ })
36
+ assert.deepEqual(find(subjects, 'lot'), ['https://seunghwa.net/class/product-M0000438-lot-L2403090386'])
37
+ assert.deepEqual(find(subjects, 'item'), ['M0000438'])
38
+ })
39
+
40
+ test('★ 품목을 찾지 못하면 품목 줄을 만들지 않는다 — 짐작으로 이으면 남의 이력에 섞인다', () => {
41
+ const subjects = subjectsOf(objectEvent(), { itemOf: () => null })
42
+ assert.equal(find(subjects, 'lot').length, 1, '개체는 그대로 적는다')
43
+ assert.deepEqual(find(subjects, 'item'), [], '품목은 비운다')
44
+ })
45
+
46
+ test('★ 찾은 값이 개체 식별자와 같으면 품목이 아니다 — itemRefOf 는 URI 를 그대로 돌려준다', () => {
47
+ /*
48
+ * `itemRefOf` 는 SGTIN 이 아닌 모양에서 문자열을 그대로 낸다(자르는 판단을 하지 않으므로). 그 값을
49
+ * 품목으로 적으면 로트와 같은 줄이 두 종류로 생기고, 품목 이력이 로트 이력과 같아진다.
50
+ */
51
+ const epc = 'https://seunghwa.net/class/product-M0000438-lot-L2403090386'
52
+ const subjects = subjectsOf(objectEvent(), { itemOf: () => epc })
53
+ assert.deepEqual(find(subjects, 'item'), [])
54
+ })
55
+
56
+ test('★ 한 사건이 여러 대상을 가리킨다 — 자리·설비·오더·작업', () => {
57
+ const subjects = subjectsOf({
58
+ eventType: 'task.status',
59
+ eventTime: '2026-08-27T05:00:00.000Z',
60
+ data: { taskId: 'tk-1', moverId: 'mixer-1', toNode: 'M2R0001', orderId: 'ORD-9' }
61
+ })
62
+ assert.deepEqual(find(subjects, 'task'), ['tk-1'])
63
+ assert.deepEqual(find(subjects, 'equipment'), ['mixer-1'])
64
+ assert.deepEqual(find(subjects, 'order'), ['ORD-9'])
65
+ assert.ok(find(subjects, 'location').length <= 1, '자리는 한 줄이다')
66
+ })
67
+
68
+ test('★ 가리키는 것이 없으면 빈 배열이다 — 없는 대상을 만들지 않는다', () => {
69
+ const subjects = subjectsOf({ eventType: 'energy.demand.window', data: { maxKW: 3 } })
70
+ assert.deepEqual(subjects, [])
71
+ })
72
+
73
+ test('★ 작업이 오간 두 자리를 다 적는다 — 승격 컬럼은 둘 중 하나만 담을 수 있어 비워 둔다', () => {
74
+ /*
75
+ * `locationOf` 는 EPCIS 자리만 읽는다. 작업이 A→B 로 갈 때 컬럼 하나에 둘을 넣을 수 없으므로
76
+ * 아무것도 넣지 않는다. 이 표는 자리마다 줄이 하나라서 둘을 함께 담는다.
77
+ */
78
+ const subjects = subjectsOf({
79
+ eventType: 'task.status',
80
+ data: { taskId: 'tk-1', fromNode: 'M2R0001', toNode: 'M2R0002' }
81
+ })
82
+ assert.deepEqual(find(subjects, 'location'), ['M2R0001', 'M2R0002'])
83
+ })
84
+
85
+ test('★ 같은 대상을 두 번 적지 않는다 — 출발지와 도착지가 같을 수 있다', () => {
86
+ const subjects = subjectsOf({
87
+ eventType: 'task.status',
88
+ data: { taskId: 'tk-1', fromNode: 'M2R0001', toNode: 'M2R0001' }
89
+ })
90
+ assert.deepEqual(find(subjects, 'location'), ['M2R0001'])
91
+ })
92
+
93
+ test('종류 목록이 닫혀 있다 — 열어 두면 넣는 쪽마다 다른 이름을 쓴다', () => {
94
+ assert.deepEqual([...SUBJECT_KINDS], ['item', 'lot', 'order', 'location', 'equipment', 'task'])
95
+ })
96
+
97
+ test('너무 긴 식별자는 적지 않는다 — 자르면 그 줄이 다른 대상을 가리킨다', () => {
98
+ const long = 'x'.repeat(300)
99
+ const subjects = subjectsOf({ eventType: 'task.status', data: { taskId: long } })
100
+ assert.deepEqual(find(subjects, 'task'), [], '알리지 않고 자르지 않고 아예 만들지 않는다')
101
+ })
102
+
103
+ test('빈 값과 공백은 대상이 아니다 — 결측을 대상으로 만들지 않는다', () => {
104
+ const subjects = subjectsOf({ eventType: 'task.status', data: { taskId: ' ', moverId: '' } })
105
+ assert.deepEqual(subjects, [])
106
+ })
107
+
108
+ test('★ 클래스로 온 사건은 품목 줄만 만든다 — 개체가 없는 사건이다', () => {
109
+ /*
110
+ * 실측한 chef 의 사건이다. 무게로 담는 현장이라 개체 목록이 비어 있고 수량 목록에 클래스가 있다.
111
+ * 그것을 로트로 적으면 뜻이 틀린다 — 개체가 아니라 품목이다.
112
+ */
113
+ const subjects = subjectsOf({
114
+ eventType: 'epcis.ObjectEvent',
115
+ data: {
116
+ epcList: [],
117
+ quantityList: [{ epcClass: 'https://seunghwa.net/class/product-M0000879', quantity: 11797.6, uom: 'KGM' }],
118
+ bizLocation: { id: 'P2F0001' }
119
+ }
120
+ })
121
+ assert.deepEqual(find(subjects, 'lot'), [], '개체 줄을 만들지 않는다')
122
+ assert.deepEqual(find(subjects, 'item'), ['https://seunghwa.net/class/product-M0000879'])
123
+ assert.deepEqual(find(subjects, 'location'), ['P2F0001'])
124
+ })
125
+
126
+ test('★ 클래스도 선언과 견주어 품목 코드로 줄인다 — 선언이 답하면 그것을 쓴다', () => {
127
+ const subjects = subjectsOf(
128
+ {
129
+ eventType: 'epcis.ObjectEvent',
130
+ data: { epcList: [], quantityList: [{ epcClass: 'https://seunghwa.net/class/product-M0000879' }] }
131
+ },
132
+ { itemOf: epc => (epc.includes('M0000879') ? 'M0000879' : null) }
133
+ )
134
+ assert.deepEqual(find(subjects, 'item'), ['M0000879'])
135
+ })
136
+
137
+ test('개체와 클래스가 함께 오면 둘 다 적는다 — 한 사건이 둘을 가리킬 수 있다', () => {
138
+ const subjects = subjectsOf({
139
+ eventType: 'epcis.ObjectEvent',
140
+ data: {
141
+ epcList: ['https://seunghwa.net/class/product-M0000438-lot-L1'],
142
+ quantityList: [{ epcClass: 'https://seunghwa.net/class/product-M0000879' }]
143
+ }
144
+ })
145
+ assert.deepEqual(find(subjects, 'lot'), ['https://seunghwa.net/class/product-M0000438-lot-L1'])
146
+ assert.deepEqual(find(subjects, 'item'), ['https://seunghwa.net/class/product-M0000879'])
147
+ })
@@ -5,9 +5,9 @@
5
5
  * 예전에는 문자열 컬럼이었고, "UTC ISO 만 들어오니 사전식 비교가 맞는다" 는 전제 위에서 정렬·범위
6
6
  * 질의가 돌았다. 그 전제는 컬럼 어디에도 적혀 있지 않았다.
7
7
  *
8
- * 날짜 컬럼으로 바꾸면서 **조용한 함정**이 하나 드러났다: sqlite 는 datetime 을 공백 구분 형식
8
+ * 날짜 컬럼으로 바꾸면서 **들어온 것이 없는 함정**이 하나 드러났다: sqlite 는 datetime 을 공백 구분 형식
9
9
  * (`2026-08-07 11:00:00.000`)으로 저장하는데, 화면은 ISO 문자열(`…T11:00:00.000Z`)을 보낸다.
10
- * 문자열 그대로 비교하면 사전식으로 어긋나 **오류 없이 0건**이 나온다 — 원장 시간창이 조용히
10
+ * 문자열 그대로 비교하면 사전식으로 어긋나 **오류 없이 0건**이 나온다 — 원장 시간창이 알리지 않고
11
11
  * 비어 버리고, 사용자는 "그 시간에 아무 일도 없었다" 로 읽는다.
12
12
  *
13
13
  * 그래서 두 가지를 못 박는다: 저장 형식이 실제로 그렇다는 것과, 경계에서 날짜로 옮기면 걸린다는 것.
@@ -48,7 +48,7 @@ test('sqlite 는 datetime 을 공백 구분 형식으로 저장한다 — ISO
48
48
  await ds.destroy()
49
49
  })
50
50
 
51
- test('ISO 문자열을 그대로 넘기면 **조용히 0건** — 오류가 아니라 빈 결과라 더 위험하다', async () => {
51
+ test('ISO 문자열을 그대로 넘기면 **알리지 않고 0건** — 오류가 아니라 빈 결과라 더 위험하다', async () => {
52
52
  const { ds, repo } = await seeded()
53
53
  const rows = await repo.find({
54
54
  where: { eventTime: Between('2026-08-07T10:00:00.000Z', '2026-08-07T12:00:00.000Z') }