@things-factory/headless-twin 10.0.15 → 10.0.17

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 (478) 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 +11 -2
  5. package/dist-server/engine/canonical-ingest.js +108 -9
  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 +1 -1
  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 +122 -12
  25. package/dist-server/engine/kpi-fold.js +247 -18
  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 +185 -28
  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 +9 -9
  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 +1 -1
  52. package/dist-server/engine/model-vocabulary.js.map +1 -1
  53. package/dist-server/engine/oee-accumulator.d.ts +4 -4
  54. package/dist-server/engine/oee-accumulator.js +3 -3
  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 +237 -54
  78. package/dist-server/engine/twin-engine.js +672 -192
  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 +27 -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 +14 -14
  133. package/dist-server/service/reference/reference-master.js +34 -12
  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 +10 -10
  137. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  138. package/dist-server/service/reference/twin-reference.d.ts +3 -3
  139. package/dist-server/service/reference/twin-reference.js.map +1 -1
  140. package/dist-server/service/twin-attention/twin-attention-query.d.ts +1 -1
  141. package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
  142. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  143. package/dist-server/service/twin-audit/command-audit.d.ts +1 -1
  144. package/dist-server/service/twin-audit/command-audit.js.map +1 -1
  145. package/dist-server/service/twin-audit/twin-audit-event.d.ts +1 -1
  146. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
  147. package/dist-server/service/twin-audit/twin-audit-query.js +1 -1
  148. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -1
  149. package/dist-server/service/twin-control/twin-control-mutation.js +1 -1
  150. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  151. package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
  152. package/dist-server/service/twin-event/twin-event-keys.js +2 -2
  153. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  154. package/dist-server/service/twin-event/twin-event-type.d.ts +1 -0
  155. package/dist-server/service/twin-event/twin-event-type.js +8 -1
  156. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  157. package/dist-server/service/twin-event/twin-event.d.ts +2 -2
  158. package/dist-server/service/twin-event/twin-event.js +32 -4
  159. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  160. package/dist-server/service/twin-forecast/forecast-metrics.js +1 -1
  161. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -1
  162. package/dist-server/service/twin-forecast/gap-analytics.d.ts +1 -1
  163. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  164. package/dist-server/service/twin-forecast/twin-forecast-query.js +29 -11
  165. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  166. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +1 -1
  167. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +1 -1
  168. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -1
  169. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +1 -1
  170. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -1
  171. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  172. package/dist-server/service/twin-journal/event-type-split-shape.d.ts +50 -0
  173. package/dist-server/service/twin-journal/event-type-split-shape.js +111 -0
  174. package/dist-server/service/twin-journal/event-type-split-shape.js.map +1 -0
  175. package/dist-server/service/twin-journal/event-type-split.d.ts +15 -0
  176. package/dist-server/service/twin-journal/event-type-split.js +102 -0
  177. package/dist-server/service/twin-journal/event-type-split.js.map +1 -0
  178. package/dist-server/service/twin-journal/journal-order.d.ts +15 -0
  179. package/dist-server/service/twin-journal/journal-order.js +17 -0
  180. package/dist-server/service/twin-journal/journal-order.js.map +1 -0
  181. package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -5
  182. package/dist-server/service/twin-journal/twin-journal-query.js +232 -21
  183. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  184. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +27 -1
  185. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +59 -3
  186. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  187. package/dist-server/service/twin-metrics/twin-metrics-query.js +14 -1
  188. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  189. package/dist-server/service/twin-model/axis-journal-evidence.js +1 -1
  190. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -1
  191. package/dist-server/service/twin-model/epcis-coverage.js +34 -3
  192. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  193. package/dist-server/service/twin-model/iec61850-coverage.js +1 -1
  194. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  195. package/dist-server/service/twin-model/isa95-coverage.d.ts +12 -9
  196. package/dist-server/service/twin-model/isa95-coverage.js +33 -9
  197. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  198. package/dist-server/service/twin-model/item-ref.js +2 -2
  199. package/dist-server/service/twin-model/item-ref.js.map +1 -1
  200. package/dist-server/service/twin-model/name-index.d.ts +1 -1
  201. package/dist-server/service/twin-model/name-index.js +1 -1
  202. package/dist-server/service/twin-model/name-index.js.map +1 -1
  203. package/dist-server/service/twin-model/project-structure.d.ts +1 -1
  204. package/dist-server/service/twin-model/project-structure.js +3 -3
  205. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  206. package/dist-server/service/twin-model/twin-equipment.d.ts +1 -1
  207. package/dist-server/service/twin-model/twin-equipment.js +1 -1
  208. package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
  209. package/dist-server/service/twin-model/twin-lineage-query.js +16 -5
  210. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  211. package/dist-server/service/twin-model/twin-location.d.ts +1 -1
  212. package/dist-server/service/twin-model/twin-location.js.map +1 -1
  213. package/dist-server/service/twin-model/twin-model-item-query.d.ts +3 -3
  214. package/dist-server/service/twin-model/twin-model-item-query.js +14 -14
  215. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  216. package/dist-server/service/twin-model/twin-model-mutation.d.ts +2 -2
  217. package/dist-server/service/twin-model/twin-model-mutation.js +4 -4
  218. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -1
  219. package/dist-server/service/twin-model/twin-model-query.js +47 -26
  220. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  221. package/dist-server/service/twin-model/twin-model-tree-query.js +22 -0
  222. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  223. package/dist-server/service/twin-model/twin-operation.d.ts +1 -1
  224. package/dist-server/service/twin-model/twin-operation.js +1 -1
  225. package/dist-server/service/twin-model/twin-operation.js.map +1 -1
  226. package/dist-server/service/twin-space/move-space.js +2 -2
  227. package/dist-server/service/twin-space/move-space.js.map +1 -1
  228. package/dist-server/service/twin-space/representation-role.d.ts +2 -0
  229. package/dist-server/service/twin-space/representation-role.js +18 -0
  230. package/dist-server/service/twin-space/representation-role.js.map +1 -0
  231. package/dist-server/service/twin-space/space-integrity.d.ts +1 -1
  232. package/dist-server/service/twin-space/space-integrity.js.map +1 -1
  233. package/dist-server/service/twin-space/twin-space-representation.d.ts +2 -0
  234. package/dist-server/service/twin-space/twin-space-representation.js +16 -0
  235. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  236. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  237. package/dist-server/service/twin-space/twin-space-resolver.js +36 -7
  238. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  239. package/dist-server/service/twin-structure/twin-structure.js +3 -3
  240. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  241. package/dist-server/service/twin-subject/event-subjects.d.ts +43 -0
  242. package/dist-server/service/twin-subject/event-subjects.js +137 -0
  243. package/dist-server/service/twin-subject/event-subjects.js.map +1 -0
  244. package/dist-server/service/twin-subject/index.d.ts +9 -0
  245. package/dist-server/service/twin-subject/index.js +16 -0
  246. package/dist-server/service/twin-subject/index.js.map +1 -0
  247. package/dist-server/service/twin-subject/item-resolver.d.ts +10 -0
  248. package/dist-server/service/twin-subject/item-resolver.js +93 -0
  249. package/dist-server/service/twin-subject/item-resolver.js.map +1 -0
  250. package/dist-server/service/twin-subject/subject-query-shape.d.ts +22 -0
  251. package/dist-server/service/twin-subject/subject-query-shape.js +88 -0
  252. package/dist-server/service/twin-subject/subject-query-shape.js.map +1 -0
  253. package/dist-server/service/twin-subject/twin-subject-backfill.d.ts +23 -0
  254. package/dist-server/service/twin-subject/twin-subject-backfill.js +159 -0
  255. package/dist-server/service/twin-subject/twin-subject-backfill.js.map +1 -0
  256. package/dist-server/service/twin-subject/twin-subject-event.d.ts +14 -0
  257. package/dist-server/service/twin-subject/twin-subject-event.js +111 -0
  258. package/dist-server/service/twin-subject/twin-subject-event.js.map +1 -0
  259. package/dist-server/service/twin-subject/twin-subject-fast-path.d.ts +39 -0
  260. package/dist-server/service/twin-subject/twin-subject-fast-path.js +295 -0
  261. package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +1 -0
  262. package/dist-server/service/twin-subject/twin-subject-query.d.ts +27 -0
  263. package/dist-server/service/twin-subject/twin-subject-query.js +232 -0
  264. package/dist-server/service/twin-subject/twin-subject-query.js.map +1 -0
  265. package/dist-server/service/twin-subject/twin-subject-writer.d.ts +27 -0
  266. package/dist-server/service/twin-subject/twin-subject-writer.js +166 -0
  267. package/dist-server/service/twin-subject/twin-subject-writer.js.map +1 -0
  268. package/dist-server/service/twin-target/twin-target-resolver.js +1 -1
  269. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
  270. package/dist-shared/entity-delta.d.ts +1 -1
  271. package/dist-shared/entity-delta.js +3 -3
  272. package/dist-shared/entity-delta.js.map +1 -1
  273. package/dist-shared/touched-items.js +2 -2
  274. package/dist-shared/touched-items.js.map +1 -1
  275. package/dist-shared/twin-level.js +1 -1
  276. package/dist-shared/twin-level.js.map +1 -1
  277. package/package.json +3 -3
  278. package/server/engine/attention-digest.ts +9 -9
  279. package/server/engine/canonical-ingest.ts +141 -14
  280. package/server/engine/command-routing.ts +1 -1
  281. package/server/engine/declared-stimulus.ts +1 -1
  282. package/server/engine/energy-topology.ts +1 -1
  283. package/server/engine/ingest-dedupe.ts +149 -0
  284. package/server/engine/ingest-health.ts +171 -22
  285. package/server/engine/integration-probes.ts +7 -7
  286. package/server/engine/kpi-baseline.ts +4 -4
  287. package/server/engine/kpi-fold.ts +395 -27
  288. package/server/engine/kpi-query.ts +212 -46
  289. package/server/engine/live-feed-registry.ts +1 -1
  290. package/server/engine/load-meter.ts +1 -1
  291. package/server/engine/local-declarations.ts +13 -13
  292. package/server/engine/log.ts +1 -1
  293. package/server/engine/measured-estimator.ts +1 -1
  294. package/server/engine/measured-yield.ts +1 -1
  295. package/server/engine/model-basis.ts +2 -2
  296. package/server/engine/model-gap.ts +3 -3
  297. package/server/engine/model-vocabulary.ts +1 -1
  298. package/server/engine/oee-accumulator.ts +4 -4
  299. package/server/engine/property-effects.ts +2 -2
  300. package/server/engine/read-time.ts +77 -0
  301. package/server/engine/restart-policy.ts +1 -1
  302. package/server/engine/runtime-key.ts +1 -1
  303. package/server/engine/spec-coverage.ts +3 -3
  304. package/server/engine/stage-path.ts +5 -5
  305. package/server/engine/state-axes.ts +2 -2
  306. package/server/engine/travel-estimator.ts +2 -2
  307. package/server/engine/twin-engine.ts +745 -202
  308. package/server/engine/warm-start.ts +6 -6
  309. package/server/index.ts +1 -1
  310. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +1 -1
  311. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +3 -3
  312. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +2 -2
  313. package/server/migrations/1786300000000-IndexEventTypeByTime.ts +48 -0
  314. package/server/migrations/1786400000000-IndexEventCreatedAt.ts +38 -0
  315. package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +93 -0
  316. package/server/migrations/index.ts +7 -1
  317. package/server/routes.ts +68 -5
  318. package/server/service/index.ts +4 -1
  319. package/server/service/reference/control-routing.ts +1 -1
  320. package/server/service/reference/discovery-result.ts +3 -3
  321. package/server/service/reference/hook-contract.ts +103 -0
  322. package/server/service/reference/ingest-space.ts +4 -4
  323. package/server/service/reference/knob-defaults.ts +4 -4
  324. package/server/service/reference/reference-adapter.ts +145 -10
  325. package/server/service/reference/reference-hook.ts +145 -0
  326. package/server/service/reference/reference-live.ts +80 -14
  327. package/server/service/reference/reference-master.ts +39 -17
  328. package/server/service/reference/reference-resolver.ts +10 -10
  329. package/server/service/reference/twin-reference.ts +3 -3
  330. package/server/service/twin-attention/twin-attention-query.ts +1 -1
  331. package/server/service/twin-audit/command-audit.ts +1 -1
  332. package/server/service/twin-audit/twin-audit-event.ts +2 -2
  333. package/server/service/twin-audit/twin-audit-query.ts +1 -1
  334. package/server/service/twin-control/twin-control-mutation.ts +1 -1
  335. package/server/service/twin-event/twin-event-keys.ts +2 -2
  336. package/server/service/twin-event/twin-event-type.ts +18 -1
  337. package/server/service/twin-event/twin-event.ts +32 -6
  338. package/server/service/twin-forecast/forecast-metrics.ts +1 -1
  339. package/server/service/twin-forecast/gap-analytics.ts +1 -1
  340. package/server/service/twin-forecast/twin-forecast-query.ts +30 -11
  341. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +1 -1
  342. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +1 -1
  343. package/server/service/twin-instance/twin-instance.ts +4 -4
  344. package/server/service/twin-journal/event-type-split-shape.ts +112 -0
  345. package/server/service/twin-journal/event-type-split.ts +144 -0
  346. package/server/service/twin-journal/journal-order.ts +20 -0
  347. package/server/service/twin-journal/twin-journal-query.ts +224 -20
  348. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +57 -3
  349. package/server/service/twin-metrics/twin-metrics-query.ts +14 -1
  350. package/server/service/twin-model/axis-journal-evidence.ts +1 -1
  351. package/server/service/twin-model/epcis-coverage.ts +34 -3
  352. package/server/service/twin-model/iec61850-coverage.ts +1 -1
  353. package/server/service/twin-model/isa95-coverage.ts +43 -16
  354. package/server/service/twin-model/item-ref.ts +2 -2
  355. package/server/service/twin-model/name-index.ts +1 -1
  356. package/server/service/twin-model/project-structure.ts +4 -4
  357. package/server/service/twin-model/twin-equipment.ts +2 -2
  358. package/server/service/twin-model/twin-lineage-query.ts +16 -5
  359. package/server/service/twin-model/twin-location.ts +1 -1
  360. package/server/service/twin-model/twin-model-item-query.ts +14 -14
  361. package/server/service/twin-model/twin-model-mutation.ts +4 -4
  362. package/server/service/twin-model/twin-model-query.ts +47 -26
  363. package/server/service/twin-model/twin-model-tree-query.ts +25 -1
  364. package/server/service/twin-model/twin-operation.ts +2 -2
  365. package/server/service/twin-space/move-space.ts +3 -3
  366. package/server/service/twin-space/representation-role.ts +15 -0
  367. package/server/service/twin-space/space-integrity.ts +1 -1
  368. package/server/service/twin-space/twin-space-representation.ts +37 -0
  369. package/server/service/twin-space/twin-space-resolver.ts +39 -7
  370. package/server/service/twin-structure/twin-structure.ts +3 -3
  371. package/server/service/twin-subject/event-subjects.ts +155 -0
  372. package/server/service/twin-subject/index.ts +10 -0
  373. package/server/service/twin-subject/item-resolver.ts +92 -0
  374. package/server/service/twin-subject/subject-query-shape.ts +84 -0
  375. package/server/service/twin-subject/twin-subject-backfill.ts +196 -0
  376. package/server/service/twin-subject/twin-subject-event.ts +101 -0
  377. package/server/service/twin-subject/twin-subject-fast-path.ts +346 -0
  378. package/server/service/twin-subject/twin-subject-query.ts +210 -0
  379. package/server/service/twin-subject/twin-subject-writer.ts +189 -0
  380. package/server/service/twin-target/twin-target-resolver.ts +1 -1
  381. package/shared/entity-delta.ts +3 -3
  382. package/shared/touched-items.ts +2 -2
  383. package/shared/twin-level.ts +1 -1
  384. package/test/adopt-structure-live.test.ts +3 -3
  385. package/test/aggregate-not-findone.test.ts +112 -0
  386. package/test/attention-digest.test.ts +6 -6
  387. package/test/axis-read.test.ts +2 -2
  388. package/test/boot-resume.test.ts +20 -7
  389. package/test/broadcast-period.test.ts +2 -2
  390. package/test/broadcast-queue-overflow.test.ts +244 -0
  391. package/test/canonical-ingest-vocabularies.test.ts +179 -7
  392. package/test/canonical-quantity-door.test.ts +2 -2
  393. package/test/checkpoint-refuses-empty.test.ts +2 -2
  394. package/test/connector-capability-declaration.test.ts +88 -0
  395. package/test/control-capability.test.ts +1 -1
  396. package/test/cursor-stall-not-read-failure.test.ts +2 -2
  397. package/test/declaration-reaches-model.test.ts +5 -5
  398. package/test/declared-stimulus.test.ts +4 -4
  399. package/test/discovery-result.test.ts +2 -2
  400. package/test/entity-delta.test.ts +4 -4
  401. package/test/event-subjects.test.ts +147 -0
  402. package/test/event-time-column.test.ts +3 -3
  403. package/test/event-type-split.test.ts +155 -0
  404. package/test/fold-gap-speaks.test.ts +53 -0
  405. package/test/forecast-metrics.test.ts +1 -1
  406. package/test/forecast-tuning.test.ts +1 -1
  407. package/test/ingest-health-engine.test.ts +2 -2
  408. package/test/ingest-health-wiring.test.ts +7 -5
  409. package/test/ingest-health.test.ts +6 -6
  410. package/test/ingest-history.test.ts +2 -2
  411. package/test/ingest-idempotent.test.ts +132 -0
  412. package/test/ingest-space.test.ts +3 -3
  413. package/test/integration-probes.test.ts +3 -3
  414. package/test/integration-runner.test.ts +1 -1
  415. package/test/item-ref.test.ts +1 -1
  416. package/test/journal-order-axis.test.ts +80 -0
  417. package/test/journal-read-discipline.test.ts +16 -6
  418. package/test/journal-retention.test.ts +171 -29
  419. package/test/journal-sort-axis.test.ts +1 -1
  420. package/test/journal-write-door.test.ts +3 -3
  421. package/test/journal-write-trend.test.ts +1 -1
  422. package/test/kernel-kind-guard.test.ts +3 -3
  423. package/test/knob-defaults.test.ts +1 -1
  424. package/test/kpi-baseline-db.test.ts +2 -2
  425. package/test/kpi-fold.test.ts +185 -14
  426. package/test/kpi-query-bench.test.ts +3 -3
  427. package/test/live-cursor-wiring.test.ts +6 -6
  428. package/test/live-kernel-facts.test.ts +11 -11
  429. package/test/live-mirror-parity.test.ts +3 -3
  430. package/test/load-meter.test.ts +1 -1
  431. package/test/local-declarations.test.ts +10 -10
  432. package/test/log-stamp.test.ts +1 -1
  433. package/test/measured-yield.test.ts +2 -2
  434. package/test/merge-streams.test.ts +70 -0
  435. package/test/mirror-resumes-from-checkpoint.test.ts +13 -13
  436. package/test/model-basis.test.ts +2 -2
  437. package/test/model-gap.test.ts +4 -4
  438. package/test/model-vocabulary.test.ts +2 -2
  439. package/test/move-space.test.ts +1 -1
  440. package/test/oee-accumulator.test.ts +2 -2
  441. package/test/operational-vocabulary.test.ts +2 -2
  442. package/test/project-structure-db.test.ts +2 -2
  443. package/test/projection-reaches-screen.test.ts +2 -2
  444. package/test/property-effects.test.ts +3 -3
  445. package/test/read-failure-ledger.test.ts +106 -0
  446. package/test/read-failure-visible.test.ts +7 -7
  447. package/test/read-time.test.ts +118 -0
  448. package/test/reference-grounding.test.ts +1 -1
  449. package/test/reference-hook.test.ts +86 -0
  450. package/test/registry-key-guard.test.ts +1 -1
  451. package/test/rename-twin.test.ts +81 -0
  452. package/test/restart-policy.test.ts +3 -3
  453. package/test/resync-origin-site.test.ts +3 -3
  454. package/test/revision-axis.test.ts +14 -8
  455. package/test/runtime-key.test.ts +3 -3
  456. package/test/snapshot-freshness.test.ts +4 -4
  457. package/test/source-outcome-audit.test.ts +1 -1
  458. package/test/space-integrity.test.ts +1 -1
  459. package/test/space-representation.test.ts +66 -0
  460. package/test/space-scope-includes-stopped.test.ts +55 -0
  461. package/test/spec-coverage.test.ts +1 -1
  462. package/test/standard-coverage.test.ts +19 -21
  463. package/test/status-tally.test.ts +1 -1
  464. package/test/structure-revision-db.test.ts +7 -7
  465. package/test/subject-fast-path.test.ts +148 -0
  466. package/test/subject-read-model.test.ts +175 -0
  467. package/test/touched-items.test.ts +2 -2
  468. package/test/twin-audit.test.ts +1 -1
  469. package/test/twin-event-keys.test.ts +1 -1
  470. package/test/twin-model-item-db.test.ts +5 -5
  471. package/test/twin-model-tree-db.test.ts +35 -2
  472. package/test/twin-origin-resync.test.ts +1 -1
  473. package/test/warm-start-seam.test.ts +1 -1
  474. package/test/warm-start.test.ts +6 -6
  475. package/test/withheld-door.test.ts +2 -2
  476. package/test/yield-loop.test.ts +4 -4
  477. package/tsconfig.shared.tsbuildinfo +1 -1
  478. package/tsconfig.tsbuildinfo +1 -1
