@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,132 @@
1
+ /*
2
+ * ★ 같은 사실을 두 번 받으면 한 번만 반영한다.
3
+ *
4
+ * ── 왜 이것이 핵심인가 (2026-08-26 · 사용자) ─────────────────────────────────
5
+ * 「웹훅 이벤트이건 폴링 데이터이건 아이뎀포턴트를 유지할 수 있도록 로직을 세우는 것」이 핵심이다.
6
+ * 그것이 서면 가져오는 방식이 무엇이든 트윈이 안전하고, 커넥터마다 각자 조심하지 않아도 된다.
7
+ *
8
+ * ── 무엇이 났나 ─────────────────────────────────────────────────────────────
9
+ * 지금까지 중복은 커넥터마다 각자 막았다. 태양광 커넥터가 10초마다 물으면서 10분마다 바뀌는 값을 매번
10
+ * 다시 보냈고, 한 주기에 76건이 되풀이됐다(커넥터를 고쳐 2건이 됐다). 다음 커넥터가 잊으면 또 난다.
11
+ * 밀어 주는 방식으로 받으면 더 자주 난다 — 재전송이 정상이다.
12
+ *
13
+ * ── 정체를 무엇으로 보나 ────────────────────────────────────────────────────
14
+ * 봉투의 `eventId` 로는 판단할 수 없다. 보낸 쪽이 매번 새로 만드는 순번이고, 호출마다 1부터 다시
15
+ * 시작한다. 그래서 사실 자체로 판단한다 — 대상 · 사건 종류 · 연결된 시스템이 준 시각.
16
+ */
17
+ import { test } from 'node:test'
18
+ import assert from 'node:assert/strict'
19
+ import { factIdentity, FactDeduper } from '../server/engine/ingest-dedupe.js'
20
+
21
+ const at = '2026-08-26T00:00:00.000Z'
22
+
23
+ const energy = (equipmentId: string, kWh: number, when = at, eventId = 'x') => ({
24
+ eventId,
25
+ eventType: 'energy.generated',
26
+ eventTime: when,
27
+ tenantId: 't',
28
+ data: { equipmentId, kWh, at: when }
29
+ })
30
+
31
+ const status = (moverId: string, s: string, when = at) => ({
32
+ eventId: `s-${moverId}`,
33
+ eventType: 'equipment.status',
34
+ eventTime: when,
35
+ tenantId: 't',
36
+ data: { moverId, kind: 'pv-array', status: s, at: when }
37
+ })
38
+
39
+ test('★ 같은 사실이 두 번 오면 한 번만 지난다 — 보낸 쪽이 되풀이 보내도 트윈이 틀리지 않는다', () => {
40
+ const d = new FactDeduper()
41
+ const { fresh, duplicates } = d.filter([energy('inv-1', 574.8), energy('inv-1', 574.8)])
42
+ assert.equal(fresh.length, 1)
43
+ assert.equal(duplicates, 1)
44
+ })
45
+
46
+ test('★ 봉투의 순번이 달라도 같은 사실이면 한 번이다 — eventId 로는 판단할 수 없다', () => {
47
+ /* 같은 측정을 두 번 보내면 보낸 쪽이 새 순번을 붙인다. 그것으로 판단하면 중복을 못 잡는다. */
48
+ const d = new FactDeduper()
49
+ const { fresh, duplicates } = d.filter([energy('inv-1', 574.8, at, 't-energy-1'), energy('inv-1', 574.8, at, 't-energy-2')])
50
+ assert.equal(fresh.length, 1)
51
+ assert.equal(duplicates, 1)
52
+ })
53
+
54
+ test('★ 다른 사실은 지난다 — 대상이 다르면 다른 사실이다', () => {
55
+ const d = new FactDeduper()
56
+ const { fresh, duplicates } = d.filter([energy('inv-1', 574.8), energy('inv-2', 577.2)])
57
+ assert.equal(fresh.length, 2)
58
+ assert.equal(duplicates, 0)
59
+ })
60
+
61
+ test('★ 잰 시각이 나아가면 다른 사실이다 — 그것이 새 측정이다', () => {
62
+ const d = new FactDeduper()
63
+ const { fresh } = d.filter([energy('inv-1', 574.8, at), energy('inv-1', 580.1, '2026-08-26T00:10:00.000Z')])
64
+ assert.equal(fresh.length, 2)
65
+ })
66
+
67
+ test('★ 사건 종류가 다르면 다른 사실이다 — 같은 설비의 같은 시각이어도', () => {
68
+ const d = new FactDeduper()
69
+ const { fresh } = d.filter([energy('inv-1', 574.8), status('inv-1', 'busy')])
70
+ assert.equal(fresh.length, 2)
71
+ })
72
+
73
+ test('★ 정체를 알 수 없으면 거르지 않는다 — 모르는 것을 같다고 하면 다른 사실이 하나로 합쳐진다', () => {
74
+ const d = new FactDeduper()
75
+ const noTarget = { eventId: 'a', eventType: 'energy.demand.window', eventTime: at, tenantId: 't', data: { maxKW: 3 } }
76
+ const noTime = { eventId: 'b', eventType: 'equipment.status', tenantId: 't', data: { moverId: 'inv-9', status: 'idle' } }
77
+ assert.equal(factIdentity(noTarget), undefined)
78
+ assert.equal(factIdentity(noTime), undefined)
79
+ const { fresh, duplicates } = d.filter([noTarget, noTarget, noTime, noTime])
80
+ assert.equal(fresh.length, 4, '거르지 않고 통과시킨다')
81
+ assert.equal(duplicates, 0)
82
+ })
83
+
84
+ test('★ 늦게 도착한 옛 사실은 버리지 않는다 — 그것이 왔다는 것도 사실이다', () => {
85
+ /*
86
+ * 상태에 반영하지 않는 판단은 커널이 한다(시각 비교). 여기서 버리면 지난 기록에서 사라지고,
87
+ * 「늦게 왔다」는 사실 자체를 아무도 알 수 없게 된다.
88
+ */
89
+ const d = new FactDeduper()
90
+ const { fresh, duplicates } = d.filter([
91
+ energy('inv-1', 580.1, '2026-08-26T00:10:00.000Z'),
92
+ energy('inv-1', 574.8, at)
93
+ ])
94
+ assert.equal(fresh.length, 2)
95
+ assert.equal(duplicates, 0)
96
+ })
97
+
98
+ test('★ 창을 넘으면 잊는다 — 전부 기억할 수는 없다', () => {
99
+ const window = FactDeduper.WINDOW
100
+ try {
101
+ FactDeduper.WINDOW = 3
102
+ const d = new FactDeduper()
103
+ d.filter([energy('inv-1', 1, '2026-08-26T00:00:00.000Z')])
104
+ d.filter([
105
+ energy('inv-1', 2, '2026-08-26T00:01:00.000Z'),
106
+ energy('inv-1', 3, '2026-08-26T00:02:00.000Z'),
107
+ energy('inv-1', 4, '2026-08-26T00:03:00.000Z')
108
+ ])
109
+ /* 첫 사실은 창 밖으로 밀려났으므로 다시 오면 통과한다 — 그 사실을 시험으로 못 박는다. */
110
+ const { duplicates } = d.filter([energy('inv-1', 1, '2026-08-26T00:00:00.000Z')])
111
+ assert.equal(duplicates, 0)
112
+ assert.ok(d.size() <= 3)
113
+ } finally {
114
+ FactDeduper.WINDOW = window
115
+ }
116
+ })
117
+
118
+ test('버린 수를 함께 낸다 — 알리지 않고 버리지 않는다', () => {
119
+ const d = new FactDeduper()
120
+ const same = [energy('inv-1', 1), energy('inv-1', 1), energy('inv-1', 1), energy('inv-2', 2)]
121
+ const { fresh, duplicates } = d.filter(same)
122
+ assert.equal(fresh.length, 2)
123
+ assert.equal(duplicates, 2)
124
+ })
125
+
126
+ test('트윈마다 따로 센다 — 한 트윈의 되풀이가 다른 트윈을 막지 않는다', () => {
127
+ const a = new FactDeduper()
128
+ const b = new FactDeduper()
129
+ a.filter([energy('inv-1', 1)])
130
+ const { duplicates } = b.filter([energy('inv-1', 1)])
131
+ assert.equal(duplicates, 0)
132
+ })
@@ -1,5 +1,5 @@
1
1
  /*
2
- * 인제스트 공간 판정 — "이 현장에 더한다" 가 조용히 빗나가지 않게.
2
+ * 인제스트 공간 판정 — "이 현장에 더한다" 가 알리지 않고 빗나가지 않게.
3
3
  *
4
4
  * 엔진 본체는 DB·TypeORM 을 물고 있어 단위테스트로 부를 수 없다. 그래서 **결정만** 순수 모듈로
5
5
  * 떼어 놓고 여기서 본다(엔진은 공간 존재 여부를 조회해 넘기는 일만 한다).
@@ -26,7 +26,7 @@ test('마스터가 이미 그 공간이면 합치기가 아니다 — 같은 공
26
26
  assert.deepEqual(c, { spaceId: 'rosarito-site', joined: false })
27
27
  })
28
28
 
29
- test('없는 현장은 거절한다 — 조용히 새 공간을 만들지 않는다', () => {
29
+ test('없는 현장은 거절한다 — 알리지 않고 새 공간을 만들지 않는다', () => {
30
30
  /* 여기서 새 공간을 만들어 주면 사용자는 합쳤다고 믿고 화면은 따로 논다. 그때는 이미 양쪽에
31
31
  이벤트가 쌓여 있어 손으로 합치는 것이 훨씬 비싸다. */
