@things-factory/headless-twin 10.1.278 → 10.1.279

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 (614) hide show
  1. package/package.json +21 -7
  2. package/dist-server/engine/attention-digest.js.map +0 -1
  3. package/dist-server/engine/canonical-ingest.js.map +0 -1
  4. package/dist-server/engine/checkpoint-fold.js.map +0 -1
  5. package/dist-server/engine/command-routing.js.map +0 -1
  6. package/dist-server/engine/declared-stimulus.js.map +0 -1
  7. package/dist-server/engine/energy-topology.js.map +0 -1
  8. package/dist-server/engine/event-time.js.map +0 -1
  9. package/dist-server/engine/index.js.map +0 -1
  10. package/dist-server/engine/ingest-dedupe.js.map +0 -1
  11. package/dist-server/engine/ingest-health.js.map +0 -1
  12. package/dist-server/engine/integration-coverage.js.map +0 -1
  13. package/dist-server/engine/integration-probes.js.map +0 -1
  14. package/dist-server/engine/integration-runner.js.map +0 -1
  15. package/dist-server/engine/integration-target-profile.js.map +0 -1
  16. package/dist-server/engine/kpi-baseline.js.map +0 -1
  17. package/dist-server/engine/kpi-fold.js.map +0 -1
  18. package/dist-server/engine/kpi-query.js.map +0 -1
  19. package/dist-server/engine/kpi-target.js.map +0 -1
  20. package/dist-server/engine/live-attentions.js.map +0 -1
  21. package/dist-server/engine/live-feed-registry.js.map +0 -1
  22. package/dist-server/engine/load-meter.js.map +0 -1
  23. package/dist-server/engine/local-declarations.js.map +0 -1
  24. package/dist-server/engine/log.js.map +0 -1
  25. package/dist-server/engine/loop-lag.js.map +0 -1
  26. package/dist-server/engine/measured-estimator.js.map +0 -1
  27. package/dist-server/engine/measured-yield.js.map +0 -1
  28. package/dist-server/engine/model-basis.js.map +0 -1
  29. package/dist-server/engine/model-gap.js.map +0 -1
  30. package/dist-server/engine/model-refusal.js.map +0 -1
  31. package/dist-server/engine/model-vocabulary.js.map +0 -1
  32. package/dist-server/engine/oee-accumulator.js.map +0 -1
  33. package/dist-server/engine/operation-basis.js.map +0 -1
  34. package/dist-server/engine/producer-guard.js.map +0 -1
  35. package/dist-server/engine/property-effects.js.map +0 -1
  36. package/dist-server/engine/read-time.js.map +0 -1
  37. package/dist-server/engine/restart-policy.js.map +0 -1
  38. package/dist-server/engine/runtime-key.js.map +0 -1
  39. package/dist-server/engine/spec-coverage.js.map +0 -1
  40. package/dist-server/engine/stage-path.js.map +0 -1
  41. package/dist-server/engine/state-axes.js.map +0 -1
  42. package/dist-server/engine/structure-diff.js.map +0 -1
  43. package/dist-server/engine/travel-estimator.js.map +0 -1
  44. package/dist-server/engine/twin-engine.js.map +0 -1
  45. package/dist-server/engine/twin-state-channel.js.map +0 -1
  46. package/dist-server/engine/warm-start.js.map +0 -1
  47. package/dist-server/index.js.map +0 -1
  48. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +0 -1
  49. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +0 -1
  50. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +0 -1
  51. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +0 -1
  52. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +0 -1
  53. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +0 -1
  54. package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.js.map +0 -1
  55. package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.js.map +0 -1
  56. package/dist-server/migrations/1786800000000-CreateTwinReferenceReconciliations.js.map +0 -1
  57. package/dist-server/migrations/index.js.map +0 -1
  58. package/dist-server/routes.js.map +0 -1
  59. package/dist-server/service/actuation/actuation-advisor.js.map +0 -1
  60. package/dist-server/service/actuation/actuation-outcome.js.map +0 -1
  61. package/dist-server/service/actuation/actuation-rule-resolver.js.map +0 -1
  62. package/dist-server/service/actuation/actuation-rule.js.map +0 -1
  63. package/dist-server/service/actuation/approval-gateway.js.map +0 -1
  64. package/dist-server/service/actuation/catch-up-effects.js.map +0 -1
  65. package/dist-server/service/actuation/command-dispatcher.js.map +0 -1
  66. package/dist-server/service/actuation/command-store.js.map +0 -1
  67. package/dist-server/service/actuation/dispatch-after-approval.js.map +0 -1
  68. package/dist-server/service/actuation/effect-match.js.map +0 -1
  69. package/dist-server/service/actuation/index.js.map +0 -1
  70. package/dist-server/service/actuation/retry-plan.js.map +0 -1
  71. package/dist-server/service/actuation/rule-evaluate.js.map +0 -1
  72. package/dist-server/service/actuation/rule-loop.js.map +0 -1
  73. package/dist-server/service/actuation/rule-runner.js.map +0 -1
  74. package/dist-server/service/actuation/rule-wiring.js.map +0 -1
  75. package/dist-server/service/actuation/settle-effects.js.map +0 -1
  76. package/dist-server/service/actuation/sweep-dispatch.js.map +0 -1
  77. package/dist-server/service/actuation/twin-command-resolver.js.map +0 -1
  78. package/dist-server/service/actuation/twin-command.js.map +0 -1
  79. package/dist-server/service/actuation/twin-now.js.map +0 -1
  80. package/dist-server/service/index.js.map +0 -1
  81. package/dist-server/service/reference/actuation-routing.js.map +0 -1
  82. package/dist-server/service/reference/actuation-target.js.map +0 -1
  83. package/dist-server/service/reference/actuation-verification.js.map +0 -1
  84. package/dist-server/service/reference/attention-lane.js.map +0 -1
  85. package/dist-server/service/reference/connection-portability-resolver.js.map +0 -1
  86. package/dist-server/service/reference/connection-portability.js.map +0 -1
  87. package/dist-server/service/reference/control-routing.js.map +0 -1
  88. package/dist-server/service/reference/discovery-result.js.map +0 -1
  89. package/dist-server/service/reference/fill-cadence.js.map +0 -1
  90. package/dist-server/service/reference/fill-dedupe.js.map +0 -1
  91. package/dist-server/service/reference/fill-loop.js.map +0 -1
  92. package/dist-server/service/reference/fill-span.js.map +0 -1
  93. package/dist-server/service/reference/hook-contract.js.map +0 -1
  94. package/dist-server/service/reference/hook-rejected.js.map +0 -1
  95. package/dist-server/service/reference/hook-store.js.map +0 -1
  96. package/dist-server/service/reference/index.js.map +0 -1
  97. package/dist-server/service/reference/ingest-space.js.map +0 -1
  98. package/dist-server/service/reference/knob-defaults.js.map +0 -1
  99. package/dist-server/service/reference/live-cadence.js.map +0 -1
  100. package/dist-server/service/reference/live-feed-lease.js.map +0 -1
  101. package/dist-server/service/reference/live-ingest.js.map +0 -1
  102. package/dist-server/service/reference/reference-adapter.js.map +0 -1
  103. package/dist-server/service/reference/reference-assessment.js.map +0 -1
  104. package/dist-server/service/reference/reference-fill-run.js.map +0 -1
  105. package/dist-server/service/reference/reference-fill.js.map +0 -1
  106. package/dist-server/service/reference/reference-hook.js.map +0 -1
  107. package/dist-server/service/reference/reference-link-groups.js.map +0 -1
  108. package/dist-server/service/reference/reference-links.js.map +0 -1
  109. package/dist-server/service/reference/reference-live.js.map +0 -1
  110. package/dist-server/service/reference/reference-master.js.map +0 -1
  111. package/dist-server/service/reference/reference-probe.js.map +0 -1
  112. package/dist-server/service/reference/reference-progress-channel.js.map +0 -1
  113. package/dist-server/service/reference/reference-progress-subscription.js.map +0 -1
  114. package/dist-server/service/reference/reference-progress.js.map +0 -1
  115. package/dist-server/service/reference/reference-reconciliation-query.js.map +0 -1
  116. package/dist-server/service/reference/reference-reconciliation-writer.js.map +0 -1
  117. package/dist-server/service/reference/reference-reconciliation.js.map +0 -1
  118. package/dist-server/service/reference/reference-registry.js.map +0 -1
  119. package/dist-server/service/reference/reference-resolver.js.map +0 -1
  120. package/dist-server/service/reference/template-registry.js.map +0 -1
  121. package/dist-server/service/reference/twin-reference-reconciliation.js.map +0 -1
  122. package/dist-server/service/reference/twin-reference.js.map +0 -1
  123. package/dist-server/service/twin-attention/index.js.map +0 -1
  124. package/dist-server/service/twin-attention/twin-attention-query.js.map +0 -1
  125. package/dist-server/service/twin-audit/command-audit.js.map +0 -1
  126. package/dist-server/service/twin-audit/index.js.map +0 -1
  127. package/dist-server/service/twin-audit/twin-audit-event.js.map +0 -1
  128. package/dist-server/service/twin-audit/twin-audit-query.js.map +0 -1
  129. package/dist-server/service/twin-backfill/backfill-period-facts.js.map +0 -1
  130. package/dist-server/service/twin-backfill/backfill-shape.js.map +0 -1
  131. package/dist-server/service/twin-backfill/index.js.map +0 -1
  132. package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +0 -1
  133. package/dist-server/service/twin-control/index.js.map +0 -1
  134. package/dist-server/service/twin-control/twin-control-mutation.js.map +0 -1
  135. package/dist-server/service/twin-event/index.js.map +0 -1
  136. package/dist-server/service/twin-event/journal-count.js.map +0 -1
  137. package/dist-server/service/twin-event/twin-event-keys.js.map +0 -1
  138. package/dist-server/service/twin-event/twin-event-type.js.map +0 -1
  139. package/dist-server/service/twin-event/twin-event.js.map +0 -1
  140. package/dist-server/service/twin-forecast/forecast-metrics.js.map +0 -1
  141. package/dist-server/service/twin-forecast/forecast-tuning.js.map +0 -1
  142. package/dist-server/service/twin-forecast/gap-analytics.js.map +0 -1
  143. package/dist-server/service/twin-forecast/index.js.map +0 -1
  144. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +0 -1
  145. package/dist-server/service/twin-ingest-window/index.js.map +0 -1
  146. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +0 -1
  147. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +0 -1
  148. package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +0 -1
  149. package/dist-server/service/twin-instance/index.js.map +0 -1
  150. package/dist-server/service/twin-instance/twin-instance.js.map +0 -1
  151. package/dist-server/service/twin-journal/event-type-split-shape.js.map +0 -1
  152. package/dist-server/service/twin-journal/event-type-split.js.map +0 -1
  153. package/dist-server/service/twin-journal/index.js.map +0 -1
  154. package/dist-server/service/twin-journal/journal-order.js.map +0 -1
  155. package/dist-server/service/twin-journal/twin-journal-query.js.map +0 -1
  156. package/dist-server/service/twin-lifecycle/domain-catalog.js.map +0 -1
  157. package/dist-server/service/twin-lifecycle/index.js.map +0 -1
  158. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +0 -1
  159. package/dist-server/service/twin-metrics/index.js.map +0 -1
  160. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +0 -1
  161. package/dist-server/service/twin-model/axis-journal-evidence.js.map +0 -1
  162. package/dist-server/service/twin-model/choose-recipe.js.map +0 -1
  163. package/dist-server/service/twin-model/epcis-coverage.js.map +0 -1
  164. package/dist-server/service/twin-model/external-resolver.js.map +0 -1
  165. package/dist-server/service/twin-model/iec61850-coverage.js.map +0 -1
  166. package/dist-server/service/twin-model/index.js.map +0 -1
  167. package/dist-server/service/twin-model/isa95-coverage.js.map +0 -1
  168. package/dist-server/service/twin-model/item-ref.js.map +0 -1
  169. package/dist-server/service/twin-model/name-index.js.map +0 -1
  170. package/dist-server/service/twin-model/project-structure.js.map +0 -1
  171. package/dist-server/service/twin-model/standard-coverage.js.map +0 -1
  172. package/dist-server/service/twin-model/status-tally.js.map +0 -1
  173. package/dist-server/service/twin-model/twin-equipment.js.map +0 -1
  174. package/dist-server/service/twin-model/twin-lineage-query.js.map +0 -1
  175. package/dist-server/service/twin-model/twin-location.js.map +0 -1
  176. package/dist-server/service/twin-model/twin-model-item-query.js.map +0 -1
  177. package/dist-server/service/twin-model/twin-model-mutation.js.map +0 -1
  178. package/dist-server/service/twin-model/twin-model-query.js.map +0 -1
  179. package/dist-server/service/twin-model/twin-model-tree-query.js.map +0 -1
  180. package/dist-server/service/twin-model/twin-operation.js.map +0 -1
  181. package/dist-server/service/twin-readiness/index.js.map +0 -1
  182. package/dist-server/service/twin-readiness/twin-readiness-query.js.map +0 -1
  183. package/dist-server/service/twin-space/area-resolver.js.map +0 -1
  184. package/dist-server/service/twin-space/index.js.map +0 -1
  185. package/dist-server/service/twin-space/move-space.js.map +0 -1
  186. package/dist-server/service/twin-space/representation-role.js.map +0 -1
  187. package/dist-server/service/twin-space/space-integrity.js.map +0 -1
  188. package/dist-server/service/twin-space/twin-area.js.map +0 -1
  189. package/dist-server/service/twin-space/twin-space-area.js.map +0 -1
  190. package/dist-server/service/twin-space/twin-space-representation.js.map +0 -1
  191. package/dist-server/service/twin-space/twin-space-resolver.js.map +0 -1
  192. package/dist-server/service/twin-space/twin-space.js.map +0 -1
  193. package/dist-server/service/twin-state/index.js.map +0 -1
  194. package/dist-server/service/twin-state/twin-state-subscription.js.map +0 -1
  195. package/dist-server/service/twin-structure/index.js.map +0 -1
  196. package/dist-server/service/twin-structure/twin-structure.js.map +0 -1
  197. package/dist-server/service/twin-subject/event-subjects.js.map +0 -1
  198. package/dist-server/service/twin-subject/index.js.map +0 -1
  199. package/dist-server/service/twin-subject/item-resolver.js.map +0 -1
  200. package/dist-server/service/twin-subject/subject-query-shape.js.map +0 -1
  201. package/dist-server/service/twin-subject/twin-subject-backfill.js.map +0 -1
  202. package/dist-server/service/twin-subject/twin-subject-event.js.map +0 -1
  203. package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +0 -1
  204. package/dist-server/service/twin-subject/twin-subject-query.js.map +0 -1
  205. package/dist-server/service/twin-subject/twin-subject-writer.js.map +0 -1
  206. package/dist-server/service/twin-target/index.js.map +0 -1
  207. package/dist-server/service/twin-target/twin-target-resolver.js.map +0 -1
  208. package/dist-server/service/twin-target/twin-target.js.map +0 -1
  209. package/dist-shared/axis-read.js.map +0 -1
  210. package/dist-shared/entity-delta.js.map +0 -1
  211. package/dist-shared/kpi-broadcast.js.map +0 -1
  212. package/dist-shared/touched-items.js.map +0 -1
  213. package/dist-shared/twin-level.js.map +0 -1
  214. package/server/engine/attention-digest.ts +0 -102
  215. package/server/engine/canonical-ingest.ts +0 -511
  216. package/server/engine/checkpoint-fold.ts +0 -12
  217. package/server/engine/command-routing.ts +0 -67
  218. package/server/engine/declared-stimulus.ts +0 -66
  219. package/server/engine/energy-topology.ts +0 -189
  220. package/server/engine/event-time.ts +0 -96
  221. package/server/engine/index.ts +0 -51
  222. package/server/engine/ingest-dedupe.ts +0 -24
  223. package/server/engine/ingest-health.ts +0 -24
  224. package/server/engine/integration-coverage.ts +0 -147
  225. package/server/engine/integration-probes.ts +0 -144
  226. package/server/engine/integration-runner.ts +0 -95
  227. package/server/engine/integration-target-profile.ts +0 -103
  228. package/server/engine/kpi-baseline.ts +0 -202
  229. package/server/engine/kpi-fold.ts +0 -1516
  230. package/server/engine/kpi-query.ts +0 -1210
  231. package/server/engine/kpi-target.ts +0 -248
  232. package/server/engine/live-attentions.ts +0 -30
  233. package/server/engine/live-feed-registry.ts +0 -59
  234. package/server/engine/load-meter.ts +0 -393
  235. package/server/engine/local-declarations.ts +0 -779
  236. package/server/engine/log.ts +0 -72
  237. package/server/engine/loop-lag.ts +0 -120
  238. package/server/engine/measured-estimator.ts +0 -91
  239. package/server/engine/measured-yield.ts +0 -89
  240. package/server/engine/model-basis.ts +0 -162
  241. package/server/engine/model-gap.ts +0 -168
  242. package/server/engine/model-refusal.ts +0 -31
  243. package/server/engine/model-vocabulary.ts +0 -82
  244. package/server/engine/oee-accumulator.ts +0 -195
  245. package/server/engine/operation-basis.ts +0 -131
  246. package/server/engine/producer-guard.ts +0 -64
  247. package/server/engine/property-effects.ts +0 -216
  248. package/server/engine/read-time.ts +0 -77
  249. package/server/engine/restart-policy.ts +0 -55
  250. package/server/engine/runtime-key.ts +0 -58
  251. package/server/engine/spec-coverage.ts +0 -105
  252. package/server/engine/stage-path.ts +0 -172
  253. package/server/engine/state-axes.ts +0 -60
  254. package/server/engine/structure-diff.ts +0 -88
  255. package/server/engine/travel-estimator.ts +0 -138
  256. package/server/engine/twin-engine.ts +0 -4979
  257. package/server/engine/twin-state-channel.ts +0 -39
  258. package/server/engine/warm-start.ts +0 -265
  259. package/server/index.ts +0 -90
  260. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +0 -43
  261. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +0 -54
  262. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +0 -53
  263. package/server/migrations/1786300000000-IndexEventTypeByTime.ts +0 -48
  264. package/server/migrations/1786400000000-IndexEventCreatedAt.ts +0 -38
  265. package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +0 -93
  266. package/server/migrations/1786600000000-RenameOperatoMesAdapterType.ts +0 -66
  267. package/server/migrations/1786700000000-AddDispatchRetryColumns.ts +0 -82
  268. package/server/migrations/1786800000000-CreateTwinReferenceReconciliations.ts +0 -39
  269. package/server/migrations/index.ts +0 -25
  270. package/server/routes.ts +0 -75
  271. package/server/service/actuation/actuation-advisor.ts +0 -131
  272. package/server/service/actuation/actuation-outcome.ts +0 -97
  273. package/server/service/actuation/actuation-rule-resolver.ts +0 -147
  274. package/server/service/actuation/actuation-rule.ts +0 -139
  275. package/server/service/actuation/approval-gateway.ts +0 -44
  276. package/server/service/actuation/catch-up-effects.ts +0 -152
  277. package/server/service/actuation/command-dispatcher.ts +0 -474
  278. package/server/service/actuation/command-store.ts +0 -236
  279. package/server/service/actuation/dispatch-after-approval.ts +0 -71
  280. package/server/service/actuation/effect-match.ts +0 -124
  281. package/server/service/actuation/index.ts +0 -27
  282. package/server/service/actuation/retry-plan.ts +0 -145
  283. package/server/service/actuation/rule-evaluate.ts +0 -352
  284. package/server/service/actuation/rule-loop.ts +0 -160
  285. package/server/service/actuation/rule-runner.ts +0 -216
  286. package/server/service/actuation/rule-wiring.ts +0 -84
  287. package/server/service/actuation/settle-effects.ts +0 -115
  288. package/server/service/actuation/sweep-dispatch.ts +0 -179
  289. package/server/service/actuation/twin-command-resolver.ts +0 -387
  290. package/server/service/actuation/twin-command.ts +0 -232
  291. package/server/service/actuation/twin-now.ts +0 -97
  292. package/server/service/index.ts +0 -100
  293. package/server/service/reference/actuation-routing.ts +0 -192
  294. package/server/service/reference/actuation-target.ts +0 -163
  295. package/server/service/reference/actuation-verification.ts +0 -85
  296. package/server/service/reference/attention-lane.ts +0 -21
  297. package/server/service/reference/connection-portability-resolver.ts +0 -171
  298. package/server/service/reference/connection-portability.ts +0 -329
  299. package/server/service/reference/control-routing.ts +0 -62
  300. package/server/service/reference/discovery-result.ts +0 -95
  301. package/server/service/reference/fill-cadence.ts +0 -29
  302. package/server/service/reference/fill-dedupe.ts +0 -66
  303. package/server/service/reference/fill-loop.ts +0 -153
  304. package/server/service/reference/fill-span.ts +0 -31
  305. package/server/service/reference/hook-contract.ts +0 -364
  306. package/server/service/reference/hook-rejected.ts +0 -53
  307. package/server/service/reference/hook-store.ts +0 -28
  308. package/server/service/reference/index.ts +0 -18
  309. package/server/service/reference/ingest-space.ts +0 -104
  310. package/server/service/reference/knob-defaults.ts +0 -59
  311. package/server/service/reference/live-cadence.ts +0 -129
  312. package/server/service/reference/live-feed-lease.ts +0 -56
  313. package/server/service/reference/live-ingest.ts +0 -94
  314. package/server/service/reference/reference-adapter.ts +0 -985
  315. package/server/service/reference/reference-assessment.ts +0 -250
  316. package/server/service/reference/reference-fill-run.ts +0 -179
  317. package/server/service/reference/reference-fill.ts +0 -223
  318. package/server/service/reference/reference-hook.ts +0 -479
  319. package/server/service/reference/reference-link-groups.ts +0 -21
  320. package/server/service/reference/reference-links.ts +0 -77
  321. package/server/service/reference/reference-live.ts +0 -420
  322. package/server/service/reference/reference-master.ts +0 -999
  323. package/server/service/reference/reference-probe.ts +0 -264
  324. package/server/service/reference/reference-progress-channel.ts +0 -26
  325. package/server/service/reference/reference-progress-subscription.ts +0 -78
  326. package/server/service/reference/reference-progress.ts +0 -95
  327. package/server/service/reference/reference-reconciliation-query.ts +0 -28
  328. package/server/service/reference/reference-reconciliation-writer.ts +0 -38
  329. package/server/service/reference/reference-reconciliation.ts +0 -55
  330. package/server/service/reference/reference-registry.ts +0 -17
  331. package/server/service/reference/reference-resolver.ts +0 -969
  332. package/server/service/reference/template-registry.ts +0 -85
  333. package/server/service/reference/twin-reference-reconciliation.ts +0 -27
  334. package/server/service/reference/twin-reference.ts +0 -190
  335. package/server/service/twin-attention/index.ts +0 -3
  336. package/server/service/twin-attention/twin-attention-query.ts +0 -70
  337. package/server/service/twin-audit/command-audit.ts +0 -114
  338. package/server/service/twin-audit/index.ts +0 -5
  339. package/server/service/twin-audit/twin-audit-event.ts +0 -158
  340. package/server/service/twin-audit/twin-audit-query.ts +0 -72
  341. package/server/service/twin-backfill/backfill-period-facts.ts +0 -168
  342. package/server/service/twin-backfill/backfill-shape.ts +0 -86
  343. package/server/service/twin-backfill/index.ts +0 -3
  344. package/server/service/twin-backfill/twin-backfill-resolver.ts +0 -35
  345. package/server/service/twin-control/index.ts +0 -3
  346. package/server/service/twin-control/twin-control-mutation.ts +0 -150
  347. package/server/service/twin-event/index.ts +0 -3
  348. package/server/service/twin-event/journal-count.ts +0 -53
  349. package/server/service/twin-event/twin-event-keys.ts +0 -215
  350. package/server/service/twin-event/twin-event-type.ts +0 -68
  351. package/server/service/twin-event/twin-event.ts +0 -250
  352. package/server/service/twin-forecast/forecast-metrics.ts +0 -106
  353. package/server/service/twin-forecast/forecast-tuning.ts +0 -40
  354. package/server/service/twin-forecast/gap-analytics.ts +0 -139
  355. package/server/service/twin-forecast/index.ts +0 -3
  356. package/server/service/twin-forecast/twin-forecast-query.ts +0 -486
  357. package/server/service/twin-ingest-window/index.ts +0 -7
  358. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +0 -125
  359. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +0 -57
  360. package/server/service/twin-ingest-window/twin-ingest-window.ts +0 -113
  361. package/server/service/twin-instance/index.ts +0 -3
  362. package/server/service/twin-instance/twin-instance.ts +0 -199
  363. package/server/service/twin-journal/event-type-split-shape.ts +0 -112
  364. package/server/service/twin-journal/event-type-split.ts +0 -144
  365. package/server/service/twin-journal/index.ts +0 -3
  366. package/server/service/twin-journal/journal-order.ts +0 -20
  367. package/server/service/twin-journal/twin-journal-query.ts +0 -615
  368. package/server/service/twin-lifecycle/domain-catalog.ts +0 -51
  369. package/server/service/twin-lifecycle/index.ts +0 -3
  370. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +0 -285
  371. package/server/service/twin-metrics/index.ts +0 -3
  372. package/server/service/twin-metrics/twin-metrics-query.ts +0 -172
  373. package/server/service/twin-model/axis-journal-evidence.ts +0 -57
  374. package/server/service/twin-model/choose-recipe.ts +0 -31
  375. package/server/service/twin-model/epcis-coverage.ts +0 -251
  376. package/server/service/twin-model/external-resolver.ts +0 -173
  377. package/server/service/twin-model/iec61850-coverage.ts +0 -111
  378. package/server/service/twin-model/index.ts +0 -25
  379. package/server/service/twin-model/isa95-coverage.ts +0 -320
  380. package/server/service/twin-model/item-ref.ts +0 -80
  381. package/server/service/twin-model/name-index.ts +0 -94
  382. package/server/service/twin-model/project-structure.ts +0 -213
  383. package/server/service/twin-model/standard-coverage.ts +0 -35
  384. package/server/service/twin-model/status-tally.ts +0 -37
  385. package/server/service/twin-model/twin-equipment.ts +0 -191
  386. package/server/service/twin-model/twin-lineage-query.ts +0 -327
  387. package/server/service/twin-model/twin-location.ts +0 -147
  388. package/server/service/twin-model/twin-model-item-query.ts +0 -1029
  389. package/server/service/twin-model/twin-model-mutation.ts +0 -503
  390. package/server/service/twin-model/twin-model-query.ts +0 -792
  391. package/server/service/twin-model/twin-model-tree-query.ts +0 -211
  392. package/server/service/twin-model/twin-operation.ts +0 -157
  393. package/server/service/twin-readiness/index.ts +0 -3
  394. package/server/service/twin-readiness/twin-readiness-query.ts +0 -96
  395. package/server/service/twin-space/area-resolver.ts +0 -75
  396. package/server/service/twin-space/index.ts +0 -8
  397. package/server/service/twin-space/move-space.ts +0 -262
  398. package/server/service/twin-space/representation-role.ts +0 -15
  399. package/server/service/twin-space/space-integrity.ts +0 -293
  400. package/server/service/twin-space/twin-area.ts +0 -63
  401. package/server/service/twin-space/twin-space-area.ts +0 -66
  402. package/server/service/twin-space/twin-space-representation.ts +0 -99
  403. package/server/service/twin-space/twin-space-resolver.ts +0 -582
  404. package/server/service/twin-space/twin-space.ts +0 -148
  405. package/server/service/twin-state/index.ts +0 -3
  406. package/server/service/twin-state/twin-state-subscription.ts +0 -62
  407. package/server/service/twin-structure/index.ts +0 -3
  408. package/server/service/twin-structure/twin-structure.ts +0 -96
  409. package/server/service/twin-subject/event-subjects.ts +0 -162
  410. package/server/service/twin-subject/index.ts +0 -10
  411. package/server/service/twin-subject/item-resolver.ts +0 -92
  412. package/server/service/twin-subject/subject-query-shape.ts +0 -84
  413. package/server/service/twin-subject/twin-subject-backfill.ts +0 -196
  414. package/server/service/twin-subject/twin-subject-event.ts +0 -101
  415. package/server/service/twin-subject/twin-subject-fast-path.ts +0 -346
  416. package/server/service/twin-subject/twin-subject-query.ts +0 -210
  417. package/server/service/twin-subject/twin-subject-writer.ts +0 -189
  418. package/server/service/twin-target/index.ts +0 -5
  419. package/server/service/twin-target/twin-target-resolver.ts +0 -142
  420. package/server/service/twin-target/twin-target.ts +0 -97
  421. package/server/types/global.d.ts +0 -22
  422. package/shared/axis-read.ts +0 -28
  423. package/shared/entity-delta.ts +0 -335
  424. package/shared/kpi-broadcast.ts +0 -13
  425. package/shared/touched-items.ts +0 -73
  426. package/shared/twin-level.ts +0 -48
  427. package/test/ack-shape-alignment.test.ts +0 -97
  428. package/test/actuation-advisor.test.ts +0 -55
  429. package/test/actuation-approval-door.test.ts +0 -411
  430. package/test/actuation-dispatch.test.ts +0 -209
  431. package/test/actuation-effect.test.ts +0 -162
  432. package/test/actuation-outcome.test.ts +0 -152
  433. package/test/actuation-rule-runner.test.ts +0 -281
  434. package/test/actuation-rule.test.ts +0 -266
  435. package/test/actuation-seam.test.ts +0 -255
  436. package/test/actuation-settle.test.ts +0 -164
  437. package/test/actuation-target.test.ts +0 -145
  438. package/test/actuation-verification-state.test.ts +0 -139
  439. package/test/adopt-structure-live.test.ts +0 -133
  440. package/test/aggregate-not-findone.test.ts +0 -112
  441. package/test/area-resolver-db.test.ts +0 -110
  442. package/test/attention-digest.test.ts +0 -161
  443. package/test/axis-declaration-passthrough-db.test.ts +0 -152
  444. package/test/axis-journal-evidence.test.ts +0 -71
  445. package/test/axis-read.test.ts +0 -188
  446. package/test/backfill-shape.test.ts +0 -89
  447. package/test/boot-resume.test.ts +0 -287
  448. package/test/broadcast-cost-baseline.test.ts +0 -201
  449. package/test/broadcast-period.test.ts +0 -58
  450. package/test/broadcast-queue-overflow.test.ts +0 -244
  451. package/test/canonical-ingest-vocabularies.test.ts +0 -451
  452. package/test/canonical-quantity-door.test.ts +0 -61
  453. package/test/capability-mapping.test.ts +0 -89
  454. package/test/checkpoint-refuses-empty.test.ts +0 -103
  455. package/test/choose-recipe.test.ts +0 -117
  456. package/test/column-type-portability.test.ts +0 -122
  457. package/test/command-routing.test.ts +0 -61
  458. package/test/command-store-writes-columns.test.ts +0 -126
  459. package/test/connection-portability-doors.test.ts +0 -87
  460. package/test/connector-capability-declaration.test.ts +0 -195
  461. package/test/contract-layer-guard.test.ts +0 -48
  462. package/test/control-capability.test.ts +0 -98
  463. package/test/cursor-stall-not-read-failure.test.ts +0 -126
  464. package/test/declaration-reaches-model.test.ts +0 -226
  465. package/test/declared-location-types.test.ts +0 -89
  466. package/test/declared-stimulus.test.ts +0 -88
  467. package/test/dedupe-transformation.test.ts +0 -112
  468. package/test/discovery-result.test.ts +0 -75
  469. package/test/dispatch-after-approval-source.test.ts +0 -81
  470. package/test/duration-estimators.test.ts +0 -144
  471. package/test/energy-topology.test.ts +0 -113
  472. package/test/entity-delta.test.ts +0 -352
  473. package/test/equipment-identity-reaches.test.ts +0 -57
  474. package/test/event-subjects.test.ts +0 -148
  475. package/test/event-time-column.test.ts +0 -67
  476. package/test/event-type-split.test.ts +0 -178
  477. package/test/external-resolver.test.ts +0 -177
  478. package/test/fact-scope-wiring.test.ts +0 -96
  479. package/test/fill-dedupe.test.ts +0 -59
  480. package/test/fill-loop.test.ts +0 -35
  481. package/test/first-envelope.test.ts +0 -153
  482. package/test/fold-gap-speaks.test.ts +0 -53
  483. package/test/fold-shift-dst.test.ts +0 -110
  484. package/test/forecast-metrics.test.ts +0 -157
  485. package/test/forecast-tuning.test.ts +0 -42
  486. package/test/gap-analytics.test.ts +0 -113
  487. package/test/generated-not-counted.test.ts +0 -99
  488. package/test/guard-roots.ts +0 -76
  489. package/test/home-in-area.test.ts +0 -90
  490. package/test/hook-address.test.ts +0 -28
  491. package/test/hook-rejected-shape.test.ts +0 -86
  492. package/test/hook-response-sum.test.ts +0 -193
  493. package/test/hook-sequence.test.ts +0 -333
  494. package/test/ingest-bench.test.ts +0 -70
  495. package/test/ingest-dedupe.test.ts +0 -56
  496. package/test/ingest-expected-quiet.test.ts +0 -111
  497. package/test/ingest-health-engine.test.ts +0 -248
  498. package/test/ingest-health-wiring.test.ts +0 -120
  499. package/test/ingest-health.test.ts +0 -306
  500. package/test/ingest-history.test.ts +0 -247
  501. package/test/ingest-idempotent.test.ts +0 -211
  502. package/test/ingest-reconcile-four-ways.test.ts +0 -97
  503. package/test/ingest-rules-header.test.ts +0 -100
  504. package/test/ingest-running-guard.test.ts +0 -136
  505. package/test/ingest-space.test.ts +0 -78
  506. package/test/ingest-window-rolls-unobserved.test.ts +0 -72
  507. package/test/ingest-wiring-guard.test.ts +0 -197
  508. package/test/instance-cache-lifecycle.test.ts +0 -110
  509. package/test/intake-mapping-parity.test.ts +0 -175
  510. package/test/integration-probes.test.ts +0 -103
  511. package/test/integration-runner.test.ts +0 -95
  512. package/test/item-ref.test.ts +0 -78
  513. package/test/journal-order-axis.test.ts +0 -80
  514. package/test/journal-read-discipline.test.ts +0 -186
  515. package/test/journal-retention.test.ts +0 -275
  516. package/test/journal-sort-axis.test.ts +0 -142
  517. package/test/journal-write-door.test.ts +0 -110
  518. package/test/journal-write-trend.test.ts +0 -115
  519. package/test/kernel-kind-guard.test.ts +0 -98
  520. package/test/knob-defaults.test.ts +0 -72
  521. package/test/kpi-baseline-db.test.ts +0 -216
  522. package/test/kpi-baseline.test.ts +0 -196
  523. package/test/kpi-fold.test.ts +0 -1456
  524. package/test/kpi-query-bench.test.ts +0 -130
  525. package/test/lens-own-places.test.ts +0 -50
  526. package/test/lineage-survives-restart.test.ts +0 -234
  527. package/test/live-cadence.test.ts +0 -170
  528. package/test/live-cursor-wiring.test.ts +0 -87
  529. package/test/live-feed-lease.test.ts +0 -63
  530. package/test/live-feed-registry.test.ts +0 -75
  531. package/test/live-kernel-facts.test.ts +0 -161
  532. package/test/live-mirror-parity.test.ts +0 -286
  533. package/test/load-meter.test.ts +0 -328
  534. package/test/local-declarations.test.ts +0 -662
  535. package/test/log-stamp.test.ts +0 -59
  536. package/test/loop-lag.test.ts +0 -82
  537. package/test/master-to-twin.test.ts +0 -279
  538. package/test/measured-yield.test.ts +0 -90
  539. package/test/merge-streams.test.ts +0 -70
  540. package/test/mirror-resumes-from-checkpoint.test.ts +0 -192
  541. package/test/model-basis.test.ts +0 -141
  542. package/test/model-gap.test.ts +0 -153
  543. package/test/model-refusal.test.ts +0 -75
  544. package/test/model-vocabulary.test.ts +0 -114
  545. package/test/move-space.test.ts +0 -149
  546. package/test/mutation-gate.test.ts +0 -122
  547. package/test/oee-accumulator.test.ts +0 -204
  548. package/test/oee-site-calendar.test.ts +0 -146
  549. package/test/open-doors.test.ts +0 -197
  550. package/test/operation-basis.test.ts +0 -127
  551. package/test/operational-vocabulary.test.ts +0 -108
  552. package/test/operations-capability-db.test.ts +0 -165
  553. package/test/order-and-transaction-split.test.ts +0 -107
  554. package/test/order-fold-host.test.ts +0 -112
  555. package/test/producer-guard.test.ts +0 -66
  556. package/test/project-structure-db.test.ts +0 -226
  557. package/test/projection-reaches-screen.test.ts +0 -277
  558. package/test/projection-reads-declared.test.ts +0 -109
  559. package/test/property-effects.test.ts +0 -128
  560. package/test/provenance-promotion.test.ts +0 -99
  561. package/test/read-failure-ledger.test.ts +0 -106
  562. package/test/read-failure-visible.test.ts +0 -145
  563. package/test/read-time.test.ts +0 -118
  564. package/test/reference-fill.test.ts +0 -235
  565. package/test/reference-grounding.test.ts +0 -70
  566. package/test/reference-hook.test.ts +0 -177
  567. package/test/reference-links.test.ts +0 -56
  568. package/test/reference-reconciliation.test.ts +0 -39
  569. package/test/registry-key-guard.test.ts +0 -80
  570. package/test/rejection-carries-event-id.test.ts +0 -101
  571. package/test/rename-twin.test.ts +0 -89
  572. package/test/resolve-ts-siblings.mjs +0 -52
  573. package/test/restart-policy.test.ts +0 -111
  574. package/test/resync-origin-site.test.ts +0 -83
  575. package/test/retry-plan.test.ts +0 -139
  576. package/test/revision-axis.test.ts +0 -110
  577. package/test/runtime-key.test.ts +0 -66
  578. package/test/save-intake-collision.test.ts +0 -91
  579. package/test/scale-twin-bench.test.ts +0 -103
  580. package/test/secrets-at-rest.test.ts +0 -135
  581. package/test/snapshot-freshness.test.ts +0 -60
  582. package/test/source-outcome-audit.test.ts +0 -104
  583. package/test/space-integrity.test.ts +0 -223
  584. package/test/space-representation.test.ts +0 -66
  585. package/test/space-scope-includes-stopped.test.ts +0 -55
  586. package/test/spec-coverage.test.ts +0 -113
  587. package/test/stage-path.test.ts +0 -95
  588. package/test/standard-coverage.test.ts +0 -148
  589. package/test/state-axes.test.ts +0 -74
  590. package/test/status-tally.test.ts +0 -55
  591. package/test/streamline-e2e.test.ts +0 -76
  592. package/test/structure-revision-db.test.ts +0 -315
  593. package/test/subject-fast-path.test.ts +0 -148
  594. package/test/subject-read-model.test.ts +0 -175
  595. package/test/sweep-dispatch.test.ts +0 -191
  596. package/test/tenant-registry-db.test.ts +0 -149
  597. package/test/time-range.test.ts +0 -152
  598. package/test/touched-items.test.ts +0 -61
  599. package/test/twin-audit.test.ts +0 -83
  600. package/test/twin-event-keys.test.ts +0 -193
  601. package/test/twin-model-item-db.test.ts +0 -319
  602. package/test/twin-model-parent-relation-db.test.ts +0 -125
  603. package/test/twin-model-tree-db.test.ts +0 -209
  604. package/test/twin-origin-resync.test.ts +0 -133
  605. package/test/twin-state-channel.test.ts +0 -111
  606. package/test/vocabulary-guard.test.ts +0 -43
  607. package/test/warm-start-seam.test.ts +0 -162
  608. package/test/warm-start.test.ts +0 -297
  609. package/test/withheld-door.test.ts +0 -95
  610. package/test/yield-loop.test.ts +0 -199
  611. package/tsconfig.json +0 -15
  612. package/tsconfig.shared.json +0 -23
  613. package/tsconfig.shared.tsbuildinfo +0 -1
  614. package/tsconfig.tsbuildinfo +0 -1
