@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
@@ -1 +1 @@
1
- {"version":3,"file":"twin-forecast-query.js","sourceRoot":"","sources":["../../../server/service/twin-forecast/twin-forecast-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { twinLog } from '../../engine/log.js'\nimport { Arg, Ctx, Int, Mutation, Query, Resolver, Directive} from 'type-graphql'\n\nimport { getRepository, ScalarObject } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { readSpecCoverage } from '../../engine/spec-coverage.js'\nimport { describeModelBasis, modelBasisChanged, type ModelBasis } from '../../engine/model-basis.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\nimport { GAP_SAMPLES, aggregatePoints, computeSpc, gapMetrics, learnedCalibration, type GapPoint } from './gap-analytics.js'\nimport { FORECAST_METRICS, type MetricFn } from './forecast-metrics.js'\nimport { forecastStepMs } from './forecast-tuning.js'\n\n/* 커널 예측 함수 — CJS require(twin-engine 과 동일 방식). */\nconst { monteCarloForecastAsync } = require('@operato/twin-kernel')\n\n/**\n * 이벤트 루프에 자리를 내주는 방법 — **커널이 아니라 여기가 안다.**\n *\n * `setImmediate` 는 대기 중인 I/O 다음 차례에 돌아온다. 마이크로태스크(`Promise.resolve()`)로는\n * 자리가 가지 않는다 — 그러면 예측이 도는 동안 라이브 틱도 HTTP 도 여전히 멈춘다.\n *\n * ── 내준 시간은 **부하가 아니다** ───────────────────────────────────────────\n * 부하 계기는 「이 트윈이 루프를 얼마나 붙잡았나」를 재는 자다. 양보를 넣은 뒤로 벽시계 시간에는\n * 남에게 넘긴 시간이 섞이는데, 그것까지 부하로 적으면 계기가 거짓말을 한다 — 하필 이 문제를 짚어낸\n * 그 계기다. 그래서 내준 시간을 따로 세어 빼고 적는다.\n */\nfunction breather() {\n let yieldedMs = 0\n return {\n fn: () =>\n new Promise<void>(resolve => {\n const t = performance.now()\n setImmediate(() => {\n yieldedMs += performance.now() - t\n resolve()\n })\n }),\n get ms() {\n return yieldedMs\n }\n }\n}\n\n/*\n * 트윈 예측 채널 — 상향 query. 트윈의 \"예측(predict)\" 면(설계 §3.5, ADR-0009).\n * 커널 monteCarloForecast(현재에서 N fork → 지표 분포) + 단일 fork 예측 궤적. 원본 live 무간섭(fork 격리).\n * metric 은 도메인-일반(스냅샷 파생), scenario 는 미래 자극(기본=계속 입고, 실 시스템은 인스턴스/설정 파생).\n */\nconst GTIN = 'urn:epc:idpat:sgtin:0614141.012345.*'\nconst DEFAULT_SCENARIO = {\n seed: 7,\n generators: [\n { kind: 'inbound-arrival', rate: { distribution: 'poisson', meanPerHour: 60 }, content: { skuMix: [{ gtin: GTIN, weight: 1 }], qtyPerLine: { min: 4, max: 12 } } }\n ]\n}\n/*\n * 지표 표는 `forecast-metrics.ts` 가 소유한다 — 여기 있던 것을 옮겼다(시험이 이 파일을 불러올 수 없다).\n *\n * **모르는 지표를 `items` 로 갈음하지 않는다.** 예전에는 `METRICS[metric] ?? METRICS.items` 였다.\n * 에너지 트윈에서 `peakKW` 를 물으면 재고 곡선이 그려지고, 재고가 없으니 **0 이 평평하게** 나왔다 —\n * 화면은 그것을 「피크가 0」으로 읽는다. 지어낸 답보다 모른다고 말하는 편이 낫다.\n */\nfunction metricFn(metric?: string): MetricFn | null {\n if (!metric) return FORECAST_METRICS.items\n return FORECAST_METRICS[metric] ?? null\n}\n\n/* 학습된 보정 저장(P3.2, 인메모리) — key=domain:instance:metric:horizon → {biasShift, halfWidth, …}.\n twinLearnCalibration 이 갭에서 학습해 저장하고, twinForecast 가 있으면 예측에 상시 적용(라이브 보정).\n 데모 트윈은 재부팅 시 저널 리셋이라 세션 스코프로 충분 — DB 영속(TwinForecastScore)은 후속. */\nconst CALIBRATION = new Map<\n string,\n { biasShift: number; halfWidth: number; at: string; samples: number; accBefore: number; accAfter: number; basis?: ModelBasis }\n>()\nconst calKey = (d: string, i: string, m: string, h: number) => `${d}:${i}:${m}:${h}`\n\n/**\n * 이 트윈이 백테스트할 수 있는 구간 — [첫 이벤트, 마지막 이벤트 − H].\n * 지평 H 만큼 앞을 봐야 \"그때 예측한 것\"과 \"실제로 벌어진 것\"을 맞대 볼 수 있으므로 끝에서 H 를 뺀다.\n */\nasync function gapWindow(domainId: string, instanceId: string, H: number): Promise<{ t0: number; lastVantage: number } | null> {\n const where = { domain: { id: domainId }, instanceId }\n const repo = getRepository(TwinEvent)\n const first = await repo.findOne({ where, order: { eventTime: 'ASC' } })\n const last = await repo.findOne({ where, order: { eventTime: 'DESC' } })\n if (!first?.eventTime || !last?.eventTime) return null\n const t0 = first.eventTime.getTime()\n const lastVantage = last.eventTime.getTime() - H\n return lastVantage > t0 ? { t0, lastVantage } : null\n}\n\n/**\n * 갭 point 계산(공용) — 주어진 vantage 각각에서 forecast-at-T vs actual-at-T+H(결정적 백테스트).\n *\n * **vantage 는 밖에서 준다.** 예전에는 이 함수가 자기 저널의 첫·마지막 이벤트로 격자를 스스로 만들었다.\n * 그러면 트윈마다 격자가 달라져, 공동배치 합산에서 `points[3]` 끼리 더할 때 **서로 다른 시각의 값**이\n * 더해졌다(자세한 사연은 `aggregatePoints` 주석).\n *\n * 반환 배열은 격자와 **같은 길이**이고, 그 vantage 를 재지 못했으면 그 자리에 `null` 을 둔다.\n * 건너뛰고 밀어 담으면 배열 인덱스가 시간축이기를 그만둔다.\n */\nasync function gapPoints(domainId: string, instanceId: string, m: (s: any) => number, H: number, grid: number[], scen: any): Promise<(any | null)[]> {\n const points: (any | null)[] = []\n for (const T of grid) {\n const k = await TwinEngine.buildForecastKernelAt(domainId, instanceId, new Date(T).toISOString()).catch(() => null)\n if (!k) { points.push(null); continue }\n const dist = await monteCarloForecastAsync(k, { runs: 12, horizonMs: H, scenario: scen, tickMs: forecastStepMs(H), metric: m, yieldFn: breather().fn })\n const naive = m(k.getSnapshot())\n const st = await TwinEngine.recover(domainId, instanceId, undefined, new Date(T + H).toISOString()).catch(() => null)\n if (!st) { points.push(null); continue }\n const a = m(st)\n const p50 = dist?.p50 ?? naive\n points.push({ t: T, p50, min: dist?.min ?? p50, max: dist?.max ?? p50, actual: a, error: a - p50, inBand: a >= (dist?.min ?? -Infinity) && a <= (dist?.max ?? Infinity), naiveAbsError: Math.abs(a - naive), modelAbsError: Math.abs(a - p50) })\n }\n return points\n}\n\n/**\n * 여러 트윈이 **함께** 백테스트할 수 있는 구간과 그 위의 vantage 격자.\n *\n * 겹치는 구간은 각자의 구간을 교집합한 것 — 시작은 가장 늦은 t0, 끝은 가장 이른 lastVantage.\n * 한 트윈이라도 그 시각에 저널이 없으면 공장 총량을 말할 수 없기 때문이다.\n * 겹치는 구간이 없으면 `null` — \"0\" 이 아니라 \"잴 수 없음\" 이다.\n */\nfunction sharedGrid(windows: { t0: number; lastVantage: number }[], N: number): number[] | null {\n if (!windows.length || N < 2) return null\n const t0 = Math.max(...windows.map(w => w.t0))\n const lastVantage = Math.min(...windows.map(w => w.lastVantage))\n if (!(lastVantage > t0)) return null\n return Array.from({ length: N }, (_, i) => t0 + ((lastVantage - t0) * i) / (N - 1))\n}\n\n@Resolver()\nexport class TwinForecastQuery {\n @Query(returns => ScalarObject, {\n nullable: true,\n description: 'Monte-Carlo forecast of a metric over a horizon (twin prediction faculty) + a single-fork predicted trajectory. Live instance is untouched (forks are isolated).'\n })\n async twinForecast(\n @Arg('instanceId') instanceId: string,\n @Arg('metric', { nullable: true }) metric: string,\n @Arg('runs', type => Int, { nullable: true }) runs: number,\n @Arg('horizonMs', type => Int, { nullable: true }) horizonMs: number,\n @Arg('scenario', type => ScalarObject, { nullable: true }) scenario: any,\n @Arg('at', { nullable: true }) at: string,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n /* vantage(at) 기준 예측 — 히스토리 노브가 과거 T 면 \"그때 서서 본 미래\"(백테스트).\n at 있으면 T 시점 커널을 저널로 재구성한다(그건 우회가 아니라 시간여행의 본질).\n 없으면 **지금 그 커널**을 쓴다 — sim 이든 live 든 인스턴스가 커널이다(통합 P2). */\n const domainId = context.state.domain.id\n const k = at\n ? await TwinEngine.buildForecastKernelAt(domainId, instanceId, at)\n : TwinEngine.owns(domainId, instanceId)\n ? TwinEngine.kernel(domainId, instanceId)\n : undefined\n if (!k) return null\n\n const m = metricFn(metric)\n /* 이 트윈이 못 세는 지표 — 빈 예측이 아니라 「그 지표는 여기 없다」고 말한다. */\n if (!m) return { metric, unsupportedMetric: true, distribution: null, trajectory: [] }\n const H = horizonMs ?? 300000\n const R = runs ?? 30\n /*\n * 틱 크기는 **지평선이 정한다**(`forecast-tuning`). 1초 고정이던 시절 5분 지평선이 회차마다\n * 300틱이었고, 30회차면 9,000틱·28.5초였다 — 실측으로 5초 단위와 답이 같다.\n */\n const step = forecastStepMs(H)\n const scen = scenario ?? DEFAULT_SCENARIO\n\n /*\n * 분포: N개 확률적 미래 표본(fork 마다 seed 변주, 원본 보존).\n *\n * **이것이 요청 처리 중 메인 이벤트 루프에서 동기로 수행된다.** 기본값으로 fork 30개 × 300틱 = 9,000틱.\n * 계기판에 남겨 두지 않으면 \"왜 서버가 굳었나\" 를 나중에 짚을 수 없다(실제로 한 번 겪었다).\n */\n const tMc = performance.now()\n const mcBreath = breather()\n const distribution = await monteCarloForecastAsync(k, { runs: R, horizonMs: H, scenario: scen, tickMs: step, metric: m, yieldFn: mcBreath.fn })\n TwinEngine.recordLoad(domainId, instanceId, 'forecast', performance.now() - tMc - mcBreath.ms)\n TwinEngine.countForks(domainId, instanceId, R)\n\n /* 학습된 보정 상시 적용(P3.2) — twinLearnCalibration 이 저장한 편향·밴드가 있으면 예측을 현실로 보정(라이브가 실제로 나아짐).\n 갭트렌드(gapPoints)는 raw 모델을 그대로 측정(학습 신호 보존) — 보정은 표시용 forecast 에만. */\n const stored = CALIBRATION.get(calKey(domainId, instanceId, metric ?? 'items', H))\n /*\n * **배운 모델에만 적용한다.** 보정은 \"그때 모델이 이만큼 치우쳐 있다\" 를 배운 것이다. 소요시간\n * 추정기(실측·거리)나 공정 명세가 바뀌어 모델이 달라지면 그 편향은 더 이상 사실이 아니고, 그대로\n * 밀어 주면 이중 보정이 된다. 그래서 지문을 비교하고, 다르면 적용하지 않되 **그 사실을 응답에\n * 싣는다** — 조용히 사라지면 사용자는 학습이 왜 안 듣는지 알 수 없다(다시 배우면 된다).\n * 지문을 알 수 없을 때는 비교하지 않는다(모르는 것을 근거로 보정을 끊지 않는다).\n */\n const basis = await TwinEngine.modelBasis(domainId, instanceId)\n const stale = !!stored && modelBasisChanged(stored.basis, basis)\n if (stale) {\n twinLog(\n `[twin-forecast] \"${instanceId}\": calibration not applied — model changed since it was learned.` +\n ` learned on: ${describeModelBasis(stored!.basis)} / now: ${describeModelBasis(basis)}`\n )\n }\n /*\n * 분위수 — 표본에서 직접 뽑는다.\n *\n * 커널이 표본 배열을 그대로 돌려주므로(forecast.ts) 여기서 정렬해 p05·p95 를 낸다. 이것이 없으면\n * 화면이 보여 줄 수 있는 폭은 관측 **최소·최대** 뿐인데, 그건 신뢰구간이 아니라 극단값 두 개다.\n * \"운이 나쁜 5% 에서는 이만큼\" 이라고 말하려면 분위수여야 한다. 표본이 적으면(runs 가 작으면)\n * 분위수도 거칠다 — 그래서 표본 수를 함께 싣는다(sampleCount). 숫자만 주고 근거를 감추지 않는다.\n */\n if (distribution) {\n const samples: number[] = Array.isArray(distribution.samples) ? distribution.samples : []\n if (samples.length) {\n const sorted = [...samples].sort((a, b) => a - b)\n const pct = (p: number) => sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))]\n distribution.p05 = pct(0.05)\n distribution.p95 = pct(0.95)\n }\n distribution.sampleCount = samples.length\n }\n\n const cal = stored && !stale ? stored : undefined\n if (cal && distribution) {\n /*\n * `p50` 은 커널 계약상 항상 있다(`MonteCarloResult.p50: number`). 그래도 메우지 않는다 —\n * 예전에는 `?? 0` 이었는데, 만약 비면 보정된 p50 이 **편향값 그 자체**가 되고 min·max 는\n * 그 둘레로 그려진다. 예측이 아닌 숫자가 예측의 자리에 앉는데 아무 신호가 없다.\n */\n if (typeof distribution.p50 !== 'number') throw new Error('forecast distribution has no p50 — kernel contract broken')\n const p50c = distribution.p50 + cal.biasShift\n distribution.p50 = Math.round(p50c)\n distribution.min = Math.round(p50c - cal.halfWidth)\n distribution.max = Math.round(p50c + cal.halfWidth)\n /*\n * 분위수는 **옮기기만** 한다. 보정이 배운 것은 \"모델이 이만큼 치우쳐 있다\"(위치)이지 분포의\n * 모양이 아니다. p50 처럼 밀어 두어야 p05~p95 폭이 표본이 말한 그대로 남는다.\n * (min·max 는 위에서 학습 밴드로 대체된다 — 보정이 걸리면 그 둘의 뜻이 '관측 극단' 에서\n * '학습 밴드' 로 바뀐다는 뜻이다. 화면이 둘을 같은 것으로 그리면 안 된다.)\n */\n if (distribution.p05 !== undefined) distribution.p05 = Math.round(distribution.p05 + cal.biasShift)\n if (distribution.p95 !== undefined) distribution.p95 = Math.round(distribution.p95 + cal.biasShift)\n if (distribution.p90 !== undefined) distribution.p90 = Math.round(distribution.p90 + cal.biasShift)\n distribution.calibrated = true\n }\n\n /*\n * 예측 궤적(예측선): 단일 fork 를 horizon 까지 실행하며 지표 기록. 이것도 잰다.\n *\n * 여기도 자리를 내준다 — 실측으로 이 한 회차가 1.3초였다(300틱 + 스냅샷 300번). 분포만큼은\n * 아니지만, 라이브 틱 간격(1초)보다 길게 붙잡으면 그 사이 트윈들이 한 박자씩 밀린다.\n */\n const tTraj = performance.now()\n const trajBreath = breather()\n const fc = k.fork()\n fc.scenario.load({ ...scen, seed: 1 })\n fc.scenario.start()\n const trajectory: { t: number; value: number }[] = []\n for (let t = 0; t < H; t += step) {\n fc.tick(step)\n trajectory.push({ t: t + step, value: m(fc.getSnapshot()) })\n /* 틱마다 내주면 양보 자체가 비용이 된다 — 라이브 틱 간격보다 훨씬 짧은 단위로 끊는다. */\n if (trajectory.length % 25 === 0) await trajBreath.fn()\n }\n\n TwinEngine.recordLoad(domainId, instanceId, 'forkTick', performance.now() - tTraj - trajBreath.ms)\n TwinEngine.countForks(domainId, instanceId, 1)\n\n const current = m(k.getSnapshot()) // vantage T 의 실제값(= naive '그대로 유지' 예측의 기준)\n\n /* 실측 오버레이 + 채점(P1) — vantage 가 과거(at)일 때만: T 이후 실제로 일어난 metric 을 T→min(now,T+H) 로 샘플.\n 현재 이후는 실측이 없음(미래). recover(T+t) 로 각 샘플 상태 재구성해 metric 추출. 비용=샘플 수 만큼 replay. */\n const actual: { t: number; value: number }[] = []\n let scored: any = null\n if (at) {\n const T = Date.parse(at)\n const endMs = Math.min(Date.now(), T + H)\n const span = endMs - T\n if (Number.isFinite(T) && span > 0) {\n const SAMPLES = 8\n const dt = span / SAMPLES\n for (let i = 1; i <= SAMPLES; i++) {\n const st = await TwinEngine.recover(domainId, instanceId, undefined, new Date(T + i * dt).toISOString()).catch(() => null)\n if (st) actual.push({ t: i * dt, value: m(st) })\n }\n /* 지평선이 지났으면(T+H ≤ now) 그 지점 실측 vs 예측 채점(단일 vantage). p50 대비 오차·밴드 적중·FVA(naive=현재값 유지 대비). */\n if (T + H <= Date.now() && actual.length) {\n const a = actual[actual.length - 1].value\n const p50 = distribution?.p50 ?? current\n scored = {\n actual: a,\n p50,\n error: a - p50, // 부호 = 편향 방향(+ 실제가 더 많음)\n inBand: a >= (distribution?.min ?? -Infinity) && a <= (distribution?.max ?? Infinity),\n modelAbsError: Math.abs(a - p50),\n naiveAbsError: Math.abs(a - current) // naive = \"그대로 유지\"(현재값). FVA = naive - model\n }\n }\n }\n }\n\n /*\n * 시뮬 명세 자기보고를 함께 낸다 — **상수로 계산한 예측과 현장 명세로 계산한 예측이 화면에서 같아\n * 보이지 않게.** 코드·수치만 싣고 사람이 읽는 문장은 클라가 만든다(언어 중립).\n * 커널이 이 API 를 갖지 않은 버전이면 키를 만들지 않는다(모르는 것을 \"전부 기본값\" 이라 단정 금지).\n */\n /* 보고는 **실제로 실행한 fork** 에서 읽는다 — 부모 커널(k)은 tick 하지 않으므로(예측은 fork 에서\n * 수행된다) 부모에서 읽으면 씨앗으로 세운 예측에서는 늘 비어 있다. 숫자를 낸 주체가 보고해야 한다. */\n const specCoverage = readSpecCoverage(fc) ?? readSpecCoverage(k)\n return {\n metric: metric ?? 'items', horizonMs: H, runs: R, current, distribution, trajectory, actual, scored,\n /* 얼마나 잘게 굴렸나 — 숫자만 주고 근거를 감추지 않는다(같은 규율로 `specCoverage` 도 싣는다). */\n tickMs: step,\n /*\n * **가정이 어떻게 됐나** — 걸린 개입·거절된 개입·덮어쓴 선언.\n *\n * 이것을 감추면 두 가지가 조용해진다: 걸리지 않은 가정(「걸었는데 왜 안 바뀌나」)과, 덮어쓴\n * 선언(사용자가 what-if 결과를 트윈의 현재 상태로 읽는다). 실제로 실행한 fork(`fc`)에서 읽는다 —\n * 가정을 겪은 것은 그쪽이다.\n */\n ...(typeof (fc as any).interventionOutcomes === 'function'\n ? (() => {\n const outcomes = (fc as any).interventionOutcomes()\n return outcomes.length ? { interventions: outcomes } : {}\n })()\n : {}),\n ...(typeof (fc as any).declarationOverrides === 'function'\n ? (() => {\n const overrides = (fc as any).declarationOverrides()\n return overrides.length ? { overrides } : {}\n })()\n : {}),\n calibrated: !!cal,\n /* 학습해 뒀지만 모델이 바뀌어 적용하지 않았다 — 사용자가 다시 배울 수 있게 알린다. */\n ...(stale ? { calibrationStale: true } : {}),\n ...(specCoverage ? { specCoverage } : {}),\n /*\n * **계보에 구멍이 몇 칸 생겼나** — 개체를 잇지 못한 채 넘어간 공정 수.\n *\n * ── 왜 이 값을 예측 응답에 싣나 (2026-08-24) ──────────────────────────────\n * 미러의 오더는 확보분을 갖지 않는다(원본이 「어느 개체가 잡혀 있나」를 말하지 않는 것이 그\n * 시스템의 정상이다). 커널은 그 자리에서 **던지지 않고 넘긴다** — 단계를 넘는 것은 공정의\n * 진행이고 개체 번호가 필요 없다. 대신 넘긴 횟수를 센다.\n *\n * 그런데 그 카운터는 **fork 안에** 있고 fork 는 버려진다. 여기서 싣지 않으면 아무에게도 닿지\n * 않는다 — 리듀서가 오래 세어 온 값을 스냅샷이 떨어뜨려 그것을 읽도록 쓰인 검사가 영원히\n * 조용했던 것과 **같은 실수**다. 세어 놓고 내보내지 않으면 안 센 것과 같다.\n *\n * 왜 화면이 이것을 알아야 하나: 계보는 **회수 범위**를 정하는 사슬이다. 구멍이 있는데 온전한\n * 것처럼 보이면 회수가 조용히 좁아진다. 식품에서 그것은 되돌릴 수 없다.\n *\n * 두 곳에서 읽어 **큰 값**을 낸다 — 분포(회차 R개의 최대)와 궤적 fork 는 **둘 다 실제로 실행됐다**.\n * 한쪽만 보면 다른 쪽이 겪은 구멍을 잃는다.\n */\n ...(() => {\n const gaps = Math.max(\n (distribution as any)?.stepsWithoutMaterial ?? 0,\n (fc as any)?.getSnapshot?.()?.stepsWithoutMaterial ?? 0\n )\n /* 0 은 싣지 않는다 — 「구멍이 없다」와 「이 축을 모른다」를 화면이 구별할 수 있게. */\n return gaps > 0 ? { stepsWithoutMaterial: gaps } : {}\n })()\n }\n }\n\n /*\n * 갭 트렌드(P2) — N개 과거 vantage 각각에서 \"그때 예측 vs 그 지평선의 실제\"를 재계산(결정적 백테스트).\n * 지평선이 이미 지난 vantage 만([t0, tLast−H]) → actual 확정. 온디맨드 재계산(누적/영속은 긴 기간용 후속 최적화).\n * 비용 = N × (buildForecastKernelAt replay + monteCarlo + recover replay). N·runs 를 낮게 bound. per-instance(클라가 co-located 합산).\n */\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'Forecast-accuracy gap diagnosis (canonical, server-computed): for N past vantages recompute forecast-at-T vs actual-at-T+h (deterministic backtest), aggregate co-located instances, and return points + SPC verdict (stability/bias) + metrics (accuracy/coverage/FVA) + learned-calibration projection. Client only renders.'\n })\n async twinForecastGapTrend(\n @Arg('instanceId') instanceId: string,\n @Arg('metric', { nullable: true }) metric: string,\n @Arg('horizonMs', type => Int, { nullable: true }) horizonMs: number,\n @Arg('samples', type => Int, { nullable: true }) samples: number,\n @Arg('scenario', type => ScalarObject, { nullable: true }) scenario: any,\n @Ctx() context: ResolverContext,\n @Arg('instanceIds', type => [String], { nullable: true }) instanceIds?: string[]\n ): Promise<any> {\n const domainId = context.state.domain.id\n const m = metricFn(metric)\n const H = horizonMs ?? 300000\n if (!m) return { metric, unsupportedMetric: true, horizonMs: H, samples: 0, points: [], spc: computeSpc([]), metrics: null, learn: null }\n const N = Math.min(30, Math.max(2, samples ?? GAP_SAMPLES)) // 표본은 서버가 관장(SPC 신뢰 하한 ~20).\n const scen = scenario ?? DEFAULT_SCENARIO\n // 공동배치(co-located) 집계도 서버에서 — 공장 단위 진단이 canonical. 미지정 시 단건.\n const ids = instanceIds?.length ? instanceIds : [instanceId]\n\n /*\n * 격자를 **먼저 하나** 만든다. 트윈마다 자기 저널로 격자를 만들면 같은 인덱스가 서로 다른\n * 시각이 되어, 합산이 다른 시점의 값을 더한다(`aggregatePoints` 주석 참조).\n *\n * 저널이 없거나 지평 H 를 담을 만큼 길지 않은 트윈은 **백테스트에 참여할 수 없다.** 그런 트윈은\n * 빼되 누구를 뺐는지 돌려준다 — 조용히 빼면 화면은 그 트윈까지 포함한 공장 총량으로 읽는다.\n */\n const windows = await Promise.all(ids.map(async id => ({ id, w: await gapWindow(domainId, id, H) })))\n const usable = windows.filter(x => x.w)\n const excluded = windows.filter(x => !x.w).map(x => x.id)\n const grid = sharedGrid(usable.map(x => x.w!), N)\n if (!grid) {\n /* 겹치는 구간이 없다 — 잴 수 없는 것이지 정확도가 0 인 것이 아니다. */\n return { metric: metric ?? 'items', horizonMs: H, samples: N, points: [], spc: computeSpc([]), metrics: null, learn: null,\n coverage: { instances: ids.length, measured: 0, excluded, vantages: N, used: 0, dropped: 0, reason: 'no-common-window' } }\n }\n\n const per = await Promise.all(usable.map(x => gapPoints(domainId, x.id, m, H, grid, scen)))\n const { points, droppedVantages } = aggregatePoints(per as (GapPoint | null)[][])\n const metrics = points.length ? gapMetrics(points) : null\n const spc = computeSpc(points.map(p => p.error))\n const learn = metrics ? learnedCalibration(points, metrics) : null\n return { metric: metric ?? 'items', horizonMs: H, samples: N, points, spc, metrics, learn,\n /* 이 숫자가 **무엇으로 계산됐는지**. 24점 중 18점이면 화면이 그렇게 말해야 한다. */\n coverage: { instances: ids.length, measured: usable.length, excluded, vantages: N, used: points.length, dropped: droppedVantages } }\n }\n\n /*\n * 학습(P3.2) — 갭에서 편향·밴드를 학습해 저장하면, 이후 twinForecast 가 상시 적용(라이브 예측이 실제로 보정됨).\n * assimilation-lite: 잔차(innovation)를 되먹여 추정치를 현실로. per-instance(클라가 co-located 합산).\n * 반환: 학습된 보정 + before/after 정확도(효과 정량화). 저장은 인메모리(세션) — DB 영속은 후속.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Learn a forecast calibration (bias + band) from the gap trend and persist it so future forecasts apply it. Returns the calibration + before/after accuracy.' })\n async twinLearnCalibration(\n @Arg('instanceId') instanceId: string,\n @Arg('metric', { nullable: true }) metric: string,\n @Arg('horizonMs', type => Int, { nullable: true }) horizonMs: number,\n @Ctx() context: ResolverContext,\n @Arg('clear', type => Boolean, { nullable: true }) clear?: boolean\n ): Promise<any> {\n const domainId = context.state.domain.id\n if (!TwinEngine.owns(domainId, instanceId)) return { ok: false } // 테넌트 격리\n const m = metricFn(metric)\n if (!m) return { ok: false, reason: 'unsupported-metric', metric }\n const H = horizonMs ?? 300000\n // 보정 해제 — 저장된 calibration 제거(이후 예측은 raw 로 복귀). 학습을 되돌리는 정직한 off 스위치.\n if (clear) {\n CALIBRATION.delete(calKey(domainId, instanceId, metric ?? 'items', H))\n return { ok: true, cleared: true, metric: metric ?? 'items', horizonMs: H }\n }\n // 표본·계산을 gap-analytics 로 일원화(gapTrend 진단과 동일 N·동일 수식 → 카드 예상치와 실제 보정 일치).\n /* 학습은 단건이라 격자도 이 트윈의 구간 하나다. 저널이 지평 H 를 담을 만큼 길지 않으면 배울 것이 없다. */\n const w = await gapWindow(domainId, instanceId, H)\n const grid = w ? sharedGrid([w], GAP_SAMPLES) : null\n if (!grid) return { ok: false, reason: 'insufficient-history' }\n /* 못 잰 vantage 는 `null` 로 온다 — 0 으로 세면 편향 학습이 그만큼 끌려간다. */\n const pts = (await gapPoints(domainId, instanceId, m, H, grid, DEFAULT_SCENARIO)).filter(Boolean) as GapPoint[]\n if (pts.length < 3) return { ok: false, reason: 'insufficient-history' }\n const learn = learnedCalibration(pts, gapMetrics(pts))\n /* 학습한 모델의 지문을 함께 남긴다 — 이후 모델이 바뀌면 이 보정은 적용되지 않는다. */\n const basis = await TwinEngine.modelBasis(domainId, instanceId)\n const cal = {\n biasShift: learn.biasShift,\n halfWidth: learn.halfWidth,\n at: '',\n samples: pts.length,\n accBefore: learn.accBefore,\n accAfter: learn.accAfter,\n ...(basis ? { basis } : {})\n }\n CALIBRATION.set(calKey(domainId, instanceId, metric ?? 'items', H), cal)\n return { ok: true, metric: metric ?? 'items', horizonMs: H, biasShift: cal.biasShift, halfWidth: cal.halfWidth, samples: pts.length, accBefore: cal.accBefore, accAfter: cal.accAfter }\n }\n}\n"]}
1
+ {"version":3,"file":"twin-forecast-query.js","sourceRoot":"","sources":["../../../server/service/twin-forecast/twin-forecast-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { twinLog } from '../../engine/log.js'\nimport { hydratedDate } from '../../engine/read-time.js'\nimport { Arg, Ctx, Int, Mutation, Query, Resolver, Directive} from 'type-graphql'\n\nimport { getRepository, ScalarObject } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { readSpecCoverage } from '../../engine/spec-coverage.js'\nimport { describeModelBasis, modelBasisChanged, type ModelBasis } from '../../engine/model-basis.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\nimport { GAP_SAMPLES, aggregatePoints, computeSpc, gapMetrics, learnedCalibration, type GapPoint } from './gap-analytics.js'\nimport { FORECAST_METRICS, type MetricFn } from './forecast-metrics.js'\nimport { forecastStepMs } from './forecast-tuning.js'\n\n/* 커널 예측 함수 — CJS require(twin-engine 과 동일 방식). */\nconst { monteCarloForecastAsync } = require('@operato/twin-kernel')\n\n/**\n * 이벤트 루프에 자리를 내주는 방법 — **커널이 아니라 여기가 안다.**\n *\n * `setImmediate` 는 대기 중인 I/O 다음 차례에 돌아온다. 마이크로태스크(`Promise.resolve()`)로는\n * 자리가 가지 않는다 — 그러면 예측이 도는 동안 라이브 틱도 HTTP 도 여전히 멈춘다.\n *\n * ── 내준 시간은 **부하가 아니다** ───────────────────────────────────────────\n * 부하 계기는 「이 트윈이 루프를 얼마나 붙잡았나」를 재는 자다. 양보를 넣은 뒤로 벽시계 시간에는\n * 남에게 넘긴 시간이 섞이는데, 그것까지 부하로 적으면 계기가 거짓말을 한다 — 하필 이 문제를 짚어낸\n * 그 계기다. 그래서 내준 시간을 따로 세어 빼고 적는다.\n */\nfunction breather() {\n let yieldedMs = 0\n return {\n fn: () =>\n new Promise<void>(resolve => {\n const t = performance.now()\n setImmediate(() => {\n yieldedMs += performance.now() - t\n resolve()\n })\n }),\n get ms() {\n return yieldedMs\n }\n }\n}\n\n/*\n * 트윈 예측 채널 — 상향 query. 트윈의 \"예측(predict)\" 면(설계 §3.5, ADR-0009).\n * 커널 monteCarloForecast(현재에서 N fork → 지표 분포) + 단일 fork 예측 궤적. 원본 live 무간섭(fork 격리).\n * metric 은 도메인-일반(스냅샷 파생), scenario 는 미래 자극(기본=계속 입고, 실 시스템은 인스턴스/설정 파생).\n */\nconst GTIN = 'urn:epc:idpat:sgtin:0614141.012345.*'\nconst DEFAULT_SCENARIO = {\n seed: 7,\n generators: [\n { kind: 'inbound-arrival', rate: { distribution: 'poisson', meanPerHour: 60 }, content: { skuMix: [{ gtin: GTIN, weight: 1 }], qtyPerLine: { min: 4, max: 12 } } }\n ]\n}\n/*\n * 지표 표는 `forecast-metrics.ts` 가 소유한다 — 여기 있던 것을 옮겼다(시험이 이 파일을 불러올 수 없다).\n *\n * **모르는 지표를 `items` 로 갈음하지 않는다.** 예전에는 `METRICS[metric] ?? METRICS.items` 였다.\n * 에너지 트윈에서 `peakKW` 를 물으면 재고 곡선이 그려지고, 재고가 없으니 **0 이 평평하게** 나왔다 —\n * 화면은 그것을 「피크가 0」으로 읽는다. 지어낸 답보다 모른다고 말하는 편이 낫다.\n */\nfunction metricFn(metric?: string): MetricFn | null {\n if (!metric) return FORECAST_METRICS.items\n return FORECAST_METRICS[metric] ?? null\n}\n\n/* 학습된 보정 저장(P3.2, 인메모리) — key=domain:instance:metric:horizon → {biasShift, halfWidth, …}.\n twinLearnCalibration 이 갭에서 학습해 저장하고, twinForecast 가 있으면 예측에 상시 적용(라이브 보정).\n 데모 트윈은 재부팅 시 저널 리셋이라 세션 스코프로 충분 — DB 영속(TwinForecastScore)은 후속. */\nconst CALIBRATION = new Map<\n string,\n { biasShift: number; halfWidth: number; at: string; samples: number; accBefore: number; accAfter: number; basis?: ModelBasis }\n>()\nconst calKey = (d: string, i: string, m: string, h: number) => `${d}:${i}:${m}:${h}`\n\n/**\n * 이 트윈이 백테스트할 수 있는 구간 — [첫 이벤트, 마지막 이벤트 − H].\n * 지평 H 만큼 앞을 봐야 \"그때 예측한 것\"과 \"실제로 벌어진 것\"을 맞대 볼 수 있으므로 끝에서 H 를 뺀다.\n */\nasync function gapWindow(domainId: string, instanceId: string, H: number): Promise<{ t0: number; lastVantage: number } | null> {\n /*\n * ── **양 끝을 집계로 묻는다** (2026-08-28) ──────────────────────────────────\n *\n * 예전에는 `findOne({ where: { domain: { id } }, order: { eventTime } })` 두 번이었다. 관계를 가진\n * 엔티티에서 `findOne` 은 두 단계 질의가 되고 **안쪽에 상한이 없다** — 그 트윈의 저널을 전부 만든 뒤\n * 한 줄을 고른다. 승화푸드는 337만 행이고, 그것을 두 번 했다.\n *\n * `MIN`/`MAX` 는 `(domain, instance, event_time)` 색인의 양 끝을 집으므로 각각 한 줄이다.\n * 이 파일의 다른 자리와 같은 규율이다(§`twin-engine` 의 `tipOf`).\n */\n const repo = getRepository(TwinEvent)\n const edge = (agg: 'MIN' | 'MAX') =>\n repo\n .createQueryBuilder('e')\n .select(`${agg}(e.eventTime)`, 'at')\n .where('e.domain = :domainId', { domainId })\n .andWhere('e.instanceId = :instanceId', { instanceId })\n .getRawOne<{ at: unknown }>()\n const [firstRow, lastRow] = await Promise.all([edge('MIN'), edge('MAX')])\n /* 드라이버가 준 값은 시간대가 없을 수 있다 — 컬럼 변환을 지나게 한다(§`hydratedDate`). */\n const first = hydratedDate(repo, 'eventTime', firstRow?.at)\n const last = hydratedDate(repo, 'eventTime', lastRow?.at)\n if (!first || !last) return null\n const t0 = first.getTime()\n const lastVantage = last.getTime() - H\n return lastVantage > t0 ? { t0, lastVantage } : null\n}\n\n/**\n * 갭 point 계산(공용) — 주어진 vantage 각각에서 forecast-at-T vs actual-at-T+H(결정적 백테스트).\n *\n * **vantage 는 밖에서 준다.** 예전에는 이 함수가 자기 저널의 첫·마지막 이벤트로 격자를 스스로 만들었다.\n * 그러면 트윈마다 격자가 달라져, 공동배치 합산에서 `points[3]` 끼리 더할 때 **서로 다른 시각의 값**이\n * 더해졌다(자세한 사연은 `aggregatePoints` 주석).\n *\n * 반환 배열은 격자와 **같은 길이**이고, 그 vantage 를 재지 못했으면 그 자리에 `null` 을 둔다.\n * 건너뛰고 밀어 담으면 배열 인덱스가 시간축이기를 그만둔다.\n */\nasync function gapPoints(domainId: string, instanceId: string, m: (s: any) => number, H: number, grid: number[], scen: any): Promise<(any | null)[]> {\n const points: (any | null)[] = []\n for (const T of grid) {\n const k = await TwinEngine.buildForecastKernelAt(domainId, instanceId, new Date(T).toISOString()).catch(() => null)\n if (!k) { points.push(null); continue }\n const dist = await monteCarloForecastAsync(k, { runs: 12, horizonMs: H, scenario: scen, tickMs: forecastStepMs(H), metric: m, yieldFn: breather().fn })\n const naive = m(k.getSnapshot())\n const st = await TwinEngine.recover(domainId, instanceId, undefined, new Date(T + H).toISOString()).catch(() => null)\n if (!st) { points.push(null); continue }\n const a = m(st)\n const p50 = dist?.p50 ?? naive\n points.push({ t: T, p50, min: dist?.min ?? p50, max: dist?.max ?? p50, actual: a, error: a - p50, inBand: a >= (dist?.min ?? -Infinity) && a <= (dist?.max ?? Infinity), naiveAbsError: Math.abs(a - naive), modelAbsError: Math.abs(a - p50) })\n }\n return points\n}\n\n/**\n * 여러 트윈이 **함께** 백테스트할 수 있는 구간과 그 위의 vantage 격자.\n *\n * 겹치는 구간은 각자의 구간을 교집합한 것 — 시작은 가장 늦은 t0, 끝은 가장 이른 lastVantage.\n * 한 트윈이라도 그 시각에 저널이 없으면 공장 총량을 말할 수 없기 때문이다.\n * 겹치는 구간이 없으면 `null` — \"0\" 이 아니라 \"잴 수 없음\" 이다.\n */\nfunction sharedGrid(windows: { t0: number; lastVantage: number }[], N: number): number[] | null {\n if (!windows.length || N < 2) return null\n const t0 = Math.max(...windows.map(w => w.t0))\n const lastVantage = Math.min(...windows.map(w => w.lastVantage))\n if (!(lastVantage > t0)) return null\n return Array.from({ length: N }, (_, i) => t0 + ((lastVantage - t0) * i) / (N - 1))\n}\n\n@Resolver()\nexport class TwinForecastQuery {\n @Query(returns => ScalarObject, {\n nullable: true,\n description: 'Monte-Carlo forecast of a metric over a horizon (twin prediction faculty) + a single-fork predicted trajectory. Live instance is untouched (forks are isolated).'\n })\n async twinForecast(\n @Arg('instanceId') instanceId: string,\n @Arg('metric', { nullable: true }) metric: string,\n @Arg('runs', type => Int, { nullable: true }) runs: number,\n @Arg('horizonMs', type => Int, { nullable: true }) horizonMs: number,\n @Arg('scenario', type => ScalarObject, { nullable: true }) scenario: any,\n @Arg('at', { nullable: true }) at: string,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n /* vantage(at) 기준 예측 — 히스토리 노브가 과거 T 면 \"그때 서서 본 미래\"(백테스트).\n at 있으면 T 시점 커널을 저널로 재구성한다(그건 우회가 아니라 시간여행의 본질).\n 없으면 **지금 그 커널**을 쓴다 — sim 이든 live 든 인스턴스가 커널이다(통합 P2). */\n const domainId = context.state.domain.id\n const k = at\n ? await TwinEngine.buildForecastKernelAt(domainId, instanceId, at)\n : TwinEngine.owns(domainId, instanceId)\n ? TwinEngine.kernel(domainId, instanceId)\n : undefined\n if (!k) return null\n\n const m = metricFn(metric)\n /* 이 트윈이 못 세는 지표 — 빈 예측이 아니라 「그 지표는 여기 없다」고 말한다. */\n if (!m) return { metric, unsupportedMetric: true, distribution: null, trajectory: [] }\n const H = horizonMs ?? 300000\n const R = runs ?? 30\n /*\n * 틱 크기는 **지평선이 정한다**(`forecast-tuning`). 1초 고정이던 시절 5분 지평선이 회차마다\n * 300틱이었고, 30회차면 9,000틱·28.5초였다 — 실측으로 5초 단위와 답이 같다.\n */\n const step = forecastStepMs(H)\n const scen = scenario ?? DEFAULT_SCENARIO\n\n /*\n * 분포: N개 확률적 미래 표본(fork 마다 seed 변주, 원본 보존).\n *\n * **이것이 요청 처리 중 메인 이벤트 루프에서 동기로 수행된다.** 기본값으로 fork 30개 × 300틱 = 9,000틱.\n * 계기판에 남겨 두지 않으면 \"왜 서버가 굳었나\" 를 나중에 짚을 수 없다(실제로 한 번 겪었다).\n */\n const tMc = performance.now()\n const mcBreath = breather()\n const distribution = await monteCarloForecastAsync(k, { runs: R, horizonMs: H, scenario: scen, tickMs: step, metric: m, yieldFn: mcBreath.fn })\n TwinEngine.recordLoad(domainId, instanceId, 'forecast', performance.now() - tMc - mcBreath.ms)\n TwinEngine.countForks(domainId, instanceId, R)\n\n /* 학습된 보정 상시 적용(P3.2) — twinLearnCalibration 이 저장한 편향·밴드가 있으면 예측을 현실로 보정(라이브가 실제로 나아짐).\n 갭트렌드(gapPoints)는 raw 모델을 그대로 측정(학습 신호 보존) — 보정은 표시용 forecast 에만. */\n const stored = CALIBRATION.get(calKey(domainId, instanceId, metric ?? 'items', H))\n /*\n * **배운 모델에만 적용한다.** 보정은 \"그때 모델이 이만큼 치우쳐 있다\" 를 배운 것이다. 소요시간\n * 추정기(실측·거리)나 공정 명세가 바뀌어 모델이 달라지면 그 편향은 더 이상 사실이 아니고, 그대로\n * 밀어 주면 이중 보정이 된다. 그래서 지문을 비교하고, 다르면 적용하지 않되 **그 사실을 응답에\n * 싣는다** — 오류 없이 사라지면 사용자는 학습이 왜 안 듣는지 알 수 없다(다시 배우면 된다).\n * 지문을 알 수 없을 때는 비교하지 않는다(모르는 것을 근거로 보정을 끊지 않는다).\n */\n const basis = await TwinEngine.modelBasis(domainId, instanceId)\n const stale = !!stored && modelBasisChanged(stored.basis, basis)\n if (stale) {\n twinLog(\n `[twin-forecast] \"${instanceId}\": calibration not applied — model changed since it was learned.` +\n ` learned on: ${describeModelBasis(stored!.basis)} / now: ${describeModelBasis(basis)}`\n )\n }\n /*\n * 분위수 — 표본에서 직접 뽑는다.\n *\n * 커널이 표본 배열을 그대로 돌려주므로(forecast.ts) 여기서 정렬해 p05·p95 를 낸다. 이것이 없으면\n * 화면이 보여 줄 수 있는 폭은 관측 **최소·최대** 뿐인데, 그건 신뢰구간이 아니라 극단값 두 개다.\n * \"운이 나쁜 5% 에서는 이만큼\" 이라고 말하려면 분위수여야 한다. 표본이 적으면(runs 가 작으면)\n * 분위수도 거칠다 — 그래서 표본 수를 함께 싣는다(sampleCount). 숫자만 주고 근거를 감추지 않는다.\n */\n if (distribution) {\n const samples: number[] = Array.isArray(distribution.samples) ? distribution.samples : []\n if (samples.length) {\n const sorted = [...samples].sort((a, b) => a - b)\n const pct = (p: number) => sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))]\n distribution.p05 = pct(0.05)\n distribution.p95 = pct(0.95)\n }\n distribution.sampleCount = samples.length\n }\n\n const cal = stored && !stale ? stored : undefined\n if (cal && distribution) {\n /*\n * `p50` 은 커널 계약상 항상 있다(`MonteCarloResult.p50: number`). 그래도 메우지 않는다 —\n * 예전에는 `?? 0` 이었는데, 만약 비면 보정된 p50 이 **편향값 그 자체**가 되고 min·max 는\n * 그 둘레로 그려진다. 예측이 아닌 숫자가 예측의 자리에 앉는데 아무 신호가 없다.\n */\n if (typeof distribution.p50 !== 'number') throw new Error('forecast distribution has no p50 — kernel contract broken')\n const p50c = distribution.p50 + cal.biasShift\n distribution.p50 = Math.round(p50c)\n distribution.min = Math.round(p50c - cal.halfWidth)\n distribution.max = Math.round(p50c + cal.halfWidth)\n /*\n * 분위수는 **옮기기만** 한다. 보정이 배운 것은 \"모델이 이만큼 치우쳐 있다\"(위치)이지 분포의\n * 모양이 아니다. p50 처럼 밀어 두어야 p05~p95 폭이 표본이 말한 그대로 남는다.\n * (min·max 는 위에서 학습 밴드로 대체된다 — 보정이 걸리면 그 둘의 뜻이 '관측 극단' 에서\n * '학습 밴드' 로 바뀐다는 뜻이다. 화면이 둘을 같은 것으로 그리면 안 된다.)\n */\n if (distribution.p05 !== undefined) distribution.p05 = Math.round(distribution.p05 + cal.biasShift)\n if (distribution.p95 !== undefined) distribution.p95 = Math.round(distribution.p95 + cal.biasShift)\n if (distribution.p90 !== undefined) distribution.p90 = Math.round(distribution.p90 + cal.biasShift)\n distribution.calibrated = true\n }\n\n /*\n * 예측 궤적(예측선): 단일 fork 를 horizon 까지 실행하며 지표 기록. 이것도 잰다.\n *\n * 여기도 자리를 내준다 — 실측으로 이 한 회차가 1.3초였다(300틱 + 스냅샷 300번). 분포만큼은\n * 아니지만, 라이브 틱 간격(1초)보다 길게 붙잡으면 그 사이 트윈들이 한 박자씩 밀린다.\n */\n const tTraj = performance.now()\n const trajBreath = breather()\n const fc = k.fork()\n fc.scenario.load({ ...scen, seed: 1 })\n fc.scenario.start()\n const trajectory: { t: number; value: number }[] = []\n for (let t = 0; t < H; t += step) {\n fc.tick(step)\n trajectory.push({ t: t + step, value: m(fc.getSnapshot()) })\n /* 틱마다 내주면 양보 자체가 비용이 된다 — 라이브 틱 간격보다 훨씬 짧은 단위로 끊는다. */\n if (trajectory.length % 25 === 0) await trajBreath.fn()\n }\n\n TwinEngine.recordLoad(domainId, instanceId, 'forkTick', performance.now() - tTraj - trajBreath.ms)\n TwinEngine.countForks(domainId, instanceId, 1)\n\n const current = m(k.getSnapshot()) // vantage T 의 실제값(= naive '그대로 유지' 예측의 기준)\n\n /* 실측 오버레이 + 채점(P1) — vantage 가 과거(at)일 때만: T 이후 실제로 일어난 metric 을 T→min(now,T+H) 로 샘플.\n 현재 이후는 실측이 없음(미래). recover(T+t) 로 각 샘플 상태 재구성해 metric 추출. 비용=샘플 수 만큼 replay. */\n const actual: { t: number; value: number }[] = []\n let scored: any = null\n if (at) {\n const T = Date.parse(at)\n const endMs = Math.min(Date.now(), T + H)\n const span = endMs - T\n if (Number.isFinite(T) && span > 0) {\n const SAMPLES = 8\n const dt = span / SAMPLES\n for (let i = 1; i <= SAMPLES; i++) {\n const st = await TwinEngine.recover(domainId, instanceId, undefined, new Date(T + i * dt).toISOString()).catch(() => null)\n if (st) actual.push({ t: i * dt, value: m(st) })\n }\n /* 지평선이 지났으면(T+H ≤ now) 그 지점 실측 vs 예측 채점(단일 vantage). p50 대비 오차·밴드 적중·FVA(naive=현재값 유지 대비). */\n if (T + H <= Date.now() && actual.length) {\n const a = actual[actual.length - 1].value\n const p50 = distribution?.p50 ?? current\n scored = {\n actual: a,\n p50,\n error: a - p50, // 부호 = 편향 방향(+ 실제가 더 많음)\n inBand: a >= (distribution?.min ?? -Infinity) && a <= (distribution?.max ?? Infinity),\n modelAbsError: Math.abs(a - p50),\n naiveAbsError: Math.abs(a - current) // naive = \"그대로 유지\"(현재값). FVA = naive - model\n }\n }\n }\n }\n\n /*\n * 시뮬 명세 자기보고를 함께 낸다 — **상수로 계산한 예측과 현장 명세로 계산한 예측이 화면에서 같아\n * 보이지 않게.** 코드·수치만 싣고 사람이 읽는 문장은 클라가 만든다(언어 중립).\n * 커널이 이 API 를 갖지 않은 버전이면 키를 만들지 않는다(모르는 것을 \"전부 기본값\" 이라 단정 금지).\n */\n /* 보고는 **실제로 실행한 fork** 에서 읽는다 — 부모 커널(k)은 tick 하지 않으므로(예측은 fork 에서\n * 수행된다) 부모에서 읽으면 씨앗으로 세운 예측에서는 늘 비어 있다. 숫자를 낸 주체가 보고해야 한다. */\n const specCoverage = readSpecCoverage(fc) ?? readSpecCoverage(k)\n return {\n metric: metric ?? 'items', horizonMs: H, runs: R, current, distribution, trajectory, actual, scored,\n /* 얼마나 잘게 굴렸나 — 숫자만 주고 근거를 감추지 않는다(같은 규율로 `specCoverage` 도 싣는다). */\n tickMs: step,\n /*\n * **가정이 어떻게 됐나** — 걸린 개입·거절된 개입·덮어쓴 선언.\n *\n * 이것을 감추면 두 가지가 들어온 것이 없어진다: 걸리지 않은 가정(「걸었는데 왜 안 바뀌나」)과, 덮어쓴\n * 선언(사용자가 what-if 결과를 트윈의 현재 상태로 읽는다). 실제로 실행한 fork(`fc`)에서 읽는다 —\n * 가정을 겪은 것은 그쪽이다.\n */\n ...(typeof (fc as any).interventionOutcomes === 'function'\n ? (() => {\n const outcomes = (fc as any).interventionOutcomes()\n return outcomes.length ? { interventions: outcomes } : {}\n })()\n : {}),\n ...(typeof (fc as any).declarationOverrides === 'function'\n ? (() => {\n const overrides = (fc as any).declarationOverrides()\n return overrides.length ? { overrides } : {}\n })()\n : {}),\n calibrated: !!cal,\n /* 학습해 뒀지만 모델이 바뀌어 적용하지 않았다 — 사용자가 다시 배울 수 있게 알린다. */\n ...(stale ? { calibrationStale: true } : {}),\n ...(specCoverage ? { specCoverage } : {}),\n /*\n * **계보에 구멍이 몇 칸 생겼나** — 개체를 잇지 못한 채 넘어간 공정 수.\n *\n * ── 왜 이 값을 예측 응답에 싣나 (2026-08-24) ──────────────────────────────\n * 미러의 오더는 확보분을 갖지 않는다(원본이 「어느 개체가 잡혀 있나」를 말하지 않는 것이 그\n * 시스템의 정상이다). 커널은 그 자리에서 **던지지 않고 넘긴다** — 단계를 넘는 것은 공정의\n * 진행이고 개체 번호가 필요 없다. 대신 넘긴 횟수를 센다.\n *\n * 그런데 그 카운터는 **fork 안에** 있고 fork 는 버려진다. 여기서 싣지 않으면 아무에게도 닿지\n * 않는다 — 리듀서가 오래 세어 온 값을 스냅샷이 떨어뜨려 그것을 읽도록 쓰인 검사가 영원히\n * 아무 신호도 내지 않았던 것과 **같은 실수**다. 세어 놓고 내보내지 않으면 안 센 것과 같다.\n *\n * 왜 화면이 이것을 알아야 하나: 계보는 **회수 범위**를 정하는 사슬이다. 구멍이 있는데 온전한\n * 것처럼 보이면 회수가 알리지 않고 좁아진다. 식품에서 그것은 되돌릴 수 없다.\n *\n * 두 곳에서 읽어 **큰 값**을 낸다 — 분포(회차 R개의 최대)와 궤적 fork 는 **둘 다 실제로 실행됐다**.\n * 한쪽만 보면 다른 쪽이 겪은 구멍을 잃는다.\n */\n ...(() => {\n const gaps = Math.max(\n (distribution as any)?.stepsWithoutMaterial ?? 0,\n (fc as any)?.getSnapshot?.()?.stepsWithoutMaterial ?? 0\n )\n /* 0 은 싣지 않는다 — 「구멍이 없다」와 「이 축을 모른다」를 화면이 구별할 수 있게. */\n return gaps > 0 ? { stepsWithoutMaterial: gaps } : {}\n })()\n }\n }\n\n /*\n * 갭 트렌드(P2) — N개 과거 vantage 각각에서 \"그때 예측 vs 그 지평선의 실제\"를 재계산(결정적 백테스트).\n * 지평선이 이미 지난 vantage 만([t0, tLast−H]) → actual 확정. 온디맨드 재계산(누적/영속은 긴 기간용 후속 최적화).\n * 비용 = N × (buildForecastKernelAt replay + monteCarlo + recover replay). N·runs 를 낮게 bound. per-instance(클라가 co-located 합산).\n */\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'Forecast-accuracy gap diagnosis (canonical, server-computed): for N past vantages recompute forecast-at-T vs actual-at-T+h (deterministic backtest), aggregate co-located instances, and return points + SPC verdict (stability/bias) + metrics (accuracy/coverage/FVA) + learned-calibration projection. Client only renders.'\n })\n async twinForecastGapTrend(\n @Arg('instanceId') instanceId: string,\n @Arg('metric', { nullable: true }) metric: string,\n @Arg('horizonMs', type => Int, { nullable: true }) horizonMs: number,\n @Arg('samples', type => Int, { nullable: true }) samples: number,\n @Arg('scenario', type => ScalarObject, { nullable: true }) scenario: any,\n @Ctx() context: ResolverContext,\n @Arg('instanceIds', type => [String], { nullable: true }) instanceIds?: string[]\n ): Promise<any> {\n const domainId = context.state.domain.id\n const m = metricFn(metric)\n const H = horizonMs ?? 300000\n if (!m) return { metric, unsupportedMetric: true, horizonMs: H, samples: 0, points: [], spc: computeSpc([]), metrics: null, learn: null }\n const N = Math.min(30, Math.max(2, samples ?? GAP_SAMPLES)) // 표본은 서버가 관장(SPC 신뢰 하한 ~20).\n const scen = scenario ?? DEFAULT_SCENARIO\n // 공동배치(co-located) 집계도 서버에서 — 공장 단위 진단이 canonical. 미지정 시 단건.\n const ids = instanceIds?.length ? instanceIds : [instanceId]\n\n /*\n * 격자를 **먼저 하나** 만든다. 트윈마다 자기 저널로 격자를 만들면 같은 인덱스가 서로 다른\n * 시각이 되어, 합산이 다른 시점의 값을 더한다(`aggregatePoints` 주석 참조).\n *\n * 저널이 없거나 지평 H 를 담을 만큼 길지 않은 트윈은 **백테스트에 참여할 수 없다.** 그런 트윈은\n * 빼되 누구를 뺐는지 돌려준다 — 알리지 않고 빼면 화면은 그 트윈까지 포함한 공장 총량으로 읽는다.\n */\n const windows = await Promise.all(ids.map(async id => ({ id, w: await gapWindow(domainId, id, H) })))\n const usable = windows.filter(x => x.w)\n const excluded = windows.filter(x => !x.w).map(x => x.id)\n const grid = sharedGrid(usable.map(x => x.w!), N)\n if (!grid) {\n /* 겹치는 구간이 없다 — 잴 수 없는 것이지 정확도가 0 인 것이 아니다. */\n return { metric: metric ?? 'items', horizonMs: H, samples: N, points: [], spc: computeSpc([]), metrics: null, learn: null,\n coverage: { instances: ids.length, measured: 0, excluded, vantages: N, used: 0, dropped: 0, reason: 'no-common-window' } }\n }\n\n const per = await Promise.all(usable.map(x => gapPoints(domainId, x.id, m, H, grid, scen)))\n const { points, droppedVantages } = aggregatePoints(per as (GapPoint | null)[][])\n const metrics = points.length ? gapMetrics(points) : null\n const spc = computeSpc(points.map(p => p.error))\n const learn = metrics ? learnedCalibration(points, metrics) : null\n return { metric: metric ?? 'items', horizonMs: H, samples: N, points, spc, metrics, learn,\n /* 이 숫자가 **무엇으로 계산됐는지**. 24점 중 18점이면 화면이 그렇게 말해야 한다. */\n coverage: { instances: ids.length, measured: usable.length, excluded, vantages: N, used: points.length, dropped: droppedVantages } }\n }\n\n /*\n * 학습(P3.2) — 갭에서 편향·밴드를 학습해 저장하면, 이후 twinForecast 가 상시 적용(라이브 예측이 실제로 보정됨).\n * assimilation-lite: 잔차(innovation)를 되먹여 추정치를 현실로. per-instance(클라가 co-located 합산).\n * 반환: 학습된 보정 + before/after 정확도(효과 정량화). 저장은 인메모리(세션) — DB 영속은 후속.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Learn a forecast calibration (bias + band) from the gap trend and persist it so future forecasts apply it. Returns the calibration + before/after accuracy.' })\n async twinLearnCalibration(\n @Arg('instanceId') instanceId: string,\n @Arg('metric', { nullable: true }) metric: string,\n @Arg('horizonMs', type => Int, { nullable: true }) horizonMs: number,\n @Ctx() context: ResolverContext,\n @Arg('clear', type => Boolean, { nullable: true }) clear?: boolean\n ): Promise<any> {\n const domainId = context.state.domain.id\n if (!TwinEngine.owns(domainId, instanceId)) return { ok: false } // 테넌트 격리\n const m = metricFn(metric)\n if (!m) return { ok: false, reason: 'unsupported-metric', metric }\n const H = horizonMs ?? 300000\n // 보정 해제 — 저장된 calibration 제거(이후 예측은 raw 로 복귀). 학습을 되돌리는 정직한 off 스위치.\n if (clear) {\n CALIBRATION.delete(calKey(domainId, instanceId, metric ?? 'items', H))\n return { ok: true, cleared: true, metric: metric ?? 'items', horizonMs: H }\n }\n // 표본·계산을 gap-analytics 로 일원화(gapTrend 진단과 동일 N·동일 수식 → 카드 예상치와 실제 보정 일치).\n /* 학습은 단건이라 격자도 이 트윈의 구간 하나다. 저널이 지평 H 를 담을 만큼 길지 않으면 배울 것이 없다. */\n const w = await gapWindow(domainId, instanceId, H)\n const grid = w ? sharedGrid([w], GAP_SAMPLES) : null\n if (!grid) return { ok: false, reason: 'insufficient-history' }\n /* 못 잰 vantage 는 `null` 로 온다 — 0 으로 세면 편향 학습이 그만큼 끌려간다. */\n const pts = (await gapPoints(domainId, instanceId, m, H, grid, DEFAULT_SCENARIO)).filter(Boolean) as GapPoint[]\n if (pts.length < 3) return { ok: false, reason: 'insufficient-history' }\n const learn = learnedCalibration(pts, gapMetrics(pts))\n /* 학습한 모델의 지문을 함께 남긴다 — 이후 모델이 바뀌면 이 보정은 적용되지 않는다. */\n const basis = await TwinEngine.modelBasis(domainId, instanceId)\n const cal = {\n biasShift: learn.biasShift,\n halfWidth: learn.halfWidth,\n at: '',\n samples: pts.length,\n accBefore: learn.accBefore,\n accAfter: learn.accAfter,\n ...(basis ? { basis } : {})\n }\n CALIBRATION.set(calKey(domainId, instanceId, metric ?? 'items', H), cal)\n return { ok: true, metric: metric ?? 'items', horizonMs: H, biasShift: cal.biasShift, halfWidth: cal.halfWidth, samples: pts.length, accBefore: cal.accBefore, accAfter: cal.accAfter }\n }\n}\n"]}
@@ -2,7 +2,7 @@
2
2
  * 한 번에 돌려주는 창 수 상한 — 144창 × 10분 = 24시간.