@@ -55,15 +55,18 @@ export interface Isa95Concept {
55
55
  *
56
56
  * 그래서 축일 수 없는 것은 갈 곳을 가리킨다. 여기가 비어 있고 축도 없으면 그것은 **진짜 빈칸**이다.
57
57
  *
58
- * ── ★ `surface` 와 짝을 맞춘다 (2026-08-24 실측으로 붙임) ─────────────────
59
- * 이 칸은 「그 사실이 어느 축에 **담겨 있나**」가 아니라 「**어디서 보이나**」다. 화면이 그것을
60
- * 「해당 축 항목에 표시됩니다」로 읽어 내므로, 둘이 어긋나면 표가 거짓을 말한다.
58
+ * ── ★ 뜻을 고쳐 적는다 (2026-08-24, 사용자 지적) ──────────────────────────
59
+ * 한때 이 칸을 「**어디서 보이나**」로 읽고, 화면이 그것을 「해당 축 항목에 표시됩니다」라는 문장으로
60
+ * 냈다. 두 가지가 잘못이었다.
61
61
  *
62
- * `surface: 'none'` 인데 여기가 차 있다 → **거짓**. 없는 화면으로 사람을 보낸다
63
- * `surface` 가 있는데 여기가 비어 있다 → **과소**. 「진짜 빈칸」으로 읽힌다(위 규칙)
62
+ * ① 그 문장은 **한국어가 아니었다**(사용자 지적). 그리고 칩이 이미 갈 곳을 보이므로 **군더더기**다.
63
+ * ② 뜻이 틀렸다. 이 칸은 「**어디에 속하나**」다 — 그 개념이 자기 목록을 갖지 않고 **어느 축의 안에
64
+ * 들어 있나**. 화면에 그려지는지는 `surface` 가 따로 답한다. 둘은 다른 물음이다.
64
65
  *
65
- * 같은 날 셋을 그렇게 틀리게 넣었다: 새 개념 둘(`TestResult`·`OperationsEvent`)에 화면이 없는데
66
- * 갈 곳을 적었고, `MaterialSublot` 은 물품 이름으로 보이는데 갈 곳을 비워 두었다.
66
+ * 그래서 **`surface` 와 짝을 맞추지 않는다.** 자기 축이 없으면 언제나 갈 곳을 적는다 — 아직 그려지지
67
+ * 않는 개념도 **어디에 속하는지는 사실**이고, 그것을 비우면 「어디에도 없는 것」으로 읽힌다.
68
+ *
69
+ * 여기가 비어 있고 축도 없으면 그것은 **진짜 빈칸**이다(위 규칙).
67
70
  */
68
71
  shownOn?: string[]
69
72
  }