@@ -1,615 +0,0 @@
1
- import { twinWarn } from '../../engine/log.js'
2
- import { Between, LessThanOrEqual, MoreThanOrEqual } from 'typeorm'
3
- import { Arg, Args, Ctx, Float, Int, Query, Resolver, UseMiddleware } from 'type-graphql'
4
-
5
- import { buildNextCursor, cursorSortings, getQueryBuilderFromListParams, getRepository, ListParam, ScalarObject, SpeakRefusals } from '@things-factory/shell'
6
-
7
- import { TwinEvent } from '../twin-event/twin-event.js'
8
- import { JOURNAL_COUNT_CAP } from '../twin-event/journal-count.js'
9
- import { TwinEventList } from '../twin-event/twin-event-type.js'
10
- import { TwinEngine } from '../../engine/index.js'
11
- import { pickAxes } from '../../engine/state-axes.js'
12
- import { digestAttentions } from '../../engine/attention-digest.js'
13
- import { computeTwinKpi, resolveTwinTargets } from '../../engine/kpi-query.js'
14
- import { journalOrder } from './journal-order.js'
15
- import { readModelPage } from '../twin-subject/twin-subject-fast-path.js'
16
- import { eventTypeSplitPage } from './event-type-split.js'
17
- import { splitShape } from './event-type-split-shape.js'
18
-
19
- /*
20
- * 저널 읽기 채널 — 상향 query.
21
- * twinEvents : append-only 이벤트 저널 조회(Ledger/History/Entity360) — instanceId·revision 범위·eventType 필터.
22
- * twinReplay : 내구 저널을 replay 해 상태 재구성(재부팅 복구·시간여행) — untilRevision 으로 임의 과거.
23
- * (실시간 스트림은 twinState subscription, 명령은 twin-control mutation. 이건 '질의' 표면.)
24
- */
25
- /*
26
- * 건수 상한은 **저널 세기의 문**에서 온다(`journal-count.ts`) — 값과 규율이 두 곳에 흩어지면 한쪽만
27
- * 고쳐진다. 근거(2026-08-22 실측): 상한 1만이면 0.04초, 상한 없이 세면 5.32초(저널 358만 행).
28
- */
29
- const COUNT_CAP = JOURNAL_COUNT_CAP
30
-
31
- /**
32
- * 시각 필터 값을 날짜로 — 배열(between)이면 원소마다.
33
- *
34
- * 못 읽는 값은 **그대로 둔다.** 여기서 지어내면 사용자가 요청하지 않은 구간을 조회하게 된다.
35
- */
36
- function toDateValue(v: any): any {
37
- const one = (x: any) => {
38
- if (x instanceof Date) return x
39
- const t = typeof x === 'string' ? Date.parse(x) : NaN
40
- return Number.isNaN(t) ? x : new Date(t)
41
- }
42
- return Array.isArray(v) ? v.map(one) : one(v)
43
- }
44
-
45
- /**
46
- * 자유 검색어가 펼쳐지는 축 — **목록과 건수가 같은 것을 본다**(2026-09-19).
47
- *
48
- * 전부 인덱스 가능한 승격 컬럼이다(payload JSON 안이 아니라). 프레임워크 질의 빌더는 이 목록에 없는
49
- * 컬럼의 LIKE 를 경고 후 무시한다 — 인덱스 없는 전체 스캔을 막기 위해서다.
50
- *
51
- * ── 왜 상수인가 ────────────────────────────────────────────────────────────
52
- * 목록과 건수가 **다른 빌더**로 만들어진다(빠른 갈래의 건수 probe). 두 자리에 배열을 따로 적으면
53
- * 한쪽만 늘어나는 날 목록은 걸러지고 건수는 안 걸러진다 — 화면에 「한 건」과 「전체 수」가 나란히 선다.
54
- *
55
- * 지금은 그 일이 **일어날 수 없다**: 빠른 갈래는 검색어가 있으면 아예 안 탄다
56
- * (`event-type-split-shape.ts` — 시험 「자유 검색어」가 그 축을 붙든다). 그래도 같은 상수를 쓴다 —
57
- * 그 규칙이 언젠가 느슨해질 때 이 자리가 조용히 갈리지 않게.
58
- */
59
- export const JOURNAL_SEARCHABLES = ['epc', 'orderId', 'locationId', 'moverId', 'bizStep', 'eventType', 'correlationId'] as const
60
-
61
- @Resolver()
62
- @UseMiddleware(SpeakRefusals)
63
- export class TwinJournalQuery {
64
- @Query(returns => [TwinEvent], {
65
- description:
66
- 'Read the append-only twin event journal (Ledger / History / audit). Newest first by event time; ' +
67
- 'ordered by revision only when a revision range is given.'
68
- })
69
- async twinEvents(
70
- @Arg('instanceId') instanceId: string,
71
- @Arg('fromRevision', type => Int, { nullable: true }) fromRevision: number,
72
- @Arg('toRevision', type => Int, { nullable: true }) toRevision: number,
73
- @Arg('eventType', { nullable: true }) eventType: string,
74
- @Arg('limit', type => Int, { nullable: true }) limit: number,
75
- @Ctx() context: ResolverContext,
76
- @Arg('untilTime', { nullable: true }) untilTime?: string,
77
- @Arg('fromTime', { nullable: true }) fromTime?: string
78
- ): Promise<TwinEvent[]> {
79
- const where: any = { domain: { id: context.state.domain.id }, instanceId }
80
- if (eventType) where.eventType = eventType
81
- /*
82
- * ── 구간으로 물을 수 있게 한다 (2026-08-25 실측) ────────────────────────────
83
- *
84
- * `untilTime` 만 있으면 **끝만 정해진 조회**다. 그러면 위쪽 경계에서 뒤로 걸어오며 종류를 하나씩
85
- * 확인해야 하고, 찾는 종류가 며칠 전에 멈춘 트윈에서는 그 사이 행을 전부 열어 본다. 실측:
86
- *
87
- * 공정 타임라인이 부르는 방식(종류만·200건) 22.0초
88
- * 같은 조회에 `untilTime` 만 더한 것 9.7초
89
- *
90
- * 이유는 계획이 말한다. 종류별 시각순 인덱스가 없어서 `(domain, instanceId, eventTime)` 로 걷고
91
- * 행마다 테이블을 열어 종류를 본다. 승화푸드에서 작업 사실은 8월 22일에 멈춰 있고 그 뒤로 리비전이
92
- * 110만 늘었으므로, 200건에 닿기까지 그만큼을 읽고 버린다.
93
- *
94
- * **구간을 주면 그 범위만 걷는다**(`event_time>? AND event_time<?`). 화면은 자기가 그리는 구간을
95
- * 알고 있으므로 그것을 그대로 물으면 된다 — 「전체」가 실제로는 상한에 걸린 6.7시간이었던 문제도
96
- * 같은 자리에서 없어진다.
97
- *
98
- * 컬럼이 날짜라 **날짜로 넘긴다**(문자열로 비교하면 sqlite 에서 오류 없이 빈 결과가 나온다).
99
- */
100
- if (untilTime != null && fromTime != null) where.eventTime = Between(new Date(fromTime), new Date(untilTime))
101
- else if (untilTime != null) where.eventTime = LessThanOrEqual(new Date(untilTime))
102
- else if (fromTime != null) where.eventTime = MoreThanOrEqual(new Date(fromTime))
103
- else if (fromRevision != null && toRevision != null) where.revision = Between(fromRevision, toRevision)
104
- else if (fromRevision != null) where.revision = MoreThanOrEqual(fromRevision)
105
- else if (toRevision != null) where.revision = LessThanOrEqual(toRevision)
106
-
107
- /*
108
- * 최신순(DESC) 기본 — Ledger/recent 뷰. 범위 필터로 시간여행 구간 조회도 가능.
109
- *
110
- * ── 정렬 축을 **거르는 축과 맞춘다** (2026-08-22 실측) ─────────────────────
111
- * `untilTime` 경로는 `eventTime <= T` 로 거르면서 `revision` 으로 정렬했다. 그러면 sqlite 는 조건에
112
- * 맞는 행을 **전부 찾아 전부 정렬한 뒤** 상한을 적용한다 — 상한이 정렬 뒤에 걸리므로 200건을 얻는
113
- * 데도 전량 정렬을 치른다. 실측(인스턴스 하나가 375만 행):
114
- *
115
- * ORDER BY revision DESC → 33.4초
116
- * ORDER BY eventTime DESC, revision DESC → 0.007초 (4,700배)
117
- *
118
- * 계획이 그것을 그대로 말한다: 앞은 `USE TEMP B-TREE FOR ORDER BY`(전체 정렬), 뒤는
119
- * `... FOR LAST TERM OF ORDER BY` — 시각은 인덱스(`ix_twin_event_1`)에서 순서대로 나오고
120
- * **동시각만** 임시 정렬한다.
121
- *
122
- * **`revision` 경로는 그대로 둔다.** 그때는 거르는 축이 `revision` 이고 정렬도 `revision` 이라 이미
123
- * 맞아 있다(`ix_twin_event_0`). 축이 어긋난 경로만 고친다 — 맞는 자리를 건드리면 그쪽이 어긋난다.
124
- *
125
- * 순서의 뜻이 **완전히 같지는 않다**(측정해서 고친 문장): `revision` 순서와 `eventTime` 순서가
126
- * 어긋나는 자리가 실재한다(426만 행 중 7건 — 미러는 원본의 시각을 싣고 도착 순서로 리비전을 받는다).
127
- * 이 목록은 사람이 이력을 훑는 화면이고 **시각순으로 보는 것이 그 화면의 뜻**이므로, 그 어긋남은
128
- * 결함이 아니라 오히려 맞는 순서다. 동시각의 순서를 위해 `revision` 을 둘째 축으로 남긴다.
129
- */
130
- /*
131
- * ── 아무 필터도 없을 때도 시각순으로 낸다 (2026-08-25 실측) ────────────────
132
- * 이 갈래가 `revision DESC` 였다. 공정 타임라인은 필터 없이 상한만 걸고 부르므로
133
- * (`loadTwinEvents(id, { eventType: 'task.status', limit })`) 그 갈래로 떨어졌다.
134
- *
135
- * `revision` 은 **적힌 순서**다. 연결된 시스템을 되읽는 트윈에서는 그 순서가 일어난 순서와 다르다 —
136
- * 커넥터가 원본의 갱신 순서를 걷기 때문에, 원본에서 나중에 손댄 행이 4월 사실을 담고 있으면 그
137
- * 행이 큰 revision 을 받는다. 승화푸드 실측:
138
- *
139
- * 작업 축에 있는 가장 늦은 사실 2026-08-23 03:20
140
- * revision 이 가장 큰 작업 사실 2026-04-15 10:49 ← 상한이 잡던 값
141
- *
142
- * 상한 안에 8월 두 달치가 들어오지 않았고, 화면은 4월 사실을 최근 것으로 그렸다.
143
- *
144
- * 아래 목록 계약(`twinEventList`)은 이미 시각순을 기본으로 골라 두었다(§`sortings`). 이 함수만
145
- * 남아 있었다. 성능도 이유가 되지 않는다 — 시각 정렬이 인덱스를 순서대로 타는 것은 아래 실측이
146
- * 말한다.
147
- *
148
- * `revision` 범위로 거를 때는 `revision DESC` 를 그대로 쓴다. 거르는 축과 정렬 축을 맞추는 규칙이고,
149
- * 그때는 부르는 쪽이 리비전으로 말하고 있으므로 리비전 순서가 그 물음의 답이다.
150
- */
151
- const order: any = journalOrder({ untilTime, fromTime, fromRevision, toRevision })
152
-
153
- /*
154
- * ── `find` 이 아니라 질의 빌더로 묻는다 (2026-08-25 실측) ───────────────────
155
- *
156
- * `find({ where: { domain: { id } }, order, take })` 는 관계를 가진 엔티티에서 **두 단계 질의**가
157
- * 된다. 실제로 나간 SQL 이 이렇다:
158
- *
159
- * SELECT DISTINCT "distinctAlias"."TwinEvent_id", …
160
- * FROM (SELECT … 모든 컬럼 … FROM twin_events WHERE …) "distinctAlias"
161
- * ORDER BY … LIMIT ?
162
- *
163
- * **안쪽에 상한이 없다.** 조건에 맞는 행을 전부 만들어 놓고 밖에서 자른다. 승화푸드의 작업 사실은
164
- * 438,913건이고 `payload` 가 함께 실리므로, 200건을 얻는 데 그 전부를 읽었다.
165
- *
166
- * 실측: 같은 조회를 손으로 쓰면 0.024초, 이 함수를 통하면 12초였다. 인덱스를 새로 얹은 뒤에도
167
- * 그대로였다 — 인덱스는 안쪽 질의를 도와줄 뿐이고, 문제는 안쪽이 전부를 만든다는 것이었다.
168
- *
169
- * 관계를 **컬럼으로** 물으면 감싸지 않는다. 이 파일의 다른 자리(`timeRange` · 최종 리비전)가 이미
170
- * 그 방식이다. `getMany` 는 조인이 없으면 `DISTINCT` 를 붙이지 않는다.
171
- */
172
- const qb = getRepository(TwinEvent)
173
- .createQueryBuilder('e')
174
- .where('e.domain = :domainId', { domainId: context.state.domain.id })
175
- .andWhere('e.instanceId = :instanceId', { instanceId })
176
- if (eventType) qb.andWhere('e.eventType = :eventType', { eventType })
177
- if (where.eventTime !== undefined) {
178
- /* 조건은 위에서 이미 정했다 — 같은 판단을 두 벌로 적지 않는다. */
179
- if (untilTime != null && fromTime != null) {
180
- qb.andWhere('e.eventTime BETWEEN :fromTime AND :untilTime', { fromTime: new Date(fromTime), untilTime: new Date(untilTime) })
181
- } else if (untilTime != null) {
182
- qb.andWhere('e.eventTime <= :untilTime', { untilTime: new Date(untilTime) })
183
- } else {
184
- qb.andWhere('e.eventTime >= :fromTime', { fromTime: new Date(fromTime as string) })
185
- }
186
- } else if (where.revision !== undefined) {
187
- if (fromRevision != null && toRevision != null) qb.andWhere('e.revision BETWEEN :from AND :to', { from: fromRevision, to: toRevision })
188
- else if (fromRevision != null) qb.andWhere('e.revision >= :from', { from: fromRevision })
189
- else qb.andWhere('e.revision <= :to', { to: toRevision })
190
- }
191
- for (const [column, direction] of Object.entries(order)) qb.addOrderBy(`e.${column}`, direction as 'DESC')
192
- return qb.limit(limit ?? 200).getMany()
193
- }
194
-
195
- /**
196
- * 지난 기록 목록 — things-factory 표준 목록 계약(`ListParam` → `{ items, total }`).
197
- *
198
- * ── 왜 `twinEvents` 와 따로 두는가 ─────────────────────────────────────────
199
- * `twinEvents` 는 배열만 돌려준다. 화면은 자기가 받은 게 전부인지 잘린 건지 알 수 없었고, 그래서
200
- * 리스트가 **알리지 않고 잘린 채 "이게 전부" 처럼** 보였다(원장 limit 120 이 대표적). 총건수를 함께
201
- * 주면 "48 / 12,904" 라고 말할 수 있고, 사용자는 좁혀야 한다는 사실을 안다.
202
- * 기존 호출자를 깨지 않으려고 `twinEvents` 는 그대로 두고 목록 계약을 새로 연다.
203
- *
204
- * ── 검색이 진짜인 이유 ─────────────────────────────────────────────────────
205
- * `searchables` 는 전부 **인덱스 가능한 승격 컬럼**이다(payload JSON 안이 아니라). 프레임워크
206
- * 질의 빌더는 `searchables` 에 없는 컬럼의 LIKE 를 경고 후 무시한다 — 인덱스 없는 전체 스캔을
207
- * 막기 위해서다. 그 규율에 맞추려고 검색 축을 컬럼으로 승격했다(`twin-event-keys.ts`).
208
- *
209
- * 정렬 기본값은 revision DESC(최신순) — 저널의 자연 순서이자 ix_twin_event_0 가 그대로 타는 축.
210
- */
211
-
212
- @Query(returns => TwinEventList, {
213
- description:
214
- 'List twin journal events with the standard list contract (filters, pagination, sortings) plus the total match count. ' +
215
- 'Scope is either one twin (instanceId) or a whole site (spaceId, which folds in every running operational twin co-located there, the same targets twinKpi uses) — one of the two is required. ' +
216
- 'Searchable fields are the columns promoted out of the payload: epc (item), orderId, locationId, moverId (equipment), bizStep, eventType. ' +
217
- 'Returns total so a caller can show an honest "shown of total" count instead of silently truncating a long journal. ' +
218
- 'Defaults to newest first (revision descending) and to a page of 100; limit is capped at 500 per page.'
219
- })
220
- async twinEventList(
221
- @Args(type => ListParam) params: ListParam,
222
- @Ctx() context: ResolverContext,
223
- @Arg('instanceId', { nullable: true, description: 'Read one twin instance journal.' }) instanceId?: string,
224
- @Arg('spaceId', { nullable: true, description: 'Read every running operational twin co-located in this space, merged on the shared time axis.' }) spaceId?: string
225
- ): Promise<TwinEventList> {
226
- const { domain } = context.state
227
- if (!instanceId && !spaceId) throw new Error('either instanceId or spaceId is required')
228
- /* 테넌트 격리 — twinKpi 와 같은 규약. 남의 트윈은 빈 결과가 아니라 명시 실패로 알린다
229
- * (들어온 것이 없는 빈 목록은 "권한 없음" 과 "데이터 없음" 을 구분할 수 없게 만든다). */
230
- if (instanceId && !(await TwinEngine.ownsRegistered(domain.id, instanceId))) {
231
- throw new Error(`twin instance not found in this tenant: ${instanceId}`)
232
- }
233
-
234
- /* 대상 해소는 KPI 와 같은 창구를 쓴다 — 같은 공간을 보면서 화면마다 대상이 달라지면 안 된다. */
235
- const instanceIds = await resolveTwinTargets(domain.id, instanceId, spaceId)
236
- if (instanceIds.length === 0) return { items: [], total: 0 }
237
-
238
- /* 페이지 상한 — 화면이 실수로(또는 악의로) 저널 전체를 한 번에 달라고 해도 서버가 버틴다. */
239
- const limit = Math.min(params.pagination?.limit ?? 100, 500)
240
- const effective: ListParam = {
241
- ...params,
242
- /*
243
- * ── 이어 읽기 지점을 **잃지 않는다** (2026-08-28 실측) ─────────────────────
244
- *
245
- * 여기서 페이지 값을 다시 만들면서 `after` 를 빼고 있었다. 그래서 응답은 다음 지점(`nextCursor`)을
246
- * 내주는데 다음 요청의 그 값은 아무 데도 쓰이지 않았고, 「더 보기」가 **1페이지를 다시 냈다.**
247
- * 실측: 같은 품목의 30건을 세 번 이어 읽었더니 90건 중 60건이 되풀었다(같은 id).
248
- *
249
- * 오류가 나지 않는 결함이라 화면에서는 「이력이 30건뿐」이나 「같은 사건이 계속 나온다」로 보인다.
250
- */
251
- pagination: { page: params.pagination?.page ?? 1, limit, after: params.pagination?.after },
252
- /* 기본 정렬은 시각 내림차순 — 여러 트윈을 합칠 때 공통 축은 revision(트윈별 카운터)이 아니라
253
- * **시각**이다. revision 으로 섞으면 서로 다른 트윈의 무관한 카운터가 뒤엉킨다. */
254
- sortings: params.sortings?.length ? params.sortings : [{ name: 'eventTime', desc: true }],
255
- /*
256
- * 시각 필터는 **날짜로 바꿔서** 질의 빌더에 넘긴다.
257
- *
258
- * 화면은 ISO 문자열(`2026-08-07T11:00:00.000Z`)을 보내는데 eventTime 은 날짜 컬럼이고,
259
- * sqlite 는 그것을 공백 구분 형식(`2026-08-07 11:00:00.000`)으로 저장한다. 문자열 그대로
260
- * 비교하면 사전식으로 어긋나 **오류 없이 빈 결과**가 나온다(실측으로 확인했다).
261
- * 화면이 날짜를 어떻게 적어 보내든 서버가 받아 주는 편이 맞다 — 계약을 좁히는 대신 경계에서 옮긴다.
262
- */
263
- filters: (params.filters ?? []).map(f => (f.name === 'eventTime' ? { ...f, value: toDateValue(f.value) } : f))
264
- }
265
-
266
- /*
267
- * ── 대상 하나의 이력은 **파생 테이블**이 답한다 (2026-08-28 실측) ───────────
268
- *
269
- * 360 화면의 이력이 이 문으로 온다. 물음은 「이 품목(오더·자리·설비)의 사건을 최신순으로 30건」
270
- * 인데, 저널에는 그 모양의 목차가 없다: 축으로 걸러도 시각으로 정렬하는 순간 목차를 못 쓰고,
271
- * 품목과 로트가 한 문자열에 붙어 있어 품목으로는 아예 찾지 못한다. 실측 **63.998초**였다.
272
- *
273
- * 그 물음의 모양대로 만든 테이블이 이미 있다(`twin_subject_events`). 같은 질의가 **0.078초**다.
274
- * 화면을 고치지 않고 여기서 답을 가져오는 곳만 바꾼다 — 계약도 커서도 그대로다.
275
- *
276
- * 맞지 않는 물음(여러 트윈·자유 검색·종류 필터·다른 정렬)이거나 파생 테이블이 그 구간을 말할 수
277
- * 없으면 `null` 이 오고, 그때는 아래 저널 경로로 간다. 느리지만 온전하다.
278
- */
279
- if (instanceIds.length === 1) {
280
- const fast = await readModelPage(domain.id, instanceIds[0], effective, limit)
281
- if (fast) return fast
282
- }
283
-
284
- /*
285
- * ── **여러 종류를 시각순으로 물으면 나눠 묻는다** (2026-08-28 실측) ──────────
286
- *
287
- * 에너지 원장이 「이 여덟 종류를 시각 내림차순으로」를 묻는다. 그 모양에서 sqlite 는 종류 색인을
288
- * 버리고 시각 색인으로 337만 행을 걸으며 행마다 종류를 확인한다 — **37.181초, 결과 0건**.
289
- * 종류를 정확일치로 하나씩 물으면 색인이 시각 순서까지 주므로 **각 0.003초**다.
290
- *
291
- * 새 색인도 새 테이블도 필요 없다. 있는 색인(`ix_twin_event_7`)을 쓰게 하는 것이 전부다.
292
- * 건수 세기는 이미 빠르다(정렬을 지우므로 같은 색인을 탄다) — 목록만 이 갈래를 탄다.
293
- */
294
- const tiebreak: 'revision' | 'id' = instanceIds.length === 1 ? 'revision' : 'id'
295
- const split = splitShape(effective, effective.sortings ?? [])
296
- if (split) {
297
- const page = await eventTypeSplitPage(domain.id, instanceIds, split, limit, tiebreak, effective.pagination?.after)
298
- if (page) {
299
- /*
300
- * 건수는 일반 경로와 같은 방식으로 센다 — 상한까지 키만 읽는다. 이 조건은 정렬이 없어 종류
301
- * 색인을 그대로 타므로 값이 싸다(실측 0.003초).
302
- */
303
- const probe = getQueryBuilderFromListParams({
304
- repository: getRepository(TwinEvent),
305
- params: { ...effective, pagination: { page: 1, limit: COUNT_CAP + 1 } },
306
- domain,
307
- searchables: [...JOURNAL_SEARCHABLES],
308
- sortables: ['eventTime', 'revision', 'eventType', 'bizStep'],
309
- defaultLimit: COUNT_CAP + 1,
310
- maxLimit: COUNT_CAP + 1
311
- })
312
- probe.andWhere(`${probe.alias}.instanceId IN (:...instanceIds)`, { instanceIds })
313
- const counted = await probe
314
- .orderBy()
315
- .select(`${probe.alias}.id`)
316
- .getRawMany()
317
- .catch(() => null)
318
- const capped = !counted || counted.length > COUNT_CAP
319
- return {
320
- items: page.items,
321
- total: capped ? undefined : counted!.length,
322
- capped,
323
- countCap: COUNT_CAP,
324
- nextCursor: page.nextCursor,
325
- oldestRevision: undefined
326
- }
327
- }
328
- }
329
-
330
- const qb = getQueryBuilderFromListParams({
331
- repository: getRepository(TwinEvent),
332
- params: effective,
333
- domain,
334
- /* 전부 인덱스 가능한 승격 컬럼 — 자유 검색어는 서버가 이 축들로 펼친다. */
335
- searchables: [...JOURNAL_SEARCHABLES],
336
- /* 정렬은 인덱스가 받쳐 주는 축으로만 — 큰 저널에서 임의 컬럼 정렬은 전체 정렬 스캔이다. */
337
- sortables: ['eventTime', 'revision', 'eventType', 'bizStep'],
338
- defaultLimit: 100,
339
- maxLimit: 500,
340
- /*
341
- * ── 동시각을 가리는 축을 **두 경로가 같게** 쓴다 (2026-08-28 실측) ─────────
342
- *
343
- * 커서는 「이 행 다음부터」이므로 시각이 같은 행들 사이에서 무엇이 앞인지 정해야 한다. 기본값은
344
- * 기본키(`id`)인데, 파생 테이블 경로는 `revision` 으로 가린다(그 테이블에 `id` 가 없다).
345
- *
346
- * 두 경로가 한 목록을 나눠 답하면 그 축이 달라지고, 시각이 같은 행이 많은 자리에서 **본 행이
347
- * 또 나온다.** 실측: 재고 사건 2,665건이 같은 시각을 가진 트윈에서 30건씩 네 번 이어 읽어
348
- * 120건 중 15건이 되풀었다.
349
- *
350
- * 그래서 트윈 하나를 볼 때는 `revision` 으로 가린다 — 그 트윈 안에서 유일하고 빈 값이 없다.
351
- * 여러 트윈을 합쳐 보는 목록에서는 리비전이 트윈마다 따로 세는 번호라 유일하지 않으므로
352
- * 기본키를 쓴다.
353
- */
354
- cursorTiebreak: tiebreak
355
- })
356
- /* 대상 범위는 호출자가 필터로 빼먹을 수 있는 값이 아니다 — 계약상 필수라 여기서 강제한다. */
357
- qb.andWhere(`${qb.alias}.instanceId IN (:...instanceIds)`, { instanceIds })
358
-
359
- /*
360
- * ── 건수는 **상한까지만** 센다 (2026-08-22 실측) ──────────────────────────
361
- * 예전에는 `getManyAndCount()` 였다. 그 두 번째 질의가 조건에 맞는 행을 **전부** 세는데, 실측한
362
- * 개발 저널에서 트윈 하나가 358만 행이라 **한 번에 5.32초**였다. 이력 화면을 열 때마다 그것을
363
- * 물었고, 그 5초 동안 이벤트 루프가 다른 요청을 처리하지 못했다.
364
- *
365
- * 상한을 두면 같은 답이 **0.04초**다(130배). 상한을 넘으면 정확한 수를 모르는 것이 사실이므로
366
- * 목록 규약의 「서버 상한에 부딪힘(total 모름)」 갈래로 낸다 — 화면은 「처음 N 건만」이라고 말하고
367
- * 좁히기를 권한다. **알리지 않고 자르지 않는다**: 상한을 넘었다는 사실을 `capped` 로 함께 낸다.
368
- *
369
- * 왜 하위 질의로 세지 않나: 5개 드라이버에서 같이 도는 형태여야 한다(이 저장소의 절대 규칙).
370
- * 그래서 **키 컬럼만 상한까지 읽어 세는** 방식을 쓴다 — 색인만 읽으므로 행 본문을 만들지 않는다.
371
- */
372
- const items = await qb.getMany()
373
- const probe = qb
374
- .clone()
375
- .orderBy() /* 세는 데 순서는 필요 없다 — 정렬을 남기면 임의 축에서 전체 정렬이 된다. */
376
- .skip(0)
377
- .take(COUNT_CAP + 1)
378
- .select(`${qb.alias}.id`)
379
- const counted = await probe.getRawMany()
380
- const capped = counted.length > COUNT_CAP
381
- /* 상한을 넘었으면 **전체 수를 모른다** — 상한값을 전체로 적으면 화면이 거짓을 말한다. */
382
- const total = capped ? undefined : counted.length
383
-
384
- /* 다음 커서는 **이번 페이지의 마지막 행**에서 만든다. 페이지가 상한보다 짧으면 뒤가 없다. */
385
- const axes = cursorSortings(effective.sortings, tiebreak)
386
- const nextCursor = items.length === limit ? buildNextCursor(items[items.length - 1], axes) : undefined
387
-
388
- /*
389
- * ── **앞부분이 지워졌는지 함께 낸다** (2026-08-27) ─────────────────────────
390
- *
391
- * 보관 기간 정리가 오래된 사건을 지운다(기본 7일). 그러면 이 목록을 아무리 내려가도 어느 지점에서
392
- * 기록이 끝나는데, 화면이 그것을 말하지 않으면 사람은 「이 트윈은 그때 시작했다」로 읽는다.
393
- * 없었던 것과 지워진 것은 다른 사실이다.
394
- *
395
- * 저장하지 않고 그때그때 구한다 — 저장한 값은 실제 데이터와 어긋날 수 있고, 어긋나면 어느 쪽이
396
- * 사실인지 알 수 없다. 색인 `(domain, instanceId, revision)` 의 첫 행 하나라서 값이 싸다.
397
- *
398
- * 트윈 하나를 물었을 때만 낸다. 여러 트윈을 합쳐 보는 목록에서는 리비전이 트윈마다 따로 세는
399
- * 번호라 하나의 답이 없다.
400
- */
401
- let oldestRevision: number | undefined
402
- if (instanceIds.length === 1) {
403
- const first = await getRepository(TwinEvent)
404
- .createQueryBuilder('e')
405
- .select('e.revision', 'revision')
406
- .where('e.domain = :domainId', { domainId: domain.id })
407
- .andWhere('e.instanceId = :instanceId', { instanceId: instanceIds[0] })
408
- .orderBy('e.revision', 'ASC')
409
- .limit(1)
410
- .getRawOne<{ revision: unknown }>()
411
- .catch(() => null)
412
- const at = Number(first?.revision)
413
- if (Number.isFinite(at) && at > 0) oldestRevision = at
414
- }
415
-
416
- return { items, total, capped, countCap: COUNT_CAP, nextCursor, oldestRevision }
417
- }
418
-
419
- /**
420
- * 업무 KPI — 시간창 처리량·소요시간·자원 점유. 저널을 **계산해서** 만든다(새 계측을 주입하지 않는다).
421
- *
422
- * `twinMetrics`(초당 이벤트 수 = 인프라 부하)와 **다른 것**이다: 이건 "지난 한 시간에 몇 건 처리했고
423
- * 한 건에 얼마나 걸렸나" 다. 이름이 비슷해 헷갈리기 쉬우므로 설명에 명시한다.
424
- *
425
- * 완료된 작업의 **시작 이벤트가 창 앞에 있을 수 있어** 조회 구간을 창보다 앞으로 넓힌다(lookback).
426
- * 그래도 못 찾은 것은 결과의 `unpaired` 로 드러난다 — 평균에서 오류 없이 빠지면 지표가 거짓이 된다.
427
- */
428
- @Query(returns => ScalarObject, {
429
- description:
430
- 'Windowed business KPI folded from the event journal, for one space (every running twin in it, folded together — averaging per-twin percentiles would be wrong) or one twin: throughput (completed tasks / orders), lead / work / wait time distributions (p50·p90), and per-resource busy ratio. Distinct from twinMetrics, which reports infrastructure throughput (events per second). When toTime is omitted the window ends at the twin\'s own last recorded event time, not wall clock — a simulated twin lives on its own clock, so asking for \'the last hour\' in real time would always find nothing. The basis used is reported in window.basis. Pass groupBy (resource | taskKind | location | order | area | shift) to break the same window down by that perspective. The shift axis is **computed, not stored**: the journal records only times, and the shift is derived from each completion\'s time plus the declared site shifts and time zone — so correcting a shift definition re-counts history correctly instead of leaving it frozen under the old boundaries. Sites that declare no shifts report every group as unknown rather than inventing shift names — per-group numbers use the same rules as the totals, so group counts sum to the total, and groups beyond groupLimit are reported as groups.truncated (how many groups) and groups.truncatedTasks (how much work those groups held) rather than silently dropped. Pass compareTo (previous | yesterday) to fold the same-length window shifted back and get comparison.delta (current minus that window); when the comparison window has no records, comparison.measured is false and no delta is produced rather than reporting zero change. Passing groupBy together with compareTo crosses the two: each group carries prevTasks / deltaTasks / deltaWorkP50Ms, groups absent from the comparison window are flagged isNew (no delta, so growth is not implied), and groups that had work before but none now are returned in groups.disappeared so a stopped area is never silently missing from the table. Area breakdown needs model locations to carry a parent area; when they do not, groups.note says so instead of implying there was no work. When the journal read hits its row cap, eventsCapped is true and every number is a lower bound — narrow the window and ask again rather than reading the smaller figures as a drop in performance. When operational targets are set for the space or instance, `targets` carries the verdicts [{metric, actual, target, direction, met, ratio}] — ratio is normalised so that 1 or more is always good, letting throughput and duration metrics be compared on one scale. A metric with no target, or with no measurement in the window, is absent from `targets` rather than reported as a miss, because a period with no work would otherwise read as a perfect score on duration metrics. Completions are counted in the window they complete; starts are looked up before the window, and any completion whose start was not found is reported in `unpaired` rather than silently dropped. Pass baselinePeriods (1-14, with baselineStride day | week) to fold the same time-of-day window on each of the last N days (or the same weekday on each of the last N weeks) and get `baseline.metrics` — per metric the distribution of those windows (min / median / max) plus where the current window sits: `rank` (1 = smallest, direction-free) and `standing` (best | better-than-typical | typical | worse-than-typical | worst, which does apply the metric direction). This answers what a single comparison cannot: comparing to yesterday alone is useless if yesterday was also bad. Every requested window is returned in `samples` with `measured` true or false — windows without records are kept and counted (measuredCount vs requested) rather than dropped, so "the last 7 days" never silently means 3. No position is reported when the current window has no value or fewer than two baseline windows were measured; `noPosition` says which, because a missing distribution drawn as neutral would read as "typical". The baseline is produced even when the current window itself has no records, since that is exactly when knowing the usual level matters (a stopped line).'
431
- })
432
- async twinKpi(
433
- @Ctx() context: ResolverContext,
434
- /* 공간이 사용자의 단위다 — 한 트윈만 보려면 instanceId 를 준다(둘 중 하나 필수). */
435
- @Arg('spaceId', { nullable: true }) spaceId?: string,
436
- @Arg('instanceId', { nullable: true }) instanceId?: string,
437
- /** 창 시작·끝(ISO). 미지정이면 최근 1시간. */
438
- @Arg('fromTime', { nullable: true }) fromTime?: string,
439
- @Arg('toTime', { nullable: true }) toTime?: string,
440
- /** 창 길이(분). fromTime 이 있으면 무시. 기본 60. */
441
- @Arg('windowMinutes', type => Int, { nullable: true }) windowMinutes?: number,
442
- /** 추세를 위해 창을 N 등분(1~48). 미지정이면 쪼개지 않는다. */
443
- @Arg('buckets', type => Int, { nullable: true }) buckets?: number,
444
- /** 시작 이벤트를 찾기 위해 창보다 앞으로 더 읽는 시간(분). 기본 60. */
445
- @Arg('lookbackMinutes', type => Int, { nullable: true }) lookbackMinutes?: number,
446
- /** 관점 축 — 같은 창을 자원별·작업종류별·도착지점별·오더별·구역별로 쪼갠다. */
447
- @Arg('groupBy', { nullable: true }) groupBy?: string,
448
- /** 축의 상한(기본 20, 최대 100). 넘치면 잘라내고 잘린 수를 알린다. */
449
- @Arg('groupLimit', type => Int, { nullable: true }) groupLimit?: number,
450
- /** 비교 기준 — 같은 길이의 앞 구간(previous)이나 하루 전 같은 시간(yesterday). */
451
- @Arg('compareTo', { nullable: true }) compareTo?: string,
452
- /** 여러 구간 동시 비교 — 최근 N 구간의 같은 시간대 분포(1~14). */
453
- @Arg('baselinePeriods', type => Int, { nullable: true }) baselinePeriods?: number,
454
- /** 분포의 간격 — day(같은 시간대) | week(같은 요일·시간대). 기본 day. */
455
- @Arg('baselineStride', { nullable: true }) baselineStride?: string
456
- ): Promise<any> {
457
- /* 테넌트 격리 — 트윈 지정이면 소속을 확인한다. 공간 지정이면 대상 해소가 도메인 목록에서
458
- * 이뤄지므로(computeTwinKpi) 남의 트윈이 섞이지 않는다. 들어온 것이 없는 빈 결과가 아니라 명시 실패. */
459
- if (instanceId && !(await TwinEngine.ownsRegistered(context.state.domain.id, instanceId))) {
460
- throw new Error(`twin instance not found in this tenant: ${instanceId}`)
461
- }
462
- if (!instanceId && !spaceId) throw new Error('either spaceId or instanceId is required')
463
- /* 창 해소·조회·폴드는 `computeTwinKpi` 가 소유한다 — 같은 규칙을 AI 도구와 공유해야 한다
464
- * (두 곳에 적으면 화면과 AI 가 다른 숫자를 말한다). */
465
- return computeTwinKpi({
466
- domainId: context.state.domain.id,
467
- instanceId,
468
- spaceId,
469
- fromTime,
470
- toTime,
471
- windowMinutes,
472
- buckets,
473
- lookbackMinutes,
474
- groupBy: groupBy as any,
475
- groupLimit,
476
- compareTo: compareTo as any,
477
- ...(baselinePeriods && baselinePeriods > 0
478
- ? { baseline: { periods: baselinePeriods, stride: (baselineStride as any) === 'week' ? 'week' : 'day' } }
479
- : {})
480
- })
481
- }
482
-
483
- @Query(returns => ScalarObject, {
484
- nullable: true,
485
- description: 'Reconstruct twin state from the durable journal (DB → replay). untilTime (ISO) for time-travel on the shared clock (preferred); untilRevision for the lens-internal counter; omit both for latest.'
486
- })
487
- async twinReplay(
488
- @Arg('instanceId') instanceId: string,
489
- @Arg('untilRevision', type => Int, { nullable: true }) untilRevision: number,
490
- @Arg('untilTime', { nullable: true }) untilTime: string,
491
- @Ctx() context: ResolverContext,
492
- /*
493
- * 필요한 축만 — 지도는 `locations`·`attentions` 만 쓰는데 상태를 통째로 받고 있었다(실 저널로
494
- * 재 보니 트윈 하나가 최대 710KB, 같은 공간에 셋이면 약 2MB). 화면 전용 질의를 만들지 않고
495
- * 호출부가 축을 말한다(무방언 — 어휘가 화면 이름이 아니라 상태의 축이다).
496
- * 말하지 않으면 자르지 않는다(기존 호출부 무영향).
497
- */
498
- @Arg('axes', type => [String], { nullable: true }) axes?: string[],
499
- /*
500
- * 신호는 몇 건만 필요한가 — 레일은 20건을 그리고 나머지는 "N건 더" 라고만 말한다. 그런데 신호가
501
- * 많은 트윈은 그 전량이 108KB 다(`hatio-yard` 397건). 상위 N 만 보내고 **전체 개수와 자리별
502
- * 최고 심각도는 전량을 근거로** 함께 보낸다 — 레일은 잘라도 되지만 지도 색은 잘라선 안 된다.
503
- * 말하지 않으면 자르지 않는다.
504
- */
505
- @Arg('attentionLimit', type => Int, { nullable: true }) attentionLimit?: number
506
- ): Promise<any> {
507
- const state = await TwinEngine.recover(context.state.domain.id, instanceId, untilRevision ?? undefined, untilTime ?? undefined)
508
- const picked = pickAxes(state, axes)
509
- /* 오타를 알리지 않고 넘기지 않는다 — 화면이 빈 채로 뜨는 이유를 아무도 모르게 되는 것이 가장 나쁘다. */
510
- if (picked.unknown.length) twinWarn(`[twin] twinReplay: unknown axes ignored for "${instanceId}": ${picked.unknown.join(', ')}`)
511
- if (!attentionLimit || !picked.state?.attentions) return picked.state
512
- const d = digestAttentions(picked.state.attentions, attentionLimit)
513
- /* 잘랐다는 사실을 값으로 남긴다 — 개수만 줄어들면 화면은 "괜찮아졌다" 로 읽는다. */
514
- return { ...picked.state, attentions: d.attentions, attentionTotal: d.attentionTotal, severityByLocation: d.severityByLocation }
515
- }
516
-
517
- @Query(returns => ScalarObject, {
518
- nullable: true,
519
- description:
520
- 'Steady-state capacity of a twin against a declared demand: per-operation supply vs required across equipment, personnel, physical assets and locations, plus the binding constraint and the daily ceiling. Answers "can this plant meet the target?" without running a simulation. Null when the twin is not running.'
521
- })
522
- async twinCapacity(
523
- @Arg('instanceId', { nullable: true }) instanceId: string,
524
- @Arg('spaceId', { nullable: true }) spaceId: string,
525
- /** 하루 몇 대를 낼 것인가 — 화면이 보고 있는 목표에서 나온다(선언된 수요). */
526
- @Arg('unitsPerDay', type => Float) unitsPerDay: number,
527
- /**
528
- * 가용 시간을 잴 기준 주의 시작(ISO, 월요일). 공휴일이 없는 평상주여야 한다 —
529
- * 공휴일은 연간 가용량을 따로 깎지, 이 공장의 평상시 상한을 정하지 않는다.
530
- */
531
- @Arg('sampleWeekStart', { nullable: true }) sampleWeekStart: string,
532
- @Ctx() context: ResolverContext
533
- ): Promise<any> {
534
- if (instanceId && !(await TwinEngine.ownsRegistered(context.state.domain.id, instanceId))) {
535
- throw new Error(`twin instance not found in this tenant: ${instanceId}`)
536
- }
537
- if (!instanceId && !spaceId) throw new Error('either spaceId or instanceId is required')
538
- if (!(unitsPerDay > 0)) throw new Error('unitsPerDay must be greater than zero')
539
-
540
- /*
541
- * 기준 주를 안 주면 **이번 주 월요일**을 쓴다. 그 주에 공휴일이 끼면 상한이 조금 낮게 나오는데,
542
- * 그것은 들어온 것이 없는 오류가 아니라 **이번 주의 실제 가용 시간**이라 답으로서 정직하다. 평상시 상한을
543
- * 보려는 호출자는 평상주를 지정한다.
544
- */
545
- const base = sampleWeekStart ? Date.parse(sampleWeekStart) : Date.now()
546
- if (Number.isNaN(base)) throw new Error(`sampleWeekStart is not a valid ISO time: ${sampleWeekStart}`)
547
- const d = new Date(base)
548
- const monday = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - ((d.getUTCDay() + 6) % 7))
549
-
550
- return TwinEngine.capacity(context.state.domain.id, { instanceId, spaceId }, unitsPerDay, monday)
551
- }
552
-
553
- @Query(returns => ScalarObject, {
554
- nullable: true,
555
- description:
556
- 'Operations capability of a twin for a time window (ISA-95 Part 4 OperationsCapability): per operation the ceiling for that window split into Committed (already promised), Unattainable (lost to resources that cannot be used — down, held, off-shift, expired qualification) and Available (what is left). Answers "can we take more work?" — which the steady-state rate alone cannot. Null when the twin is not running.'
557
- })
558
- async twinOperationsCapability(
559
- @Arg('instanceId', { nullable: true }) instanceId: string,
560
- @Arg('spaceId', { nullable: true }) spaceId: string,
561
- @Arg('unitsPerDay', type => Float) unitsPerDay: number,
562
- /** 능력을 물을 구간 — **구간 없이 능력은 존재하지 않는다**(표준 StartTime/EndTime). */
563
- @Arg('fromTime') fromTime: string,
564
- @Arg('toTime') toTime: string,
565
- @Arg('sampleWeekStart', { nullable: true }) sampleWeekStart: string,
566
- @Ctx() context: ResolverContext
567
- ): Promise<any> {
568
- if (instanceId && !(await TwinEngine.ownsRegistered(context.state.domain.id, instanceId))) {
569
- throw new Error(`twin instance not found in this tenant: ${instanceId}`)
570
- }
571
- if (!instanceId && !spaceId) throw new Error('either spaceId or instanceId is required')
572
- if (!(unitsPerDay > 0)) throw new Error('unitsPerDay must be greater than zero')
573
- /* 구간은 **부르는 쪽이 말해야 한다** — 기본값을 두면 화면마다 다른 구간을 보면서 같은 값으로 읽는다. */
574
- const from = Date.parse(fromTime)
575
- const to = Date.parse(toTime)
576
- if (Number.isNaN(from) || Number.isNaN(to)) throw new Error(`window is not a valid ISO time: ${fromTime} ~ ${toTime}`)
577
- if (to <= from) throw new Error(`window is inverted: ${fromTime} ~ ${toTime}`)
578
-
579
- /* 기준 주 규율은 `twinCapacity` 와 같다(같은 계산을 쓰므로 같은 주를 골라야 한다). */
580
- const base = sampleWeekStart ? Date.parse(sampleWeekStart) : from
581
- if (Number.isNaN(base)) throw new Error(`sampleWeekStart is not a valid ISO time: ${sampleWeekStart}`)
582
- const d = new Date(base)
583
- const monday = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - ((d.getUTCDay() + 6) % 7))
584
-
585
- return TwinEngine.operationsCapability(
586
- context.state.domain.id,
587
- { instanceId, spaceId },
588
- unitsPerDay,
589
- monday,
590
- { fromTime, toTime }
591
- )
592
- }
593
-
594
- @Query(returns => ScalarObject, {
595
- description:
596
- "Structural revisions a twin has been through, newest last: when each took effect, how many journal events happened under it, and what changed from the previous one (counts by location type and equipment kind). The model itself is not returned — a revision holds the whole plant, but what a person wants is the difference."
597
- })
598
- async twinStructures(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any> {
599
- /* 이력 질문이므로 **등록부**에 소유를 묻는다 — 꺼진 트윈의 이력도 읽을 수 있어야 한다. */
600
- if (!(await TwinEngine.ownsRegistered(context.state.domain.id, instanceId))) {
601
- throw new Error(`twin instance not found in this tenant: ${instanceId}`)
602
- }
603
- return TwinEngine.structureHistory(context.state.domain.id, instanceId)
604
- }
605
-
606
- @Query(returns => ScalarObject, {
607
- description: 'Event time range (min/max ISO) across all co-located twins of a space — the history scrubber time axis.'
608
- })
609
- async twinTimeRange(
610
- @Arg('spaceId') spaceId: string,
611
- @Ctx() context: ResolverContext
612
- ): Promise<{ minTime: string | null; maxTime: string | null }> {
613
- return TwinEngine.timeRange(context.state.domain.id, spaceId)
614
- }
615
- }