3
3
  *
4
4
  * 상한이 필요한 이유는 이 표가 **자란다**는 것이다(트윈 1,000개에 하루 14.4만 행). 상한에서 밀려난
5
- * 수는 **세어서 낸다** — 조용히 자르면 화면이 「그 앞에는 아무 일도 없었다」로 읽는다.
5
+ * 수는 **세어서 낸다** — 알리지 않고 자르면 화면이 「그 앞에는 아무 일도 없었다」로 읽는다.
6
6
  */
7
7
  export declare const HISTORY_WINDOW_LIMIT = 144;
8
8
  /** 물을 수 있는 기간의 상한(시간). 넘겨도 여기서 자르고, 자른 사실을 낸다. */
@@ -29,7 +29,7 @@ const twin_ingest_window_js_1 = require("./twin-ingest-window.js");
29
29
  * 한 번에 돌려주는 창 수 상한 — 144창 × 10분 = 24시간.
30
30
  *
31
31
  * 상한이 필요한 이유는 이 표가 **자란다**는 것이다(트윈 1,000개에 하루 14.4만 행). 상한에서 밀려난
32
- * 수는 **세어서 낸다** — 조용히 자르면 화면이 「그 앞에는 아무 일도 없었다」로 읽는다.
32
+ * 수는 **세어서 낸다** — 알리지 않고 자르면 화면이 「그 앞에는 아무 일도 없었다」로 읽는다.
33
33
  */
