@things-factory/headless-twin 10.0.12 → 10.0.13

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 (312) hide show
  1. package/dist-server/engine/canonical-ingest.d.ts +57 -2
  2. package/dist-server/engine/canonical-ingest.js +43 -2
  3. package/dist-server/engine/canonical-ingest.js.map +1 -1
  4. package/dist-server/engine/command-routing.js +1 -1
  5. package/dist-server/engine/command-routing.js.map +1 -1
  6. package/dist-server/engine/index.d.ts +3 -0
  7. package/dist-server/engine/index.js +4 -0
  8. package/dist-server/engine/index.js.map +1 -1
  9. package/dist-server/engine/ingest-health.d.ts +335 -0
  10. package/dist-server/engine/ingest-health.js +434 -0
  11. package/dist-server/engine/ingest-health.js.map +1 -0
  12. package/dist-server/engine/integration-coverage.d.ts +76 -0
  13. package/dist-server/engine/integration-coverage.js +73 -0
  14. package/dist-server/engine/integration-coverage.js.map +1 -0
  15. package/dist-server/engine/integration-probes.d.ts +65 -0
  16. package/dist-server/engine/integration-probes.js +100 -0
  17. package/dist-server/engine/integration-probes.js.map +1 -0
  18. package/dist-server/engine/integration-runner.d.ts +45 -0
  19. package/dist-server/engine/integration-runner.js +59 -0
  20. package/dist-server/engine/integration-runner.js.map +1 -0
  21. package/dist-server/engine/integration-target-profile.d.ts +57 -0
  22. package/dist-server/engine/integration-target-profile.js +79 -0
  23. package/dist-server/engine/integration-target-profile.js.map +1 -0
  24. package/dist-server/engine/kpi-fold.d.ts +27 -0
  25. package/dist-server/engine/kpi-fold.js +47 -4
  26. package/dist-server/engine/kpi-fold.js.map +1 -1
  27. package/dist-server/engine/kpi-query.js +128 -14
  28. package/dist-server/engine/kpi-query.js.map +1 -1
  29. package/dist-server/engine/live-feed-registry.js +2 -16
  30. package/dist-server/engine/live-feed-registry.js.map +1 -1
  31. package/dist-server/engine/load-meter.d.ts +1 -1
  32. package/dist-server/engine/load-meter.js +12 -3
  33. package/dist-server/engine/load-meter.js.map +1 -1
  34. package/dist-server/engine/log.d.ts +18 -0
  35. package/dist-server/engine/log.js +80 -0
  36. package/dist-server/engine/log.js.map +1 -0
  37. package/dist-server/engine/loop-lag.d.ts +54 -0
  38. package/dist-server/engine/loop-lag.js +87 -0
  39. package/dist-server/engine/loop-lag.js.map +1 -0
  40. package/dist-server/engine/model-gap.d.ts +108 -0
  41. package/dist-server/engine/model-gap.js +95 -0
  42. package/dist-server/engine/model-gap.js.map +1 -0
  43. package/dist-server/engine/restart-policy.d.ts +3 -3
  44. package/dist-server/engine/restart-policy.js +4 -4
  45. package/dist-server/engine/restart-policy.js.map +1 -1
  46. package/dist-server/engine/runtime-key.js +1 -1
  47. package/dist-server/engine/runtime-key.js.map +1 -1
  48. package/dist-server/engine/stage-path.d.ts +83 -0
  49. package/dist-server/engine/stage-path.js +118 -0
  50. package/dist-server/engine/stage-path.js.map +1 -0
  51. package/dist-server/engine/twin-engine.d.ts +352 -16
  52. package/dist-server/engine/twin-engine.js +1037 -166
  53. package/dist-server/engine/twin-engine.js.map +1 -1
  54. package/dist-server/index.js +34 -2
  55. package/dist-server/index.js.map +1 -1
  56. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.d.ts +5 -0
  57. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +58 -0
  58. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -0
  59. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.d.ts +5 -0
  60. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +55 -0
  61. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -0
  62. package/dist-server/migrations/index.js +7 -1
  63. package/dist-server/migrations/index.js.map +1 -1
  64. package/dist-server/service/index.d.ts +5 -2
  65. package/dist-server/service/index.js +18 -7
  66. package/dist-server/service/index.js.map +1 -1
  67. package/dist-server/service/reference/discovery-result.d.ts +1 -1
  68. package/dist-server/service/reference/discovery-result.js +1 -1
  69. package/dist-server/service/reference/discovery-result.js.map +1 -1
  70. package/dist-server/service/reference/index.d.ts +4 -1
  71. package/dist-server/service/reference/index.js +6 -1
  72. package/dist-server/service/reference/index.js.map +1 -1
  73. package/dist-server/service/reference/reference-adapter.d.ts +184 -3
  74. package/dist-server/service/reference/reference-adapter.js +28 -4
  75. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  76. package/dist-server/service/reference/reference-assessment.d.ts +68 -0
  77. package/dist-server/service/reference/reference-assessment.js +136 -0
  78. package/dist-server/service/reference/reference-assessment.js.map +1 -0
  79. package/dist-server/service/reference/reference-live.d.ts +12 -1
  80. package/dist-server/service/reference/reference-live.js +116 -11
  81. package/dist-server/service/reference/reference-live.js.map +1 -1
  82. package/dist-server/service/reference/reference-master.d.ts +47 -1
  83. package/dist-server/service/reference/reference-master.js +34 -4
  84. package/dist-server/service/reference/reference-master.js.map +1 -1
  85. package/dist-server/service/reference/reference-probe.d.ts +92 -0
  86. package/dist-server/service/reference/reference-probe.js +186 -0
  87. package/dist-server/service/reference/reference-probe.js.map +1 -0
  88. package/dist-server/service/reference/reference-progress-subscription.d.ts +17 -0
  89. package/dist-server/service/reference/reference-progress-subscription.js +94 -0
  90. package/dist-server/service/reference/reference-progress-subscription.js.map +1 -0
  91. package/dist-server/service/reference/reference-progress.d.ts +38 -0
  92. package/dist-server/service/reference/reference-progress.js +71 -0
  93. package/dist-server/service/reference/reference-progress.js.map +1 -0
  94. package/dist-server/service/reference/reference-resolver.js +210 -43
  95. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  96. package/dist-server/service/reference/twin-reference.d.ts +32 -0
  97. package/dist-server/service/reference/twin-reference.js +10 -0
  98. package/dist-server/service/reference/twin-reference.js.map +1 -1
  99. package/dist-server/service/twin-control/twin-control-mutation.js +4 -3
  100. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  101. package/dist-server/service/twin-event/journal-count.d.ts +21 -0
  102. package/dist-server/service/twin-event/journal-count.js +26 -0
  103. package/dist-server/service/twin-event/journal-count.js.map +1 -0
  104. package/dist-server/service/twin-event/twin-event-keys.d.ts +9 -0
  105. package/dist-server/service/twin-event/twin-event-keys.js +15 -18
  106. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  107. package/dist-server/service/twin-event/twin-event-type.d.ts +3 -1
  108. package/dist-server/service/twin-event/twin-event-type.js +18 -1
  109. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  110. package/dist-server/service/twin-event/twin-event.d.ts +1 -0
  111. package/dist-server/service/twin-event/twin-event.js +9 -1
  112. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  113. package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
  114. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  115. package/dist-server/service/twin-ingest-window/index.d.ts +5 -0
  116. package/dist-server/service/twin-ingest-window/index.js +10 -0
  117. package/dist-server/service/twin-ingest-window/index.js.map +1 -0
  118. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +12 -0
  119. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +123 -0
  120. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -0
  121. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.d.ts +1 -0
  122. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +51 -0
  123. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -0
  124. package/dist-server/service/twin-ingest-window/twin-ingest-window.d.ts +16 -0
  125. package/dist-server/service/twin-ingest-window/twin-ingest-window.js +108 -0
  126. package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +1 -0
  127. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  128. package/dist-server/service/twin-journal/twin-journal-query.js +58 -5
  129. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  130. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +14 -0
  131. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +72 -2
  132. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  133. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  134. package/dist-server/service/twin-metrics/twin-metrics-query.js +78 -3
  135. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  136. package/dist-server/service/twin-model/axis-journal-evidence.d.ts +2 -0
  137. package/dist-server/service/twin-model/axis-journal-evidence.js +89 -0
  138. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -0
  139. package/dist-server/service/twin-model/epcis-coverage.d.ts +1 -1
  140. package/dist-server/service/twin-model/epcis-coverage.js +19 -7
  141. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  142. package/dist-server/service/twin-model/iec61850-coverage.d.ts +1 -1
  143. package/dist-server/service/twin-model/iec61850-coverage.js +18 -7
  144. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  145. package/dist-server/service/twin-model/isa95-coverage.d.ts +14 -0
  146. package/dist-server/service/twin-model/isa95-coverage.js +23 -9
  147. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  148. package/dist-server/service/twin-model/item-ref.d.ts +24 -0
  149. package/dist-server/service/twin-model/item-ref.js +88 -0
  150. package/dist-server/service/twin-model/item-ref.js.map +1 -0
  151. package/dist-server/service/twin-model/name-index.d.ts +36 -0
  152. package/dist-server/service/twin-model/name-index.js +116 -0
  153. package/dist-server/service/twin-model/name-index.js.map +1 -0
  154. package/dist-server/service/twin-model/project-structure.js +1 -1
  155. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  156. package/dist-server/service/twin-model/status-tally.d.ts +9 -0
  157. package/dist-server/service/twin-model/status-tally.js +37 -0
  158. package/dist-server/service/twin-model/status-tally.js.map +1 -0
  159. package/dist-server/service/twin-model/twin-lineage-query.js +40 -12
  160. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  161. package/dist-server/service/twin-model/twin-model-item-query.js +38 -39
  162. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  163. package/dist-server/service/twin-model/twin-model-query.js +156 -4
  164. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  165. package/dist-server/service/twin-model/twin-model-tree-query.js +7 -0
  166. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  167. package/dist-server/service/twin-readiness/index.d.ts +2 -0
  168. package/dist-server/service/twin-readiness/index.js +6 -0
  169. package/dist-server/service/twin-readiness/index.js.map +1 -0
  170. package/dist-server/service/twin-readiness/twin-readiness-query.d.ts +3 -0
  171. package/dist-server/service/twin-readiness/twin-readiness-query.js +103 -0
  172. package/dist-server/service/twin-readiness/twin-readiness-query.js.map +1 -0
  173. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  174. package/dist-server/service/twin-space/twin-space-resolver.js +5 -4
  175. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  176. package/dist-shared/entity-delta.d.ts +23 -3
  177. package/dist-shared/entity-delta.js +12 -8
  178. package/dist-shared/entity-delta.js.map +1 -1
  179. package/dist-shared/kpi-broadcast.js +1 -1
  180. package/dist-shared/kpi-broadcast.js.map +1 -1
  181. package/dist-shared/touched-items.d.ts +7 -0
  182. package/dist-shared/touched-items.js +80 -0
  183. package/dist-shared/touched-items.js.map +1 -0
  184. package/package.json +7 -7
  185. package/server/engine/canonical-ingest.ts +92 -7
  186. package/server/engine/command-routing.ts +1 -1
  187. package/server/engine/index.ts +4 -0
  188. package/server/engine/ingest-health.ts +704 -0
  189. package/server/engine/integration-coverage.ts +147 -0
  190. package/server/engine/integration-probes.ts +144 -0
  191. package/server/engine/integration-runner.ts +95 -0
  192. package/server/engine/integration-target-profile.ts +103 -0
  193. package/server/engine/kpi-fold.ts +72 -4
  194. package/server/engine/kpi-query.ts +129 -15
  195. package/server/engine/live-feed-registry.ts +2 -1
  196. package/server/engine/load-meter.ts +12 -3
  197. package/server/engine/log.ts +72 -0
  198. package/server/engine/loop-lag.ts +120 -0
  199. package/server/engine/model-gap.ts +168 -0
  200. package/server/engine/restart-policy.ts +4 -4
  201. package/server/engine/runtime-key.ts +1 -1
  202. package/server/engine/stage-path.ts +172 -0
  203. package/server/engine/twin-engine.ts +1135 -162
  204. package/server/index.ts +35 -2
  205. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +54 -0
  206. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +53 -0
  207. package/server/migrations/index.ts +7 -1
  208. package/server/service/index.ts +11 -0
  209. package/server/service/reference/discovery-result.ts +1 -1
  210. package/server/service/reference/index.ts +6 -1
  211. package/server/service/reference/reference-adapter.ts +197 -5
  212. package/server/service/reference/reference-assessment.ts +215 -0
  213. package/server/service/reference/reference-live.ts +123 -11
  214. package/server/service/reference/reference-master.ts +64 -4
  215. package/server/service/reference/reference-probe.ts +264 -0
  216. package/server/service/reference/reference-progress-subscription.ts +73 -0
  217. package/server/service/reference/reference-progress.ts +95 -0
  218. package/server/service/reference/reference-resolver.ts +200 -12
  219. package/server/service/reference/twin-reference.ts +39 -1
  220. package/server/service/twin-control/twin-control-mutation.ts +4 -3
  221. package/server/service/twin-event/journal-count.ts +53 -0
  222. package/server/service/twin-event/twin-event-keys.ts +16 -1
  223. package/server/service/twin-event/twin-event-type.ts +26 -2
  224. package/server/service/twin-event/twin-event.ts +7 -0
  225. package/server/service/twin-forecast/twin-forecast-query.ts +2 -1
  226. package/server/service/twin-ingest-window/index.ts +7 -0
  227. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +125 -0
  228. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +57 -0
  229. package/server/service/twin-ingest-window/twin-ingest-window.ts +113 -0
  230. package/server/service/twin-instance/twin-instance.ts +1 -1
  231. package/server/service/twin-journal/twin-journal-query.ts +59 -5
  232. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +77 -3
  233. package/server/service/twin-metrics/twin-metrics-query.ts +81 -3
  234. package/server/service/twin-model/axis-journal-evidence.ts +57 -0
  235. package/server/service/twin-model/epcis-coverage.ts +2 -8
  236. package/server/service/twin-model/iec61850-coverage.ts +2 -8
  237. package/server/service/twin-model/isa95-coverage.ts +22 -9
  238. package/server/service/twin-model/item-ref.ts +80 -0
  239. package/server/service/twin-model/name-index.ts +94 -0
  240. package/server/service/twin-model/project-structure.ts +1 -1
  241. package/server/service/twin-model/status-tally.ts +37 -0
  242. package/server/service/twin-model/twin-lineage-query.ts +38 -9
  243. package/server/service/twin-model/twin-model-item-query.ts +26 -27
  244. package/server/service/twin-model/twin-model-query.ts +155 -4
  245. package/server/service/twin-model/twin-model-tree-query.ts +7 -0
  246. package/server/service/twin-readiness/index.ts +3 -0
  247. package/server/service/twin-readiness/twin-readiness-query.ts +96 -0
  248. package/server/service/twin-space/twin-space-resolver.ts +5 -4
  249. package/shared/entity-delta.ts +31 -7
  250. package/shared/kpi-broadcast.ts +1 -1
  251. package/shared/touched-items.ts +73 -0
  252. package/test/axis-journal-evidence.test.ts +71 -0
  253. package/test/axis-read.test.ts +101 -1
  254. package/test/boot-resume.test.ts +92 -26
  255. package/test/broadcast-cost-baseline.test.ts +200 -0
  256. package/test/broadcast-period.test.ts +58 -0
  257. package/test/canonical-ingest-vocabularies.test.ts +36 -1
  258. package/test/canonical-quantity-door.test.ts +61 -0
  259. package/test/command-routing.test.ts +1 -1
  260. package/test/declared-location-types.test.ts +89 -0
  261. package/test/discovery-result.test.ts +1 -1
  262. package/test/duration-estimators.test.ts +1 -1
  263. package/test/entity-delta.test.ts +2 -2
  264. package/test/ingest-bench.test.ts +3 -3
  265. package/test/ingest-health-engine.test.ts +248 -0
  266. package/test/ingest-health-wiring.test.ts +119 -0
  267. package/test/ingest-health.test.ts +306 -0
  268. package/test/ingest-history.test.ts +247 -0
  269. package/test/integration-probes.test.ts +103 -0
  270. package/test/integration-runner.test.ts +95 -0
  271. package/test/item-ref.test.ts +78 -0
  272. package/test/journal-read-discipline.test.ts +177 -0
  273. package/test/journal-retention.test.ts +133 -0
  274. package/test/journal-sort-axis.test.ts +142 -0
  275. package/test/journal-write-door.test.ts +110 -0
  276. package/test/journal-write-trend.test.ts +115 -0
  277. package/test/kernel-kind-guard.test.ts +4 -4
  278. package/test/kpi-fold.test.ts +90 -3
  279. package/test/kpi-query-bench.test.ts +2 -2
  280. package/test/lineage-survives-restart.test.ts +17 -1
  281. package/test/live-cursor-wiring.test.ts +87 -0
  282. package/test/live-feed-registry.test.ts +9 -1
  283. package/test/live-kernel-facts.test.ts +7 -7
  284. package/test/live-mirror-parity.test.ts +6 -0
  285. package/test/load-meter.test.ts +29 -15
  286. package/test/local-declarations.test.ts +1 -1
  287. package/test/log-stamp.test.ts +59 -0
  288. package/test/loop-lag.test.ts +82 -0
  289. package/test/model-gap.test.ts +153 -0
  290. package/test/oee-accumulator.test.ts +4 -0
  291. package/test/operational-vocabulary.test.ts +4 -4
  292. package/test/read-failure-visible.test.ts +145 -0
  293. package/test/reference-grounding.test.ts +70 -0
  294. package/test/resolve-ts-siblings.mjs +52 -0
  295. package/test/restart-policy.test.ts +7 -7
  296. package/test/revision-axis.test.ts +93 -0
  297. package/test/runtime-key.test.ts +2 -2
  298. package/test/scale-twin-bench.test.ts +2 -2
  299. package/test/spec-coverage.test.ts +1 -1
  300. package/test/stage-path.test.ts +95 -0
  301. package/test/standard-coverage.test.ts +15 -3
  302. package/test/status-tally.test.ts +55 -0
  303. package/test/structure-revision-db.test.ts +4 -0
  304. package/test/tenant-registry-db.test.ts +1 -1
  305. package/test/time-range.test.ts +152 -0
  306. package/test/touched-items.test.ts +61 -0
  307. package/test/twin-event-keys.test.ts +10 -2
  308. package/test/twin-model-item-db.test.ts +2 -0
  309. package/test/warm-start-seam.test.ts +4 -0
  310. package/test/yield-loop.test.ts +57 -2
  311. package/tsconfig.shared.tsbuildinfo +1 -1
  312. package/tsconfig.tsbuildinfo +1 -1