@@ -124,7 +127,17 @@ const PART2: Isa95Concept[] = [
124
127
  * `TestSpecificationID`(로트별 기준 참조 — 품목 정의가 대신 답하므로 두지 않았다) · `Status` ·
125
128
  * `HierarchyScope` · `Version`. 채운 것만 세는 것이 이 표의 규율이다.
126
129
  */
127
- { std: 'MaterialLot', part: '2', label: 'twin.isa95.MaterialLot', axis: null, structure: 'partial', behavior: 'full', surface: 'partial', note: 'twin.isa95.note.lot' },
130
+ /*
131
+ * ── 2026-08-25 — 목록이 쌓이기만 하던 것을 고쳤다 ──────────────────────────
132
+ * 연결된 시스템은 매 주기 현재 재고를 전부 보내는데, 트윈은 그것을 낱낱의 관측으로 받아 **더하기만
133
+ * 했다.** 「그리고 이것 말고는 없다」를 받는 곳이 없어서 목록에서 빠진 줄이 영원히 남았다.
134
+ * 실측: 실제 1,704건인데 트윈이 10,038건.
135
+ *
136
+ * 그동안 이 줄은 「기능 있음」이었다. **현재 재고를 맞게 유지하지 못하는 상태로 그렇게 적을 수
137
+ * 없다.** 오늘 「이 목록이 전부다」를 받는 길을 냈고(커널 0.7.63), 수량으로 온 재고도 없어질 수
138
+ * 있게 했다. 이제 사실이다.
139
+ */
140
+ { std: 'MaterialLot', part: '2', label: 'twin.isa95.MaterialLot', axis: 'items', structure: 'partial', behavior: 'full', surface: 'partial', note: 'twin.isa95.note.lot' },
128
141
  /*
129
142
  * ── 2026-08-24 판정 정정: `none` 이 아니었다 ──────────────────────────────
130
143
  * 이 줄은 셋 다 `none` 이었는데 **표가 코드보다 뒤처져 있었다.** 커널은 서브로트를 든다:
@@ -134,7 +147,7 @@ const PART2: Isa95Concept[] = [
134
147
  * `itemKeyOf(item)` **개체의 정체성이 이 값이다** — `subLotId ?? epc`
135
148
  *
136
149
  * 기능이 `full` 인 근거: 이 축이 없으면 같은 로트를 rack-1 에 100개·rack-2 에 60개 관측했을 때
137
- * **뒤에 온 관측이 앞을 덮어 100개가 조용히 사라진다**(합계 160 → 60). 실제로 그 결함을 이 축으로
150
+ * **뒤에 온 관측이 앞을 덮어 100개가 오류 없이 사라진다**(합계 160 → 60). 실제로 그 결함을 이 축으로
138
151
  * 고쳤다. 즉 장식이 아니라 정체성을 지탱한다.
139
152
  *
140
153
  * 구조가 `partial` 인 이유: 서브로트는 로트와 같은 모양이고(위 참조) 그 로트가 `partial` 이다.
@@ -154,7 +167,7 @@ const PART2: Isa95Concept[] = [
154
167
  * 기능은 **부분**이다: 호스트의 주행 추정기가 `speed` 를 읽어 이동 시간을 만드는 것이 유일한 소비처다
155
168
  * (다른 속성은 아직 아무도 읽지 않는다). 그 사실을 `full` 로 올리면 표가 거짓이 된다.
156
169
  */
157
- { std: 'Property', part: '2', label: 'twin.isa95.Property', axis: null, structure: 'full', behavior: 'partial', surface: 'full', note: 'twin.isa95.note.property' },
170
+ { std: 'Property', part: '2', label: 'twin.isa95.Property', axis: null, shownOn: ['equipment', 'persons', 'assets'], structure: 'full', behavior: 'partial', surface: 'full', note: 'twin.isa95.note.property' },
158
171
  /*
159
172
  * **자리의 상시 관측** — 2026-08-24 신설(커널 0.7.56). 표에 이 개념이 아예 없었다.
160
173
  *
@@ -172,12 +185,26 @@ const PART2: Isa95Concept[] = [
172
185
  * 계층이고 냉장실은 `StorageZone` 수준의 설비다. `OperationalLocationType` 에는 이 축이 없다.
173
186
  *
174
187
  * 구조가 `partial`: 봉투 전체(`ID`·`Description`·사건 분류·`OperationsRecord` 구조)가 아니라 관측
175
- * 하나를 담는 데 필요한 칸만 든다.
188
+ * 하나를 담는 데 필요한 칸만 든다. 2026-08-25 에 지난 기록의 봉투가 사람이 적은 문장을 함께
189
+ * 나르게 됐지만(`CanonicalEnvelope.description`), 그것은 **기록에 남는 말**이고 이 타입의
190
+ * `Description` 칸이 아니다 — 상태에 담기지 않으므로 이 줄의 등급을 올리지 않는다.
176
191
  * 기능이 `full`: 사건 채널(`location.measured`)이 상태를 만들고, 선언된 기준으로 판정해
177
192
  * `observation-out-of-limit` 신호를 세운다.
178
193
  * 화면이 `none`: 클라이언트에 이 축을 그리는 곳이 없다(실측 0곳).
179
194
  */
180
- { std: 'OperationsEvent', part: '2', label: 'twin.isa95.OperationsEvent', axis: null, structure: 'partial', behavior: 'full', surface: 'none' }
195
+ /*
196
+ * ── 2026-08-25 — 이 줄이 실제보다 높게 적혀 있었다 ────────────────────────
197
+ * 「기능 있음」으로 적어 두었다. 커널이 관측을 반영하고 기준으로 판단하는 것은 사실이었으므로
198
+ * 그렇게 적었는데, **연결된 시스템이 그 값을 보낼 길이 없었다** — 유입 경로가 `locationId` 를
199
+ * 보지 않아 「어느 사실인지 모른다」로 거부했다.
200
+ *
201
+ * **아무도 먹일 수 없는 기능은 동작하는 기능이 아니다.** 오늘 그 경로를 냈고(커널 0.7.62),
202
+ * 이제 「기능 있음」이 사실이다. 그때까지는 「일부」였어야 했다.
203
+ *
204
+ * 같은 부류를 오늘 세 번 만났다 — 표가 **할 수 있는 일을 못 한다고** 적은 것이 여섯 줄이었고,
205
+ * 이것은 반대로 **못 하는 일을 할 수 있다고** 적은 것이다. 뒤쪽이 더 위험하다.
206
+ */
207
+ { std: 'OperationsEvent', part: '2', label: 'twin.isa95.OperationsEvent', axis: null, shownOn: ['locations'], structure: 'partial', behavior: 'full', surface: 'none' }
181
208
  ]
