@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,155 @@
1
+ /*
2
+ * ★ 여러 종류를 시각순으로 — 거르는 축과 정렬 축이 어긋나면 색인이 버려진다.
3
+ *
4
+ * ── 실측 (2026-08-28 · 승화푸드 저널 3,374,894행) ───────────────────────────
5
+ * 종류 8개 IN + 시각순 50건 SEARCH USING INDEX ix_twin_event_1 (domain, instance) 37.181초 · 0건
6
+ * 같은 조건 · 정렬 없음 SEARCH USING INDEX ix_twin_event_7 (…, event_type=?) 0.003초
7
+ * 종류 하나 + 시각순 SEARCH USING INDEX ix_twin_event_7 (…, event_type=?) 0.003초
8
+ *
9
+ * 정렬을 붙이는 순간 여러 종류의 순서를 합칠 방법이 없어 시각 색인으로 337만 행을 걸으며 행마다 종류를
10
+ * 확인한다. 그 트윈에 에너지 사건은 **0건**이다 — 없는 것을 찾느라 37초를 쓴다.
11
+ *
12
+ * 360 이력이 64초였던 것과 같은 부류다. 고침도 같은 성질이다: **있는 색인을 쓰게 한다.**
13
+ */
14
+ import { test } from 'node:test'
15
+ import assert from 'node:assert/strict'
16
+ import { readFileSync } from 'node:fs'
17
+ import { join } from 'node:path'
18
+
19
+ import { splitShape } from '../server/service/twin-journal/event-type-split-shape.js'
20
+
21
+ const dir = (...p: string[]) => join(import.meta.dirname, '..', 'server', ...p)
22
+ const split = readFileSync(dir('service', 'twin-journal', 'event-type-split.ts'), 'utf8')
23
+ const shape = readFileSync(dir('service', 'twin-journal', 'event-type-split-shape.ts'), 'utf8')
24
+ const journal = readFileSync(dir('service', 'twin-journal', 'twin-journal-query.ts'), 'utf8')
25
+
26
+ const DESC = [{ name: 'eventTime', desc: true }]
27
+ const ENERGY = ['energy.measured', 'energy.equipment', 'energy.demand.window', 'energy.peak']
28
+
29
+ test('★ 에너지 원장이 보내는 모양이 이 갈래로 간다', () => {
30
+ const shape = splitShape({ filters: [{ name: 'eventType', operator: 'in', value: ENERGY }] }, DESC)
31
+ assert.ok(shape)
32
+ assert.deepEqual(shape!.eventTypes, ENERGY)
33
+ })
34
+
35
+ test('시간창이 함께 와도 간다 — 화면이 구간을 지정한다', () => {
36
+ const shape = splitShape(
37
+ {
38
+ filters: [
39
+ { name: 'eventType', operator: 'in', value: ENERGY },
40
+ { name: 'eventTime', operator: 'between', value: ['2026-08-01T00:00:00.000Z', '2026-08-02T00:00:00.000Z'] }
41
+ ]
42
+ },
43
+ DESC
44
+ )
45
+ assert.ok(shape)
46
+ assert.equal(shape!.fromTime?.toISOString(), '2026-08-01T00:00:00.000Z')
47
+ assert.equal(shape!.untilTime?.toISOString(), '2026-08-02T00:00:00.000Z')
48
+ })
49
+
50
+ test('★ 맞지 않는 물음은 일반 경로로 보낸다 — 억지로 답하면 목록이 알리지 않고 달라진다', () => {
51
+ const cases: [string, any, any[]][] = [
52
+ ['종류가 하나', { filters: [{ name: 'eventType', operator: 'in', value: ['a'] }] }, DESC],
53
+ ['종류가 너무 많다', { filters: [{ name: 'eventType', operator: 'in', value: Array.from({ length: 13 }, (_, i) => `t${i}`) }] }, DESC],
54
+ ['종류 필터가 없다', { filters: [] }, DESC],
55
+ ['정확일치', { filters: [{ name: 'eventType', operator: 'eq', value: 'a' }] }, DESC],
56
+ ['제외 목록', { filters: [{ name: 'eventType', operator: 'notin_with_null', value: ENERGY }] }, DESC],
57
+ ['자유 검색어', { search: '전력', filters: [{ name: 'eventType', operator: 'in', value: ENERGY }] }, DESC],
58
+ ['다른 필터가 섞였다', { filters: [{ name: 'eventType', operator: 'in', value: ENERGY }, { name: 'epc', operator: 'search', value: 'x' }] }, DESC],
59
+ ['리비전 정렬', { filters: [{ name: 'eventType', operator: 'in', value: ENERGY }] }, [{ name: 'revision', desc: true }]],
60
+ ['오름차순', { filters: [{ name: 'eventType', operator: 'in', value: ENERGY }] }, [{ name: 'eventTime', desc: false }]],
61
+ ['못 읽는 시각', { filters: [{ name: 'eventType', operator: 'in', value: ENERGY }, { name: 'eventTime', operator: 'lte', value: '어제' }] }, DESC]
62
+ ]
63
+ for (const [why, params, sortings] of cases) {
64
+ assert.equal(splitShape(params, sortings), null, `${why} 는 일반 경로로 가야 한다`)
65
+ }
66
+ })
67
+
68
+ test('★ 종류를 정확일치로 묻는다 — 이 한 줄이 색인을 쓰게 한다', () => {
69
+ assert.match(split, /andWhere\('e\.eventType = :eventType', \{ eventType \}\)/)
70
+ assert.doesNotMatch(split, /eventType IN/, '나눈 뒤에 다시 여러 값으로 묻지 않는다')
71
+ /* 정렬 축이 색인의 마지막 칸과 같아야 한다. */
72
+ assert.match(split, /orderBy\('e\.eventTime', 'DESC'\)/)
73
+ })
74
+
75
+ test('★ 한 갈래라도 못 읽으면 반쪽을 전부인 것처럼 내지 않는다', () => {
76
+ assert.match(split, /if \(!page\) return null/)
77
+ })
78
+
79
+ test('★ 이어 읽기 지점을 모든 갈래에 같이 건다', () => {
80
+ /* 갈래마다 다르게 걸면 어떤 종류는 이미 지나간 구간을 다시 낸다. */
81
+ assert.match(split, /e\.eventTime < :curAt OR \(e\.eventTime = :curAt AND e\.\$\{tiebreak\} < :curTie\)/)
82
+ assert.match(split, /if \(!cursor\) return null/, '못 읽는 커서로는 답하지 않는다')
83
+ })
84
+
85
+ test('★ 나눌 수를 제한한다 — 나누는 것이 손해가 되는 지점이 있다', () => {
86
+ assert.match(shape, /export const MAX_SPLIT = 24/)
87
+ assert.match(split, /if \(!pairs \|\| pairs > MAX_SPLIT\) return null/)
88
+ })
89
+
90
+ test('★ 화면이 부르는 자리에 붙어 있다', () => {
91
+ assert.match(journal, /const split = splitShape\(effective, effective\.sortings \?\? \[\]\)/)
92
+ assert.match(journal, /await eventTypeSplitPage\(domain\.id, instanceIds, split, limit, tiebreak, effective\.pagination\?\.after\)/)
93
+ /* 파생 테이블 갈래보다 뒤, 일반 저널 질의보다 앞이어야 한다. */
94
+ assert.ok(journal.indexOf('readModelPage(domain.id') < journal.indexOf('splitShape(effective'))
95
+ assert.ok(journal.indexOf('splitShape(effective') < journal.indexOf('const qb = getQueryBuilderFromListParams'))
96
+ })
97
+
98
+ test('★ 걷을 조건이 적혀 있다 — 부르는 데가 없어지면 지운다', () => {
99
+ /*
100
+ * 앱에서 종류를 여러 값으로 보내는 곳은 에너지 원장 한 곳뿐이다. 그 화면이 종류를 하나씩 고르는
101
+ * 모양으로 바뀌면 이 갈래는 아무도 부르지 않는다 — 그때 지워야 한다. 그 조건을 코드에 적어 두지
102
+ * 않으면 아무것도 하지 않는 갈래가 무언가를 받쳐 주는 것처럼 남는다.
103
+ */
104
+ assert.match(split, /이 갈래를 걷을 조건/)
105
+ assert.match(split, /그때 \*\*지운다\.\*\*/)
106
+ })
107
+
108
+ /* ── 공정 단계 칩 (2026-08-29) ─────────────────────────────────────────────
109
+ *
110
+ * ★ 칩을 고른 순간 이 갈래가 꺼져서, **고르지 않은 쪽이 더 빨랐다.**
111
+ *
112
+ * 원장은 종류 넷을 담아 묻고 그 위에 단계 칩을 겹친다. 앞 판은 `bizStep` 필터를 만나면 모양을
113
+ * 알아보지 못해 예전 경로로 떨어졌다 — 좁혀 물으면 느려지는 화면이 된다.
114
+ */
115
+
116
+ test('공정 단계 하나로 좁혀도 갈래 경로가 답한다', () => {
117
+ const shape = splitShape(
118
+ {
119
+ filters: [
120
+ { name: 'eventType', operator: 'in', value: ['epcis.ObjectEvent', 'epcis.AggregationEvent'] },
121
+ { name: 'bizStep', operator: 'eq', value: 'shipping' }
122
+ ]
123
+ } as any,
124
+ [{ name: 'eventTime', desc: true }] as any
125
+ )
126
+ assert.ok(shape, '이 모양을 알아봐야 한다')
127
+ assert.equal(shape?.bizStep, 'shipping')
128
+ assert.deepEqual(shape?.eventTypes, ['epcis.ObjectEvent', 'epcis.AggregationEvent'])
129
+ })
130
+
131
+ test('단계를 여러 값으로 주면 답하지 않는다 — 갈래가 곱해진다', () => {
132
+ const shape = splitShape(
133
+ {
134
+ filters: [
135
+ { name: 'eventType', operator: 'in', value: ['epcis.ObjectEvent', 'epcis.AggregationEvent'] },
136
+ { name: 'bizStep', operator: 'in', value: ['shipping', 'receiving'] }
137
+ ]
138
+ } as any,
139
+ [{ name: 'eventTime', desc: true }] as any
140
+ )
141
+ assert.equal(shape, null)
142
+ })
143
+
144
+ test('빈 단계 값은 받지 않는다 — 모든 행을 뜻하는지 없는 단계를 뜻하는지 알 수 없다', () => {
145
+ const shape = splitShape(
146
+ {
147
+ filters: [
148
+ { name: 'eventType', operator: 'in', value: ['epcis.ObjectEvent', 'epcis.AggregationEvent'] },
149
+ { name: 'bizStep', operator: 'eq', value: ' ' }
150
+ ]
151
+ } as any,
152
+ [{ name: 'eventTime', desc: true }] as any
153
+ )
154
+ assert.equal(shape, null)
155
+ })
@@ -0,0 +1,96 @@
1
+ /*
2
+ * 마감된 구간 사실의 **범위 배선** — 유입을 부르는 곳은 모두 어느 트윈인지 말해야 한다.
3
+ *
4
+ * ── 왜 이 시험이 있나 (2026-08-30) ────────────────────────────────────────
5
+ * 커널은 `new Kernel(domainId, …)` 으로 서므로 자기가 어느 트윈인지 모른다. 설비 번호·계량 지점 번호는
6
+ * 트윈 안에서만 통하는 이름표라, 범위를 넘기지 않으면 **서로 다른 설비의 같은 날이 한 사실**이 된다.
7
+ * 실측으로 그 일이 났다 — 두 발전소의 `002` 가 겹쳐, 저널에 6줄이 있는데 서로 다른 이름은 5개였다.
8
+ * 여러 현장을 함께 계산하는 성과 화면은 이름으로 중복을 걷어내므로 한쪽 발전량이 사라진다.
9
+ *
10
+ * 배선은 조용히 빠진다. 인자를 안 넘겨도 컴파일되고, 시험도 초록이고, 실 서버에서 값이 하나 사라질
11
+ * 뿐이다. 그래서 **부르는 자리를 세어서** 지킨다 — `ingest-wiring-guard` 와 같은 부류다.
12
+ */
13
+ import { test } from 'node:test'
14
+ import assert from 'node:assert/strict'
15
+ import { readFileSync, readdirSync, statSync } from 'node:fs'
16
+ import { fileURLToPath } from 'node:url'
17
+ import { dirname, join } from 'node:path'
18
+
19
+ import { resolveSubject, ingestEnergyGenerationPeriodRecords } from '@operato/ops-contract'
20
+
21
+ const here = dirname(fileURLToPath(import.meta.url))
22
+ const serverDir = join(here, '..', 'server')
23
+
24
+ function allTs(dir: string, out: string[] = []): string[] {
25
+ for (const name of readdirSync(dir)) {
26
+ const full = join(dir, name)
27
+ if (statSync(full).isDirectory()) allTs(full, out)
28
+ else if (name.endsWith('.ts')) out.push(full)
29
+ }
30
+ return out
31
+ }
32
+
33
+ /**
34
+ * 주석을 걷어낸다 — **설명에 적힌 이름을 호출로 세지 않는다.**
35
+ *
36
+ * 처음 이 시험을 만들었을 때 `reference-live.ts` 의 흐름 설명 한 줄(`ingestCanonicalRecords(정규→봉투)`)을
37
+ * 배선 빠진 호출로 잡았다. 주석을 고치게 만드는 시험은 아무것도 지키지 않는다.
38
+ */
39
+ function withoutComments(src: string): string {
40
+ return src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/[^\n]*/g, '$1')
41
+ }
42
+
43
+ test('★ 유입을 부르는 곳은 모두 어느 트윈인지 말한다 — 안 말하면 두 현장의 같은 번호가 겹친다', () => {
44
+ const bad: string[] = []
45
+ for (const file of allTs(serverDir)) {
46
+ if (file.endsWith('canonical-ingest.ts')) continue // 정의하는 자리
47
+ const src = withoutComments(readFileSync(file, 'utf-8'))
48
+ let i = src.indexOf('ingestCanonicalRecords(')
49
+ while (i >= 0) {
50
+ /* 호출 하나를 괄호 균형으로 잘라 낸다 — 줄바꿈이 있어도 본다. */
51
+ let depth = 0
52
+ let j = i + 'ingestCanonicalRecords'.length
53
+ for (; j < src.length; j++) {
54
+ if (src[j] === '(') depth++
55
+ else if (src[j] === ')') {
56
+ depth--
57
+ if (depth === 0) break
58
+ }
59
+ }
60
+ const call = src.slice(i, j + 1)
61
+ if (!call.includes('factScope')) {
62
+ bad.push(`${file.slice(serverDir.length + 1)}: ${call.replace(/\s+/g, ' ').slice(0, 90)}`)
63
+ }
64
+ i = src.indexOf('ingestCanonicalRecords(', j)
65
+ }
66
+ }
67
+ assert.deepEqual(
68
+ bad,
69
+ [],
70
+ `범위를 넘기지 않는 유입 호출:\n${bad.join('\n')}\n` +
71
+ 'TwinEngine.factScope(domainId, instanceId) 를 넷째 인자로 넘겨라 — ' +
72
+ '넘기지 않으면 다른 현장의 같은 설비 번호가 한 사실로 겹치고, 성과 화면에서 한쪽이 사라진다.'
73
+ )
74
+ })
75
+
76
+ test('★ 범위를 넘기면 두 현장의 같은 번호가 다른 사실이다', () => {
77
+ const rec = { equipmentId: '002', from: '2026-08-28T15:00:00.000Z', to: '2026-08-29T15:00:00.000Z', kWh: 400.6 }
78
+ const a = ingestEnergyGenerationPeriodRecords(rec, { tenantId: 'd', scopeId: 'twin-a' }).accepted[0]
79
+ const b = ingestEnergyGenerationPeriodRecords(rec, { tenantId: 'd', scopeId: 'twin-b' }).accepted[0]
80
+ assert.notEqual(a.eventId, b.eventId)
81
+ })
82
+
83
+ test('선언된 정체성이 있으면 범위와 무관하게 한 사실이다 — 그릇이 정체성을 정하지 않는다', () => {
84
+ const rec = { equipmentId: '002', from: '2026-08-28T15:00:00.000Z', to: '2026-08-29T15:00:00.000Z', kWh: 400.6 }
85
+ const identityOf = () => 'https://example.com/obj/inv-002'
86
+ const a = ingestEnergyGenerationPeriodRecords(rec, { tenantId: 'd', scopeId: 'twin-a', identityOf }).accepted[0]
87
+ const b = ingestEnergyGenerationPeriodRecords(rec, { tenantId: 'd', scopeId: 'twin-b', identityOf }).accepted[0]
88
+ assert.equal(a.eventId, b.eventId)
89
+ assert.equal((a.data as any).subjectBasis, 'declared')
90
+ })
91
+
92
+ test('커널이 범위를 답할 자리가 있다 — 호스트가 그것을 넘긴다', () => {
93
+ const r = resolveSubject('equipment', '002', { tenantId: 'd', scopeId: 'ppms-호현' })
94
+ assert.equal(r.basis, 'twin-local')
95
+ assert.equal(r.subject, 'ppms-호현/002')
96
+ })
@@ -0,0 +1,53 @@
1
+ /*
2
+ * ★ 저장본 뒤의 사건 — 계산했든 못 했든 **말한다**.
3
+ *
4
+ * ── 무엇이 침묵했나 (2026-08-28 실측) ───────────────────────────────────────
5
+ * 미러는 저장본에서 상태를 되세우고 그 뒤의 사건만 따라 계산한다. 그 자리가 이랬다.
6
+ *
7
+ * 10:34:37 found cached state → revision 3372228
8
+ * 10:34:37 resumed from the stored fold seed at revision 3372228 — items 2665 · orders 4794
9
+ *
10
+ * 그 시각 저널의 머리는 3,374,894였다. 2,666건이 저장본보다 앞서 있었는데 **계산했다는 줄도, 못
11
+ * 했다는 줄도 없었다.** 갈래가 셋인데 말하는 갈래가 하나뿐이었기 때문이다.
12
+ *
13
+ * ① 계산해서 나아갔다 → 말한다
14
+ * ② 계산이 오류로 실패했다 → 말한다
15
+ * ③ 오류 없이 끝났는데 그대로 → **아무 말도 없었다** ← 여기
16
+ * ④ 저널 머리를 못 읽었다 → **아무 말도 없었다** ← 여기
17
+ *
18
+ * ③④에서 트윈은 그 사건들의 결과 없이 뜨고, 화면은 그것을 「없는 사실」로 그린다.
19
+ */
20
+ import { test } from 'node:test'
21
+ import assert from 'node:assert/strict'
22
+ import { readFileSync } from 'node:fs'
23
+ import { join } from 'node:path'
24
+
25
+ const engine = readFileSync(join(import.meta.dirname, '..', 'server', 'engine', 'twin-engine.ts'), 'utf8')
26
+
27
+ test('★ 저널 머리를 못 읽었으면 그 사실을 말하고 그대로 뜬다', () => {
28
+ assert.match(engine, /if \(!tip\) \{/, '못 읽은 갈래가 있어야 한다')
29
+ assert.match(engine, /could not read the journal tip/, '무엇을 못 했는지 말해야 한다')
30
+ assert.match(engine, /without folding what came after it/, '무엇을 잃는지 말해야 한다')
31
+ })
32
+
33
+ test('★ 계산이 아무 일도 하지 않았으면 그것도 말한다', () => {
34
+ assert.match(engine, /\} else if \(!failed\) \{/, '오류가 아닌데 나아가지 않은 갈래가 있어야 한다')
35
+ assert.match(engine, /left the seed at revision/)
36
+ /* 오류로 실패한 것을 두 번 말하지 않는다 — 이미 위에서 말했다. */
37
+ assert.match(engine, /let failed: string \| undefined/)
38
+ })
39
+
40
+ test('★ 부팅마다 부르는 질의를 관계로 묻지 않는다 — 이 파일이 스스로 금지한 모양이다', () => {
41
+ /*
42
+ * `findOne({ where: { domain: { id } } })` 는 안쪽에 상한이 없는 두 단계 질의가 된다(실측 12초,
43
+ * 저널 375만 행). `tipOf` 는 부팅마다 트윈마다 불리므로 그 한 번이 부팅을 붙잡는다.
44
+ */
45
+ const from = engine.indexOf('private static async tipOf')
46
+ assert.ok(from > 0, 'tipOf 가 있어야 한다')
47
+ const tipOf = engine.slice(from, from + 2600)
48
+ /* 주석에 인용된 나쁜 모양과 헷갈리지 않게, 실제 코드 모양만 본다. */
49
+ assert.doesNotMatch(tipOf, /findOne\(\{ where: \{ domain: \{ id: domainId \}/, '관계로 묻는 모양이 남아 있다')
50
+ assert.match(tipOf, /createQueryBuilder\('e'\)/)
51
+ assert.match(tipOf, /orderBy\('e\.revision', 'DESC'\)/)
52
+ assert.match(tipOf, /\.limit\(1\)/)
53
+ })
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * 예측 지표 — **스냅샷에서 무엇을 세는가.**
3
3
  *
4
- * 여기서 틀리면 예측 그래프가 조용히 다른 것을 그린다(오류 없이 곡선이 나온다). 특히 에너지 지표는
4
+ * 여기서 틀리면 예측 그래프가 알리지 않고 다른 것을 그린다(오류 없이 곡선이 나온다). 특히 에너지 지표는
5
5
  * 「없음」과 「0」이 같은 그림이 되기 쉬워서, 그 경계를 명시로 잡아 둔다.
6
6
  */
7
7
  import { test } from 'node:test'
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * 예측 틱 크기 — **지평선이 정하고, 양끝은 묶인다.**
3
3
  *
4
- * 이 규칙이 틀리면 예측이 조용히 비싸지거나(1초 고정 시절 9,000틱·28.5초) 조용히 성글어진다
4
+ * 이 규칙이 틀리면 예측이 알리지 않고 비싸지거나(1초 고정 시절 9,000틱·28.5초) 알리지 않고 성글어진다
5
5
  * (짧은 작업이 한 틱에 삼켜진다). 둘 다 화면에는 그럴듯한 숫자로 나오므로 시험이 지킨다.
6
6
  */
7
7
  import { test } from 'node:test'
@@ -0,0 +1,99 @@
1
+ /*
2
+ * **창에 다 들어오지 않은 발전 기간을 조용히 버리지 않는다.**
3
+ *
4
+ * ── 무엇이 일어났나 (2026-08-30) ──────────────────────────────────────────
5
+ * 어제 발전량이 저널에 멀쩡히 있는데 화면이 비어 있었다. 발전 기간은 하루 단위이고(`08-28 15:00 →
6
+ * 08-29 15:00`), 성과 구간은 「지금부터 24시간 전」이라 한낮에서 시작한다(`08-29 02:53 → 08-30 02:53`).
7
+ * 하루 기간이 그 구간에 **온전히 들어오지 않는다.**
8
+ *
9
+ * 더하지 않는 것 자체는 맞다 — 걸친 기간을 더하면 다른 날의 값이 이 창의 값인 척한다. 틀린 것은
10
+ * **아무 말도 하지 않은 것**이다. 사용자는 빈 화면을 「발전량이 없다」로 읽는다. 그 둘은 완전히
11
+ * 다른 상황이다.
12
+ */
13
+ import { test } from 'node:test'
14
+ import assert from 'node:assert/strict'
15
+
16
+ import { foldKpi } from '../server/engine/kpi-fold.ts'
17
+
18
+ const T = (iso: string) => Date.parse(iso)
19
+
20
+ /** 실측값 — 오창의 8월 29일 하루. */
21
+ const DAY_START = '2026-08-28T15:00:00.000Z'
22
+ const DAY_END = '2026-08-29T15:00:00.000Z'
23
+
24
+ const period = (equipmentId: string, kWh: number) => ({
25
+ eventType: 'energy.generation.period',
26
+ eventTime: DAY_END,
27
+ payload: {
28
+ eventId: `id|${equipmentId}`,
29
+ data: {
30
+ equipmentId,
31
+ kWh,
32
+ periodStart: DAY_START,
33
+ periodEnd: DAY_END,
34
+ observedFrom: DAY_START,
35
+ observedTo: DAY_END,
36
+ accumulation: 'daily',
37
+ boundary: 'offset'
38
+ }
39
+ }
40
+ })
41
+
42
+ const EVENTS = [period('002', 127.1), period('003', 104.1), period('004', 312.4), period('005', 102.3)] as any
43
+
44
+ test('★ 창이 기간을 온전히 담으면 더한다', () => {
45
+ const r = foldKpi(EVENTS, { fromMs: T(DAY_START), toMs: T(DAY_END) })
46
+ const g = r.energy?.generated
47
+ assert.ok(g, '발전량이 있어야 한다')
48
+ assert.equal(g.counted, 4)
49
+ assert.equal(Math.round(g.kWh * 10) / 10, 645.9)
50
+ assert.equal(g.notCounted, undefined, '버린 것이 없으면 말하지 않는다')
51
+ })
52
+
53
+ test('★ 창이 기간을 반만 담으면 더하지 않되 **버렸다고 말한다**', () => {
54
+ /* 실제로 났던 구간 — 하루 기간의 한가운데에서 시작한다. */
55
+ const r = foldKpi(EVENTS, { fromMs: T('2026-08-29T02:53:00.000Z'), toMs: T('2026-08-30T02:53:00.000Z') })
56
+ const g = r.energy?.generated
57
+ assert.ok(g, '값이 없다고 아무것도 내지 않으면 화면은 「발전량 없음」으로 읽는다')
58
+ assert.equal(g.kWh, 0, '걸친 기간은 더하지 않는다 — 더하면 다른 날 값이 섞인다')
59
+ assert.equal(g.counted, 0)
60
+ assert.ok(g.notCounted, '버린 것을 말해야 한다')
61
+ assert.equal(g.notCounted.periods, 4)
62
+ assert.equal(Math.round(g.notCounted.kWh * 10) / 10, 645.9, '얼마를 못 세었는지 말한다')
63
+ assert.equal(g.notCounted.earliestStart, DAY_START)
64
+ assert.equal(g.notCounted.latestEnd, DAY_END)
65
+ })
66
+
67
+ test('창이 기간과 아예 겹치지 않으면 말할 것이 없다 — 없는 것을 만들지 않는다', () => {
68
+ const r = foldKpi(EVENTS, { fromMs: T('2026-07-01T00:00:00.000Z'), toMs: T('2026-07-02T00:00:00.000Z') })
69
+ assert.equal(r.energy?.generated, undefined)
70
+ })
71
+
72
+ test('온전히 든 것과 걸친 것이 섞이면 더한 것과 못 센 것을 함께 낸다', () => {
73
+ const earlier = {
74
+ ...period('002', 100),
75
+ eventTime: '2026-08-28T15:00:00.000Z',
76
+ payload: {
77
+ eventId: 'id|earlier',
78
+ data: {
79
+ equipmentId: '002',
80
+ kWh: 100,
81
+ periodStart: '2026-08-27T15:00:00.000Z',
82
+ periodEnd: '2026-08-28T15:00:00.000Z',
83
+ observedFrom: '2026-08-27T15:00:00.000Z',
84
+ observedTo: '2026-08-28T15:00:00.000Z',
85
+ accumulation: 'daily',
86
+ boundary: 'offset'
87
+ }
88
+ }
89
+ }
90
+ const r = foldKpi([earlier, ...EVENTS] as any, {
91
+ fromMs: T('2026-08-27T15:00:00.000Z'),
92
+ toMs: T('2026-08-29T02:00:00.000Z')
93
+ })
94
+ const g = r.energy?.generated
95
+ assert.ok(g)
96
+ assert.equal(g.counted, 1, '27→28 하루만 온전히 들었다')
97
+ assert.equal(g.kWh, 100)
98
+ assert.equal(g.notCounted?.periods, 4, '28→29 넷은 창의 끝을 넘는다')
99
+ })
@@ -7,7 +7,8 @@
7
7
  import { test } from 'node:test'
8
8
  import assert from 'node:assert/strict'
9
9
 
10
- import { StateProjector, objectEvent, BIZSTEP, DISP } from '@operato/twin-kernel'
10
+ import { StateProjector } from '@operato/twin-kernel'
11
+ import { objectEvent, BIZSTEP, DISP } from '@operato/ops-contract'
11
12
  import { buildEntityDeltas } from '../shared/entity-delta.ts'
12
13
 
13
14
  const T0 = '2026-07-19T00:00:00.000Z'
@@ -5,7 +5,7 @@
5
5
  * 실제로 받는 판정은 그 앞에 한 층이 더 있다: **피드 사실을 어디서 얻는가**(`liveFeedStateOf` — 이 트윈이
6
6
  * 미러인가 시뮬인가, 지금 도는가) 그리고 **장부를 어느 트윈의 것으로 찾는가**(도메인+인스턴스 키).
7
7
  *
8
- * 그 층이 틀리면 순수 시험은 전부 초록인 채로 화면만 거짓말한다. 실제로 두 가지가 조용히 틀릴 수 있다:
8
+ * 그 층이 틀리면 순수 시험은 전부 초록인 채로 화면만 거짓말한다. 실제로 두 가지가 알리지 않고 틀릴 수 있다:
9
9
  * ① 시뮬 트윈이 「연결 안 됨」으로 보인다 — 거짓 경보이고, 거짓 경보는 참 경보를 죽인다
10
10
  * ② 남의 도메인 장부를 집는다 — 키를 도메인 없이 만들면 그렇게 된다
11
11
  *
@@ -206,7 +206,7 @@ test('멈춘 미러는 「해당 없음」이다 — 고치려고 멈춰 둔 트
206
206
 
207
207
  test('멈춘 트윈에 계속 들어오면 「버려지는 중」이라 말한다 — 실제로 겪은 거짓 안심', async () => {
208
208
  /*
209
- * 겪은 모습: 트윈이 멈췄는데 피드는 살아 레코드를 나르고, 커널은 조용히 버렸다. 화면은 「해당 없음 ·
209
+ * 겪은 모습: 트윈이 멈췄는데 피드는 살아 레코드를 나르고, 커널은 알리지 않고 버렸다. 화면은 「해당 없음 ·
210
210
  * 150 통과 · 100%」였다. 여기서는 그 상황을 그대로 만들어 판정과 수치를 확인한다.
211
211
  */
212
212
  /* 사람이 트윈을 멈춘다 — 그런데 피드는 남는다. 그 조합이 사실을 잃게 만든다. */
@@ -4,7 +4,7 @@
4
4
  * ── 왜 기계가 지켜야 하나 ───────────────────────────────────────────────────
5
5
  * 피드를 여는 경로가 둘이다(커넥터 `openLiveFeed` · 시나리오 `twin-ingest` 태스크). 한쪽만 장부에
6
6
  * 적으면 그 경로로 먹이는 트윈이 **「한 번도 받지 않은 것」**으로 보인다. 그 판정은 `unknown` 이고,
7
- * `unknown` 은 화면에서 「정상」과 구별되지 않는다 — 즉 **조용히 틀린 답**이 된다.
7
+ * `unknown` 은 화면에서 「정상」과 구별되지 않는다 — 즉 **오류 없이 틀린 답**이 된다.
8
8
  *
9
9
  * 세 번째 경로가 생길 때 사람이 이 사실을 기억할 것이라고 믿지 않는다. 인제스트를 부르는 자리를
10
10
  * 소스에서 찾아 장부 기록이 함께 있는지 본다.
@@ -86,7 +86,7 @@ test('검사가 실제로 경로를 보고 있다 — 대상이 사라지면 초
86
86
 
87
87
  test('유입 경로는 커널이 실제로 받은 수를 견준다 — 버려진 것을 통과로 세지 않게', () => {
88
88
  /*
89
- * 겪은 일: 트윈이 멈춘 뒤에도 피드는 레코드를 나르고 커널은 조용히 버렸는데, 장부는 그것을 「통과」로
89
+ * 겪은 일: 트윈이 멈춘 뒤에도 피드는 레코드를 나르고 커널은 알리지 않고 버렸는데, 장부는 그것을 「통과」로
90
90
  * 셌다. 화면은 멈춘 트윈 옆에 「150 통과 · 100%」라고 적었다 — 사실이 사라지는 동안 안심시킨 것이다.
91
91
  *
92
92
  * 고친 방법은 `ingestLive` 가 **넣은 수를 돌려주는** 것이다. 새 경로가 그 반환을 안 쓰고 0 을 넘기면
@@ -100,9 +100,11 @@ test('유입 경로는 커널이 실제로 받은 수를 견준다 — 버려진
100
100
  missing,
101
101
  [],
102
102
  `커널이 받은 수를 견주지 않는 유입 경로 ${missing.length}곳:\n ${missing.join('\n ')}\n\n` +
103
- `TwinEngine.ingestLive(...) 의 반환(넣은 수)을 받아 accepted.length 와의 차이를 ` +
104
- `recordIngestResult(..., nowMs, undelivered) 로 넘긴다. 그러지 않으면 트윈이 멈춘 사이 ` +
105
- `버려진 레코드가 「통과」로 집계되어 화면이 100% 라고 말한다.`
103
+ `TwinEngine.ingestLive(...) 또는 ingestLiveResult(...) 의 반환(넣은 수)을 받아 accepted.length 와의 ` +
104
+ `차이를 undelivered 라는 이름으로 recordIngestResult(..., nowMs, undelivered) 에 넘긴다. ` +
105
+ `그러지 않으면 트윈이 멈춘 사이 버려진 레코드가 「통과」로 집계되어 화면이 100% 라고 말한다.\n\n` +
106
+ `이 검사는 **그 이름이 있는지**만 본다(소스를 글자로 본다). 이름을 적고 0 을 넘기면 통과한다 — ` +
107
+ `그 경우는 사람이 보는 수밖에 없다.`
106
108
  )
107
109
  })
108
110
 
@@ -53,7 +53,7 @@ test('오래 끊긴 피드는 건강했던 옛 창으로 초록을 내지 않는
53
53
  assert.equal(
54
54
  syncVerdictOf({ feedState: 'attached', recent: healthy, lastAtMs: 0, nowMs: STALE_AFTER_MS }),
55
55
  'syncing',
56
- '경계까지는 아직 조용하다고 하지 않는다'
56
+ '경계까지는 아직 들어온 것이 없다고 하지 않는다'
57
57
  )
58
58
  })
59
59
 
@@ -73,7 +73,7 @@ test('붙었지만 한 번도 받지 않은 것을 「유입 없음」이라 단
73
73
  )
74
74
  })
75
75
 
76
- test('시뮬 트윈은 오래 조용해도 「유입 없음」이 아니다', () => {
76
+ test('시뮬 트윈은 오래 들어온 것이 없어도 「유입 없음」이 아니다', () => {
77
77
  assert.equal(
78
78
  syncVerdictOf({ feedState: 'not-applicable', recent: null, lastAtMs: -1, nowMs: STALE_AFTER_MS * 100 }),
79
79
  'not-applicable'
@@ -82,7 +82,7 @@ test('시뮬 트윈은 오래 조용해도 「유입 없음」이 아니다', ()
82
82
 
83
83
  test('들어오는데 트윈이 안 돌면 「버려지는 중」이다 — 「해당 없음」으로 안심시키지 않는다', () => {
84
84
  /*
85
- * 실제로 겪었다: 트윈이 멈춘 뒤에도 피드는 남아 레코드를 나르고, 커널은 그것을 조용히 버렸다.
85
+ * 실제로 겪었다: 트윈이 멈춘 뒤에도 피드는 남아 레코드를 나르고, 커널은 그것을 알리지 않고 버렸다.
86
86
  * 그런데 화면은 「해당 없음 · 150 통과 · 100%」였다 — 사실이 사라지는 동안 화면이 안심시킨 것이다.
87
87
  *
88
88
  * 멈춘 트윈의 피드 사실은 `not-applicable` 이므로, 미전달을 **그보다 먼저** 봐야 이 상태가 드러난다.
@@ -141,7 +141,7 @@ test('채우는 중인 창을 「최근」이라 부르지 않는다', () => {
141
141
  recordIngest(l, 100, [], 0)
142
142
  recordIngest(l, 100, [], 1000)
143
143
 
144
- /* 읽는 시각을 넘긴다 — 넘기지 않으면 벽시계와 견주어 「오래 조용함」으로 읽힌다(1970년 장부이므로). */
144
+ /* 읽는 시각을 넘긴다 — 넘기지 않으면 벽시계와 견주어 「오래 들어온 것이 없음」으로 읽힌다(1970년 장부이므로). */
145
145
  const v = ingestHealth(l, 'attached', undefined, 1000)
146
146
  assert.equal(v.recent, null, '창이 아직 닫히지 않았다')
147
147
  assert.equal(v.verdict, 'unknown', '그래서 판정도 모름이다')
@@ -167,7 +167,7 @@ test('추이 상한을 넘으면 오래된 창이 밀려나고, 밀려난 수를
167
167
 
168
168
  const v = ingestHealth(l, 'attached')
169
169
  assert.equal(v.trend.length, TREND_LIMIT)
170
- assert.ok(v.trendDropped > 0, `밀려난 창을 조용히 버리지 않는다 — 실제 ${v.trendDropped}`)
170
+ assert.ok(v.trendDropped > 0, `밀려난 창을 알리지 않고 버리지 않는다 — 실제 ${v.trendDropped}`)
171
171
  })
172
172
 
173
173
  /* ── ③ 0 과 없음 ────────────────────────────────────────────────────────── */
@@ -213,7 +213,7 @@ test('사유 종류가 무한히 자라지 않는다 — 문구에 변수가 섞
213
213
  assert.equal(v.reasonsCapped, true, '접혔다는 사실을 말한다')
214
214
  assert.ok(
215
215
  v.reasons.some(r => r.reason === OTHER_REASON),
216
- '접힌 것들이 갈 칸이 있다'
216
+ '모은 것들이 갈 칸이 있다'
217
217
  )
218
218
  assert.equal(v.total.rejected, REASON_LIMIT * 5, '건수는 하나도 잃지 않는다')
219
219
  })
@@ -12,7 +12,7 @@
12
12
  * 거짓이 된다. 겹치면 메모리 쪽을 쓴다(같은 사실의 원본이다).
13
13
  * ② 어디서부터 메모리인지 밝힌다(`liveFrom`) — 화면이 근거를 말할 수 있게. 재기동 직후에는 `null`
14
14
  * 이고, 그때 보이는 것은 전부 표에서 온 것이다.
15
- * ③ **조용히 자르지 않는다** — 상한에서 밀려난 창 수와, 물은 기간이 잘렸다는 사실을 함께 낸다.
15
+ * ③ **알리지 않고 자르지 않는다** — 상한에서 밀려난 창 수와, 물은 기간이 잘렸다는 사실을 함께 낸다.
16
16
  * ④ 남의 도메인·남의 트윈의 창을 집지 않는다.
17
17
  * ⑤ 제시가 0 인 창의 통과율은 `null` 이다 — 빈 창은 완벽한 창이 아니다.
18
18
  */
@@ -157,7 +157,7 @@ test('기간 상한을 넘겨 물으면 자르고, 자른 사실을 낸다', asy
157
157
  const r = await ask(TWIN, HISTORY_HOURS_MAX + 100)
158
158
 
159
159
  assert.equal(r.hours, HISTORY_HOURS_MAX, '상한까지만 본다')
160
- assert.equal(r.hoursClamped, HISTORY_HOURS_MAX + 100, '얼마를 물었는지 밝힌다 — 조용히 자르지 않는다')
160
+ assert.equal(r.hoursClamped, HISTORY_HOURS_MAX + 100, '얼마를 물었는지 밝힌다 — 알리지 않고 자르지 않는다')
161
161
  })
162
162
 
163
163
  test('상한 안에서는 잘렸다고 말하지 않는다 — 없는 문제를 만들지 않는다', async () => {