@@ -7,6 +7,7 @@ import { type ModelBasis } from './model-basis.js';
7
7
  import { type LoadMeter, type LoadPhase } from './load-meter.js';
8
8
  import { type StructureDiff } from './structure-diff.js';
9
9
  import { OeeAccumulator } from './oee-accumulator.js';
10
+ import { type IngestHealthView, type IngestWindow } from './ingest-health.js';
10
11
  import type { TwinModelDef, StructureShift, TwinRuntime as TwinRuntimeType, CanonicalEnvelope } from '@operato/twin-kernel';
11
12
  interface TwinMetrics {
12
13
  ingestedTotal: number;
@@ -15,6 +16,18 @@ interface TwinMetrics {
15
16
  ingestRate: number;
16
17
  broadcastRate: number;
17
18
  journalRate: number;
19
+ /**
20
+ * **지금 대기 중인** 저널 건수 — 아직 DB 로 나가지 않은 것.
21
+ *
22
+ * ── 무엇이 틀렸었나 (2026-08-21) ──────────────────────────────────────────
23
+ * 예전에는 「마지막 flush 에서 쓴 건수」를 여기 넣었다. 그런데 200ms 마다 모아 한 문으로 넣는 것은
24
+ * **정상 동작**이다(그렇게 만든 것이 어제 고친 성능 작업이다). 그래서 정상적으로 10건을 쓴 직후
25
+ * 화면에 「밀림 10」 경고가 떴고, 다음 flush 까지 그 값이 남았다 — 큐는 이미 0 인데도.
26
+ *
27
+ * 운영자에게 그것은 「저장이 10건 밀려 있다」로 읽힌다. 거짓 경보는 참 경보를 죽인다.
28
+ *
29
+ * 이제 이 값은 **지금 남아 있는 것**이다: 다 나갔으면 0 이고, 정말로 쌓이는 중이면 그 수가 자란다.
30
+ */
18
31
  backlog: number;
19
32
  _accIngest: number;
20
33
  _accBroadcast: number;
@@ -50,8 +63,19 @@ interface InstanceRuntime {
50
63
  timer?: any;
51
64
  /** 엔티티별 마지막 발행 시그니처 — 값이 바뀐 엔티티만 재발행(무변화 반복 push 방지). */
52
65
  entitySigs?: Map<string, string>;
53
- /** live 방송 병합(ingest-scale §4.4) — ingest 마다 방송하지 않고 dirty 표시 → coalescer tick 이 주기 방송. */
66
+ /** live 브로드캐스팅 병합(ingest-scale §4.4) — ingest 마다 브로드캐스팅하지 않고 dirty 표시 → coalescer tick 이 주기 브로드캐스팅. */
54
67
  dirty?: boolean;
68
+ /**
69
+ * 이 창에 **건드린 물품 태그** — 브로드캐스팅이 그것만 만든다.
70
+ *
71
+ * `undefined` 는 「말할 수 없다」다(낯선 사건이 하나라도 왔다) → 전부 만든다. 빈 집합은 「물품은
72
+ * 하나도 건드리지 않았다」이고, 그 둘은 다른 사실이다.
73
+ */
74
+ dirtyItems?: Set<string>;
75
+ /** 다음 브로드캐스팅에서 전부 만들어야 하나 — 주기마다 한 번은 그물로 전부 만든다. */
76
+ fullBroadcastDue?: boolean;
77
+ /** 전부 만들기까지 남은 창 수. */
78
+ fullBroadcastCountdown?: number;
55
79
  /** live 저널 결선 — ingest 이벤트를 모았다가 coalescer tick 에서 배치 기록(이벤트마다 DB write 금지). */
56
80
  pendingJournal?: any[];
57
81
  /** live 저널 revision 카운터(sim 의 delta revision 대응 — 시간여행·히스토리 노브 앵커). */
@@ -76,6 +100,15 @@ export declare class TwinEngine {
76
100
  /** 웜스타트 씨앗 — `instances` 와 **같은 키**(겹치면 남의 스냅샷으로 재고가 섞인다). */
77
101
  static recovered: Record<string, any>;
78
102
  static TICK_MS: number;
103
+ /** 부팅 때 트윈 하나를 되살린 뒤 루프를 비워 주는 시간(ms) — 그 사이 도착한 요청이 처리된다. */
104
+ static BOOT_YIELD_MS: number;
105
+ private static tickPhase;
106
+ /**
107
+ * 흩어진 첫 발화 뒤 주기 틱. **핸들은 항상 진짜 타이머**다 — 정지하는 쪽이 `clearInterval(inst.timer)`
108
+ * 하나로 끝내야 하므로, 첫 발화 전에는 그 `setTimeout` 을, 이후에는 `setInterval` 을 같은 자리에 둔다
109
+ * (감싼 객체를 주면 `clearInterval` 이 아무 일도 하지 않고 트윈이 멈추지 않는다 — 조용한 결함이 된다).
110
+ */
111
+ private static startTickTimer;
79
112
  /** 굶김 문턱 — 틱 간격의 배수(1초 간격이면 5초). 이 시간만큼 호스트가 멈춘다. */
80
113
  static STARVE_FACTOR: number;
81
114
  /** 연속 몇 번이면 멈추나 — 3번이면 15초를 굶긴 셈이고, 그건 우연이 아니다. */
@@ -93,6 +126,44 @@ export declare class TwinEngine {
93
126
  static CHAIN_KEEP: number;
94
127
  static SNAPSHOT_TTL_S: number;
95
128
  static CHECKPOINT_MS: number;
129
+ /**
130
+ * **저널 보존 — 지우는 것은 선언이 있을 때만 한다.**
131
+ *
132
+ * ── 왜 필요한가 (2026-08-22 실측) ─────────────────────────────────────────
133
+ * 개발 저널이 시간당 **581,794행 · 0.85GB** 로 자랐다(하루 20GB). 데모 MES 트윈 셋이 전체의 **97%**
134
+ * 를 만들고 지우는 것이 없었다. 오늘 고친 것들은 「그 크기에서도 질의가 빠르게」이고, **크기 자체를
135
+ * 줄이는 것은 아무것도 없었다.** 그러면 며칠마다 같은 자리로 돌아온다.
136
+ *
137
+ * ── 그런데 저널을 지우는 것은 사실을 잃는 일이다 ──────────────────────────
138
+ * 그래서 세 규율을 지킨다.
139
+ *
140
+ * ① **선언이 없으면 아무것도 지우지 않는다.** 기본값은 없음이다 — 조용히 지우는 편이 조용히 쌓는
141
+ * 것보다 나쁘다. 지우는 것은 사람이 정한다.
142
+ * ② **체크포인트가 대신할 수 있는 만큼만.** 스냅샷이 없거나 그 리비전을 넘는 자리는 건드리지 않는다.
143
+ * 주석이 「만료 시 저널 replay 폴백」이라고 적어 둔 그대로 — 스냅샷이 사라지면 저널이 **유일한**
144
+ * 복구 수단이므로, 둘을 함께 잃으면 그 트윈의 상태는 되돌릴 수 없다.
145
+ * ③ **지운 것을 말한다.** 몇 건을 어느 시각까지 지웠는지 로그에 남긴다. 조용히 줄어든 저널은
146
+ * 「없었던 일」과 구별되지 않는다.
147
+ *
148
+ * 그리고 보존 기간은 **스냅샷 TTL(7일)보다 짧을 수 없다.** 더 짧으면 스냅샷이 살아 있는데 그것이
149
+ * 가리키는 앞쪽 저널이 없는 구간이 생기고, 시간여행·계보 추적이 그 구간에서 조용히 빈다.
150
+ */
151
+ static JOURNAL_RETENTION_DAYS?: number;
152
+ /**
153
+ * **도메인마다 다른 보존 기간을 앱이 답한다** — 엔진은 그 값이 어디서 오는지 모른다.
154
+ *
155
+ * 보존 기간은 테넌트의 정책이다: 한 고객은 30일이 필요하고 다른 고객은 7일이면 된다. 그런데 엔진이
156
+ * `Setting` 을 읽게 만들면 **엔진이 화면 관심사를 알게 된다** — 방향이 거꾸로다(엔진은 커널의 호스트이고
157
+ * 그 위에 어떤 화면이 있는지 몰라야 한다).
158
+ *
159
+ * 그래서 시임을 둔다. 앱이 이 함수를 심고, 그 안에서 `Setting` 이든 다른 무엇이든 읽는다. 심지 않으면
160
+ * `JOURNAL_RETENTION_DAYS`(설정 파일에서 온 프로세스 기본값)가 답한다.
161
+ *
162
+ * `undefined` 를 돌려주면 **그 도메인은 지우지 않는다** — 「모른다」를 「지워도 된다」로 읽지 않는다.
163
+ */
164
+ static retentionDaysOf?: (domainId: string) => Promise<number | undefined>;
165
+ static RETENTION_SWEEP_MS: number;
166
+ private static retentionTimer?;
96
167
  private static checkpointTimer?;
97
168
  /** 최신 스냅샷을 cache-service 에 체크포인트(도메인+instanceId 키). display-only·비차단·오류흡수. */
98
169
  static persistSnapshot(domainId: string, instanceId: string): Promise<void>;
@@ -146,6 +217,42 @@ export declare class TwinEngine {
146
217
  private static keepChainPoint;
147
218
  /** 이 트윈 저널의 끝 리비전 · 최신 구조 리비전 — 스냅샷이 지금의 사실인지 가리는 두 값. */
148
219
  private static tipOf;
220
+ /**
221
+ * **저널을 보존 기간까지만 둔다** — 체크포인트가 대신할 수 있는 만큼만 지운다.
222
+ *
223
+ * 한 인스턴스에서 지우는 조건은 **둘 다** 만족해야 한다.
224
+ *
225
+ * · `createdAt` 이 보존 기간보다 오래됐다 — **행이 쓰인 실제 시각**이 기준이다
226
+ * · `revision` 이 **체크포인트 리비전 이하**다 — 그 앞은 스냅샷이 대신한다
227
+ *
228
+ * ── 왜 `eventTime` 이 아니라 `createdAt` 인가 (2026-08-22 실측으로 고침) ────
229
+ * 처음에 `eventTime` 으로 적었다. 그것은 **트윈의 시계**다 — 시뮬레이션은 자기 시계로 사건을 찍고,
230
+ * 그 시계는 실제 시각과 무관하게 앞서거나 뒤선다. 실측:
231
+ *
232
+ * order-check 가장 늦은 eventTime = 2027-06-08 ← 실제 시각보다 10개월 앞
233
+ * hatio-mx2 2026-01-01 ~ 지금 ← 7개월치를 한 번에 지우게 된다
234
+ *
235
+ * 즉 「7일」이 트윈마다 다른 뜻이 된다: 시계가 앞선 트윈은 영원히 지워지지 않고, 과거부터 찍은 트윈은
236
+ * 거의 전부가 한 번에 지워진다. **보존은 저장 나이의 문제**이고 저장 나이는 실제 시계다.
237
+ *
238
+ * `createdAt` 은 그 행이 DB 에 쓰인 시각이고 1,599만 행 전부 채워져 있다(확인함). 도메인 시각을
239
+ * 정책에 쓰지 않는다 — 그 둘을 섞으면 시뮬과 미러에서 같은 설정이 다르게 동작한다.
240
+ *
241
+ * 스냅샷이 없으면 **그 인스턴스는 건드리지 않는다.** 저널이 유일한 복구 수단인 상태이므로, 지우면
242
+ * 그 트윈의 상태를 되돌릴 수 없다. 「지울 수 없었다」는 사실도 함께 센다 — 조용히 넘기면 「보존이
243
+ * 도는데 왜 안 줄어드나」가 된다.
244
+ *
245
+ * `createdAt` 이 빈 옛 행은 **지우지 않는다**(시각을 모르는 것을 「오래됐다」로 읽지 않는다).
246
+ *
247
+ * 드라이버 다섯을 다 지나야 하므로 raw SQL 을 쓰지 않는다 — 조건 삭제는 쿼리빌더가 이식한다.
248
+ */
249
+ static pruneJournal(domainId: string): Promise<{
250
+ deleted: number;
251
+ instances: number;
252
+ skipped: string[];
253
+ }>;
254
+ /** 보존 정리 주기 기동(1회) — 선언이 없으면 아무것도 하지 않는다. */
255
+ static startRetentionLoop(domainId: string): void;
149
256
  /** 체크포인트 루프 기동(1회) — 라이브 인스턴스들의 최신 스냅샷을 주기 영속. */
150
257
  static startCheckpointLoop(): void;
151
258
  /**
@@ -385,33 +492,94 @@ export declare class TwinEngine {
385
492
  static withSpaceTimeBase(model: TwinModelDef, domainId: string, spaceId?: string): Promise<TwinModelDef>;
386
493
  static startLive(id: string, domainId: string, kind: string, model: TwinModelDef): InstanceRuntime;
387
494
  /**
388
- * live 이벤트 인제스트 — projector 구동 + data(tag) 방송(폐루프의 인바운드 도착 지점, command-routing §8.2).
495
+ * live 이벤트 인제스트 — projector 구동 + data(tag) 브로드캐스팅(폐루프의 인바운드 도착 지점, command-routing §8.2).
389
496
  * reference 어댑터가 낸 records → 커널 face2-adapter.ingest → CanonicalEnvelope 를 여기로 밀어넣는다.
390
497
  * (State 채널 델타/저널 결선은 후속 — 스켈레톤은 data(tag) 미러 중심.)
498
+ *
499
+ * ── 넣은 수를 **답한다** (2026-08-20) ────────────────────────────────────────
500
+ * 트윈이 라이브로 돌지 않으면 여기서 봉투를 버린다. 그것 자체는 맞다(넣을 커널이 없다). 문제는
501
+ * **조용히** 버린 것이었다: 트윈이 멈춘 뒤에도 피드는 남아 레코드를 나르고, 유입 장부는 그것을
502
+ * 「통과」로 셌다. 화면은 멈춘 트윈 옆에 「150 통과 · 100%」라고 적었다 — 사실이 사라지는 동안
503
+ * 화면이 안심시킨 것이다.
504
+ *
505
+ * 그래서 **넣은 수를 돌려준다.** 부르는 쪽이 제시 수와 견주어 버려진 수를 장부에 적는다. 반환을
506
+ * 무시하는 호출부는 그대로 동작한다(전과 같다).
391
507
  */
392
- static ingestLive(domainId: string, id: string, envelopes: CanonicalEnvelope[]): void;
508
+ static ingestLive(domainId: string, id: string, envelopes: CanonicalEnvelope[]): number;
393
509
  /**
394
- * 구간 성과 방송은 **없앴다**(2026-08-06). 카드가 `twinKpi` 를 직접 묻는다.
510
+ * 구간 성과 브로드캐스팅은 **없앴다**(2026-08-06). 카드가 `twinKpi` 를 직접 묻는다.
395
511
  *
396
- * 왜: 카드를 여러 단계(공간·트윈·구역·자리·설비)에 붙이려면 방송으로는 태그가 트윈당 1,200개가 되고,
512
+ * 왜: 카드를 여러 단계(공간·트윈·구역·자리·설비)에 붙이려면 브로드캐스팅으로는 태그가 트윈당 1,200개가 되고,
397
513
  * **모델에 카드를 하나도 안 놓아도** 30초마다 트윈마다 저널을 접었다. 질의로 바꾸니 보고 있는 카드
398
514
  * 수만큼만 들고, 같은 (대상·창·축) 은 클라이언트가 하나로 합친다.
399
515
  *
400
516
  * 덤으로 질의만 할 수 있는 것이 둘 생겼다 — **과거 시각**(`toTime`)과 **공간 단위 합산**(여러 트윈을
401
- * 한 번에 접기). 방송 루프는 트윈별이라 둘 다 못 했다.
517
+ * 한 번에 접기). 브로드캐스팅 루프는 트윈별이라 둘 다 못 했다.
402
518
  *
403
519
  * 축을 나눠도 폴드 비용이 같다는 실측이 근거다(`test/kpi-query-bench.test.ts`).
404
520
  */
405
- /** 방송 병합 주기(ms) — 방송률 상한. 인제스트가 아무리 빨라도 이 주기로만 방송. */
521
+ /**
522
+ * 몇 창마다 한 번은 **전부** 만드나 — 사건 없이 값이 바뀌는 자리에 대한 그물.
523
+ *
524
+ * 25 창이면 기본 주기에서 5초다. 보장이 아니라 그물이다(위 `publishEntityData` 주석).
525
+ */
526
+ static FULL_BROADCAST_EVERY: number;
527
+ /** 전부 만든 횟수 — 범위를 좁히지 못한 창이 얼마나 되는지 값으로 남는다. */
528
+ static broadcastFullPasses: number;
529
+ /**
530
+ * 브로드캐스팅을 만든 횟수 전부 — **전부 만든 횟수의 분모.**
531
+ *
532
+ * 분자만 내면 「전부 만들기 1,200회」가 많은 것인지 적은 것인지 읽을 수 없다. 좁히기가 듣고 있으면
533
+ * 이 값의 `1/FULL_BROADCAST_EVERY` 쯤이 전부 만든 횟수이고, 두 값이 비슷하면 범위를 거의 못 좁힌
534
+ * 것이다(원인은 대개 「모른다」로 떨어지는 사건이다).
535
+ */
536
+ static broadcastPasses: number;
537
+ /**
538
+ * 이 창에 건드린 물품을 모은다 — **말할 수 없으면 범위를 버린다(전부 만든다).**
539
+ *
540
+ * `events` 를 주지 않으면 「무엇이 바뀌었는지 모른다」다(구조 전환처럼 상태 전반이 달라지는 자리).
541
+ * 한 창에서 한 번 「모른다」가 되면 그 창은 끝까지 모르는 채로 둔다 — 뒤에 온 사건으로 범위를
542
+ * 되살리면 앞 사건이 건드린 것을 빠뜨린다.
543
+ */
544
+ static markItemsDirty(inst: InstanceRuntime, events?: any[]): void;
545
+ /** 브로드캐스팅 병합 주기(ms) — 브로드캐스팅률 상한. 인제스트가 아무리 빨라도 이 주기로만 브로드캐스팅. */
406
546
  static BROADCAST_COALESCE_MS: number;
547
+ /**
548
+ * ── 브로드캐스팅 주기는 **재 본 비용에 맞춘다** (2026-08-21 실측) ────────────────────
549
+ * 한 번의 브로드캐스팅은 상태 크기에 비례한다(실측: 물품 2,400 개인 트윈 하나가 4.5ms — 상태 투영 1.7ms,
550
+ * payload 만들기 1.8ms, 시그니처 1.0ms). 트윈이 스무 개면 200ms 마다 90ms 가 브로드캐스팅에 들어가고,
551
+ * 그 시간에는 HTTP 도 구독도 서지 못한다.
552
+ *
553
+ * 그래서 한 창에서 브로드캐스팅에 쓴 시간이 주기의 일정 몫을 넘으면 **주기를 늘린다**. 화면은 조금 늦게
554
+ * 갱신되고(최신-상태 채널이라 값은 마지막 것 하나뿐이므로 내용은 같다), 늘렸다는 것은 계기판이
555
+ * 말한다(`broadcastCoalesceMs`). 여유가 생기면 원래 주기로 되돌린다.
556
+ *
557
+ * 이것은 브로드캐스팅 비용을 **줄이는 것이 아니다** — 비용을 줄이는 것은 변경분만 만드는 일이고 그것은 별
558
+ * 작업이다. 여기서는 그때까지 호스트가 굶지 않게 상한을 둔다.
559
+ */
560
+ static BROADCAST_MAX_COALESCE_MS: number;
561
+ /** 주기의 몇 몫까지 브로드캐스팅에 써도 되는가 — 넘으면 주기를 늘린다(절반이면 나머지 절반은 남긴다). */
562
+ static BROADCAST_LOAD_RATIO: number;
563
+ /** 지금 쓰고 있는 주기(ms) — 계기판이 이 값을 읽는다. 늘어난 채로 있으면 그것이 사실이다. */
564
+ static broadcastPeriodMs: number;
565
+ /** 주기를 늘린 횟수 — 조용히 늦추지 않는다. */
566
+ static broadcastBackoffs: number;
407
567
  private static broadcastTimer?;
408
- /** live 방송 coalescer — dirty 인 live 인스턴스만 주기적으로 1회 방송(entitySigs 로 변경 엔티티만). */
568
+ /** live 브로드캐스팅 coalescer — dirty 인 live 인스턴스만 주기적으로 1회 브로드캐스팅(entitySigs 로 변경 엔티티만). */
409
569
  private static ensureBroadcastCoalescer;
410
570
  /**
411
- * dirty 인스턴스 방송 flush(주기 tick 또는 명시 호출) **시뮬과 라이브 다.**
571
+ * 브로드캐스팅에 시간을 보고 주기를 정한다 **늘리는 것도 줄이는 것도 값에 근거한다.**
412
572
  *
413
- * 예전에는 라이브만 봤다(`mode !== 'live'` 건너뜀). 시뮬은 delta 마다 곧바로 방송했고, 그것이
414
- * tick 에서 수백 반복되며 이벤트 루프를 막았다(위 `start()` 주석의 35초 틱). 방송을 모으는
573
+ * 번의 flush 주기의 `BROADCAST_LOAD_RATIO` 넘게 쓰면 주기를 배로(상한까지), 몫의
574
+ * 절반 아래로 내려오면 절반으로(원래 주기까지) 되돌린다. 문턱을 두는 이유는 하나면 경계에서
575
+ * 늘리고 줄이기를 반복하기 때문이다.
576
+ */
577
+ private static adjustBroadcastPeriod;
578
+ /**
579
+ * dirty 인스턴스 브로드캐스팅 flush(주기 tick 또는 명시 호출) — **시뮬과 라이브 둘 다.**
580
+ *
581
+ * 예전에는 라이브만 봤다(`mode !== 'live'` 면 건너뜀). 시뮬은 delta 마다 곧바로 브로드캐스팅했고, 그것이
582
+ * 한 tick 에서 수백 번 반복되며 이벤트 루프를 막았다(위 `start()` 주석의 35초 틱). 브로드캐스팅을 모으는
415
583
  * 규율은 모드의 성질이 아니라 **채널의 성질**이다 — 최신-상태 채널이면 중간 상태는 보낼 값이 없다.
416
584
  */
417
585
  static flushLiveBroadcasts(): void;
@@ -429,8 +597,45 @@ export declare class TwinEngine {
429
597
  * 없으면 `undefined` 다 — 0 이 아니다. 구조 리비전이 생기기 전에 만들어진 트윈은 아직 리비전이
430
598
  * 없고, 그 사실을 0 이라는 **유효해 보이는 번호**로 위장하면 안 된다.
431
599
  */
600
+ /** 구조 리비전 캐시 — `null` 은 **없다는 것을 알고 있다**는 뜻이다(모름과 구별한다, §7.1). */
432
601
  private static structureRevCache;
433
602
  static structureRevOf(domainId: string, instanceId: string): Promise<number | undefined>;
603
+ /** 계기 한 벌 — **두 구동이 같은 것을 든다**(한쪽만 들면 그 구동은 물어도 답이 없다). */
604
+ private static newMetrics;
605
+ /**
606
+ * 모아 둔 저널을 **한 번에** 쓴다 — 두 구동이 같은 문을 쓴다 (§7.1).
607
+ *
608
+ * ── 왜 한 함수인가 ─────────────────────────────────────────────────────────
609
+ * 쓰는 자리가 둘이면(주기 flush · 정지) 한쪽만 고쳐지고, 그 어긋남은 **사실이 조용히 사라지는**
610
+ * 모양으로 나타난다. 그래서 흘리는 규칙을 여기 한 곳에 둔다.
611
+ *
612
+ * ── 리비전을 누가 매기나 ───────────────────────────────────────────────────
613
+ * · 라이브 — 원천은 리비전을 주지 않으므로 **호스트가** 이어 붙인다(저널 high-water 에서 시드).
614
+ * · 시뮬 — **커널의 리비전**이 실려 온다(저널이 그것으로 접히고 시간여행이 그것을 딛는다).
615
+ * 그래서 버퍼는 두 모양을 함께 든다: 봉투만 있으면 라이브, `{ event, revision }` 이면 시뮬이다.
616
+ */
617
+ private static flushJournal;
618
+ /**
619
+ * 커널이 매긴 리비전을 그대로 들고 벌크 저장 — 시뮬 경로.
620
+ *
621
+ * 예전에는 이 경로가 **델타마다 한 행씩** 저장했다(그리고 행마다 구조 리비전을 물었다). 규모에서 그것이
622
+ * 호스트를 먹었다(§7.1 실측). 여기서 구조 리비전은 **한 번만** 묻는다.
623
+ */
624
+ static persistCarried(domainId: string, instanceId: string, items: {
625
+ event: any;
626
+ revision: number;
627
+ }[]): Promise<void>;
628
+ /**
629
+ * 저널 행을 넣는다 — **넣기만 한다**(2026-08-20).
630
+ *
631
+ * 예전에는 `save()` 였다. 그런데 `save` 는 넣은 뒤 생성 컬럼을 읽으려고 **행마다 SELECT 를 한 번 더**
632
+ * 한다(시험 로그에서 그 질의가 그대로 보였다: `SELECT … FROM twin_events WHERE id = ?`). 저널은
633
+ * append-only 이고 부르는 쪽은 돌려받은 엔티티를 쓰지 않으므로 그 왕복이 순수 낭비다.
634
+ *
635
+ * 묶음은 **나눠서** 넣는다: 한 문에 열이 열다섯인 행을 수천 개 실으면 드라이버의 파라미터 한계에
636
+ * 걸린다(pg 는 65,535개). 500행이면 어느 드라이버에서도 안전하다.
637
+ */
638
+ private static insertRows;
434
639
  /** live 저널 배치 기록 — 모아둔 CanonicalEnvelope 들에 startRevision+1.. 을 부여해 벌크 저장(coalescer tick 당 1회). */
435
640
  static persistBatch(domainId: string, instanceId: string, envelopes: any[], startRevision: number): Promise<void>;
436
641
  /** 레지스트리 upsert(도메인+instanceId 유니크). status 인자로 provision(stopped)/start(running) 공용. */
@@ -623,14 +828,14 @@ export declare class TwinEngine {
623
828
  * 보드 컴포넌트가 tag 로 구독(board-ui provider)해 `component.data` 로 라이브 갱신. delta 시에만(희소).
624
829
  * tag=엔티티 id(데모=단일 인스턴스). 멀티 인스턴스/보드 재사용 시 tag 네임스페이스는 후속.
625
830
  */
626
- /** 방송 실패 로그 조절 — 창마다 한 줄(막힌 구독자는 초당 수십 번 실패한다). */
831
+ /** 브로드캐스팅 실패 로그 조절 — 창마다 한 줄(막힌 구독자는 초당 수십 번 실패한다). */
627
832
  private static publishDrops;
628
833
  private static readonly PUBLISH_DROP_LOG_MS;
629
834
  /**
630
- * 한 번의 방송 — **구독자 하나가 호스트를 죽이지 못하게.**
835
+ * 한 번의 브로드캐스팅 — **구독자 하나가 호스트를 죽이지 못하게.**
631
836
  *
632
837
  * ── 무엇이 죽였나 (2026-08-14) ─────────────────────────────────────────────
633
- * 밀린 push 가 1024를 넘으면 pubsub 이 던진다(`RepeaterOverflowError`). 그 방송은 타이머 콜백
838
+ * 밀린 push 가 1024를 넘으면 pubsub 이 오류를 낸다(`RepeaterOverflowError`). 그 브로드캐스팅은 타이머 콜백
634
839
  * 안에서 일어나므로 예외가 잡히는 곳 없이 올라가 **프로세스가 끝났다** — 트윈 14개가 도는 호스트가
635
840
  * 소비를 멈춘 구독자 하나 때문에 통째로.
636
841
  *
@@ -639,7 +844,6 @@ export declare class TwinEngine {
639
844
  */
640
845
  private static publishGuarded;
641
846
  static publishEntityData(inst: InstanceRuntime): void;
642
- static persist(domainId: string, instanceId: string, msg: any): Promise<void>;
643
847
  /**
644
848
  * 재부팅 복구 / 시간여행 — DB 저널을 replay 해 상태 재구성.
645
849
  * model 는 레지스트리(TwinInstance)에서, 이벤트는 TwinEvent(revision ASC)에서.
@@ -652,6 +856,29 @@ export declare class TwinEngine {
652
856
  /**
653
857
  * 공간(공동배치) 시각 범위 — 스크러버 앵커(runtime-state-model §4·§6). 그 공간 전 인스턴스 저널의 min/max eventTime.
654
858
  * 반환 {minTime, maxTime}(ISO) — 이벤트/시각 없으면 null. 클라 히스토리 스크러버가 이 범위를 시각축으로 그린다.
859
+ *
860
+ * ── 두 수를 구하려고 저널을 다 읽지 않는다 (2026-08-20 실측으로 잡음) ────────
861
+ * 여기서 인스턴스마다 `find()` 로 **행을 전부 엔티티로 하이드레이션**한 다음 JS 에서 min/max 를
862
+ * 골랐다. 그런데 이 함수는 화면 상단의 컨텍스트 띠가 **4초마다** 부른다. 실측한 개발 서버에서
863
+ * `order-check` 한 트윈이 275,882행 · `payload` 131MB 였다 — 4초마다 그 JSON 을 전부 파싱한 것이다.
864
+ *
865
+ * 그 결과가 이랬다: 프로세스 CPU 81~152%, 아무 일도 하지 않는 질의가 8~14초. JS 프로파일의 상위가
866
+ * TypeORM 의 `RelationIdLoader`·`RawSqlResultsToEntityTransformer`·`stringToSimpleJson`(= `payload`
867
+ * 파싱)이고 GC 가 16.7% 였다. **트윈 틱도 저널 쓰기도 아니라 이 읽기였다.**
868
+ *
869
+ * 집계는 DB 가 한다. `(domain, instanceId, eventTime)` 인덱스가 이미 있어(`ix_twin_event_1`)
870
+ * 인덱스의 **양 끝을 집는다** — 행을 하나도 실어 오지 않는다.
871
+ *
872
+ * ── 왜 MIN 과 MAX 를 한 문장에 넣지 않나 (실측) ─────────────────────────────
873
+ * 처음에 `SELECT MIN(...), MAX(...) ... WHERE instanceId IN (...)` 한 방으로 두었더니 **21ms** 였다.
874
+ * 집계가 **둘이면** 옵티마이저의 「인덱스 끝을 집는」 최적화가 걸리지 않아 인덱스 구간을 훑는다 —
875
+ * 즉 비용이 여전히 **행 수에 비례**한다. 트윈마다 단일 집계로 나눠 물으면 **0.32ms**(트윈 3개 · 6왕복)
876
+ * 이고, 비용이 **트윈 수에 비례**한다. 규모 기준(엔티티 10만)에서는 이 차이가 본질이다.
877
+ *
878
+ * 왕복은 트윈당 둘이지만 **함께 띄운다** — 원격 DB 에서 직렬로 돌면 왕복 지연이 그대로 쌓인다.
879
+ *
880
+ * 드라이버 다섯을 다 지나가야 하므로 raw SQL 을 쓰지 않는다(쿼리빌더의 MIN/MAX 는 이식된다).
881
+ * 돌려주는 값의 **모양은 드라이버마다 다르다**(문자열·Date) — 받은 뒤에 한 번 정규화한다(`parseTime`).
655
882
  */
656
883
  static timeRange(domainId: string, spaceId: string): Promise<{
657
884
  minTime: string | null;
@@ -772,7 +999,116 @@ export declare class TwinEngine {
772
999
  static countForks(domainId: string, id: string, count: number): void;
773
1000
  /** 엔진 밖(예측 질의 등)에서 무거운 작업을 잰다 — 같은 계기판에 모인다. */
774
1001
  static recordLoad(domainId: string, id: string, phase: LoadPhase, tookMs: number): void;
775
- /** 전체 라이브 인스턴스 계측(모니터 대시보드용). */
1002
+ private static ingestLedgers;
1003
+ /** 창이 닫힐 때 부를 곳 — 영속 계층이 부팅에서 한 번 등록한다(커널 층은 그 계층을 모른다). */
1004
+ private static onWindowClosed?;
1005
+ /**
1006
+ * 닫힌 창을 받을 곳을 등록한다 — **영속(B)이 붙는 유일한 문.**
1007
+ *
1008
+ * 이 패키지는 저장 계층을 모른다. 등록하는 쪽이 자기 방식으로 쓴다. 훅이 오류를 내도 장부는 계속 굴러간다
1009
+ * (`rollIngestWindow` 가 감싼다) — 영속을 지키려고 관측을 멈추지 않는다.
1010
+ */
1011
+ static onIngestWindowClosed(fn: (at: {
1012
+ domainId: string;
1013
+ instanceId: string;
1014
+ }, closed: IngestWindow) => void): void;
1015
+ /**
1016
+ * 한 번의 인제스트 결과를 장부에 적는다.
1017
+ *
1018
+ * `offered` 는 **제시된 레코드 수**다(거부 여부 무관). 통과율을 서로 다른 두 계수기에서 나눠 계산하면
1019
+ * 분모와 분자가 다른 것을 세게 되므로, 한자리에서 본 수를 그대로 넘긴다.
1020
+ */
1021
+ static recordIngestResult(domainId: string, instanceId: string, offered: number, rejected: {
1022
+ record?: unknown;
1023
+ errors?: string[];
1024
+ }[] | undefined, nowMs?: number,
1025
+ /**
1026
+ * 매핑은 통과했는데 **커널이 받지 않은** 수(`ingestLive` 의 반환과 견주어 얻는다).
1027
+ *
1028
+ * 「통과」로 세지 않는다 — 트윈이 멈춘 사이 사실이 사라지는데 화면이 100% 라고 말하게 된다.
1029
+ */
1030
+ undelivered?: number): void;
1031
+ /**
1032
+ * **원본에 닿지 못했다**를 적는다 — 「받은 것이 없다」와 가른다(§`recordReadFailure`).
1033
+ *
1034
+ * 이 문이 없던 동안 실 원본이 끊겨도 트윈의 조회 가능한 상태에 그 사실이 없었다. 화면이 볼 수 있는
1035
+ * 것은 「새 사실이 없다」뿐이었고 그것은 「원본이 조용하다」와 구별되지 않는다 — 실증 중에 원본이
1036
+ * 끊기면 사용자가 원인을 찾을 수 없다.
1037
+ *
1038
+ * 로그로는 말하고 있었다(어댑터가 재시도를 경고한다). 그러나 **로그는 사람이 볼 때만 값이 있다** —
1039
+ * 화면이 말하려면 상태에 있어야 한다.
1040
+ */
1041
+ static recordIngestReadFailure(domainId: string, instanceId: string, reason: string, nowMs?: number, stream?: string): void;
1042
+ /**
1043
+ * 읽기가 성공했다 — 단절 기록을 지운다.
1044
+ *
1045
+ * **빈 읽기도 성공이다.** 원본이 「새 것이 없다」고 답한 것은 닿았다는 뜻이므로, 그때도 부른다.
1046
+ * 그 둘을 같게 두면 조용한 원본이 끊긴 원본으로 보인다.
1047
+ */
1048
+ static clearIngestReadFailure(domainId: string, instanceId: string): void;
1049
+ /**
1050
+ * 저널에 **적은 것**을 같은 장부에 남긴다 — 유입과 같은 10분 창에.
1051
+ *
1052
+ * ── 왜 유입 장부에 넣나 ────────────────────────────────────────────────────
1053
+ * 새 장부를 만들면 「닫힌 창만 최근」·「0 과 없음을 가른다」·영속을 두 벌 지켜야 하고, 그중 한 벌만
1054
+ * 고쳐지는 것이 보통이다. 유입 창은 그 규율이 이미 들어 있고 닫힐 때 행으로 남는다.
1055
+ *
1056
+ * ── 이 값이 없으면 무엇을 못 보나 ──────────────────────────────────────────
1057
+ * 계기(`journalRate`)는 **지금**만 답한다. 프로세스를 다시 띄우면 0 에서 시작하므로 「어제 이 시각에도
1058
+ * 이랬나」·「고친 뒤로 줄었나」를 물을 자리가 없다. 2026-08-20 에 고친 것이 바로 쓰기 경로이므로,
1059
+ * 되돌아가는 것을 볼 수 있어야 한다.
1060
+ */
1061
+ static recordJournalWrite(domainId: string, instanceId: string, rows: number, nowMs?: number): void;
1062
+ /**
1063
+ * 「이 트윈이 현장과 맞춰지고 있나」 — 한눈 판정 + 추이 + 사유 + 표본.
1064
+ *
1065
+ * 조회할 때 창을 한 번 굴린다: 유입이 멈추면 다음 인제스트가 없어 창이 영원히 닫히지 않는데, 그러면
1066
+ * 「최근」이 옛것으로 남는다. 타이머를 두지 않는 이유는 트윈마다 타이머를 걸면 그 타이머들이 다시
1067
+ * 메인 루프에 얹히기 때문이다(오늘 확인한 그 부하를 이 기능이 다시 만들 이유가 없다).
1068
+ */
1069
+ /**
1070
+ * 도메인의 트윈마다 **한 줄 요약** — 목록 화면과 미니 추이용.
1071
+ *
1072
+ * ── 왜 상세와 따로인가 ─────────────────────────────────────────────────────
1073
+ * `ingestHealthOf` 는 사유 문구와 **레코드 원문 표본**까지 낸다. 목록에 트윈이 스무 개면 그 원문이
1074
+ * 스무 벌 실려 조회가 무거워지고, 화면은 어차피 그것을 그리지 않는다. 그래서 여기서는 **숫자만** 낸다.
1075
+ *
1076
+ * 추이는 창당 숫자 넷이라 스파크라인 하나에 충분하고 가볍다.
1077
+ *
1078
+ * ── 등록된 트윈을 기준으로 훑는다 ──────────────────────────────────────────
1079
+ * 도는 트윈만 훑으면 「고치려고 멈춰 둔 트윈」이 목록에서 사라진다 — 사람이 방금 멈춘 그것을 보려고
1080
+ * 목록을 여는데 없으면 결함으로 읽는다. 그래서 호출부(목록 화면)가 아는 instanceId 들을 받는다.
1081
+ */
1082
+ static ingestHealthBrief(domainId: string, instanceIds: string[]): {
1083
+ instanceId: string;
1084
+ verdict: string;
1085
+ acceptedRatio: number | null;
1086
+ trend: {
1087
+ offered: number;
1088
+ rejected: number;
1089
+ undelivered: number;
1090
+ rows: number;
1091
+ }[];
1092
+ lastAt: string | null;
1093
+ }[];
1094
+ /**
1095
+ * 한 트윈의 동기화 건강.
1096
+ *
1097
+ * ── 조회가 장부를 전진시킨다(의도) ─────────────────────────────────────────
1098
+ * 창은 **다음 유입이 있을 때** 닫힌다. 그래서 피드가 죽으면 마지막 창이 열린 채로 남아 영원히 영속되지
1099
+ * 않고, 추이에도 들어가지 않는다. 조회 시점에 한 번 굴려 주면 그 마지막 창이 닫히면서 영속 훅도 불린다
1100
+ * — 즉 **죽은 피드의 마지막 구간을 잃지 않기 위해** 읽기가 굴린다. 부수효과지만 필요한 부수효과다.
1101
+ *
1102
+ * 시각은 **한 번만 읽어** 굴리기와 판정에 같은 값을 쓴다. 두 번 읽으면 그 사이에 창이 닫혀 판정이
1103
+ * 굴리기 전 상태를 보는 일이 생긴다.
1104
+ */
1105
+ static ingestHealthOf(domainId: string, instanceId: string): IngestHealthView;
1106
+ /**
1107
+ * 도는 인스턴스 전체의 계측(모니터 대시보드용) — **시뮬과 미러를 함께**.
1108
+ *
1109
+ * 예전에는 시뮬에 계기가 없어 이 목록에서 조용히 빠졌다(계기가 `null` 이라 걸러졌다). 도는 트윈
1110
+ * 대부분이 시뮬인 서버에서 그 목록은 「부하가 거의 없다」로 보였다.
1111
+ */
776
1112
  static allMetrics(domainId?: string): Promise<any[]>;
777
1113
  /**
778
1114
  * 예측용 커널을 **임의 시각 T 기준**으로 재구성 — 과거-vantage 예측(백테스트)·"그때 서서 본 미래".