182
209
 
183
210
  /*
@@ -199,7 +226,7 @@ const PART4: Isa95Concept[] = [
199
226
  *
200
227
  * `surface` 도 `full` 이다(같은 날 두 번째 단계): 목록·상세·관계에 더해 **과거로 다시 계산하는 길**이 화면에
201
228
  * 있다 — 시각 커서를 옮기면 그 시점의 사실이 나오고, 무엇을 근거로 답했는지도 화면이 말한다
202
- * (라이브=권위 · 저널=파생 · 접은 리비전). 실측: 이른 시각 0건 · 하루 뒤 504건.
229
+ * (라이브=권위 · 저널=파생 · 계산한 리비전). 실측: 이른 시각 0건 · 하루 뒤 504건.
203
230
  */
204
231
  { std: 'OperationsSchedule', part: '4', label: 'twin.isa95.OperationsSchedule', axis: 'orders', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.schedule' },
205
232
  { std: 'OperationsPerformance', part: '4', label: 'twin.isa95.OperationsPerformance', axis: 'tasks', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.performance' },
@@ -242,7 +269,7 @@ const PART4: Isa95Concept[] = [
242
269
  *
243
270
  * 화면은 `none` 이다 — 클라이언트에 이 축을 그리는 곳이 없다(실측: `operato-twin/client` 에 0곳).
244
271
  */
245
- { std: 'TestResult', part: '4', label: 'twin.isa95.TestResult', axis: null, structure: 'partial', behavior: 'partial', surface: 'none' },
272
+ { std: 'TestResult', part: '4', label: 'twin.isa95.TestResult', axis: null, shownOn: ['items'], structure: 'partial', behavior: 'partial', surface: 'none' },
246
273
  { std: 'WorkMaster', part: '4', label: 'twin.isa95.WorkMaster', axis: 'recipes', structure: 'full', behavior: 'full', surface: 'full' },
247
274
  { std: 'WorkDirective', part: '4', label: 'twin.isa95.WorkDirective', axis: null, structure: 'none', behavior: 'none', surface: 'none' }
248
275
  ]
@@ -254,10 +281,10 @@ export const ISA95_CONCEPTS: Isa95Concept[] = [...PART2, ...PART4]
254
281
  * 적합성 보고 — **채운 것과 빠진 것을 함께.**
255
282
  *
256
283
  * `axis` 를 커널 축 목록과 맞춰 본다: 축이 있다고 적혀 있는데 커널에 없으면 그 선언이 낡은 것이므로
257
- * **구조 없음으로 낮추고 그 사실을 말한다**(조용히 통과시키면 이 표가 거짓이 된다).
284
+ * **구조 없음으로 낮추고 그 사실을 말한다**(알리지 않고 통과시키면 이 표가 거짓이 된다).
258
285
  */
259
286
  /**
260
- * 선언한 축을 커널의 축 목록과 맞춘다 — **어긋난 것을 조용히 지우지 않는다.**
287
+ * 선언한 축을 커널의 축 목록과 맞춘다 — **어긋난 것을 알리지 않고 지우지 않는다.**
261
288
  *
262
289
  * 두 자리가 어긋날 수 있다.
263
290
  * · `axis` — 그 개념 자신의 문. 커널에 없으면 구조를 「없음」으로 낮추고 그 사실을 말한다.
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * **개체에서 품목으로** — 정체성 모양이 하나가 아니다 (2026-08-23).
3
3
  *
4
- * ── 무엇이 조용히 비어 있었나 ───────────────────────────────────────────────
4
+ * ── 무엇이 알리지 않고 비어 있었나 ───────────────────────────────────────────────
5
5
  * 화면이 「이 개체는 선언된 자재가 아닙니다(라인 내부의 중간 산물일 수 있습니다)」라고 말했는데, 그
6
6
  * 품목은 원본의 선언에 이름까지 있었다(「냉동그린빈스」). 대응 표(`productionSpec.binding`)도 멀쩡했다.
7
7
  *
@@ -11,7 +11,7 @@
11
11
  * https://…/class/product-M0000438@M2R0001 → null (모르던 모양)
12
12
  *
13
13
  * 웹 URI 로 정체성을 말하는 원본에서는 언제나 `null` 이 나왔고, 그래서 **개체를 자재에 이을 수 없었다.**
14
- * 그 결과가 화면 두 곳에서 조용한 빈칸이었다(계보 적합성 · 오더가 무엇을 만드나). 「없으면 붙이지
14
+ * 그 결과가 화면 두 곳에서 들어온 것이 없는 빈칸이었다(계보 적합성 · 오더가 무엇을 만드나). 「없으면 붙이지
15
15
  * 않는다」는 규율을 지키기 때문에 아무 오류도 나지 않았다.
16
16
  *
17
17
  * ── 무엇을 돌려주나 ─────────────────────────────────────────────────────────
@@ -16,7 +16,7 @@ export const at = (o: any, path: string) => path.split('.').reduce((a: any, k: s
16
16
  *
17
17
  * 도메인 모양을 코드가 아는 것이 아니라 **식별자 관례**를 아는 것이다. 후보를 순서대로 보고 첫
18
18
  * 값을 쓴다. 하나도 없으면 그 항목은 가리킬 수 없으므로 `null` — 목록에서 빼지 않고 **id 없음**으로
19
- * 낸다(조용히 빠지면 건수가 안 맞는다).
19
+ * 낸다(오류 없이 빠지면 건수가 안 맞는다).
20
20
  */
21
21
  export const idOf = (o: any, info?: { idField?: string | string[] }): string | null => {
22
22
  if (typeof o === 'string') return o
@@ -26,7 +26,7 @@ export interface ProjectionReport {
26
26
  locations: number
27
27
  equipment: number
28
28
  operations: number
29
- /** 자리를 못 찾아 기준 자리를 비운 설비 — **조용히 넘기지 않는다.** */
29
+ /** 자리를 못 찾아 기준 자리를 비운 설비 — **알리지 않고 넘기지 않는다.** */
30
30
  unresolvedHome: string[]
31
31
  /** 구역을 못 찾아 비운 자리. 구역은 model 밖(호스트 개념)이라 어긋날 수 있다. */
32
32
  unresolvedArea: string[]
@@ -48,7 +48,7 @@ export async function projectStructure(domainId: string, instanceId: string, mod
48
48
  * 실측(2026-08-18): 현장 값을 그 현장의 트윈 전부에 얹다가 한 트윈에서 투영이 터졌다. 올라온 것은
49
49
  * `SQLITE_CONSTRAINT: UNIQUE constraint failed: twin_equipment...` 뿐이라 **무엇이 잘못됐는지 아무도
50
50
  * 알 수 없었다.** 원인은 그 모델에 설비 `load-1` 이 두 번 들어 있던 것이다(id 는 트윈 안에서 유일해야
51
- * 한다). 조용히 하나만 남기지 않는다 — 그것은 모델링 오류를 감추는 일이고, 어느 쪽이 맞는지 우리는
51
+ * 한다). 알리지 않고 하나만 남기지 않는다 — 그것은 모델링 오류를 감추는 일이고, 어느 쪽이 맞는지 우리는
52
52
  * 모른다.
53
53
  */
54
54
  const dupOf = (list: any[] | undefined, what: string): string | undefined => {
@@ -117,7 +117,7 @@ export async function projectStructure(domainId: string, instanceId: string, mod
117
117
  parallelism: l.parallelism ?? null,
118
118
  area: area ? ({ id: area.id } as any) : null,
119
119
  gs1Id: l.gs1Id ?? null,
120
- /* 자리의 속성도 옮긴다 — 옮기지 않으면 계약전력·요금이 항목 패널에서 사라진다(조용한 누락). */
120
+ /* 자리의 속성도 옮긴다 — 옮기지 않으면 계약전력·요금이 항목 패널에서 사라진다(들어온 것이 없는 누락). */
121
121
  properties: l.properties ?? null,
122
122
  sourceRef,
123
123
  ingestedAt
@@ -173,7 +173,7 @@ export async function projectStructure(domainId: string, instanceId: string, mod
173
173
  intent: o.intent ?? null,
174
174
  locationType: o.locationType ?? null,
175
175
  resourceType: o.resourceType ?? null,
176
- /* 소요시간은 ISO 8601 문자열이 정본 — 분으로 접으면 초가 조용히 사라진다. */
176
+ /* 소요시간은 ISO 8601 문자열이 정본 — 분으로 줄이면 초가 오류 없이 사라진다. */
177
177
  duration: o.duration ?? null,
178
178
  variability: o.variability ?? null,
179
179
  parameters: o.parameters ?? null,
@@ -14,7 +14,7 @@ import { TwinLocation } from './twin-location.js'
14
14
  *
15
15
  * ── 테이블 이름을 명시한다 ──────────────────────────────────────────────────
16
16
  * `equipment` 는 영어에서 **불가산**이라 자동 복수화가 다른 표들과 규칙이 어긋난다
17
- * (`twin_locations` 인데 `twin_equipment`). 조용한 비일관은 나중에 "왜 이것만 다르지" 를
17
+ * (`twin_locations` 인데 `twin_equipment`). 들어온 것이 없는 비일관은 나중에 "왜 이것만 다르지" 를
18
18
  * 만들므로 이름을 고정한다(ADR-0032-I).
19
19
  */
20
20
  @Entity({ name: 'twin_equipment' })
@@ -97,7 +97,7 @@ export class TwinEquipment {
97
97
  * 그 시험들의 **결과** — 자격이 성립하는지는 이것이 말한다.
98
98
  *
99
99
  * 참조만 있으면 "무엇으로 검증한다" 까지다. 오늘 이 저장소에서 네 번 나온 결손과 같은 부류이므로
100
- * 행에 자리를 함께 만든다 — 모델에는 있는데 행에 없으면 화면이 조용히 잃는다.
100
+ * 행에 자리를 함께 만든다 — 모델에는 있는데 행에 없으면 화면이 알리지 않고 잃는다.
101
101
  */
102
102
  @Column({ type: 'simple-json', nullable: true })
103
103
  @Field(type => ScalarObject, { nullable: true, description: 'Results of those tests (pass/fail with dates). Declared by the source — the twin does not produce them.' })
@@ -52,11 +52,22 @@ export class TwinLineageQuery {
52
52
  * 변환 이벤트만 읽는다. **최신부터** 읽는다 — 계보를 묻는 개체는 대개 최근에 만들어진 것이고,
53
53
  * 창을 넘겨 잘릴 때 잃는 쪽이 오래된 것이어야 덜 아프다.
54
54
  */
55
- const rows: any[] = await getRepository(TwinEvent).find({
56
- where: { domain: { id: domainId } as any, instanceId, eventType: 'epcis.TransformationEvent' } as any,
57
- order: { eventTime: 'DESC' } as any,
58
- take: windowCap
59
- })
55
+ /*
56
+ * 관계를 **컬럼으로** 묻는다 (2026-08-28). `find({ where: { domain: { id } } })` 은 관계를 가진
57
+ * 엔티티에서 두 단계 질의가 되고 **안쪽에 상한이 없다** — `take` 가 있어도 조건에 맞는 행을 전부
58
+ * 만든 뒤 밖에서 자른다. 변환 사건이 적은 트윈에서는 표가 안 나지만, 변환을 많이 내는 트윈에서는
59
+ * 이 한 번이 그 종류 전체를 만든다. 질의 빌더로 물으면 상한이 안쪽에 걸린다.
60
+ *
61
+ * 종류가 정확일치이므로 `(domain, instance, event_type, event_time)` 색인이 시각 순서까지 준다.
62
+ */
63
+ const rows: any[] = await getRepository(TwinEvent)
64
+ .createQueryBuilder('e')
65
+ .where('e.domain = :domainId', { domainId })
66
+ .andWhere('e.instanceId = :instanceId', { instanceId })
67
+ .andWhere('e.eventType = :eventType', { eventType: 'epcis.TransformationEvent' })
68
+ .orderBy('e.eventTime', 'DESC')
69
+ .limit(windowCap)
70
+ .getMany()
60
71
  const truncated = rows.length >= windowCap
61
72
 
62
73
  /* 산출 → 그 변환. 한 개체는 한 번만 만들어지지만, 데이터가 그렇지 않을 수 있으므로 목록으로 든다. */
@@ -115,7 +115,7 @@ export class TwinLocation {
115
115
  * ── 왜 늦게 생겼나 (2026-08-18) ────────────────────────────────────────────
116
116
  * 설비 행에는 이 칸이 있었는데 자리 행에는 없었다. 그래서 수전 지점에 계약전력·요금을 넣어도 **항목
117
117
  * 패널의 「원본이 말한 값」에 그 줄이 아예 없었다** — 사용자는 값을 넣고도 화면에서 찾을 수 없었고,
118
- * 「이 값이 무엇을 바꾸나」는 문장도 붙을 자리가 없었다(조용한 누락).
118
+ * 「이 값이 무엇을 바꾸나」는 문장도 붙을 자리가 없었다(들어온 것이 없는 누락).
119
119
  */
120
120
  @Column({ type: 'simple-json', nullable: true })
121
121
  @Field(type => ScalarObject, { nullable: true, description: 'Properties declared on this location (ISA-95 Property: value + dataType + unit of measure). Carries locally-declared numbers too, with source: local.' })
@@ -54,7 +54,7 @@ function matches(o: any, q: string): boolean {
54
54
  * 그래서 야드의 도크도어나 에너지의 계량기가 늘어도 여기 손댈 것이 없다(ADR-0031). 이 파일에
55
55
  * 도메인 이름이 한 번도 나오지 않는 것이 그 증거다.
56
56
  *
57
- * ── 조용히 자르지 않는다 ──────────────────────────────────────────────────────
57
+ * ── 알리지 않고 자르지 않는다 ──────────────────────────────────────────────────────
58
58
  * 목록은 항상 **총 건수**와 함께 온다. 그리고 어떻게 셌는지(`basis`)를 밝힌다 — 행이면 DB 가
59
59
  * 페이징했고, 문서면 서버가 문서를 통째로 읽어 잘라 냈다. 후자는 규모에서 한계가 되므로 그 사실이
60
60
  * 화면에 드러나야 이주 압력이 살아 있다.
@@ -118,7 +118,7 @@ export class TwinModelItemQuery {
118
118
  /*
119
119
  * 검색은 아직 DB 로 내리지 않았다. **그 사실을 밝힌다**(`basis: 'rows-scanned'`) —
120
120
  * 흉내로 덮으면 규모가 오는 날까지 아무도 이 한계를 모른다. 훑는 건수를 상한으로 막고,
121
- * 상한에 닿았으면 잘렸다고 말한다(조용히 자르지 않는다).
121
+ * 상한에 닿았으면 잘렸다고 말한다(알리지 않고 자르지 않는다).
122
122
  */
123
123
  const SCAN_MAX = 5000
124
124
  const repo = getRepository(row.entity)
@@ -159,11 +159,11 @@ export class TwinModelItemQuery {
159
159
  * **라이브는 권위, 히스토리는 파생.**
160
160
  *
161
161
  * 지금 도는 커널의 상태가 이 축의 진실이다. 그런데 커널이 멈춰 있거나 사용자가 **과거 시각**을
162
- * 물으면 답할 것이 없어진다 — 그때 저널을 그 시점까지 접어 답한다(`recover`). 둘은 같은 모양이지만
162
+ * 물으면 답할 것이 없어진다 — 그때 저널을 그 시점까지 계산해 답한다(`recover`). 둘은 같은 모양이지만
163
163
  * **성질이 다르므로 `basis` 로 구별해 낸다**: 파생을 권위처럼 내면 다시 계산한 값을 보고 "지금 이렇다"
164
164
  * 로 읽게 된다.
165
165
  *
166
- * 규모: 접기 비용은 이벤트 수에 비례한다. 감추지 않고 **어느 리비전까지 접었는지 함께 낸다** —
166
+ * 규모: 계산 비용은 이벤트 수에 비례한다. 감추지 않고 **어느 리비전까지 계산했는지 함께 낸다** —
167
167
  * 이 화면이 목록마다 `basis` 를 밝혀 온 규율 그대로다.
168
168
  */
169
169
  const live = TwinEngine.isRunning(domainId, instanceId) && !at_
@@ -341,9 +341,9 @@ export class TwinModelItemQuery {
341
341
  */
342
342
  if (axisSource(axis) === 'state') {
343
343
  /*
344
- * 동작하지 않으면 **목록과 같은 길로 저널을 접는다.**
344
+ * 동작하지 않으면 **목록과 같은 길로 저널을 계산한다.**
345
345
  *
346
- * 목록만 접고 상세는 안 접었더니, 목록에는 472건이 보이는데 그 줄을 열면 "자기 속성이 없습니다"
346
+ * 목록만 계산하고 상세는 안 계산했더니, 목록에는 472건이 보이는데 그 줄을 열면 "자기 속성이 없습니다"
347
347
  * 가 나왔다 — 한 화면에서 두 답이 갈리면 어느 쪽이 맞는지 사용자가 알 수 없다.
348
348
  */
349
349
  const liveItem = TwinEngine.isRunning(domainId, instanceId)
@@ -471,7 +471,7 @@ export class TwinModelItemQuery {
471
471
  observed,
472
472
  /*
473
473
  * 관측 열이 **자기 근거를 말한다** — 목록의 그 낱말 그대로(`state`=라이브 권위, `journal`=다시 계산한
474
- * 파생). 없으면 비교하지 못한 것이다. 파생이면 몇 번째 리비전까지 접었는지 함께 낸다.
474
+ * 파생). 없으면 비교하지 못한 것이다. 파생이면 몇 번째 리비전까지 계산했는지 함께 낸다.
475
475
  */
476
476
  ...('value' in seen
477
477
  ? { observedBasis: seen.basis, ...(seen.foldedRevision != null ? { observedRevision: seen.foldedRevision } : {}) }
@@ -631,8 +631,8 @@ export class TwinModelItemQuery {
631
631
  *
632
632
  * 근거를 값과 함께 낸다 — 목록이 오래 해 온 그대로다(§twinModelItemList). 라이브(권위)와 저널
633
633
  * 폴드(파생)는 **같은 모양의 객체**라서, 근거가 없으면 다시 계산한 값을 "지금 이렇다" 로 읽는다. 근거를
634
- * 실을 자리가 없던 동안에는 멈춘 트윈의 저널을 아예 접지 않고 `—` 로 두는 편이 정직했다. 이제
635
- * 자리가 있으니 접는다 — 멈춘 트윈에도 마지막으로 알던 사실이 있고, 그것을 감출 이유는 없다.
634
+ * 실을 자리가 없던 동안에는 멈춘 트윈의 저널을 아예 계산하지 않고 `—` 로 두는 편이 정직했다. 이제
635
+ * 자리가 있으니 계산한다 — 멈춘 트윈에도 마지막으로 알던 사실이 있고, 그것을 감출 이유는 없다.
636
636
  *
637
637
  * 다시 계산할 것이 없으면(모델도 저널도 없다) `null` 이다 — **없는 것과 비교하지 못한 것은 다르다.**
638
638
  */
@@ -652,9 +652,9 @@ export class TwinModelItemQuery {
652
652
  const rec = await TwinEngine.recover(domainId, instanceId)
653
653
  folded = Number((rec as any)?.revision) || undefined
654
654
  /*
655
- * **이벤트를 하나도 접지 않은 폴드는 관측이 아니다.** 저널이 비어도 복원은 선언을 다시 투영한
655
+ * **이벤트를 하나도 계산하지 않은 폴드는 관측이 아니다.** 저널이 비어도 복원은 선언을 다시 투영한
656
656
  * 기준선을 돌려준다 — 그것을 "트윈이 관측한 값" 열에 실으면 **선언이 관측으로 위장한다**
657
- * (원본이 말한 값이 오른쪽에도 똑같이 앉아, 견준 것처럼 보인다). 접은 것이 없으면 없다고 한다.
657
+ * (원본이 말한 값이 오른쪽에도 똑같이 앉아, 견준 것처럼 보인다). 계산한 것이 없으면 없다고 한다.
658
658
  */
659
659
  if (folded == null) return { absence: 'not-running' as const }
660
660
  state = unwrapState(rec)
@@ -700,7 +700,7 @@ export class TwinModelItemQuery {
700
700
  const model = inst.model ?? {}
701
701
 
702
702
  /*
703
- * 관측 축의 항목들 — **한 번만** 읽는다(관계마다 다시 접으면 저널을 여러 번 접는다).
703
+ * 관측 축의 항목들 — **한 번만** 읽는다(관계마다 다시 계산하면 저널을 여러 번 계산한다).
704
704
  * 라이브가 있으면 그것, 없으면 저널 폴드. 없으면 빈 배열(없는 것을 지어내지 않는다).
705
705
  */
706
706
  let observedState: any
@@ -725,7 +725,7 @@ export class TwinModelItemQuery {
725
725
  *
726
726
  * 관계 선언의 필드 이름(`locations.parentId`)은 **원본 문서의 어휘**다. 행은 같은 사실을 자기
727
727
  * 컬럼 이름으로 담는다(자리의 구역은 `area` FK). 행을 보고 `parentId` 를 찾으면 늘 비어서
728
- * "이어진 것이 없다" 로 조용히 답한다 — 실제로 그랬다.
728
+ * "이어진 것이 없다" 로 알리지 않고 답한다 — 실제로 그랬다.
729
729
  *
730
730
  * 어느 쪽이 정본인가로 구분하면 답이 분명하다: 관계를 선언한 어휘가 문서 어휘이므로 **문서에서
731
731
  * 푼다.** 행에만 있고 문서에 없는 항목(있을 수 있다)은 이어진 것이 비지만, 그것은 사실이다.
@@ -762,7 +762,7 @@ export class TwinModelItemQuery {
762
762
  const repo = getRepository(row.entity)
763
763
  /*
764
764
  * **FK 는 부르지 않으면 오지 않는다.** 관계 필드를 로딩하지 않고 조회하면 값이 늘 `undefined`
765
- * 라서 "나를 가리키는 것이 하나도 없다" 로 조용히 답한다 — 실제로 그랬다(설비 둘이 이 자리를
765
+ * 라서 "나를 가리키는 것이 하나도 없다" 로 알리지 않고 답한다 — 실제로 그랬다(설비 둘이 이 자리를
766
766
  * 기준으로 삼는데 이웃이 비어 있었다).
767
767
  *
768
768
  * 어느 필드가 관계인지는 **엔티티 메타데이터**가 안다. 축마다 표를 두면 축이 늘 때마다
@@ -71,7 +71,7 @@ async function spreadSpaceLayer(
71
71
  t.localDeclarations = out.instanceLayerApplied as any
72
72
  const running = TwinEngine.modeOf(domainId, t.instanceId)
73
73
  /*
74
- * **트윈 하나가 실패해도 나머지는 받는다** — 다만 조용히 넘기지 않는다(2026-08-18 실측).
74
+ * **트윈 하나가 실패해도 나머지는 받는다** — 다만 알리지 않고 넘기지 않는다(2026-08-18 실측).
75
75
  *
76
76
  * 현장 선언을 처음 퍼뜨렸을 때 그 현장의 어떤 트윈에서 구조 투영이 터졌고(그 트윈 모델에 설비 id가
77
77
  * 겹쳐 있었다 — 선재 결함), 그 예외가 올라와 **한 트윈 때문에 현장 전체 선언이 실패**했다. 사용자는
@@ -132,12 +132,12 @@ export class TwinModelMutation {
132
132
  * 계량이 흐르는 트윈에서도 **금액이 영원히 나오지 않았다** — 넣을 문이 없었기 때문이다.
133
133
  *
134
134
  * 값은 `model` 에 직접 쓰지 않고 **겹**으로 보관한다: 모델은 원천의 사본이므로 다시 읽기가 그것을
135
- * 새로 그리면 직접 쓴 값은 조용히 사라진다. 겹은 모델을 그릴 때마다 다시 얹힌다.
135
+ * 새로 그리면 직접 쓴 값은 오류 없이 사라진다. 겹은 모델을 그릴 때마다 다시 얹힌다.
136
136
  *
137
137
  * ── 돌고 있는 트윈에 어떻게 반영하나 ────────────────────────────────────────
138
138
  * 미러는 **동작 중에** 새 구조를 받는다(현실은 멈추지 않는다). 시뮬레이션은 받지 않는다 — 조건이
139
139
  * 바뀐 실험은 다른 실험이고, 커널이 그렇게 거절한다. 그때는 저장만 하고 **그 사실을 말한다**:
140
- * 조용히 성공으로 답하면 사용자는 지금 화면의 수가 새 선언으로 계산된 줄로 읽는다.
140
+ * 알리지 않고 성공으로 답하면 사용자는 지금 화면의 수가 새 선언으로 계산된 줄로 읽는다.
141
141
  */
142
142
  @Directive('@privilege(category: "twin", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
143
143
  @Mutation(returns => ScalarObject, {
@@ -165,7 +165,7 @@ export class TwinModelMutation {
165
165
  if (!declarations?.length) return { ok: false, error: 'no declaration arrived — the declarations argument was empty' }
166
166
 
167
167
  /*
168
- * 검사부터 — 받지 않은 것을 조용히 버리지 않는다(사용자는 값을 넣었다고 믿는다).
168
+ * 검사부터 — 받지 않은 것을 알리지 않고 버리지 않는다(사용자는 값을 넣었다고 믿는다).
169
169
  *
170
170
  * 공정 이름은 **대조로만** 걸러진다(모델에 행이 없어도 성립하는 선언이라 다른 안전망이 없다). 아직
171
171
  * 돌지도 않고 명세 행도 없는 트윈이면 목록이 `undefined` 이고, 그때는 받아 주되 답에 그 사실을 싣는다.
@@ -1,4 +1,5 @@
1
1
  import { axisSeenInJournal } from './axis-journal-evidence.js'
2
+ import { hydratedDate } from '../../engine/read-time.js'
2
3
  import { statusTally } from './status-tally.js'
3
4
  import { TwinEvent } from '../twin-event/twin-event.js'
4
5
  import { Arg, Ctx, Query, Resolver } from 'type-graphql'
@@ -117,8 +118,8 @@ async function emptyStateReason(domainId: string, instanceId: string, axis: stri
117
118
  /**
118
119
  * 되세운 상태를 담아 둔다 — **열쇠에 저널 끝 리비전이 들어 있어** 새 사건이 오면 저절로 낡는다.
119
120
  *
120
- * 접기는 저널 전체를 훑는 일이라(리비전 12만에서 2~3초) 모델 화면을 열 때마다 그 값을 낼 수 없다.
121
- * 프로세스 안에만 두고 몇 개만 남긴다 — 다시 뜨면 다시 접는다(정확함이 캐시보다 앞선다).
121
+ * 계산은 저널 전체를 훑는 일이라(리비전 12만에서 2~3초) 모델 화면을 열 때마다 그 값을 낼 수 없다.
122
+ * 프로세스 안에만 두고 몇 개만 남긴다 — 다시 뜨면 다시 계산한다(정확함이 캐시보다 앞선다).
122
123
  */
123
124
  const RECALL_CACHE = new Map<string, { snap: any; heardAt: string | null }>()
124
125
  const RECALL_CACHE_MAX = 8
@@ -194,7 +195,7 @@ export class TwinModelQuery {
194
195
  *
195
196
  * 그래서 요약의 `source` 가 **언제나 `null`** 이었다. 레퍼런스로 세운 트윈인데 화면이 그 원본을
196
197
  * 못 찾았고, 「마지막 동기화」도 흐름별 신선도도 그릴 자리가 아예 없었다 — 그런데 오류는 없었다
197
- * (`ref ? … : null` 이 조용히 갈렸다).
198
+ * (`ref ? … : null` 이 알리지 않고 갈렸다).
198
199
  *
199
200
  * 인스턴스가 자기 원본을 말하고 있으므로 **그 말을 먼저 듣는다.** 옛 트윈은 두 값이 같으므로
200
201
  * 뒤의 것으로 여전히 찾힌다 — 가산적이다.
@@ -206,13 +207,13 @@ export class TwinModelQuery {
206
207
 
207
208
  const model = inst.model ?? {}
208
209
  /*
209
- * **한 요청에 저널은 한 번만 접는다.**
210
+ * **한 요청에 저널은 한 번만 계산한다.**
210
211
  *
211
212
  * 관측 축(`orders`·`tasks`)은 상태에서 센다. 그 둘이 각자 `recover()` 를 부르고 있었고, 커널이
212
- * 멈춰 있으면 그것은 **저널 전체를 다시 접는 일**이다(23,731건짜리 트윈에서 4.6초, 두 번이면 곱절).
213
+ * 멈춰 있으면 그것은 **저널 전체를 다시 계산하는 일**이다(23,731건짜리 트윈에서 4.6초, 두 번이면 곱절).
213
214
  * 트윈을 전부 멈춰도 이 화면이 5~7초 걸린 이유가 이것이다 — 부하가 아니라 같은 일을 두 번 한 것이다.
214
215
  *
215
- * 한 번만 접어 나눠 쓴다. 값이 같아지는 것은 덤이 아니라 **옳음**이다: 두 번 접으면 그 사이에
216
+ * 한 번만 계산해 나눠 쓴다. 값이 같아지는 것은 덤이 아니라 **옳음**이다: 두 번 계산하면 그 사이에
216
217
  * 들어온 이벤트 때문에 오더와 작업이 서로 다른 시점의 사실이 될 수 있다.
217
218
  */
218
219
  let statePromise: Promise<{ snap: any; folded: boolean; observing: boolean }> | null = null
@@ -239,29 +240,49 @@ export class TwinModelQuery {
239
240
  * 그런데 읽기 커서가 정직해진 뒤로는 원본이 **지난 것을 다시 말하지 않는다.** 그래서 재고 741건이
240
241
  * 저널에 있는데 화면은 「0」을 보였다. 사용자가 「재고현황이 없다」고 물은 자리가 이것이다.
241
242
  *
242
- * 그 0 은 사실이 아니라 **우리가 잊은 것**이다. 저널을 접으면 그대로 되살아난다(실측: 리비전
243
- * 97,689 을 2.4초에 접어 물품 741 · 작업 9,145 · 지시 2,856).
243
+ * 그 0 은 사실이 아니라 **우리가 잊은 것**이다. 저널을 계산하면 그대로 되살아난다(실측: 리비전
244
+ * 97,689 을 2.4초에 계산해 물품 741 · 작업 9,145 · 지시 2,856).
244
245
  *
245
- * ── 그래서 접는다. 다만 **언제 들은 값인지 함께 낸다** ──────────────────────
246
- * 저널을 접는 것은 **원천이 우리에게 말한 것을 다시 세우는 일**이지, 없는 값을 심는 일이 아니다.
246
+ * ── 그래서 계산한다. 다만 **언제 들은 값인지 함께 낸다** ──────────────────────
247
+ * 저널을 계산하는 것은 **원천이 우리에게 말한 것을 다시 세우는 일**이지, 없는 값을 심는 일이 아니다.
247
248
  * 그러나 물품·재고처럼 **절대값인 축**은 우리가 못 듣는 사이 떠났을 수 있다 — 그래서 값과 함께
248
- * `heardAt`(그 접기의 시계)을 낸다. 사용자가 신선도를 스스로 판정할 수 있어야 하고, 화면은 그것을
249
+ * `heardAt`(그 계산의 시계)을 낸다. 사용자가 신선도를 스스로 판정할 수 있어야 하고, 화면은 그것을
249
250
  * 값 옆에 적는다. 시각 없이 수만 내면 낡은 값을 지금으로 읽는다.
250
251
  *
251
- * 접는 조건을 좁게 둔다: **관측 축이 비어 있고, 저널에 그 축의 사실이 있을 때만**(그 판정은 색인
252
- * 한 줄이다). 그리고 한 요청에 **한 번만** 접는다 — 위 `stateOnce` 와 같은 규율이다.
252
+ * 계산하는 조건을 좁게 둔다: **관측 축이 비어 있고, 저널에 그 축의 사실이 있을 때만**(그 판정은 색인
253
+ * 한 줄이다). 그리고 한 요청에 **한 번만** 계산한다 — 위 `stateOnce` 와 같은 규율이다.
253
254
  */
254
255
  let journalPromise: Promise<{ snap: any; heardAt: string | null } | null> | null = null
255
256
  const journalOnce = () =>
256
257
  (journalPromise ??= (async () => {
257
258
  try {
258
- const tip = await getRepository(TwinEvent).findOne({
259
- where: { domain: { id: domainId } as any, instanceId } as any,
260
- order: { revision: 'DESC' } as any
261
- })
262
- if (!tip?.revision) return null
263
259
  /*
264
- * **한 번 접은 것을 다시 접지 않는다.** 접기는 저널 전체를 훑는 일이다(리비전 12만에서 2~3초).
260
+ * 관계로 묻지 않는다 — 두 단계 질의가 되어 안쪽에 상한이 없고, 그 트윈의 저널을 전부 만든 뒤
261
+ * 한 줄을 고른다(2026-08-28 · 같은 모양이 현장 성과 화면에서 7.2초였다). 색인의 끝만 읽는다.
262
+ */
263
+ const [tipRow, latestRow] = await Promise.all([
264
+ getRepository(TwinEvent)
265
+ .createQueryBuilder('e')
266
+ .select('MAX(e.revision)', 'max')
267
+ .where('e.domain = :domainId', { domainId })
268
+ .andWhere('e.instanceId = :instanceId', { instanceId })
269
+ .getRawOne<{ max: unknown }>(),
270
+ /*
271
+ * 「저널 끝의 사건 시각」은 **가장 늦은 사건 시각**이다 — 리비전이 가장 큰 행의 시각이
272
+ * 아니다. 그 둘은 어긋난다(연결된 시스템을 되읽는 트윈은 원본의 시각을 싣고 도착 순서로
273
+ * 리비전을 받는다). 색인의 끝을 집으므로 질의 둘이어도 각각 한 줄이다.
274
+ */
275
+ getRepository(TwinEvent)
276
+ .createQueryBuilder('e')
277
+ .select('MAX(e.eventTime)', 'at')
278
+ .where('e.domain = :domainId', { domainId })
279
+ .andWhere('e.instanceId = :instanceId', { instanceId })
280
+ .getRawOne<{ at: unknown }>()
281
+ ])
282
+ const tip = { revision: Number(tipRow?.max) || 0, eventTime: hydratedDate(getRepository(TwinEvent), 'eventTime', latestRow?.at) }
283
+ if (!tip.revision) return null
284
+ /*
285
+ * **한 번 계산한 것을 다시 계산하지 않는다.** 계산은 저널 전체를 훑는 일이다(리비전 12만에서 2~3초).
265
286
  * 이 화면은 사람이 여러 번 들어오는 자리이므로 그대로 두면 매번 그 값을 낸다 — 그리고 저널이
266
287
  * 수백만 행이 되면 그 비용이 화면을 못 쓰게 만든다.
267
288
  *
@@ -273,14 +294,14 @@ export class TwinModelQuery {
273
294
  if (hit) return hit
274
295
  const snap = unwrapState(await TwinEngine.recover(domainId, instanceId, tip.revision))
275
296
  if (!snap) return null
276
- /* 그 접기의 시계가 「언제 들은 값인가」다. 없으면 저널 끝의 사건 시각으로 답한다. */
277
- const out = { snap, heardAt: (snap as any).nowTime ?? tip.eventTime ?? null }
297
+ /* 그 계산의 시계가 「언제 들은 값인가」다. 없으면 저널 끝의 사건 시각으로 답한다. */
298
+ const out = { snap, heardAt: (snap as any).nowTime ?? tip.eventTime?.toISOString() ?? null }
278
299
  RECALL_CACHE.set(key, out)
279
300
  /* 오래된 열쇠부터 버린다 — 트윈 몇 개를 번갈아 봐도 메모리가 자라지 않게. */
280
301
  while (RECALL_CACHE.size > RECALL_CACHE_MAX) RECALL_CACHE.delete(RECALL_CACHE.keys().next().value as string)
281
302
  return out
282
303
  } catch {
283
- /* 접지 못했으면 아무 말도 만들지 않는다 — 라이브가 말한 것을 그대로 둔다. */
304
+ /* 계산하지 못했으면 아무 말도 만들지 않는다 — 라이브가 말한 것을 그대로 둔다. */
284
305
  return null
285
306
  }
286
307
  })())
@@ -332,7 +353,7 @@ export class TwinModelQuery {
332
353
  *
333
354
  * 행으로 옮긴 축이라도 그 트윈이 투영을 거치지 않았으면 행은 0이다(인제스트 없이
334
355
  * warm-start 된 트윈이 그렇다). 행만 보고 답하면 문서에 자리 6개가 있는데 화면은
335
- * **0 이라고 말한다** — 조용한 거짓이고, 이 프로젝트가 내내 잡아 온 바로 그 부류다.
356
+ * **0 이라고 말한다** — 들어온 것이 없는 거짓이고, 이 프로젝트가 내내 잡아 온 바로 그 부류다.
336
357
  *
337
358
  * 그래서 어느 하나로 메우지 않는다. 행 수를 답하되 문서 수를 함께 내고, 다르면
338
359
  * `stale` 로 표시한다 — 그 차이 자체가 "이 트윈은 투영 이후 인제스트되지 않았다" 는 정보다.
@@ -366,7 +387,7 @@ export class TwinModelQuery {
366
387
  /*
367
388
  * 관측 중인지는 **런타임이 있는지**로 본다 — 캐시된 스냅샷은 관측이 아니다.
368
389
  *
369
- * 동작하지 않으면 저널을 접어 답한다(라이브=권위 · 히스토리=파생). 카드는 그 차이를 `basis` 로
390
+ * 동작하지 않으면 저널을 계산해 답한다(라이브=권위 · 히스토리=파생). 카드는 그 차이를 `basis` 로
370
391
  * 말한다: 파생을 권위처럼 내면 다시 계산한 수를 "지금 이만큼" 으로 읽게 된다.
371
392
  */
372
393
  const { snap, folded, observing } = await stateOnce()
@@ -377,7 +398,7 @@ export class TwinModelQuery {
377
398
  let fromJournal = false
378
399
  /*
379
400
  * 라이브가 잊은 축은 **저널에서 되세운다**(§`journalOnce`). 그 축의 사실이 저널에 있을 때만
380
- * 접고, 값과 함께 「언제 들은 값인가」를 낸다 — 시각 없이 수만 내면 낡은 값을 지금으로 읽는다.
401
+ * 계산하고, 값과 함께 「언제 들은 값인가」를 낸다 — 시각 없이 수만 내면 낡은 값을 지금으로 읽는다.
381
402
  */
382
403
  if (!folded && Array.isArray(bucket) && bucket.length === 0) {
383
404
  const seen = await axisSeenInJournal(getRepository(TwinEvent), { domain: { id: domainId }, instanceId }, a.axis).catch(() => null)
@@ -469,7 +490,7 @@ export class TwinModelQuery {
469
490
  /*
470
491
  * **원본이 모르는데 실행 중인 것** — 커널은 마스터에 없던 자원도 이벤트로 알게 되고
471
492
  * `origin: 'observed'` 를 단다(`observed-reducer`). 그것을 표에 밀어 넣지 않는다 — 그러면
472
- * 틱 루프에서 DB 로 쓰는 **두 번째 쓰기 경로**가 생기고, 실패하면 조용히 어긋난다
493
+ * 틱 루프에서 DB 로 쓰는 **두 번째 쓰기 경로**가 생기고, 실패하면 알리지 않고 어긋난다
473
494
  * (쓰기 경로는 인제스트 하나, ADR-0032-D).
474
495
  *
475
496
  * 대신 **여기서 비교해 낸다.** 이 차이는 숨겨야 할 불일치가 아니라 인스펙션이 답해야 할
@@ -21,7 +21,7 @@ const { TWIN_RELATIONS, axisInfo } = require('@operato/twin-kernel')
21
21
  * 대신 **선언 그래프에서 자기 축으로 돌아오는 두 홉 순환을 찾는다.** 자재만이 아니라 앞으로
22
22
  * 같은 모양을 갖는 어떤 축이든 그대로 펼쳐진다.
23
23
  *
24
- * ── 조용히 고르지 않는다 ───────────────────────────────────────────────────────
24
+ * ── 알리지 않고 고르지 않는다 ───────────────────────────────────────────────────────
25
25
  * 순환이 여럿이면 **아무거나 집지 않는다.** 후보를 돌려주고 부르는 쪽이 고르게 한다 — 한쪽을
26
26
  * 집으면 역전개(누가 나를 쓰나)를 전개(내가 무엇으로 되나)로 보여 주고도 아무도 모른다.
27
27
  */
@@ -102,12 +102,36 @@ export class TwinModelTreeQuery {
102
102
  const all = patternsFor(axis)
103
103
  if (!all.length) return { axis, itemId, patterns: [], items: [], edges: [], reason: 'no-recursive-relation' }
104
104
 
105
+
105
106
  const chosen = inVia && outVia ? all.find(p => p.inVia === inVia && p.outVia === outVia) : all.length === 1 ? all[0] : undefined
106
107
  if (!chosen)
107
108
  /* 여럿인데 안 고르면 **아무것도 펼치지 않는다.** 한쪽을 집으면 역전개를 전개로 보여 주고도
108
109
  아무도 모른다 — 방향이 뒤집힌 BOM 은 틀린 답이 아니라 반대의 답이다. */
109
110
  return { axis, itemId, patterns: all, items: [], edges: [], reason: 'ambiguous-pattern' }
110
111
 
112
+ /*
113
+ * ── ★ **선언에 없는 축은 펼칠 것이 없다** (2026-08-24 실측으로 터짐) ────────
114
+ *
115
+ * 이 전개는 **트윈 모델의 선언**을 읽는다(`axisInfo(...).path` 로 모델 안을 찾아 들어간다). 그런데
116
+ * 커널 카탈로그의 축 중 셋은 **선언되는 것이 아니라 일어나는 것**이다 — `orders`·`tasks`·`items`
117
+ * 는 사건이 만들고 끝나면 사라진다. 모델이 그 목록을 가질 수 없으므로 `path` 가 비어 있다.
118
+ *
119
+ * 그래서 그런 축을 지나려 하면 `at(model, undefined)` 에서 터졌다:
120
+ *
121
+ * Cannot read properties of undefined (reading 'split')
122
+ *
123
+ * ★ **터진 것은 요청한 축이 아니라 「거쳐 가는 축」이다.** `locations` 를 물으면 그 순환이
124
+ * `tasks` 를 거치는데(작업이 `from`·`at` 으로 자리를 두 번 가리킨다) 그 `tasks` 에 경로가 없다.
125
+ * 그래서 요청한 축만 지켜서는 막히지 않는다 — 둘 다 본다.
126
+ *
127
+ * ── 터지지 않게 하는 것만으로는 모자라다 ──────────────────────────────────
128
+ * 빈 트리를 돌려주면 화면이 **「이어진 것이 없다」**로 읽는다. 그것은 거짓이다 — 작업은 자리를
129
+ * 가리키고 있고, 다만 **이 질의가 그것을 아는 곳이 아니다**(그 관계는 상태와 저널이 안다).
130
+ * 그래서 이유를 말한다. 왜 못 하는지 말하지 않는 빈 화면이 이 저장소가 반복해 고친 결함이다.
131
+ */
132
+ if (!info.path || !axisInfo(chosen.through)?.path)
133
+ return { axis, itemId, patterns: all, items: [], edges: [], reason: 'not-declared-in-model' }
134
+
111
135
  const model = inst.model ?? {}
112
136
  const throughInfo = axisInfo(chosen.through)
113
137
  const listOf = (i: any) => {
@@ -11,7 +11,7 @@ import { TwinInstance } from '../twin-instance/twin-instance.js'
11
11
  *
12
12
  * ── 왜 이 표가 인스펙션의 중심인가 ──────────────────────────────────────────
13
13
  * 인스펙션 화면의 존재 이유는 `선언 | 관측` 두 열이고(`plans/twin-inspection.md` §2.4),
14
- * 그 첫 열이 여기서 온다. 둘째 열은 저널에서 접힌다(`kpi-fold.workTime`).
14
+ * 그 첫 열이 여기서 온다. 둘째 열은 저널에서 계산된다(`kpi-fold.workTime`).
15
15
  * 실적은 보이는데 기준이 안 보이면 "절단이 5분 걸렸다" 를 보고도 빠른지 느린지 알 수 없다.
16
16
  *
17
17
  * 자리·설비와 같은 규율 — **원본에서 온 캐시**, `sourceRef`·`ingestedAt` 필수,
@@ -83,7 +83,7 @@ export class TwinOperation {
83
83
  /**
84
84
  * 소요시간 — **ISO 8601 기간 문자열이 정본**(`PT1H10M`).
85
85
  *
86
- * 분으로 저장하면 `PT1H10M30S` 의 초가 사라지고 그 손실은 조용하다. 사람이 읽을 분은
86
+ * 분으로 저장하면 `PT1H10M30S` 의 초가 사라지고 그 손실은 들어온 것이 없다. 사람이 읽을 분은
87
87
  * 화면이 곁들여 보여 주고, 못 읽는 값은 `—` 로 낸다(0 으로 채우면 "즉시 끝나는 공정" 이라는 거짓).
88
88
  */
89
89
  @Column({ nullable: true })