34
34
  exports.HISTORY_WINDOW_LIMIT = 144;
35
35
  /** 물을 수 있는 기간의 상한(시간). 넘겨도 여기서 자르고, 자른 사실을 낸다. */
@@ -1 +1 @@
1
- {"version":3,"file":"twin-ingest-window-query.js","sourceRoot":"","sources":["../../../server/service/twin-ingest-window/twin-ingest-window-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Int, Query, Resolver } from 'type-graphql'\nimport { Between } from 'typeorm'\n\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { TwinIngestWindow } from './twin-ingest-window.js'\n\n/*\n * 인제스트 창 **이력** — 재기동 뒤에도 추이를 이어 본다.\n *\n * ── 왜 따로 있나 (2026-08-20) ───────────────────────────────────────────────\n * 창을 표로 남기기 시작했는데 **읽는 코드가 없었다.** 21행이 쌓여 있고 아무도 보지 않았다 — 이\n * 프로젝트가 결함으로 보는 상태다(「필드만 만들고 아무도 읽지 않는다」). 표를 만든 이유가 「메모리\n * 장부는 재기동에 사라지고 6시간까지인데 추이는 그보다 오래 봐야 한다」였으니, 목적이 미완이었다.\n *\n * ── 왜 `twinIngestHealth` 에 합치지 않나 ────────────────────────────────────\n * 그 조회는 **화면이 자주 묻는다**(상세를 열 때마다, 목록은 응답에 요약이 함께 온다). 거기에 DB 주사를\n * 얹으면 이 세션에서 없앤 것과 같은 종류의 비용이 돌아온다 — 자주 묻는 조회가 자라는 표를 읽는 것.\n * 이력은 **다른 질문**이고 덜 자주 묻는다: 「더 뒤까지 보여 달라」.\n *\n * ── 이어 붙이기는 서버가 한다 ───────────────────────────────────────────────\n * 표와 메모리는 겹친다(프로세스가 살아 있는 동안 닫힌 창은 둘 다에 있다). 겹침을 화면이 정리하게 두면\n * 화면마다 규칙이 갈리고, 같은 구간이 두 번 그려지거나 사라진다. 그래서 여기서 한 벌로 이어 붙이고\n * **어디서부터 메모리인지**(`liveFrom`)를 함께 낸다 — 화면이 근거를 말할 수 있게.\n */\n\n/**\n * 한 번에 돌려주는 창 수 상한 — 144창 × 10분 = 24시간.\n *\n * 상한이 필요한 이유는 이 표가 **자란다**는 것이다(트윈 1,000개에 하루 14.4만 행). 상한에서 밀려난\n * 수는 **세어서 낸다** — 조용히 자르면 화면이 「그 앞에는 아무 일도 없었다」로 읽는다.\n */\nexport const HISTORY_WINDOW_LIMIT = 144\n\n/** 물을 수 있는 기간의 상한(시간). 넘겨도 여기서 자르고, 자른 사실을 낸다. */\nexport const HISTORY_HOURS_MAX = 168\n\n@Resolver()\nexport class TwinIngestWindowQuery {\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'Ingest windows for one twin over a past span, stitched from two sources so the trend survives a restart: closed windows persisted in the table (older) and the in-memory ledger (recent, roughly the last 6 hours). Overlap is resolved here rather than on screen — a window present in both is reported once, from memory, and `liveFrom` says where memory takes over so a screen can state its basis. Each window carries offered / accepted / rejected / undelivered plus the journal rows written and the write round-trips behind them; `acceptedRatio` is null when nothing was offered, because an empty window is not a perfect one. Deliberately separate from twinIngestHealth: that query is polled whenever a screen opens, and making it scan a growing table would reintroduce the cost this codebase just removed elsewhere. Counts that could not be returned are reported rather than dropped — `storedDropped` when the row cap was hit and `hoursClamped` when the requested span was longer than allowed. Null if the instance is not in the current domain.'\n })\n async twinIngestHistory(\n @Arg('instanceId') instanceId: string,\n @Arg('hours', type => Int, { nullable: true, description: 'How far back to read, in hours. Defaults to 24 and is clamped to 168.' })\n hours: number | undefined,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain.id\n /* 테넌트 격리 — 멈춘 트윈의 이력도 볼 수 있어야 한다(그때가 가장 보고 싶은 때다). */\n if (!TwinEngine.owns(domainId, instanceId) && !(await TwinEngine.ownsRegistered(domainId, instanceId))) return null\n\n const asked = Number.isFinite(Number(hours)) && Number(hours) > 0 ? Math.floor(Number(hours)) : 24\n const span = Math.min(asked, HISTORY_HOURS_MAX)\n const now = Date.now()\n const fromMs = now - span * 3_600_000\n\n /*\n * 표에서 그 구간의 닫힌 창을 읽는다. **상한을 둔다** — 자라는 표이고, 이 조회가 화면에서 열린다.\n * 최신 쪽이 중요하므로 내려서 정렬해 가져오고, 낼 때 다시 올린다.\n */\n const repo = getRepository(TwinIngestWindow)\n const where = {\n domain: { id: domainId } as any,\n instanceId,\n closedAt: Between(new Date(fromMs), new Date(now))\n }\n const rows = await repo.find({ where, order: { closedAt: 'DESC' }, take: HISTORY_WINDOW_LIMIT })\n\n /*\n * 상한에 닿았으면 **몇 개가 밀렸는지 센다.** 「더 있음」만 말하면 화면이 그 앞을 짐작하게 된다.\n * 상한에 닿지 않았으면 셀 필요가 없다(왕복을 아낀다).\n */\n const storedDropped = rows.length === HISTORY_WINDOW_LIMIT ? Math.max(0, (await repo.count({ where })) - rows.length) : 0\n\n const stored = rows.reverse().map(r => ({\n from: r.openedAt?.toISOString?.() ?? new Date(r.openedAt as any).toISOString(),\n to: r.closedAt?.toISOString?.() ?? new Date(r.closedAt as any).toISOString(),\n offered: r.offered,\n /* 통과는 **트윈에 닿은** 것만. 표의 값을 그대로 쓴다 — 읽는 쪽에서 다시 나누면 반올림이 갈린다. */\n accepted: r.offered - r.rejected - (r.undelivered ?? 0),\n rejected: r.rejected,\n undelivered: r.undelivered ?? 0,\n acceptedRatio: typeof r.acceptedRatio === 'number' ? r.acceptedRatio : null,\n rows: r.journaledRows ?? 0,\n flushes: r.journalFlushes ?? 0,\n source: 'stored' as const\n }))\n\n /* 메모리 장부의 닫힌 창 — 이쪽이 최신이고, 겹치는 구간은 이쪽을 쓴다(같은 사실의 원본이다). */\n const live = (TwinEngine.ingestHealthOf(domainId, instanceId).trend ?? []).map((w: any) => ({\n ...w,\n source: 'live' as const\n }))\n\n /*\n * 겹침 정리 — 창의 **여는 시각**이 키다. 같은 창이 표와 메모리에 둘 다 있으면 하나만 낸다.\n * 시각 문자열을 그대로 키로 쓰지 않는다: 드라이버가 밀리초를 자르거나 형식을 바꿀 수 있어\n * 같은 창이 다른 키가 된다. epoch 로 견준다.\n */\n const liveStarts = new Set(live.map((w: any) => Date.parse(w.from)))\n const windows = [...stored.filter(w => !liveStarts.has(Date.parse(w.from))), ...live]\n\n return {\n windows,\n hours: span,\n /* 물은 것보다 짧게 잘렸으면 밝힌다. */\n ...(span < asked ? { hoursClamped: asked } : {}),\n from: new Date(fromMs).toISOString(),\n to: new Date(now).toISOString(),\n /** 표에서 읽은 창 수 · 상한에서 밀려난 수. */\n storedCount: stored.length,\n storedDropped,\n /**\n * 어디서부터 메모리인가 — 화면이 근거를 말할 수 있게. 메모리에 닫힌 창이 없으면 `null` 이다\n * (재기동 직후가 그렇다: 그때 보이는 것은 전부 표에서 온 것이다).\n */\n liveFrom: live.length ? live[0].from : null,\n liveCount: live.length\n }\n }\n}\n"]}
1
+ {"version":3,"file":"twin-ingest-window-query.js","sourceRoot":"","sources":["../../../server/service/twin-ingest-window/twin-ingest-window-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Int, Query, Resolver } from 'type-graphql'\nimport { Between } from 'typeorm'\n\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { TwinIngestWindow } from './twin-ingest-window.js'\n\n/*\n * 인제스트 창 **이력** — 재기동 뒤에도 추이를 이어 본다.\n *\n * ── 왜 따로 있나 (2026-08-20) ───────────────────────────────────────────────\n * 창을 표로 남기기 시작했는데 **읽는 코드가 없었다.** 21행이 쌓여 있고 아무도 보지 않았다 — 이\n * 프로젝트가 결함으로 보는 상태다(「필드만 만들고 아무도 읽지 않는다」). 표를 만든 이유가 「메모리\n * 장부는 재기동에 사라지고 6시간까지인데 추이는 그보다 오래 봐야 한다」였으니, 목적이 미완이었다.\n *\n * ── 왜 `twinIngestHealth` 에 합치지 않나 ────────────────────────────────────\n * 그 조회는 **화면이 자주 묻는다**(상세를 열 때마다, 목록은 응답에 요약이 함께 온다). 거기에 DB 주사를\n * 얹으면 이 세션에서 없앤 것과 같은 종류의 비용이 돌아온다 — 자주 묻는 조회가 자라는 표를 읽는 것.\n * 이력은 **다른 질문**이고 덜 자주 묻는다: 「더 뒤까지 보여 달라」.\n *\n * ── 이어 붙이기는 서버가 한다 ───────────────────────────────────────────────\n * 표와 메모리는 겹친다(프로세스가 살아 있는 동안 닫힌 창은 둘 다에 있다). 겹침을 화면이 정리하게 두면\n * 화면마다 규칙이 갈리고, 같은 구간이 두 번 그려지거나 사라진다. 그래서 여기서 한 벌로 이어 붙이고\n * **어디서부터 메모리인지**(`liveFrom`)를 함께 낸다 — 화면이 근거를 말할 수 있게.\n */\n\n/**\n * 한 번에 돌려주는 창 수 상한 — 144창 × 10분 = 24시간.\n *\n * 상한이 필요한 이유는 이 표가 **자란다**는 것이다(트윈 1,000개에 하루 14.4만 행). 상한에서 밀려난\n * 수는 **세어서 낸다** — 알리지 않고 자르면 화면이 「그 앞에는 아무 일도 없었다」로 읽는다.\n */\nexport const HISTORY_WINDOW_LIMIT = 144\n\n/** 물을 수 있는 기간의 상한(시간). 넘겨도 여기서 자르고, 자른 사실을 낸다. */\nexport const HISTORY_HOURS_MAX = 168\n\n@Resolver()\nexport class TwinIngestWindowQuery {\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'Ingest windows for one twin over a past span, stitched from two sources so the trend survives a restart: closed windows persisted in the table (older) and the in-memory ledger (recent, roughly the last 6 hours). Overlap is resolved here rather than on screen — a window present in both is reported once, from memory, and `liveFrom` says where memory takes over so a screen can state its basis. Each window carries offered / accepted / rejected / undelivered plus the journal rows written and the write round-trips behind them; `acceptedRatio` is null when nothing was offered, because an empty window is not a perfect one. Deliberately separate from twinIngestHealth: that query is polled whenever a screen opens, and making it scan a growing table would reintroduce the cost this codebase just removed elsewhere. Counts that could not be returned are reported rather than dropped — `storedDropped` when the row cap was hit and `hoursClamped` when the requested span was longer than allowed. Null if the instance is not in the current domain.'\n })\n async twinIngestHistory(\n @Arg('instanceId') instanceId: string,\n @Arg('hours', type => Int, { nullable: true, description: 'How far back to read, in hours. Defaults to 24 and is clamped to 168.' })\n hours: number | undefined,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain.id\n /* 테넌트 격리 — 멈춘 트윈의 이력도 볼 수 있어야 한다(그때가 가장 보고 싶은 때다). */\n if (!TwinEngine.owns(domainId, instanceId) && !(await TwinEngine.ownsRegistered(domainId, instanceId))) return null\n\n const asked = Number.isFinite(Number(hours)) && Number(hours) > 0 ? Math.floor(Number(hours)) : 24\n const span = Math.min(asked, HISTORY_HOURS_MAX)\n const now = Date.now()\n const fromMs = now - span * 3_600_000\n\n /*\n * 표에서 그 구간의 닫힌 창을 읽는다. **상한을 둔다** — 자라는 표이고, 이 조회가 화면에서 열린다.\n * 최신 쪽이 중요하므로 내려서 정렬해 가져오고, 낼 때 다시 올린다.\n */\n const repo = getRepository(TwinIngestWindow)\n const where = {\n domain: { id: domainId } as any,\n instanceId,\n closedAt: Between(new Date(fromMs), new Date(now))\n }\n const rows = await repo.find({ where, order: { closedAt: 'DESC' }, take: HISTORY_WINDOW_LIMIT })\n\n /*\n * 상한에 닿았으면 **몇 개가 밀렸는지 센다.** 「더 있음」만 말하면 화면이 그 앞을 짐작하게 된다.\n * 상한에 닿지 않았으면 셀 필요가 없다(왕복을 아낀다).\n */\n const storedDropped = rows.length === HISTORY_WINDOW_LIMIT ? Math.max(0, (await repo.count({ where })) - rows.length) : 0\n\n const stored = rows.reverse().map(r => ({\n from: r.openedAt?.toISOString?.() ?? new Date(r.openedAt as any).toISOString(),\n to: r.closedAt?.toISOString?.() ?? new Date(r.closedAt as any).toISOString(),\n offered: r.offered,\n /* 통과는 **트윈에 닿은** 것만. 표의 값을 그대로 쓴다 — 읽는 쪽에서 다시 나누면 반올림이 갈린다. */\n accepted: r.offered - r.rejected - (r.undelivered ?? 0),\n rejected: r.rejected,\n undelivered: r.undelivered ?? 0,\n acceptedRatio: typeof r.acceptedRatio === 'number' ? r.acceptedRatio : null,\n rows: r.journaledRows ?? 0,\n flushes: r.journalFlushes ?? 0,\n source: 'stored' as const\n }))\n\n /* 메모리 장부의 닫힌 창 — 이쪽이 최신이고, 겹치는 구간은 이쪽을 쓴다(같은 사실의 원본이다). */\n const live = (TwinEngine.ingestHealthOf(domainId, instanceId).trend ?? []).map((w: any) => ({\n ...w,\n source: 'live' as const\n }))\n\n /*\n * 겹침 정리 — 창의 **여는 시각**이 키다. 같은 창이 표와 메모리에 둘 다 있으면 하나만 낸다.\n * 시각 문자열을 그대로 키로 쓰지 않는다: 드라이버가 밀리초를 자르거나 형식을 바꿀 수 있어\n * 같은 창이 다른 키가 된다. epoch 로 견준다.\n */\n const liveStarts = new Set(live.map((w: any) => Date.parse(w.from)))\n const windows = [...stored.filter(w => !liveStarts.has(Date.parse(w.from))), ...live]\n\n return {\n windows,\n hours: span,\n /* 물은 것보다 짧게 잘렸으면 밝힌다. */\n ...(span < asked ? { hoursClamped: asked } : {}),\n from: new Date(fromMs).toISOString(),\n to: new Date(now).toISOString(),\n /** 표에서 읽은 창 수 · 상한에서 밀려난 수. */\n storedCount: stored.length,\n storedDropped,\n /**\n * 어디서부터 메모리인가 — 화면이 근거를 말할 수 있게. 메모리에 닫힌 창이 없으면 `null` 이다\n * (재기동 직후가 그렇다: 그때 보이는 것은 전부 표에서 온 것이다).\n */\n liveFrom: live.length ? live[0].from : null,\n liveCount: live.length\n }\n }\n}\n"]}
@@ -12,7 +12,7 @@ const twin_ingest_window_js_1 = require("./twin-ingest-window.js");
12
12
  * 두었고(`onIngestWindowClosed`), 이 파일이 부팅에서 한 번 등록한다. 그래서 기록 경로가 갈리지 않는다.