32
32
  assert.throws(
@@ -62,7 +62,7 @@ test('사이트 하나면 그 현장에 넣는다 — 렌즈를 더하는 정상
62
62
  assert.equal(refuseImportSpace(1, 'rosarito-site'), undefined)
63
63
  })
64
64
 
65
- test('사이트 여럿을 한 현장에 넣는 것은 거절한다 — 서로 다른 현장이 조용히 합쳐진다', () => {
65
+ test('사이트 여럿을 한 현장에 넣는 것은 거절한다 — 서로 다른 현장이 알리지 않고 합쳐진다', () => {
66
66
  const r = refuseImportSpace(3, 'rosarito-site')
67
67
  assert.ok(r, '거절해야 한다')
68
68
  assert.equal(r.code, 'space-with-multiple-sites')
@@ -0,0 +1,197 @@
1
+ /*
2
+ * 유입 배선 감시 — **커널이 문을 열었는데 그 문으로 가는 길이 없는 것**을 잡는다.
3
+ *
4
+ * ── 왜 있나 (2026-08-30) ────────────────────────────────────────────────────
5
+ * 2026-08-29 하루에 이 부류가 다섯 번 났다. 커널에 새 어휘의 문을 만들고, 정규 유입의 갈림에
6
+ * 그 길을 내지 않은 것이다. 그때마다 레코드는 **거부되지도 않고 아무 문에도 들어가지 못한 채**
7
+ * 사라졌다 — 커넥터는 보냈다고 믿고, 화면은 값이 없다고 말한다. 사람이 하나씩 찾아냈다.
8
+ *
9
+ * 다음 날 같은 부류의 **두 번째 얼굴**이 드러났다. EPCIS 갈래는 「나머지 전부」라, 새 어휘를 그
10
+ * 목록에서 빼 주지 않으면 레코드가 제 문으로 가면서 **EPCIS 문으로도 들어가 거부된다.** 받아들여
11
+ * 지기도 하고 거부되기도 하는 상태가 되어, 유입 장부의 거부 수가 늘고 사람이 그것을 진짜 결함으로
12
+ * 읽는다.
13
+ *
14
+ * ── 무엇을 지키나 ──────────────────────────────────────────────────────────
15
+ * ① 커널의 유입 함수마다 부르는 곳이 있다
16
+ * ② 어휘마다 **정확히 한 문**으로 간다 — 어느 문에도 못 가거나 두 문에 걸치지 않는다
17
+ *
18
+ * ── 목록을 손으로 적지 않는다 ──────────────────────────────────────────────
19
+ * 커널이 내보내는 이름에서 읽는다. 커널이 문을 늘리면 이 시험이 먼저 빨갛게 난다. 손 목록을 두면
20
+ * 늘어난 것을 모른다 — 이 저장소가 이미 그 실패를 겪었다(`vocabulary-labels` 의 주석).
21
+ */
22
+ import { test } from 'node:test'
23
+ import assert from 'node:assert/strict'
24
+ import { readFileSync } from 'node:fs'
25
+ import { fileURLToPath } from 'node:url'
26
+ import { dirname, join } from 'node:path'
27
+
28
+ /* 유입 문과 판별 함수는 계약에 있다 — 커널은 엔진만 내보낸다(2026-08-30 층 분리). */
29
+ import * as kernel from '@operato/ops-contract'
30
+ import { ingestCanonicalRecords } from '../server/engine/canonical-ingest.ts'
31
+
32
+ const here = dirname(fileURLToPath(import.meta.url))
33
+ const routerSource = readFileSync(join(here, '..', 'server', 'engine', 'canonical-ingest.ts'), 'utf-8')
34
+
35
+ /**
36
+ * 정규 유입이 부르지 **않아도 되는** 것 — 각각 이유가 있다.
37
+ *
38
+ * 이 목록에 올릴 때는 이유를 함께 적는다. 이유 없이 올리면 이 시험은 아무것도 지키지 않는다.
39
+ */
40
+ const NOT_ROUTED: Record<string, string> = {
41
+ /* 물류 어휘의 진입점 자체다 — 갈림이 이것을 부른다(이름이 `ingest` 하나뿐이라 규칙에 안 걸린다). */
42
+ ingest: 'EPCIS 어휘의 진입점. 갈림이 직접 부른다',
43
+ /* 마스터 데이터는 사건이 아니라 어휘 갱신이라 다른 경로로 간다. */
44
+ ingestMasterData: '사건이 아니라 어휘 갱신 — 참조 계층이 부른다'
45
+ }
46
+
47
+ test('★ 커널의 유입 함수마다 부르는 곳이 있다', () => {
48
+ const ingests = Object.keys(kernel).filter(n => /^ingest/.test(n))
49
+ assert.ok(ingests.length >= 8, `유입 함수를 못 읽었다(${ingests.length}개) — 커널을 다시 읽어야 한다`)
50
+
51
+ const unrouted = ingests.filter(n => !NOT_ROUTED[n] && !routerSource.includes(n))
52
+ assert.deepEqual(
53
+ unrouted,
54
+ [],
55
+ `커널이 문을 열었는데 정규 유입에 길이 없다:\n${unrouted.join('\n')}\n` +
56
+ '그 문으로 보내는 레코드는 거부되지도 않고 사라진다.\n' +
57
+ '갈림에 길을 내거나, 부르지 않아도 되는 이유를 NOT_ROUTED 에 적어라.'
58
+ )
59
+ })
60
+
61
+ test('부르지 않아도 되는 목록에 이유가 적혀 있다', () => {
62
+ for (const [name, why] of Object.entries(NOT_ROUTED)) {
63
+ assert.ok(why.trim().length > 5, `${name} 에 이유가 없다`)
64
+ }
65
+ })
66
+
67
+ /**
68
+ * 어휘마다 대표 레코드 하나 — **그 문으로만 가야 한다.**
69
+ *
70
+ * 값은 실제로 오는 모양에서 가져왔다(2026-08-29 실측). 지어낸 모양으로 검사하면 실제 레코드가
71
+ * 다른 문으로 가는 것을 놓친다.
72
+ */
73
+ const NOW = '2026-08-30T00:00:00.000Z'
74
+ const SAMPLES: { vocabulary: string; eventType: string; record: Record<string, unknown> }[] = [
75
+ {
76
+ vocabulary: '물품 관측(EPCIS)',
77
+ eventType: 'epcis.ObjectEvent',
78
+ record: {
79
+ epc: 'urn:epc:id:sgtin:0614141.107346.2017',
80
+ action: 'OBSERVE',
81
+ bizStep: 'urn:epcglobal:cbv:bizstep:storing',
82
+ readPoint: 'rack-1'
83
+ }
84
+ },
85
+ { vocabulary: '계량 표본', eventType: 'energy.measured', record: { meterId: 'incoming', kW: 100, at: NOW } },
86
+ {
87
+ vocabulary: '설비 상태',
88
+ eventType: 'energy.equipment',
89
+ record: { equipmentId: 'inv-1', generatedKW: 24.6, dcVoltage: 469.13, at: NOW }
90
+ },
91
+ { vocabulary: '발전 적산', eventType: 'energy.generated', record: { equipmentId: 'inv-1', kWh: 174, at: NOW } },
92
+ {
93
+ vocabulary: '마감된 사용 구간',
94
+ eventType: 'energy.usage.period',
95
+ record: {
96
+ meterId: 'incoming',
97
+ from: '2026-08-29T15:00:00.000Z',
98
+ to: '2026-08-29T16:00:00.000Z',
99
+ kWh: 474.48,
100
+ maxKW: 478.08,
101
+ unitPrice: 87.3,
102
+ currency: 'KRW',
103
+ basis: 'provider'
104
+ }
105
+ },
106
+ {
107
+ vocabulary: '요금 기준',
108
+ eventType: 'energy.tariff.basis',
109
+ record: {
110
+ /* 기준 선언은 **끝나지 않은 달**이 정상이다 — 사건 시각이 기간의 시작이라 미래에 적히지 않는다. */
111
+ from: '2026-07-31T15:00:00.000Z',
112
+ to: '2026-08-31T15:00:00.000Z',
113
+ billingDemandKW: 1650,
114
+ demandChargePerKW: 8320,
115
+ currency: 'KRW'
116
+ }
117
+ },
118
+ {
119
+ vocabulary: '청구서',
120
+ eventType: 'energy.bill',
121
+ record: {
122
+ /*
123
+ * 청구서는 **마감된 달**이다. 처음 이 표본은 끝나지 않은 8월을 담고 있었는데, 그것은 실제로
124
+ * 들어와 있던 잘못된 데이터를 그대로 베낀 것이었다(2026-08-30). 그 데이터 때문에 한 현장의
125
+ * 성과 구간이 미래로 밀려 화면이 비어 있었다. 표본이 결함을 베끼면 시험이 결함을 지킨다.
126
+ */
127
+ from: '2026-06-30T15:00:00.000Z',
128
+ to: '2026-07-31T15:00:00.000Z',
129
+ demandCharge: 13_728_000,
130
+ currency: 'KRW',
131
+ billingDemandKW: 1650
132
+ }
133
+ },
134
+ {
135
+ vocabulary: '발전 단가',
136
+ eventType: 'energy.generation.price',
137
+ record: { from: '2026-08-29T15:00:00.000Z', to: '2026-08-30T15:00:00.000Z', unitPrice: 148.61, currency: 'KRW' }
138
+ },
139
+ {
140
+ /*
141
+ * 라이브에서는 커널이 스스로 마감해 내지만, **지난 기록을 채울 때**는 밖에서 들어온다.
142
+ * 구간이 있으므로 적산의 문(`energy.generated`)이 아니라 이쪽으로 가야 한다 — 겹치면 지난
143
+ * 기록이 「지금 적산」으로도 읽혀 트윈의 시계가 과거로 끌린다.
144
+ */
145
+ vocabulary: '마감된 발전 기간',
146
+ eventType: 'energy.generation.period',
147
+ record: {
148
+ equipmentId: '001',
149
+ from: '2026-08-28T15:00:00.000Z',
150
+ to: '2026-08-29T15:00:00.000Z',
151
+ kWh: 394.3,
152
+ accumulation: 'daily'
153
+ }
154
+ }
155
+ ]
156
+
157
+ test('★ 어휘마다 정확히 한 문으로 간다 — 사라지지도, 두 문에 걸치지도 않는다', () => {
158
+ const wrong: string[] = []
159
+ for (const s of SAMPLES) {
160
+ const r = ingestCanonicalRecords([s.record] as any, 't', NOW)
161
+ const accepted = (r.accepted as any[]).map(e => e.eventType)
162
+ if (r.rejected.length) {
163
+ wrong.push(`${s.vocabulary}: 거부됐다 — ${JSON.stringify(r.rejected[0]?.errors)}`)
164
+ continue
165
+ }
166
+ if (accepted.length !== 1) {
167
+ wrong.push(`${s.vocabulary}: 봉투가 ${accepted.length}개다(${accepted.join(', ')}) — 한 문으로만 가야 한다`)
168
+ continue
169
+ }
170
+ if (accepted[0] !== s.eventType) wrong.push(`${s.vocabulary}: ${s.eventType} 이어야 하는데 ${accepted[0]} 로 갔다`)
171
+ }
172
+ assert.deepEqual(wrong, [], `\n${wrong.join('\n')}`)
173
+ })
174
+
175
+ test('★ 한 배치에 모든 어휘가 섞여 와도 각자의 문으로 간다', () => {
176
+ const r = ingestCanonicalRecords(SAMPLES.map(s => s.record) as any, 't', NOW)
177
+ assert.deepEqual(r.rejected, [], `거부가 없어야 한다: ${JSON.stringify(r.rejected)}`)
178
+ assert.deepEqual(
179
+ (r.accepted as any[]).map(e => e.eventType).sort(),
180
+ SAMPLES.map(s => s.eventType).sort()
181
+ )
182
+ })
183
+
184
+ test('커널이 어휘를 늘리면 이 시험이 먼저 안다', () => {
185
+ /*
186
+ * 판별 함수의 수와 위 표본의 수가 어긋나면, 커널에 어휘가 늘었는데 여기 표본이 없다는 뜻이다.
187
+ * (EPCIS 안의 갈림 둘(담김·변환)은 한 어휘로 센다 — 같은 문으로 간다.)
188
+ */
189
+ const gates = Object.keys(kernel).filter(n => /^is.*Record$/.test(n))
190
+ const counted = gates.filter(n => !['isAggregationRecord', 'isTransformationRecord', 'isMasterDataRecord'].includes(n))
191
+ assert.equal(
192
+ counted.length,
193
+ SAMPLES.length,
194
+ `커널의 어휘는 ${counted.length}개인데 표본은 ${SAMPLES.length}개다:\n${counted.join(' ')}\n` +
195
+ '늘어난 어휘의 표본을 SAMPLES 에 더하라 — 그래야 그 어휘도 한 문으로 가는지 검사된다.'
196
+ )
197
+ })
@@ -2,7 +2,7 @@
2
2
  * ★ 연결 적합성 하네스 — **절반만 돌린 스위트가 초록을 주장하지 못하게.**
3
3
  *
4
4
  * 지키는 것:
5
- * ① 돌리지 않은 프로브는 **「모른다」**로 접힌다 → 판정이 그 단계를 달성으로 만들지 않는다.
5
+ * ① 돌리지 않은 프로브는 **「모른다」**로 처리된다 → 판정이 그 단계를 달성으로 만들지 않는다.
6
6
  * ② **실패한 프로브의 수를 0 으로 읽지 않는다.** 「0건이었다」와 「세지 못했다」는 다른 사실이다.
7
7
  * ③ **쓰기 프로브는 동의 없이 계획에 들어오지 않는다** — 남의 운영 시스템에 명령을 넣는 일이다.
8
8
  * ④ **게이팅 실패 뒤는 돌리지 않는다** — 빨간 칸을 늘리는 것은 원인을 알려 주지 않는다.
@@ -29,7 +29,7 @@ test('시퀀스는 읽기부터 시작하고 쓰기 프로브는 하나뿐이다
29
29
  assert.deepEqual(PROBE_SEQUENCE.filter(p => p.writes).map(p => p.id), ['actuate-effect'])
30
30
  })
31
31
 
32
- test('아무것도 돌리지 않았으면 첫 칸부터 「모른다」다 — 실패로 접지 않는다', () => {
32
+ test('아무것도 돌리지 않았으면 첫 칸부터 「모른다」다 — 실패로 계산하지 않는다', () => {
33
33
  const p = assessStagePath(foldProbeResults([]))
34
34
  assert.equal(p.reached, null)
35
35
  assert.deepEqual(p.next?.stage, 'structure')
@@ -64,7 +64,7 @@ test('측정하지 못한 프로브는 수를 들지 않는다 — 0 을 지어
64
64
  assert.deepEqual(assessStagePath(e).stages[1].unmeasured, ['removals'])
65
65
  })
66
66
 
67
- test('세어서 0 을 얻은 것은 사실이다 — 모른다로 접지 않는다', () => {
67
+ test('세어서 0 을 얻은 것은 사실이다 — 모른다로 계산하지 않는다', () => {
68
68
  const rs: ProbeResult[] = [...readOnlyAllPass().filter(r => r.id !== 'live-removal'),
69
69
  { id: 'live-removal', ran: true, passed: false, count: 0 }]
70
70
  const e = foldProbeResults(rs)
@@ -39,7 +39,7 @@ test('시뮬 트윈은 라이브가 해당 없음이고, 그 사유를 말한다
39
39
  const a = assessSuite({ ...live(), subject: 'sim', journalTotal: null, removalsJournaled: null })
40
40
  assert.deepEqual(a.notApplicable.map(n => n.id), ['live-ingest', 'live-removal'])
41
41
  assert.deepEqual(a.path.stages[1].blockedBy, ['subject-is-simulation'])
42
- assert.deepEqual(a.path.stages[1].unmeasured, [], '해당 없음을 미측정으로 접지 않는다')
42
+ assert.deepEqual(a.path.stages[1].unmeasured, [], '해당 없음을 미측정으로 계산하지 않는다')
43
43
  assert.equal(a.path.reached, 'structure')
44
44
  assert.ok(!a.planned.includes('live-removal'), '해당 없는 프로브는 계획하지 않는다')
45
45
  })
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * 개체 → 품목 참조 — **정체성 모양이 하나가 아니고, 구분자는 우리가 정하지 않는다** (2026-08-23).
3
3
  *
4
- * ── 무엇이 조용히 비어 있었나 ───────────────────────────────────────────────
4
+ * ── 무엇이 알리지 않고 비어 있었나 ───────────────────────────────────────────────
5
5
  * 화면이 「이 개체는 선언된 자재가 아닙니다」라고 말했다. 그런데 그 품목은 원본의 선언에 이름까지 있었고
6
6
  * (「냉동그린빈스」), 대응 표도 멀쩡했다. 원인은 참조를 꺼내는 규칙이 GS1 SGTIN 하나만 알았던 것이다.
7
7
  *
@@ -0,0 +1,80 @@
1
+ /*
2
+ * ★ 필터가 없을 때도 시각순으로 낸다 — 상한이 시간축의 임의 구간을 잘라내던 자리.
3
+ *
4
+ * ── 무엇이 났나 (2026-08-25 실측 · 승화푸드) ─────────────────────────────────
5
+ * 공정 타임라인은 필터 없이 상한만 걸고 지난 기록을 부른다.
6
+ *
7
+ * loadTwinEvents(id, { eventType: 'task.status', limit: TASK_CAP })
8
+ *
9
+ * 그 갈래의 정렬이 `revision DESC` 였다. `revision` 은 **적힌 순서**다. 연결된 시스템을 되읽는
10
+ * 트윈에서는 적힌 순서와 일어난 순서가 다르다 — 커넥터가 연결된 시스템의 갱신 순서를 걷기 때문에,
11
+ * 그쪽에서 나중에 손댄 행이 4월 사실을 담고 있으면 그 행이 큰 revision 을 받는다.
12
+ *
13
+ * 작업 축에 있는 가장 늦은 사실 2026-08-23 03:20
14
+ * revision 이 가장 큰 작업 사실 2026-04-15 10:49 ← 상한이 잡던 값
15
+ *
16
+ * 상한 안에 8월 두 달치가 들어오지 않았고, 화면은 4월 사실을 최근 것으로 그렸다. 사용자가 세 번
17
+ * 짚었다. 화면이 낸 「앞부분이 빠져 있습니다」도 사실과 달랐다 — 빠진 것은 앞부분이 아니라 시간축의
18
+ * 임의 구간이었다.
19
+ *
20
+ * ── 성능은 이유가 되지 않는다 ───────────────────────────────────────────────
21
+ * 오히려 시각 정렬이 인덱스(`ix_twin_event_1`)를 순서대로 타서 빠르다. 같은 파일의 실측 기록:
22
+ * `revision DESC` 200건 33.4초 · `eventTime DESC, revision DESC` 200건 0.007초.
23
+ *
24
+ * ── `revision` 순서를 없애지는 않는다 ───────────────────────────────────────────
25
+ * 부르는 쪽이 `revision` 범위로 물었으면 그 순서가 물음의 답이다. 거르는 축과 정렬 축을 맞추는
26
+ * 규칙과도 같다(`journal-sort-axis.test.ts`).
27
+ */
28
+ import { test } from 'node:test'
29
+ import assert from 'node:assert/strict'
30
+ import { journalOrder } from '../server/service/twin-journal/journal-order.js'
31
+
32
+ test('★ 아무 필터도 없으면 시각순이다 — 타임라인이 부르는 방식이 이것이다', () => {
33
+ assert.deepEqual(journalOrder({}), { eventTime: 'DESC', revision: 'DESC' })
34
+ assert.deepEqual(journalOrder({ untilTime: null, fromRevision: null, toRevision: null }), {
35
+ eventTime: 'DESC',
36
+ revision: 'DESC'
37
+ })
38
+ })
39
+
40
+ test('★ 시각으로 물으면 시각순이다 — 거르는 축과 정렬 축이 맞아야 상한이 일한다', () => {
41
+ assert.deepEqual(journalOrder({ untilTime: '2026-08-25T00:00:00.000Z' }), { eventTime: 'DESC', revision: 'DESC' })
42
+ })
43
+
44
+ test('★ `revision` 범위로 물으면 그 순서로 낸다 — 그때는 그것이 물음의 답이다', () => {
45
+ assert.deepEqual(journalOrder({ fromRevision: 100, toRevision: 200 }), { revision: 'DESC' })
46
+ assert.deepEqual(journalOrder({ toRevision: 200 }), { revision: 'DESC' })
47
+ assert.deepEqual(journalOrder({ fromRevision: 100 }), { revision: 'DESC' })
48
+ })
49
+
50
+ test('시각과 `revision` 이 함께 오면 시각이 이긴다 — 거르는 쪽이 그렇게 정해져 있다', () => {
51
+ /*
52
+ * `where` 절이 `untilTime` 을 먼저 보고 `revision` 조건을 쓰지 않는다. 정렬만 `revision` 으로 두면 축이
53
+ * 어긋나고, 그 어긋남이 바로 위 실측의 33.4초다.
54
+ */
55
+ assert.deepEqual(journalOrder({ untilTime: '2026-08-25T00:00:00.000Z', fromRevision: 1, toRevision: 9 }), {
56
+ eventTime: 'DESC',
57
+ revision: 'DESC'
58
+ })
59
+ })
60
+
61
+ test('동시각의 순서는 `revision` 이 정한다 — 둘째 축을 없애지 않는다', () => {
62
+ /* 같은 시각에 여러 사실이 들어오는 것은 흔하다(한 번의 폴링이 여러 행을 낸다). 그때 순서가 없으면
63
+ 화면이 새로 고칠 때마다 줄 순서가 바뀐다. */
64
+ for (const q of [{}, { untilTime: '2026-08-25T00:00:00.000Z' }]) {
65
+ assert.equal(journalOrder(q).revision, 'DESC')
66
+ }
67
+ })
68
+
69
+ test('구간으로 물으면 시각순이다 — 화면이 그리는 구간을 그대로 묻는 길', () => {
70
+ assert.deepEqual(journalOrder({ fromTime: '2026-08-22T00:00:00.000Z' }), { eventTime: 'DESC', revision: 'DESC' })
71
+ assert.deepEqual(
72
+ journalOrder({ fromTime: '2026-08-22T00:00:00.000Z', untilTime: '2026-08-23T00:00:00.000Z' }),
73
+ { eventTime: 'DESC', revision: 'DESC' }
74
+ )
75
+ /* 구간과 `revision` 범위가 함께 와도 시각이 이긴다 — 거르는 쪽이 그렇게 정해져 있다. */
76
+ assert.deepEqual(
77
+ journalOrder({ fromTime: '2026-08-22T00:00:00.000Z', fromRevision: 1, toRevision: 9 }),
78
+ { eventTime: 'DESC', revision: 'DESC' }
79
+ )
80
+ })
@@ -12,7 +12,7 @@
12
12
  * ── 규율 ────────────────────────────────────────────────────────────────────
13
13
  * 저널(`TwinEvent`)을 `find()` 로 읽는 자리는 둘 중 하나여야 한다.
14
14
  * ① **상한이 있다**(`take:`) — 목록·표본처럼 화면이 쓰는 만큼만 읽는 경우.
15
- * ② **폴드다** — 사실을 하나씩 접는 것이 목적이라 그 구간의 행이 필요한 경우. 이때는 바로 앞에
15
+ * ② **폴드다** — 사실을 하나씩 계산하는 것이 목적이라 그 구간의 행이 필요한 경우. 이때는 바로 앞에
16
16
  * `journal-fold:` 로 시작하는 주석을 두어 **왜 필요한지**, 그리고 상한이 없다면 **없어질 조건**을
17
17
  * 함께 적는다.
18
18
  *
@@ -101,7 +101,7 @@ test('저널을 통째로 읽는 자리는 폴드라고 밝히고 이유를 적
101
101
  `이유 없이 저널 전체를 읽는 자리 ${bare.length}곳:\n ${bare.join('\n ')}\n\n` +
102
102
  `둘 중 하나로 만든다.\n` +
103
103
  ` ① 화면이 쓰는 만큼만 읽는다면 \`take:\` 를 준다.\n` +
104
- ` ② 사실을 접는 것이 목적이라면 바로 앞에 \`journal-fold:\` 주석으로 왜 행이 필요한지,\n` +
104
+ ` ② 사실을 계산하는 것이 목적이라면 바로 앞에 \`journal-fold:\` 주석으로 왜 행이 필요한지,\n` +
105
105
  ` 상한이 없다면 그 예외가 없어질 조건까지 적는다.\n\n` +
106
106
  `수 몇 개만 필요하다면 셋째 길이 맞다: 집계는 DB 가 한다(MIN/MAX/COUNT + createQueryBuilder).\n` +
107
107
  `\`timeRange\` 가 min/max 두 수를 구하려고 275,882행·payload 131MB 를 4초마다 파싱했던 자리다.`
@@ -111,10 +111,20 @@ test('저널을 통째로 읽는 자리는 폴드라고 밝히고 이유를 적
111
111
  test('검사가 실제로 저널 읽기를 보고 있다 — 대상이 사라지면 초록이 거짓이 된다', () => {
112
112
  const sites = journalFindSites()
113
113
 
114
+ /*
115
+ * ── 문턱을 셋에서 둘로 내렸다 (2026-08-28) ─────────────────────────────────
116
+ * 자리 하나를 **질의 빌더로 옮겨서** 줄었다(계보 조회 — 관계로 물어 두 단계 질의가 되고 `take` 가
117
+ * 있어도 안쪽에 상한이 없던 자리다). 그 방향이 이 검사가 바라는 방향이므로 문턱을 따라 내린다.
118
+ *
119
+ * 이 단정의 뜻은 「자리가 많아야 한다」가 아니라 **「검사가 무언가를 보고 있어야 한다」**다. 남은 둘도
120
+ * 옮겨지면 이 시험이 빨개지는데, 그때가 바로 사람이 알아야 하는 순간이다 — 검사가 아무것도 보지 않는
121
+ * 채로 초록이면 그 초록이 거짓이다. 그때는 검사의 패턴을 질의 빌더 쪽으로 옮긴다.
122
+ */
114
123
  assert.ok(
115
- sites.length >= 3,
116
- `저널 \`find()\` 를 ${sites.length}곳 찾았다 — 셋보다 적으면 호출 형태가 바뀐 것이다(리포지토리를 ` +
117
- `변수에 담아 부르면 이 검사가 놓친다). 그때는 이 검사의 패턴을 함께 옮긴다.`
124
+ sites.length >= 2,
125
+ `저널 \`find()\` 를 ${sites.length}곳 찾았다 — 둘보다 적으면 이 검사가 보는 대상이 없어졌다는 뜻이다. ` +
126
+ `호출을 질의 빌더로 옮겼다면 이 검사의 패턴도 그쪽으로 옮긴다(리포지토리를 변수에 담아 부르는 ` +
127
+ `형태도 함께 본다).`
118
128
  )
119
129
  })
120
130
 
@@ -173,5 +183,5 @@ test('건수 상한이 값으로 선언돼 있고, 넘으면 전체를 비운다
173
183
  assert.match(src, /take\(COUNT_CAP \+ 1\)/, '상한을 넘었는지 알려면 하나 더 읽어야 한다')
174
184
  /* 상한을 넘으면 **전체를 비운다** — 상한값을 전체로 적으면 화면이 거짓을 말한다. */
175
185
  assert.match(src, /capped \? undefined : counted\.length/)
176
- assert.match(src, /capped/, '넘었다는 사실을 함께 낸다(조용히 자르지 않는다)')
186
+ assert.match(src, /capped/, '넘었다는 사실을 함께 낸다(알리지 않고 자르지 않는다)')
177
187
  })