13
13
  *
14
14
  * ── 실패를 삼키지 않는다 ────────────────────────────────────────────────────
15
- * 쓰기가 터져도 메모리 장부는 계속 굴러간다(`rollIngestWindow` 가 훅을 감싼다). 다만 **조용히 넘기지는
15
+ * 쓰기가 터져도 메모리 장부는 계속 굴러간다(`rollIngestWindow` 가 훅을 감싼다). 다만 **알리지 않고 넘기지는
16
16
  * 않는다** — 실패를 로그로 남긴다. 그러지 않으면 추이 표에 구멍이 생긴 이유를 나중에 알 수 없다.
17
17
  *
18
18
  * ── 왜 await 하지 않나 ──────────────────────────────────────────────────────
@@ -1 +1 @@
1
- {"version":3,"file":"twin-ingest-window-writer.js","sourceRoot":"","sources":["../../../server/service/twin-ingest-window/twin-ingest-window-writer.ts"],"names":[],"mappings":";;AAoBA,8DAiCC;AArDD,gDAA8C;AAC9C,iDAAqD;AAErD,oDAAkD;AAClD,mEAA0D;AAE1D;;;;;;;;;;;;;GAaG;AACH,SAAgB,yBAAyB;IACvC,qBAAU,CAAC,oBAAoB,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE;QAC7C,MAAM,IAAI,GAAG,IAAA,qBAAa,EAAC,wCAAgB,CAAC,CAAA;QAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;QAC9B,IAAI;aACD,IAAI,CACH,IAAI,CAAC,MAAM,CAAC;YACV,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,QAAQ,EAAS;YAClC,UAAU,EAAE,EAAE,CAAC,UAAU;YACzB,QAAQ,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;YAClC,QAAQ,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YAChC,OAAO;YACP,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,kDAAkD;YAClD,aAAa,EAAE,MAAM,CAAC,IAAI;YAC1B,cAAc,EAAE,MAAM,CAAC,OAAO;YAC9B;;;eAGG;YACH,GAAG,CAAC,OAAO,GAAG,CAAC;gBACb,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,OAAO,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,EAAE;gBACvF,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CACH;aACA,KAAK,CAAC,GAAG,CAAC,EAAE,CACX,IAAA,iBAAQ,EACN,yBAAyB,EAAE,CAAC,UAAU,sDAAsD,EAC5F,GAAG,EAAE,OAAO,IAAI,GAAG,CACpB,CACF,CAAA;IACL,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,mEAAmE;AACnE,MAAM,MAAM,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK,CAAA","sourcesContent":["import { twinWarn } from '../../engine/log.js'\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { TwinIngestWindow } from './twin-ingest-window.js'\n\n/*\n * 닫힌 창을 표로 옮긴다 — **영속이 붙는 유일한 자리.**\n *\n * 커널 층(`engine/ingest-health.ts`)은 저장 계층을 모른다. 창이 닫힐 때 부를 곳을 엔진이 문으로 열어\n * 두었고(`onIngestWindowClosed`), 이 파일이 부팅에서 한 번 등록한다. 그래서 기록 경로가 갈리지 않는다.\n *\n * ── 실패를 삼키지 않는다 ────────────────────────────────────────────────────\n * 쓰기가 터져도 메모리 장부는 계속 굴러간다(`rollIngestWindow` 가 훅을 감싼다). 다만 **조용히 넘기지는\n * 않는다** — 실패를 로그로 남긴다. 그러지 않으면 추이 표에 구멍이 생긴 이유를 나중에 알 수 없다.\n *\n * ── 왜 await 하지 않나 ──────────────────────────────────────────────────────\n * 이 훅은 인제스트 경로에서 불린다(라이브 피드의 콜백 안). 여기서 DB 를 기다리면 **유입이 DB 속도에\n * 묶인다** — 이 기능이 막으려던 부하가 그 모양으로 돌아온다. 그래서 띄워 보내고 결과만 로그로 받는다.\n */\nexport function installIngestWindowWriter(): void {\n TwinEngine.onIngestWindowClosed((at, closed) => {\n const repo = getRepository(TwinIngestWindow)\n const offered = closed.offered\n repo\n .save(\n repo.create({\n domain: { id: at.domainId } as any,\n instanceId: at.instanceId,\n openedAt: new Date(closed.startMs),\n closedAt: new Date(closed.endMs),\n offered,\n rejected: closed.rejected,\n undelivered: closed.undelivered,\n /* 적힌 것도 같은 행에 — 창이 유입 없이 닫혔어도(시뮬) 이 두 수는 사실이다. */\n journaledRows: closed.rows,\n journalFlushes: closed.flushes,\n /*\n * 제시가 0 이면 비워 둔다 — 1 로 채우면 「전부 통과」로 읽힌다.\n * 통과는 **트윈에 닿은** 것만이다: 버려진 것을 빼지 않으면 멈춘 트윈의 창이 100% 로 남는다.\n */\n ...(offered > 0\n ? { acceptedRatio: round4((offered - closed.rejected - closed.undelivered) / offered) }\n : {})\n })\n )\n .catch(err =>\n twinWarn(\n `[twin-ingest-window] \"${at.instanceId}\" window not persisted (trend will have a gap here):`,\n err?.message ?? err\n )\n )\n })\n}\n\n/** 0.999 가 1 로 반올림되면 「전부 통과」로 읽힌다 — 자릿수를 넉넉히 준다(메모리 장부와 같은 규칙). */\nconst round4 = (v: number) => Math.round(v * 10000) / 10000\n"]}
1
+ {"version":3,"file":"twin-ingest-window-writer.js","sourceRoot":"","sources":["../../../server/service/twin-ingest-window/twin-ingest-window-writer.ts"],"names":[],"mappings":";;AAoBA,8DAiCC;AArDD,gDAA8C;AAC9C,iDAAqD;AAErD,oDAAkD;AAClD,mEAA0D;AAE1D;;;;;;;;;;;;;GAaG;AACH,SAAgB,yBAAyB;IACvC,qBAAU,CAAC,oBAAoB,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE;QAC7C,MAAM,IAAI,GAAG,IAAA,qBAAa,EAAC,wCAAgB,CAAC,CAAA;QAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;QAC9B,IAAI;aACD,IAAI,CACH,IAAI,CAAC,MAAM,CAAC;YACV,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,QAAQ,EAAS;YAClC,UAAU,EAAE,EAAE,CAAC,UAAU;YACzB,QAAQ,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;YAClC,QAAQ,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YAChC,OAAO;YACP,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,kDAAkD;YAClD,aAAa,EAAE,MAAM,CAAC,IAAI;YAC1B,cAAc,EAAE,MAAM,CAAC,OAAO;YAC9B;;;eAGG;YACH,GAAG,CAAC,OAAO,GAAG,CAAC;gBACb,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,OAAO,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,EAAE;gBACvF,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CACH;aACA,KAAK,CAAC,GAAG,CAAC,EAAE,CACX,IAAA,iBAAQ,EACN,yBAAyB,EAAE,CAAC,UAAU,sDAAsD,EAC5F,GAAG,EAAE,OAAO,IAAI,GAAG,CACpB,CACF,CAAA;IACL,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,mEAAmE;AACnE,MAAM,MAAM,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK,CAAA","sourcesContent":["import { twinWarn } from '../../engine/log.js'\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { TwinIngestWindow } from './twin-ingest-window.js'\n\n/*\n * 닫힌 창을 표로 옮긴다 — **영속이 붙는 유일한 자리.**\n *\n * 커널 층(`engine/ingest-health.ts`)은 저장 계층을 모른다. 창이 닫힐 때 부를 곳을 엔진이 문으로 열어\n * 두었고(`onIngestWindowClosed`), 이 파일이 부팅에서 한 번 등록한다. 그래서 기록 경로가 갈리지 않는다.\n *\n * ── 실패를 삼키지 않는다 ────────────────────────────────────────────────────\n * 쓰기가 터져도 메모리 장부는 계속 굴러간다(`rollIngestWindow` 가 훅을 감싼다). 다만 **알리지 않고 넘기지는\n * 않는다** — 실패를 로그로 남긴다. 그러지 않으면 추이 표에 구멍이 생긴 이유를 나중에 알 수 없다.\n *\n * ── 왜 await 하지 않나 ──────────────────────────────────────────────────────\n * 이 훅은 인제스트 경로에서 불린다(라이브 피드의 콜백 안). 여기서 DB 를 기다리면 **유입이 DB 속도에\n * 묶인다** — 이 기능이 막으려던 부하가 그 모양으로 돌아온다. 그래서 띄워 보내고 결과만 로그로 받는다.\n */\nexport function installIngestWindowWriter(): void {\n TwinEngine.onIngestWindowClosed((at, closed) => {\n const repo = getRepository(TwinIngestWindow)\n const offered = closed.offered\n repo\n .save(\n repo.create({\n domain: { id: at.domainId } as any,\n instanceId: at.instanceId,\n openedAt: new Date(closed.startMs),\n closedAt: new Date(closed.endMs),\n offered,\n rejected: closed.rejected,\n undelivered: closed.undelivered,\n /* 적힌 것도 같은 행에 — 창이 유입 없이 닫혔어도(시뮬) 이 두 수는 사실이다. */\n journaledRows: closed.rows,\n journalFlushes: closed.flushes,\n /*\n * 제시가 0 이면 비워 둔다 — 1 로 채우면 「전부 통과」로 읽힌다.\n * 통과는 **트윈에 닿은** 것만이다: 버려진 것을 빼지 않으면 멈춘 트윈의 창이 100% 로 남는다.\n */\n ...(offered > 0\n ? { acceptedRatio: round4((offered - closed.rejected - closed.undelivered) / offered) }\n : {})\n })\n )\n .catch(err =>\n twinWarn(\n `[twin-ingest-window] \"${at.instanceId}\" window not persisted (trend will have a gap here):`,\n err?.message ?? err\n )\n )\n })\n}\n\n/** 0.999 가 1 로 반올림되면 「전부 통과」로 읽힌다 — 자릿수를 넉넉히 준다(메모리 장부와 같은 규칙). */\nconst round4 = (v: number) => Math.round(v * 10000) / 10000\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"twin-instance.js","sourceRoot":"","sources":["../../../server/service/twin-instance/twin-instance.ts"],"names":[],"mappings":"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","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'\nimport { ObjectType, Field, ID } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { modelColumnTransformer } from '../../engine/model-vocabulary.js'\n\n/*\n * TwinInstance — 트윈 인스턴스 레지스트리(재부팅 복구의 앵커).\n * 복구(DB→replay)는 model(마스터 청사진)가 있어야 이벤트를 상태로 다시 계산할 수 있다 → model·kind 를 영속.\n * 이벤트(TwinEvent)와 달리 가변(status 갱신) — 인스턴스의 \"현재 등록 상태\".\n * model 는 simple-json(멀티DB 이식 — postgres/mysql/sqlite/mssql/oracle 공통).\n * (CLAUDE.md: 모든 @ObjectType/@Field 는 영문 description 필수.)\n */\n@Entity()\n@Index('ix_twin_instance_0', (e: TwinInstance) => [e.domain, e.instanceId], { unique: true })\n@Index('ix_twin_instance_1', (e: TwinInstance) => [e.domain, e.spaceId]) // 공간→인스턴스 1급 질의(공간 중심 존재론)\n@ObjectType({ description: 'Registry of a hosted twin runtime instance — anchors reboot recovery (model + kind).' })\nexport class TwinInstance {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the registry record.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinInstance) => e.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'Twin runtime instance id (unique within a domain).' })\n instanceId: string\n\n /**\n * **사람이 부르는 이름** — 업무키(`instanceId`)와 나란히 두는 표시명(프레임워크 엔티티 규약).\n *\n * ── 왜 업무키와 이름을 나눠 두나 ─────────────────────────────────────────────\n * `instanceId` 는 URL·저널·구독이 전부 쓰는 **계약**이라 바꿀 수 없다. 반대로 이름은 바뀌고,\n * 중복되고(사이트 둘에 \"도장\" 이 있는 것이 정상), 언어마다 다르다. 둘을 한 컬럼에 담으면 개명이\n * 곧 다른 트윈이 되고 재동기가 끊긴다 — 그래서 나눈다(이 저장소에서 식별자가 밖에서 오는 것은\n * 전부 이 규약을 쓴다: `spaceId`·`locationId`·`equipmentId`·`operationKey`).\n *\n * 나눴으면 **이름을 반드시 함께 둬야 한다.** 이 자리가 없던 동안 사이트 이름은 model 안에만 있어서\n * 목록이 `crew-two` 만 보였다 — 나누기만 하고 절반을 빼먹으면 화면에는 id 만 남는다.\n *\n * 원본이 이름을 말하지 않으면 비운다(id 를 베끼지 않는다).\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Human name of this twin (the site name from its master). Empty when the source does not name it — never copied from instanceId.' })\n name?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Longer description when the source provides one.' })\n description?: string\n\n /* 어느 도메인 커널이 이 트윈을 운영하는가. 커널 없는 트윈은 없으므로 비어 있을 수 없다\n (`TwinEngine.start`·`register` 모두 필수 인자로 받는다). */\n @Column()\n @Field({ description: 'Domain kernel kind: wms | yms | mes.' })\n kind: string\n\n /*\n * **재기동 정책** — 이 트윈은 다시 세워질 때 자기 과거를 어떻게 대하나(ADR-0029 §2).\n *\n * 'resync' = 외부 실물에서 다시 읽는다(원본이 진실이므로 우리 과거를 이어 붙이지 않는다 → startLive).\n * 'resume' = 저널을 이어 세운다(이 트윈이 낳은 타임라인이 곧 사실이다).\n * 'reset' = 저널을 비우고 씨앗부터 다시 돌린다(재현 가능한 실험 — 백지 시작이 선언된 거동이다).\n *\n * ── 왜 트윈에 남았나 (옛 이름 `realityMode`) ────────────────────────────────\n * 「현실이 어디서 오나」는 ADR-0029 가 **원본의 종류**로 옮긴 물음이다(`TwinReference.adapterType`) —\n * 트윈은 한 종류이고 언제나 원본을 반영한다. 그런데 코드가 이 축으로 실제로 가른 것은 **재기동 거동\n * 하나**였고, 그것은 원본의 성질이 아니라 **트윈과 자기 과거의 관계**다: 같은 시뮬레이터 원본을 보는 두\n * 트윈이 하나는 이어가고 하나는 매번 처음부터 재현할 수 있다. 그래서 축은 남고 이름만 바뀌었다.\n *\n * **비어 있을 수 없고, 기본값도 없다.** 예전에는 미선언이 조용히 `sim-experiment`(=저널 초기화)로\n * 떨어졌다 — 값이 하나 어긋나는 순간 이력을 지우는 길이다. 이제 읽는 쪽이 **오류를 낸다**\n * (`engine/restart-policy.ts`). 선언은 프로비저닝하는 쪽의 몫이다.\n */\n @Column()\n @Field({ description: \"Restart policy: 'resync' (read again from the external source) | 'resume' (continue this twin's own journal) | 'reset' (wipe and replay from the seed). No default — an unspecified policy is refused.\" })\n restartPolicy: string\n\n /* 읽을 때 어휘 세대를 흡수한다(`nodes`/`movers`/`equipmentList` → `locations`/`equipment`). (vocabulary-guard: allow — 세대 이름 없이는 설명할 수 없다.)\n 자리마다 리더를 쓰게 하면 다음에 추가되는 자리가 또 빠지고, 빠진 것은 오류가 아니라 **빈 공장**이라\n 조용하다(2026-08-06: 모델러 엔티티 목록이 통째로 비었다). 상세 `engine/model-vocabulary.ts`. */\n @Column({ type: 'simple-json', nullable: true, transformer: modelColumnTransformer })\n @Field(type => ScalarObject, { nullable: true, description: 'TwinModelDef blueprint (locations/equipment) required to replay events into state. Older stored boards used nodes/movers/equipmentList; they are normalised on read so consumers see one vocabulary.' }) // vocabulary-guard: allow — 옛 어휘를 흡수하는 것이 이 코드의 일이다(세대 이름을 적어야 흡수할 수 있다)\n model?: any\n\n /*\n * 로컬 선언 겹 — **구조는 원천이, 수는 우리가.**\n *\n * 요금 단가·계약 정보·축전지 충전율처럼 원 시스템이 모르는 값을 넣는 자리다. `model` 에 직접 쓰지\n * 않는 이유는 그것이 **원천의 사본**이기 때문이다: 다시 읽기가 사본을 새로 그리면 직접 쓴 값은 조용히\n * 사라진다(요금이 사라지면 금액도 사라지고, 사용자는 이유를 알 수 없다).\n *\n * 그래서 여기 남기고, 모델을 그릴 때마다 그 위에 얹는다(`engine/local-declarations`). 얹힌 값에는\n * `source: 'local'` 이 붙는다 — 커널 계약의 `PropertySource` 축(`reference` ↔ `local`)이고, 「밖에서\n * 참조해 온 값이냐, 이 안에서 정한 값이냐」에 답한다.\n *\n * **이름에 장소 낱말을 쓰지 않는다**(옛 이름 `siteDeclarations`): 이 코드에서 `site` 는 **외부\n * 시스템이 부르는 사업장**이고(SAP `P3`·Oracle `W01` 이 같은 현장), 여기 담기는 것은 그 외부 시스템이\n * **모르는** 값이다 — 이름이 내용과 정반대였다. `space` 도 아니다: 그것은 「어디」에 답하고 이 축은\n * 「누가 말했나」에 답한다.\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: \"Locally-declared values layered on top of the reference-derived model (tariff, contract, state of charge) — values whose authority sits here, not in the source system. Kept apart from `model` so a resync cannot silently drop them.\" })\n localDeclarations?: any\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Owning space id (co-location anchor). Promoted from model.spaceId — first-class \"space → its twins\" query (space-centered ontology).' })\n spaceId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Owning area id within the space (optional scope). Promoted from model.areaId.' })\n areaId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Lifecycle status: running | stopped.' })\n status?: string\n\n /*\n * purpose — 이 인스턴스가 운영 트윈인지, 격리된 벤치 사본인지 1급 선언(이름 접두사가 아니라 속성으로 구별).\n * 'bench' 는 규모/부하 실험용 격리 사본 — 목록·정리·안전 가드가 이 속성을 키로 동작(운영 트윈 무오염 보장).\n *\n * **비어 있을 수 없다.** 프로비저닝이 `register` 에서 'operational' 로 각인하고, 벤치 사본만\n * `setPurpose` 로 뒤집는다. 예전에는 비워 둔 채 읽는 쪽에서 메웠는데, 그러면 \"운영이라고 선언된 것\"\n * 과 \"아무도 말하지 않은 것\" 이 같은 값으로 보여서 벤치 격리가 추측에 기대게 된다.\n */\n @Column()\n @Field({ description: \"Instance purpose: 'operational' (real twin) | 'bench' (isolated copy for scale/load experiments).\" })\n purpose: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'For a bench copy: the source spaceId it was copied from (provenance).' })\n copyOf?: string\n\n /**\n * **이 트윈을 다시 읽는 방법** — 어떤 원본에서 나왔나(`MasterOrigin`).\n *\n * 트윈은 원본을 비출 뿐이므로(ADR-0028) 원본이 달라지면 다시 읽어야 한다. 그런데 이 자리가 없던\n * 동안 트윈은 **자기가 무엇에서 나왔는지 몰랐다** — 어댑터로 들어온 트윈은 `TwinReference` 행이\n * 있었지만 템플릿으로 세운 트윈에는 아무 표식도 없었고, 그래서 첫날의 모습에 영원히 머무는\n * 트윈이 만들어졌다. 원천이 무엇이든 여기 한 자리에 남긴다.\n *\n * **비어 있을 수 있다** — 이 자리가 생기기 전에 만들어진 트윈이 그렇다. 그때는 \"다시 읽을 수\n * 없다\" 가 사실이므로 화면이 그대로 말한다(짐작해 채우지 않는다).\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, {\n nullable: true,\n description:\n \"How this twin's master can be produced again — {kind:'reference', source} or {kind:'template', templateId, params}. Absent means this twin cannot be re-read from its source.\"\n })\n origin?: any\n\n /*\n * `revision` 컬럼은 **없앴다**(2026-08-08).\n *\n * `ADR-0015` 가 \"정의만 있고 write 안 됨 — 프로비저닝에서 최신 저널 revision 을 반영하도록\n * 채운다\" 고 적어 두었는데, 채우지 않는 편이 맞다. 저널의 최신 번호는 **저널이 아는 사실**이고\n * (`TwinEngine.list` 가 `MAX(revision)` 으로 낸다), 여기 복제하면 **같은 사실의 집이 둘**이 된다.\n * 이 세션에만 그 부류를 셋 겪었다(`model.operations` 두 집 · `materials` 두 집 · tenantId 중복).\n *\n * 복제한 값은 반드시 어긋난다 — 저널은 틱마다 늘고 이 컬럼은 저장할 때만 갱신되므로, 화면이\n * 어느 쪽을 읽느냐로 다른 숫자가 나온다. 그 조용한 어긋남을 만들 이유가 없다.\n */\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Timestamp when the instance was first registered.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Timestamp when the instance registry row was last updated.' })\n updatedAt?: Date\n\n /*\n * 누가 만들고 누가 고쳤나 — **이 행은 사람이 만든다**(프로비저닝은 사용자 행위)이므로 규약의\n * 풀세트를 갖는다. 투영 행(자리·설비·공정)은 반대로 인제스트가 그린 것이라 저자가 사람이 아니고,\n * 그 저자는 `sourceRef`+`ingestedAt` 이 이미 말한다 — 그래서 그쪽에는 두지 않는다.\n *\n * 비어 있을 수 있다: 부팅 자동 프로비저닝처럼 **사람이 없는 경로**로 만들어진 행이 그렇다.\n * 그때 아무 사용자를 적어 넣으면 감사 기록이 거짓이 된다.\n */\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The user who first provisioned this twin. Empty when it was created by a system path (boot provisioning).' })\n creator?: User\n\n @RelationId((e: TwinInstance) => e.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The user who last changed this twin registry row.' })\n updater?: User\n\n @RelationId((e: TwinInstance) => e.updater)\n updaterId?: string\n}\n"]}
1
+ {"version":3,"file":"twin-instance.js","sourceRoot":"","sources":["../../../server/service/twin-instance/twin-instance.ts"],"names":[],"mappings":"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","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'\nimport { ObjectType, Field, ID } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { modelColumnTransformer } from '../../engine/model-vocabulary.js'\n\n/*\n * TwinInstance — 트윈 인스턴스 레지스트리(재부팅 복구의 앵커).\n * 복구(DB→replay)는 model(마스터 청사진)가 있어야 이벤트를 상태로 다시 계산할 수 있다 → model·kind 를 영속.\n * 이벤트(TwinEvent)와 달리 가변(status 갱신) — 인스턴스의 \"현재 등록 상태\".\n * model 는 simple-json(멀티DB 이식 — postgres/mysql/sqlite/mssql/oracle 공통).\n * (CLAUDE.md: 모든 @ObjectType/@Field 는 영문 description 필수.)\n */\n@Entity()\n@Index('ix_twin_instance_0', (e: TwinInstance) => [e.domain, e.instanceId], { unique: true })\n@Index('ix_twin_instance_1', (e: TwinInstance) => [e.domain, e.spaceId]) // 공간→인스턴스 1급 질의(공간 중심 존재론)\n@ObjectType({ description: 'Registry of a hosted twin runtime instance — anchors reboot recovery (model + kind).' })\nexport class TwinInstance {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the registry record.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinInstance) => e.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'Twin runtime instance id (unique within a domain).' })\n instanceId: string\n\n /**\n * **사람이 부르는 이름** — 업무키(`instanceId`)와 나란히 두는 표시명(프레임워크 엔티티 규약).\n *\n * ── 왜 업무키와 이름을 나눠 두나 ─────────────────────────────────────────────\n * `instanceId` 는 URL·저널·구독이 전부 쓰는 **계약**이라 바꿀 수 없다. 반대로 이름은 바뀌고,\n * 중복되고(사이트 둘에 \"도장\" 이 있는 것이 정상), 언어마다 다르다. 둘을 한 컬럼에 담으면 개명이\n * 곧 다른 트윈이 되고 재동기가 끊긴다 — 그래서 나눈다(이 저장소에서 식별자가 밖에서 오는 것은\n * 전부 이 규약을 쓴다: `spaceId`·`locationId`·`equipmentId`·`operationKey`).\n *\n * 나눴으면 **이름을 반드시 함께 둬야 한다.** 이 자리가 없던 동안 사이트 이름은 model 안에만 있어서\n * 목록이 `crew-two` 만 보였다 — 나누기만 하고 절반을 빼먹으면 화면에는 id 만 남는다.\n *\n * 원본이 이름을 말하지 않으면 비운다(id 를 베끼지 않는다).\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Human name of this twin (the site name from its master). Empty when the source does not name it — never copied from instanceId.' })\n name?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Longer description when the source provides one.' })\n description?: string\n\n /* 어느 도메인 커널이 이 트윈을 운영하는가. 커널 없는 트윈은 없으므로 비어 있을 수 없다\n (`TwinEngine.start`·`register` 모두 필수 인자로 받는다). */\n @Column()\n @Field({ description: 'Domain kernel kind: wms | yms | mes.' })\n kind: string\n\n /*\n * **재기동 정책** — 이 트윈은 다시 세워질 때 자기 과거를 어떻게 대하나(ADR-0029 §2).\n *\n * 'resync' = 외부 실물에서 다시 읽는다(원본이 진실이므로 우리 과거를 이어 붙이지 않는다 → startLive).\n * 'resume' = 저널을 이어 세운다(이 트윈이 낳은 타임라인이 곧 사실이다).\n * 'reset' = 저널을 비우고 씨앗부터 다시 돌린다(재현 가능한 실험 — 백지 시작이 선언된 거동이다).\n *\n * ── 왜 트윈에 남았나 (옛 이름 `realityMode`) ────────────────────────────────\n * 「현실이 어디서 오나」는 ADR-0029 가 **원본의 종류**로 옮긴 물음이다(`TwinReference.adapterType`) —\n * 트윈은 한 종류이고 언제나 원본을 반영한다. 그런데 코드가 이 축으로 실제로 가른 것은 **재기동 거동\n * 하나**였고, 그것은 원본의 성질이 아니라 **트윈과 자기 과거의 관계**다: 같은 시뮬레이터 원본을 보는 두\n * 트윈이 하나는 이어가고 하나는 매번 처음부터 재현할 수 있다. 그래서 축은 남고 이름만 바뀌었다.\n *\n * **비어 있을 수 없고, 기본값도 없다.** 예전에는 미선언이 알리지 않고 `sim-experiment`(=저널 초기화)로\n * 떨어졌다 — 값이 하나 어긋나는 순간 이력을 지우는 길이다. 이제 읽는 쪽이 **오류를 낸다**\n * (`engine/restart-policy.ts`). 선언은 프로비저닝하는 쪽의 몫이다.\n */\n @Column()\n @Field({ description: \"Restart policy: 'resync' (read again from the external source) | 'resume' (continue this twin's own journal) | 'reset' (wipe and replay from the seed). No default — an unspecified policy is refused.\" })\n restartPolicy: string\n\n /* 읽을 때 어휘 세대를 흡수한다(`nodes`/`movers`/`equipmentList` → `locations`/`equipment`). (vocabulary-guard: allow — 세대 이름 없이는 설명할 수 없다.)\n 자리마다 리더를 쓰게 하면 다음에 추가되는 자리가 또 빠지고, 빠진 것은 오류가 아니라 **빈 공장**이라\n 들어온 것이 없다(2026-08-06: 모델러 엔티티 목록이 통째로 비었다). 상세 `engine/model-vocabulary.ts`. */\n @Column({ type: 'simple-json', nullable: true, transformer: modelColumnTransformer })\n @Field(type => ScalarObject, { nullable: true, description: 'TwinModelDef blueprint (locations/equipment) required to replay events into state. Older stored boards used nodes/movers/equipmentList; they are normalised on read so consumers see one vocabulary.' }) // vocabulary-guard: allow — 옛 어휘를 흡수하는 것이 이 코드의 일이다(세대 이름을 적어야 흡수할 수 있다)\n model?: any\n\n /*\n * 로컬 선언 겹 — **구조는 원천이, 수는 우리가.**\n *\n * 요금 단가·계약 정보·축전지 충전율처럼 원 시스템이 모르는 값을 넣는 자리다. `model` 에 직접 쓰지\n * 않는 이유는 그것이 **원천의 사본**이기 때문이다: 다시 읽기가 사본을 새로 그리면 직접 쓴 값은 알리지 않고\n * 사라진다(요금이 사라지면 금액도 사라지고, 사용자는 이유를 알 수 없다).\n *\n * 그래서 여기 남기고, 모델을 그릴 때마다 그 위에 얹는다(`engine/local-declarations`). 얹힌 값에는\n * `source: 'local'` 이 붙는다 — 커널 계약의 `PropertySource` 축(`reference` ↔ `local`)이고, 「밖에서\n * 참조해 온 값이냐, 이 안에서 정한 값이냐」에 답한다.\n *\n * **이름에 장소 낱말을 쓰지 않는다**(옛 이름 `siteDeclarations`): 이 코드에서 `site` 는 **외부\n * 시스템이 부르는 사업장**이고(SAP `P3`·Oracle `W01` 이 같은 현장), 여기 담기는 것은 그 외부 시스템이\n * **모르는** 값이다 — 이름이 내용과 정반대였다. `space` 도 아니다: 그것은 「어디」에 답하고 이 축은\n * 「누가 말했나」에 답한다.\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: \"Locally-declared values layered on top of the reference-derived model (tariff, contract, state of charge) — values whose authority sits here, not in the source system. Kept apart from `model` so a resync cannot silently drop them.\" })\n localDeclarations?: any\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Owning space id (co-location anchor). Promoted from model.spaceId — first-class \"space → its twins\" query (space-centered ontology).' })\n spaceId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Owning area id within the space (optional scope). Promoted from model.areaId.' })\n areaId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Lifecycle status: running | stopped.' })\n status?: string\n\n /*\n * purpose — 이 인스턴스가 운영 트윈인지, 격리된 벤치 사본인지 1급 선언(이름 접두사가 아니라 속성으로 구별).\n * 'bench' 는 규모/부하 실험용 격리 사본 — 목록·정리·안전 가드가 이 속성을 키로 동작(운영 트윈 무오염 보장).\n *\n * **비어 있을 수 없다.** 프로비저닝이 `register` 에서 'operational' 로 각인하고, 벤치 사본만\n * `setPurpose` 로 뒤집는다. 예전에는 비워 둔 채 읽는 쪽에서 메웠는데, 그러면 \"운영이라고 선언된 것\"\n * 과 \"아무도 말하지 않은 것\" 이 같은 값으로 보여서 벤치 격리가 추측에 기대게 된다.\n */\n @Column()\n @Field({ description: \"Instance purpose: 'operational' (real twin) | 'bench' (isolated copy for scale/load experiments).\" })\n purpose: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'For a bench copy: the source spaceId it was copied from (provenance).' })\n copyOf?: string\n\n /**\n * **이 트윈을 다시 읽는 방법** — 어떤 원본에서 나왔나(`MasterOrigin`).\n *\n * 트윈은 원본을 비출 뿐이므로(ADR-0028) 원본이 달라지면 다시 읽어야 한다. 그런데 이 자리가 없던\n * 동안 트윈은 **자기가 무엇에서 나왔는지 몰랐다** — 어댑터로 들어온 트윈은 `TwinReference` 행이\n * 있었지만 템플릿으로 세운 트윈에는 아무 표식도 없었고, 그래서 첫날의 모습에 영원히 머무는\n * 트윈이 만들어졌다. 원천이 무엇이든 여기 한 자리에 남긴다.\n *\n * **비어 있을 수 있다** — 이 자리가 생기기 전에 만들어진 트윈이 그렇다. 그때는 \"다시 읽을 수\n * 없다\" 가 사실이므로 화면이 그대로 말한다(짐작해 채우지 않는다).\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, {\n nullable: true,\n description:\n \"How this twin's master can be produced again — {kind:'reference', source} or {kind:'template', templateId, params}. Absent means this twin cannot be re-read from its source.\"\n })\n origin?: any\n\n /*\n * `revision` 컬럼은 **없앴다**(2026-08-08).\n *\n * `ADR-0015` 가 \"정의만 있고 write 안 됨 — 프로비저닝에서 최신 저널 revision 을 반영하도록\n * 채운다\" 고 적어 두었는데, 채우지 않는 편이 맞다. 저널의 최신 번호는 **저널이 아는 사실**이고\n * (`TwinEngine.list` 가 `MAX(revision)` 으로 낸다), 여기 복제하면 **같은 사실의 집이 둘**이 된다.\n * 이 세션에만 그 부류를 셋 겪었다(`model.operations` 두 집 · `materials` 두 집 · tenantId 중복).\n *\n * 복제한 값은 반드시 어긋난다 — 저널은 틱마다 늘고 이 컬럼은 저장할 때만 갱신되므로, 화면이\n * 어느 쪽을 읽느냐로 다른 숫자가 나온다. 그 들어온 것이 없는 어긋남을 만들 이유가 없다.\n */\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Timestamp when the instance was first registered.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Timestamp when the instance registry row was last updated.' })\n updatedAt?: Date\n\n /*\n * 누가 만들고 누가 고쳤나 — **이 행은 사람이 만든다**(프로비저닝은 사용자 행위)이므로 규약의\n * 풀세트를 갖는다. 투영 행(자리·설비·공정)은 반대로 인제스트가 그린 것이라 저자가 사람이 아니고,\n * 그 저자는 `sourceRef`+`ingestedAt` 이 이미 말한다 — 그래서 그쪽에는 두지 않는다.\n *\n * 비어 있을 수 있다: 부팅 자동 프로비저닝처럼 **사람이 없는 경로**로 만들어진 행이 그렇다.\n * 그때 아무 사용자를 적어 넣으면 감사 기록이 거짓이 된다.\n */\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The user who first provisioned this twin. Empty when it was created by a system path (boot provisioning).' })\n creator?: User\n\n @RelationId((e: TwinInstance) => e.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'The user who last changed this twin registry row.' })\n updater?: User\n\n @RelationId((e: TwinInstance) => e.updater)\n updaterId?: string\n}\n"]}
@@ -0,0 +1,50 @@
1
+ /** 질의를 몇 개까지 나눌까 — 대상 × 종류. 이 이상이면 나누는 것이 손해다. */
2
+ export declare const MAX_SPLIT = 24;
3
+ export interface SplitShape {
4
+ eventTypes: string[];
5
+ /**
6
+ * 공정 단계 하나로 좁힌 경우 — 그 값을 갈래마다 함께 건다.
7
+ *
8
+ * 이 필터를 만나면 갈래를 포기하던 것을 고쳤다(2026-08-29). 원장은 종류를 담아 묻고 그 위에 단계
9
+ * 칩을 겹치는데, 칩을 고른 순간 예전 경로로 떨어져 **고르지 않은 쪽이 더 빨랐다.**
10
+ *
11
+ * 단계 컬럼에는 색인이 없다. 그래도 느려지지 않는다 — 종류 정확일치가 색인으로 시각 순서까지 주고,
12
+ * 단계는 그 안에서 행마다 확인하는 조건이다(걷는 행이 그 종류의 것으로 이미 좁혀져 있다).
13
+ */
14
+ bizStep?: string;
15
+ untilTime?: Date;
16
+ fromTime?: Date;
17
+ }
18
+ /**
19
+ * 이 물음이 「여러 종류를 시각순으로」인가 — 아니면 `null`.
20
+ *
21
+ * 받는 모양
22
+ * `eventType` 을 `in` 으로 · 시각 상한이나 구간 · 시각 내림차순 정렬
23
+ *
24
+ * 보내는 모양(일반 경로로)
25
+ * 자유 검색어 · 그 밖의 필터(축·상관 식별자·업무 단계) · 그 밖의 정렬이나 연산자
26
+ */
27
+ export declare function splitShape(params: any, sortings: any[]): SplitShape | null;
28
+ /**
29
+ * **나눠 물은 갈래들을 합쳐 앞에서 `limit` 개** — 이 규율이 정확성의 근거다.
30
+ *
31
+ * ── 왜 갈래마다 `limit` 까지 받아야 하나 ────────────────────────────────────
32
+ * 갈래마다 `limit` 개를 받아 합치면, 합친 것의 앞 `limit` 개는 **전체에서도** 앞 `limit` 개다.
33
+ * 어떤 갈래에서 빠진 행은 그 갈래의 `limit` 번째보다 뒤이고, 우리는 전체에서 `limit` 개만 취하므로
34
+ * 그 행이 이 페이지에 들어올 수 없다.
35
+ *
36
+ * 갈래마다 **더 적게** 받으면 그 성질이 깨진다. 한 갈래가 앞을 다 차지하는 경우
37
+ * (종류 A 의 마지막이 종류 B 의 첫째보다 앞선다) 그 갈래에서 못 받은 것이 빠지고, 결과는 구간의
38
+ * 앞부분만 계산한 것이 된다 — 그런데 화면은 「일부만 읽었다」고만 말하고 어느 부분인지는 말하지 못한다.
39
+ * (2026-08-28 · UX 레인이 미리 짚어 준 함정이다.)
40
+ *
41
+ * 그래서 이 함수는 **`limit` 미만으로 받은 갈래가 있으면 그것을 말한다** — 부르는 쪽이 그 상태로
42
+ * 답하지 않게.
43
+ */
44
+ export declare function mergeStreams<T>(streams: readonly T[][], limit: number,
45
+ /** 앞에 와야 하는 것이 음수 — `Array.prototype.sort` 와 같은 규약. */
46
+ compare: (a: T, b: T) => number): {
47
+ items: T[];
48
+ capped: boolean;
49
+ underfilled: boolean;
50
+ };
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_SPLIT = void 0;
4
+ exports.splitShape = splitShape;
5
+ exports.mergeStreams = mergeStreams;
6
+ /*
7
+ * **이 물음이 「여러 종류를 시각순으로」인가** — 순수 판단만 여기 둔다.
8
+ *
9
+ * 데이터베이스를 부르지 않는다. 그래서 시험이 이 파일을 그대로 부를 수 있다(데코레이터가 없다).
10
+ * 판단이 조회 코드 안에 섞여 있으면 그 판단만 확인할 방법이 없고, 그러면 「어떤 물음이 나눠 묻는
11
+ * 갈래로 가는가」가 아무도 읽지 못하는 규칙이 된다.
12
+ *
13
+ * 실측 근거와 고침의 뜻은 `event-type-split.ts` 머리에 있다.
14
+ */
15
+ const read_time_js_1 = require("../../engine/read-time.js");
16
+ /** 질의를 몇 개까지 나눌까 — 대상 × 종류. 이 이상이면 나누는 것이 손해다. */
17
+ exports.MAX_SPLIT = 24;
18
+ /** 종류 목록의 상한 — 이보다 많으면 「이 종류만」이 아니라 사실상 전체다. */
19
+ const MAX_TYPES = 12;
20
+ /**
21
+ * 이 물음이 「여러 종류를 시각순으로」인가 — 아니면 `null`.
22
+ *
23
+ * 받는 모양
24
+ * `eventType` 을 `in` 으로 · 시각 상한이나 구간 · 시각 내림차순 정렬
25
+ *
26
+ * 보내는 모양(일반 경로로)
27
+ * 자유 검색어 · 그 밖의 필터(축·상관 식별자·업무 단계) · 그 밖의 정렬이나 연산자
28
+ */
29
+ function splitShape(params, sortings) {
30
+ const filters = Array.isArray(params?.filters) ? params.filters : [];
31
+ if (typeof params?.search === 'string' && params.search.trim())
32
+ return null;
33
+ if (sortings.length !== 1 || sortings[0]?.name !== 'eventTime' || sortings[0]?.desc !== true)
34
+ return null;
35
+ let eventTypes;
36
+ let untilTime;
37
+ let fromTime;
38
+ let bizStep;
39
+ for (const f of filters) {
40
+ if (f?.name === 'eventType') {
41
+ if (f.operator !== 'in' || !Array.isArray(f.value))
42
+ return null;
43
+ const types = f.value.map((v) => String(v ?? '').trim()).filter((t) => !!t);
44
+ /* 하나면 나눌 것이 없다(이미 정확일치라 색인을 탄다). 너무 많으면 사실상 전체다. */
45
+ if (types.length < 2 || types.length > MAX_TYPES)
46
+ return null;
47
+ if (eventTypes)
48
+ return null;
49
+ eventTypes = [...new Set(types)];
50
+ }
51
+ else if (f?.name === 'bizStep') {
52
+ /* 하나로 좁힌 경우만 받는다 — 여러 값이면 종류 × 단계로 갈래가 곱해진다. */
53
+ if (f.operator !== 'eq')
54
+ return null;
55
+ const v = String(f.value ?? '').trim();
56
+ if (!v)
57
+ return null;
58
+ if (bizStep)
59
+ return null;
60
+ bizStep = v;
61
+ }
62
+ else if (f?.name === 'eventTime') {
63
+ if (f.operator === 'lte' || f.operator === 'lt') {
64
+ untilTime = (0, read_time_js_1.absoluteTime)(f.value) ?? undefined;
65
+ if (!untilTime)
66
+ return null;
67
+ }
68
+ else if (f.operator === 'between' && Array.isArray(f.value)) {
69
+ fromTime = (0, read_time_js_1.absoluteTime)(f.value[0]) ?? undefined;
70
+ untilTime = (0, read_time_js_1.absoluteTime)(f.value[1]) ?? undefined;
71
+ if (!fromTime || !untilTime)
72
+ return null;
73
+ }
74
+ else
75
+ return null;
76
+ }
77
+ else
78
+ return null;
79
+ }
80
+ return eventTypes ? { eventTypes, untilTime, fromTime, bizStep } : null;
81
+ }
82
+ /**
83
+ * **나눠 물은 갈래들을 합쳐 앞에서 `limit` 개** — 이 규율이 정확성의 근거다.
84
+ *
85
+ * ── 왜 갈래마다 `limit` 까지 받아야 하나 ────────────────────────────────────
86
+ * 갈래마다 `limit` 개를 받아 합치면, 합친 것의 앞 `limit` 개는 **전체에서도** 앞 `limit` 개다.
87
+ * 어떤 갈래에서 빠진 행은 그 갈래의 `limit` 번째보다 뒤이고, 우리는 전체에서 `limit` 개만 취하므로
88
+ * 그 행이 이 페이지에 들어올 수 없다.
89
+ *
90
+ * 갈래마다 **더 적게** 받으면 그 성질이 깨진다. 한 갈래가 앞을 다 차지하는 경우
91
+ * (종류 A 의 마지막이 종류 B 의 첫째보다 앞선다) 그 갈래에서 못 받은 것이 빠지고, 결과는 구간의
92
+ * 앞부분만 계산한 것이 된다 — 그런데 화면은 「일부만 읽었다」고만 말하고 어느 부분인지는 말하지 못한다.
93
+ * (2026-08-28 · UX 레인이 미리 짚어 준 함정이다.)
94
+ *
95
+ * 그래서 이 함수는 **`limit` 미만으로 받은 갈래가 있으면 그것을 말한다** — 부르는 쪽이 그 상태로
96
+ * 답하지 않게.
97
+ */
98
+ function mergeStreams(streams, limit,
99
+ /** 앞에 와야 하는 것이 음수 — `Array.prototype.sort` 와 같은 규약. */
100
+ compare) {
101
+ const underfilled = streams.some(s => s.length > limit);
102
+ const merged = streams.flat().sort(compare);
103
+ return {
104
+ items: merged.slice(0, limit),
105
+ /* 합친 것이 상한을 넘었으면 더 있었다는 뜻이다 — 잘랐다는 사실을 부르는 쪽이 말해야 한다. */
106
+ capped: merged.length > limit || streams.some(s => s.length >= limit),
107
+ /* 갈래가 상한보다 많이 받아 왔다 — 있을 수 없는 일이므로 부르는 쪽이 알아야 한다. */
108
+ underfilled
109
+ };
110
+ }
111
+ //# sourceMappingURL=event-type-split-shape.js.map