@things-factory/headless-twin 10.0.11 → 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 (369) hide show
  1. package/dist-server/engine/canonical-ingest.d.ts +81 -3
  2. package/dist-server/engine/canonical-ingest.js +89 -10
  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/declared-stimulus.d.ts +43 -0
  7. package/dist-server/engine/declared-stimulus.js +57 -0
  8. package/dist-server/engine/declared-stimulus.js.map +1 -0
  9. package/dist-server/engine/index.d.ts +5 -0
  10. package/dist-server/engine/index.js +6 -0
  11. package/dist-server/engine/index.js.map +1 -1
  12. package/dist-server/engine/ingest-health.d.ts +335 -0
  13. package/dist-server/engine/ingest-health.js +434 -0
  14. package/dist-server/engine/ingest-health.js.map +1 -0
  15. package/dist-server/engine/integration-coverage.d.ts +76 -0
  16. package/dist-server/engine/integration-coverage.js +73 -0
  17. package/dist-server/engine/integration-coverage.js.map +1 -0
  18. package/dist-server/engine/integration-probes.d.ts +65 -0
  19. package/dist-server/engine/integration-probes.js +100 -0
  20. package/dist-server/engine/integration-probes.js.map +1 -0
  21. package/dist-server/engine/integration-runner.d.ts +45 -0
  22. package/dist-server/engine/integration-runner.js +59 -0
  23. package/dist-server/engine/integration-runner.js.map +1 -0
  24. package/dist-server/engine/integration-target-profile.d.ts +57 -0
  25. package/dist-server/engine/integration-target-profile.js +79 -0
  26. package/dist-server/engine/integration-target-profile.js.map +1 -0
  27. package/dist-server/engine/kpi-fold.d.ts +39 -0
  28. package/dist-server/engine/kpi-fold.js +68 -5
  29. package/dist-server/engine/kpi-fold.js.map +1 -1
  30. package/dist-server/engine/kpi-query.d.ts +3 -3
  31. package/dist-server/engine/kpi-query.js +132 -18
  32. package/dist-server/engine/kpi-query.js.map +1 -1
  33. package/dist-server/engine/live-feed-registry.d.ts +1 -1
  34. package/dist-server/engine/live-feed-registry.js +3 -17
  35. package/dist-server/engine/live-feed-registry.js.map +1 -1
  36. package/dist-server/engine/load-meter.d.ts +1 -1
  37. package/dist-server/engine/load-meter.js +12 -3
  38. package/dist-server/engine/load-meter.js.map +1 -1
  39. package/dist-server/engine/local-declarations.d.ts +3 -6
  40. package/dist-server/engine/local-declarations.js +97 -16
  41. package/dist-server/engine/local-declarations.js.map +1 -1
  42. package/dist-server/engine/log.d.ts +18 -0
  43. package/dist-server/engine/log.js +80 -0
  44. package/dist-server/engine/log.js.map +1 -0
  45. package/dist-server/engine/loop-lag.d.ts +54 -0
  46. package/dist-server/engine/loop-lag.js +87 -0
  47. package/dist-server/engine/loop-lag.js.map +1 -0
  48. package/dist-server/engine/measured-yield.d.ts +42 -0
  49. package/dist-server/engine/measured-yield.js +75 -0
  50. package/dist-server/engine/measured-yield.js.map +1 -0
  51. package/dist-server/engine/model-gap.d.ts +108 -0
  52. package/dist-server/engine/model-gap.js +95 -0
  53. package/dist-server/engine/model-gap.js.map +1 -0
  54. package/dist-server/engine/operation-basis.d.ts +16 -0
  55. package/dist-server/engine/operation-basis.js +20 -2
  56. package/dist-server/engine/operation-basis.js.map +1 -1
  57. package/dist-server/engine/property-effects.js +17 -0
  58. package/dist-server/engine/property-effects.js.map +1 -1
  59. package/dist-server/engine/restart-policy.d.ts +13 -0
  60. package/dist-server/engine/restart-policy.js +52 -0
  61. package/dist-server/engine/restart-policy.js.map +1 -0
  62. package/dist-server/engine/runtime-key.js +1 -1
  63. package/dist-server/engine/runtime-key.js.map +1 -1
  64. package/dist-server/engine/spec-coverage.d.ts +8 -0
  65. package/dist-server/engine/spec-coverage.js +3 -1
  66. package/dist-server/engine/spec-coverage.js.map +1 -1
  67. package/dist-server/engine/stage-path.d.ts +83 -0
  68. package/dist-server/engine/stage-path.js +118 -0
  69. package/dist-server/engine/stage-path.js.map +1 -0
  70. package/dist-server/engine/twin-engine.d.ts +400 -31
  71. package/dist-server/engine/twin-engine.js +1198 -187
  72. package/dist-server/engine/twin-engine.js.map +1 -1
  73. package/dist-server/index.js +35 -3
  74. package/dist-server/index.js.map +1 -1
  75. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.d.ts +5 -0
  76. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +58 -0
  77. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -0
  78. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.d.ts +5 -0
  79. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +55 -0
  80. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -0
  81. package/dist-server/migrations/index.js +7 -1
  82. package/dist-server/migrations/index.js.map +1 -1
  83. package/dist-server/service/index.d.ts +5 -2
  84. package/dist-server/service/index.js +18 -7
  85. package/dist-server/service/index.js.map +1 -1
  86. package/dist-server/service/reference/control-routing.d.ts +14 -0
  87. package/dist-server/service/reference/control-routing.js +64 -0
  88. package/dist-server/service/reference/control-routing.js.map +1 -0
  89. package/dist-server/service/reference/discovery-result.d.ts +1 -1
  90. package/dist-server/service/reference/discovery-result.js +1 -1
  91. package/dist-server/service/reference/discovery-result.js.map +1 -1
  92. package/dist-server/service/reference/index.d.ts +5 -1
  93. package/dist-server/service/reference/index.js +8 -1
  94. package/dist-server/service/reference/index.js.map +1 -1
  95. package/dist-server/service/reference/reference-adapter.d.ts +251 -3
  96. package/dist-server/service/reference/reference-adapter.js +43 -1
  97. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  98. package/dist-server/service/reference/reference-assessment.d.ts +68 -0
  99. package/dist-server/service/reference/reference-assessment.js +136 -0
  100. package/dist-server/service/reference/reference-assessment.js.map +1 -0
  101. package/dist-server/service/reference/reference-live.d.ts +12 -1
  102. package/dist-server/service/reference/reference-live.js +119 -14
  103. package/dist-server/service/reference/reference-live.js.map +1 -1
  104. package/dist-server/service/reference/reference-master.d.ts +47 -1
  105. package/dist-server/service/reference/reference-master.js +34 -4
  106. package/dist-server/service/reference/reference-master.js.map +1 -1
  107. package/dist-server/service/reference/reference-probe.d.ts +92 -0
  108. package/dist-server/service/reference/reference-probe.js +186 -0
  109. package/dist-server/service/reference/reference-probe.js.map +1 -0
  110. package/dist-server/service/reference/reference-progress-subscription.d.ts +17 -0
  111. package/dist-server/service/reference/reference-progress-subscription.js +94 -0
  112. package/dist-server/service/reference/reference-progress-subscription.js.map +1 -0
  113. package/dist-server/service/reference/reference-progress.d.ts +38 -0
  114. package/dist-server/service/reference/reference-progress.js +71 -0
  115. package/dist-server/service/reference/reference-progress.js.map +1 -0
  116. package/dist-server/service/reference/reference-resolver.d.ts +3 -3
  117. package/dist-server/service/reference/reference-resolver.js +242 -58
  118. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  119. package/dist-server/service/reference/twin-reference.d.ts +32 -0
  120. package/dist-server/service/reference/twin-reference.js +10 -0
  121. package/dist-server/service/reference/twin-reference.js.map +1 -1
  122. package/dist-server/service/twin-audit/command-audit.d.ts +34 -0
  123. package/dist-server/service/twin-audit/command-audit.js +15 -1
  124. package/dist-server/service/twin-audit/command-audit.js.map +1 -1
  125. package/dist-server/service/twin-audit/twin-audit-event.d.ts +3 -0
  126. package/dist-server/service/twin-audit/twin-audit-event.js +10 -0
  127. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
  128. package/dist-server/service/twin-control/twin-control-mutation.d.ts +15 -2
  129. package/dist-server/service/twin-control/twin-control-mutation.js +88 -36
  130. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  131. package/dist-server/service/twin-event/journal-count.d.ts +21 -0
  132. package/dist-server/service/twin-event/journal-count.js +26 -0
  133. package/dist-server/service/twin-event/journal-count.js.map +1 -0
  134. package/dist-server/service/twin-event/twin-event-keys.d.ts +9 -0
  135. package/dist-server/service/twin-event/twin-event-keys.js +15 -18
  136. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  137. package/dist-server/service/twin-event/twin-event-type.d.ts +3 -1
  138. package/dist-server/service/twin-event/twin-event-type.js +18 -1
  139. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  140. package/dist-server/service/twin-event/twin-event.d.ts +1 -0
  141. package/dist-server/service/twin-event/twin-event.js +9 -1
  142. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  143. package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
  144. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  145. package/dist-server/service/twin-ingest-window/index.d.ts +5 -0
  146. package/dist-server/service/twin-ingest-window/index.js +10 -0
  147. package/dist-server/service/twin-ingest-window/index.js.map +1 -0
  148. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +12 -0
  149. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +123 -0
  150. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -0
  151. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.d.ts +1 -0
  152. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +51 -0
  153. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -0
  154. package/dist-server/service/twin-ingest-window/twin-ingest-window.d.ts +16 -0
  155. package/dist-server/service/twin-ingest-window/twin-ingest-window.js +108 -0
  156. package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +1 -0
  157. package/dist-server/service/twin-instance/twin-instance.d.ts +1 -1
  158. package/dist-server/service/twin-instance/twin-instance.js +2 -2
  159. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  160. package/dist-server/service/twin-journal/twin-journal-query.js +58 -5
  161. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  162. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +14 -0
  163. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +74 -3
  164. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  165. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  166. package/dist-server/service/twin-metrics/twin-metrics-query.js +78 -3
  167. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  168. package/dist-server/service/twin-model/axis-journal-evidence.d.ts +2 -0
  169. package/dist-server/service/twin-model/axis-journal-evidence.js +89 -0
  170. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -0
  171. package/dist-server/service/twin-model/epcis-coverage.d.ts +1 -1
  172. package/dist-server/service/twin-model/epcis-coverage.js +19 -7
  173. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  174. package/dist-server/service/twin-model/iec61850-coverage.d.ts +1 -1
  175. package/dist-server/service/twin-model/iec61850-coverage.js +18 -7
  176. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  177. package/dist-server/service/twin-model/isa95-coverage.d.ts +14 -0
  178. package/dist-server/service/twin-model/isa95-coverage.js +23 -9
  179. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  180. package/dist-server/service/twin-model/item-ref.d.ts +24 -0
  181. package/dist-server/service/twin-model/item-ref.js +88 -0
  182. package/dist-server/service/twin-model/item-ref.js.map +1 -0
  183. package/dist-server/service/twin-model/name-index.d.ts +36 -0
  184. package/dist-server/service/twin-model/name-index.js +116 -0
  185. package/dist-server/service/twin-model/name-index.js.map +1 -0
  186. package/dist-server/service/twin-model/project-structure.js +1 -1
  187. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  188. package/dist-server/service/twin-model/status-tally.d.ts +9 -0
  189. package/dist-server/service/twin-model/status-tally.js +37 -0
  190. package/dist-server/service/twin-model/status-tally.js.map +1 -0
  191. package/dist-server/service/twin-model/twin-lineage-query.js +40 -12
  192. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  193. package/dist-server/service/twin-model/twin-model-item-query.js +38 -39
  194. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  195. package/dist-server/service/twin-model/twin-model-query.js +157 -5
  196. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  197. package/dist-server/service/twin-model/twin-model-tree-query.js +7 -0
  198. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  199. package/dist-server/service/twin-readiness/index.d.ts +2 -0
  200. package/dist-server/service/twin-readiness/index.js +6 -0
  201. package/dist-server/service/twin-readiness/index.js.map +1 -0
  202. package/dist-server/service/twin-readiness/twin-readiness-query.d.ts +3 -0
  203. package/dist-server/service/twin-readiness/twin-readiness-query.js +103 -0
  204. package/dist-server/service/twin-readiness/twin-readiness-query.js.map +1 -0
  205. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  206. package/dist-server/service/twin-space/twin-space-resolver.js +5 -4
  207. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  208. package/dist-shared/entity-delta.d.ts +23 -3
  209. package/dist-shared/entity-delta.js +12 -8
  210. package/dist-shared/entity-delta.js.map +1 -1
  211. package/dist-shared/kpi-broadcast.js +1 -1
  212. package/dist-shared/kpi-broadcast.js.map +1 -1
  213. package/dist-shared/touched-items.d.ts +7 -0
  214. package/dist-shared/touched-items.js +80 -0
  215. package/dist-shared/touched-items.js.map +1 -0
  216. package/package.json +7 -7
  217. package/server/engine/canonical-ingest.ts +164 -15
  218. package/server/engine/command-routing.ts +1 -1
  219. package/server/engine/declared-stimulus.ts +66 -0
  220. package/server/engine/index.ts +6 -0
  221. package/server/engine/ingest-health.ts +704 -0
  222. package/server/engine/integration-coverage.ts +147 -0
  223. package/server/engine/integration-probes.ts +144 -0
  224. package/server/engine/integration-runner.ts +95 -0
  225. package/server/engine/integration-target-profile.ts +103 -0
  226. package/server/engine/kpi-fold.ts +101 -5
  227. package/server/engine/kpi-query.ts +137 -23
  228. package/server/engine/live-feed-registry.ts +4 -3
  229. package/server/engine/load-meter.ts +12 -3
  230. package/server/engine/local-declarations.ts +97 -19
  231. package/server/engine/log.ts +72 -0
  232. package/server/engine/loop-lag.ts +120 -0
  233. package/server/engine/measured-yield.ts +89 -0
  234. package/server/engine/model-gap.ts +168 -0
  235. package/server/engine/operation-basis.ts +33 -2
  236. package/server/engine/property-effects.ts +17 -0
  237. package/server/engine/restart-policy.ts +55 -0
  238. package/server/engine/runtime-key.ts +1 -1
  239. package/server/engine/spec-coverage.ts +23 -3
  240. package/server/engine/stage-path.ts +172 -0
  241. package/server/engine/twin-engine.ts +1318 -186
  242. package/server/index.ts +36 -3
  243. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +54 -0
  244. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +53 -0
  245. package/server/migrations/index.ts +7 -1
  246. package/server/service/index.ts +11 -0
  247. package/server/service/reference/control-routing.ts +62 -0
  248. package/server/service/reference/discovery-result.ts +1 -1
  249. package/server/service/reference/index.ts +7 -1
  250. package/server/service/reference/reference-adapter.ts +275 -5
  251. package/server/service/reference/reference-assessment.ts +215 -0
  252. package/server/service/reference/reference-live.ts +126 -14
  253. package/server/service/reference/reference-master.ts +64 -4
  254. package/server/service/reference/reference-probe.ts +264 -0
  255. package/server/service/reference/reference-progress-subscription.ts +73 -0
  256. package/server/service/reference/reference-progress.ts +95 -0
  257. package/server/service/reference/reference-resolver.ts +246 -19
  258. package/server/service/reference/twin-reference.ts +39 -1
  259. package/server/service/twin-audit/command-audit.ts +34 -1
  260. package/server/service/twin-audit/twin-audit-event.ts +20 -0
  261. package/server/service/twin-control/twin-control-mutation.ts +78 -30
  262. package/server/service/twin-event/journal-count.ts +53 -0
  263. package/server/service/twin-event/twin-event-keys.ts +16 -1
  264. package/server/service/twin-event/twin-event-type.ts +26 -2
  265. package/server/service/twin-event/twin-event.ts +7 -0
  266. package/server/service/twin-forecast/twin-forecast-query.ts +2 -1
  267. package/server/service/twin-ingest-window/index.ts +7 -0
  268. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +125 -0
  269. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +57 -0
  270. package/server/service/twin-ingest-window/twin-ingest-window.ts +113 -0
  271. package/server/service/twin-instance/twin-instance.ts +16 -10
  272. package/server/service/twin-journal/twin-journal-query.ts +59 -5
  273. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +79 -4
  274. package/server/service/twin-metrics/twin-metrics-query.ts +81 -3
  275. package/server/service/twin-model/axis-journal-evidence.ts +57 -0
  276. package/server/service/twin-model/epcis-coverage.ts +2 -8
  277. package/server/service/twin-model/iec61850-coverage.ts +2 -8
  278. package/server/service/twin-model/isa95-coverage.ts +22 -9
  279. package/server/service/twin-model/item-ref.ts +80 -0
  280. package/server/service/twin-model/name-index.ts +94 -0
  281. package/server/service/twin-model/project-structure.ts +1 -1
  282. package/server/service/twin-model/status-tally.ts +37 -0
  283. package/server/service/twin-model/twin-lineage-query.ts +38 -9
  284. package/server/service/twin-model/twin-model-item-query.ts +26 -27
  285. package/server/service/twin-model/twin-model-query.ts +156 -5
  286. package/server/service/twin-model/twin-model-tree-query.ts +7 -0
  287. package/server/service/twin-readiness/index.ts +3 -0
  288. package/server/service/twin-readiness/twin-readiness-query.ts +96 -0
  289. package/server/service/twin-space/twin-space-resolver.ts +5 -4
  290. package/shared/entity-delta.ts +31 -7
  291. package/shared/kpi-broadcast.ts +1 -1
  292. package/shared/touched-items.ts +73 -0
  293. package/test/adopt-structure-live.test.ts +7 -7
  294. package/test/axis-journal-evidence.test.ts +71 -0
  295. package/test/axis-read.test.ts +101 -1
  296. package/test/boot-resume.test.ts +93 -27
  297. package/test/broadcast-cost-baseline.test.ts +200 -0
  298. package/test/broadcast-period.test.ts +58 -0
  299. package/test/canonical-ingest-vocabularies.test.ts +36 -1
  300. package/test/canonical-quantity-door.test.ts +61 -0
  301. package/test/command-routing.test.ts +1 -1
  302. package/test/control-capability.test.ts +103 -0
  303. package/test/declared-location-types.test.ts +89 -0
  304. package/test/declared-stimulus.test.ts +88 -0
  305. package/test/discovery-result.test.ts +1 -1
  306. package/test/duration-estimators.test.ts +1 -1
  307. package/test/entity-delta.test.ts +2 -2
  308. package/test/ingest-bench.test.ts +3 -3
  309. package/test/ingest-health-engine.test.ts +248 -0
  310. package/test/ingest-health-wiring.test.ts +119 -0
  311. package/test/ingest-health.test.ts +306 -0
  312. package/test/ingest-history.test.ts +247 -0
  313. package/test/ingest-running-guard.test.ts +5 -5
  314. package/test/instance-cache-lifecycle.test.ts +1 -1
  315. package/test/integration-probes.test.ts +103 -0
  316. package/test/integration-runner.test.ts +95 -0
  317. package/test/item-ref.test.ts +78 -0
  318. package/test/journal-read-discipline.test.ts +177 -0
  319. package/test/journal-retention.test.ts +133 -0
  320. package/test/journal-sort-axis.test.ts +142 -0
  321. package/test/journal-write-door.test.ts +110 -0
  322. package/test/journal-write-trend.test.ts +115 -0
  323. package/test/kernel-kind-guard.test.ts +4 -4
  324. package/test/kpi-baseline-db.test.ts +1 -1
  325. package/test/kpi-fold.test.ts +90 -3
  326. package/test/kpi-query-bench.test.ts +3 -3
  327. package/test/lineage-survives-restart.test.ts +20 -4
  328. package/test/live-cursor-wiring.test.ts +87 -0
  329. package/test/live-feed-registry.test.ts +15 -7
  330. package/test/live-kernel-facts.test.ts +7 -7
  331. package/test/live-mirror-parity.test.ts +6 -0
  332. package/test/load-meter.test.ts +29 -15
  333. package/test/local-declarations.test.ts +109 -2
  334. package/test/log-stamp.test.ts +59 -0
  335. package/test/loop-lag.test.ts +82 -0
  336. package/test/measured-yield.test.ts +90 -0
  337. package/test/model-gap.test.ts +153 -0
  338. package/test/oee-accumulator.test.ts +4 -0
  339. package/test/operation-basis.test.ts +28 -1
  340. package/test/operational-vocabulary.test.ts +108 -0
  341. package/test/operations-capability-db.test.ts +4 -4
  342. package/test/project-structure-db.test.ts +1 -1
  343. package/test/projection-reaches-screen.test.ts +1 -1
  344. package/test/property-effects.test.ts +28 -0
  345. package/test/read-failure-visible.test.ts +145 -0
  346. package/test/reference-grounding.test.ts +70 -0
  347. package/test/resolve-ts-siblings.mjs +52 -0
  348. package/test/restart-policy.test.ts +111 -0
  349. package/test/resync-origin-site.test.ts +7 -1
  350. package/test/revision-axis.test.ts +93 -0
  351. package/test/runtime-key.test.ts +2 -2
  352. package/test/scale-twin-bench.test.ts +2 -2
  353. package/test/source-outcome-audit.test.ts +104 -0
  354. package/test/spec-coverage.test.ts +1 -1
  355. package/test/stage-path.test.ts +95 -0
  356. package/test/standard-coverage.test.ts +15 -3
  357. package/test/status-tally.test.ts +55 -0
  358. package/test/structure-revision-db.test.ts +31 -27
  359. package/test/tenant-registry-db.test.ts +3 -3
  360. package/test/time-range.test.ts +152 -0
  361. package/test/touched-items.test.ts +61 -0
  362. package/test/twin-event-keys.test.ts +10 -2
  363. package/test/twin-model-item-db.test.ts +5 -3
  364. package/test/twin-model-tree-db.test.ts +7 -7
  365. package/test/twin-origin-resync.test.ts +4 -4
  366. package/test/warm-start-seam.test.ts +4 -0
  367. package/test/yield-loop.test.ts +199 -0
  368. package/tsconfig.shared.tsbuildinfo +1 -1
  369. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /*
3
+ * 한 사건이 **어느 물품을 건드렸나** — 브로드캐스팅이 바뀐 것만 만들 수 있게. 순수(import 없음).
4
+ *
5
+ * ── 왜 물품만인가 (2026-08-21 실측) ─────────────────────────────────────────
6
+ * 브로드캐스팅 한 번이 만드는 payload 는 엔티티 수만큼이고, 그 대부분이 물품이다(실측: 엔티티 3,611 중
7
+ * 물품 2,400). 자리·설비·인원·자산은 모델이 정한 수라 적고(자리 최대 200, 평균 19), 작업·오더는 수백이다.
8
+ * 그래서 **물품 축만 걸러도** 만드는 양의 3분의 2가 사라진다.
9
+ *
10
+ * 나머지 축은 **매번 전부 만든다.** 걸러서 얻는 것이 적고, 잘못 걸러서 잃는 것이 크기 때문이다 —
11
+ * 예를 들어 물품이 한 자리에서 다른 자리로 가면 **두 자리의 점유가 함께 바뀌는데** EPCIS 사건은 읽은
12
+ * 지점 하나만 말한다. 그것으로 자리를 걸렀으면 떠난 자리의 점유가 낡은 값으로 남는다(오류 없이).
13
+ *
14
+ * ── 모르면 전부다 ───────────────────────────────────────────────────────────
15
+ * 이 함수가 `null` 을 돌려주면 「이 사건이 어느 물품을 건드렸는지 말할 수 없다」는 뜻이고, 그때 호출자는
16
+ * **전부 다시 만든다**(지금까지의 거동). 낯선 어휘가 오면 조용히 빠뜨리는 것이 아니라 비싸게 안전한
17
+ * 쪽으로 떨어진다 — 화면이 낡는 것보다 계산을 한 번 더 하는 것이 낫다.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.touchedItemKeys = touchedItemKeys;
21
+ /** 물품 태그는 개체 식별자(epc)다 — 자리·설비와 달리 id 가 아니다(entity-delta 와 같은 규약). */
22
+ function push(out, v) {
23
+ if (typeof v === 'string' && v)
24
+ out.add(v);
25
+ }
26
+ /**
27
+ * 물품을 건드리지 않는다고 **선언된** 사건들 — 커널의 운영 델타와 에너지 사건.
28
+ *
29
+ * 이 목록은 커널 계약(`OP_EVENT`·`ENERGY_EVENT`)에서 왔다. 여기 없는 낱말이 오면 「모른다」로 떨어져
30
+ * 전부 다시 만든다 — 커널이 사건을 늘렸을 때 조용히 빠뜨리는 대신 비싸게 안전한 쪽을 고른다.
31
+ *
32
+ * 근거: 미러의 리듀서에서 이 사건들은 작업·설비·인원·자산·오더·계량만 바꾼다(물품 축을 만지지 않는다).
33
+ * 자리 점유는 이 사건들도 바꾸지만, 자리는 **어차피 매번 전부 다시 만든다**(위 주석).
34
+ */
35
+ const ITEM_FREE_EVENTS = new Set([
36
+ 'task.status',
37
+ 'equipment.status',
38
+ 'person.status',
39
+ 'asset.status',
40
+ 'order.status',
41
+ 'quality.output',
42
+ 'attention.acked'
43
+ ]);
44
+ /**
45
+ * 이 사건이 건드린 물품 태그들. **말할 수 없으면 `null`** — 호출자는 그때 전부 다시 만든다.
46
+ *
47
+ * 개체를 말하는 자리를 전부 본다: `epcList`(관측·커미셔닝) · `parentID`(집약의 부모) ·
48
+ * `childEPCs`(집약의 자식) · `inputEPCList`·`outputEPCList`(변환의 입출력).
49
+ */
50
+ function touchedItemKeys(envelope) {
51
+ const type = envelope?.eventType;
52
+ if (typeof type !== 'string')
53
+ return null;
54
+ const d = envelope?.data ?? envelope;
55
+ if (!d || typeof d !== 'object')
56
+ return null;
57
+ if (type.startsWith('energy.') || ITEM_FREE_EVENTS.has(type))
58
+ return new Set();
59
+ /* EPCIS 밖의 낯선 낱말 — 무엇을 바꿨는지 우리가 모른다. */
60
+ if (!type.startsWith('epcis.'))
61
+ return null;
62
+ const out = new Set();
63
+ for (const v of d.epcList ?? [])
64
+ push(out, v);
65
+ for (const v of d.childEPCs ?? [])
66
+ push(out, v);
67
+ for (const v of d.inputEPCList ?? [])
68
+ push(out, v);
69
+ for (const v of d.outputEPCList ?? [])
70
+ push(out, v);
71
+ push(out, d.parentID);
72
+ /*
73
+ * 개체를 하나도 말하지 않은 EPCIS 사건은 **부류 수량**으로 말한 것이다(`quantityList`). 그 부류의
74
+ * 물품이 어느 것인지는 여기서 알 수 없으므로 전부 다시 만든다.
75
+ */
76
+ if (!out.size)
77
+ return null;
78
+ return out;
79
+ }
80
+ //# sourceMappingURL=touched-items.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"touched-items.js","sourceRoot":"","sources":["../shared/touched-items.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;AAgCH,0CAwBC;AAtDD,sEAAsE;AACtE,SAAS,IAAI,CAAC,GAAgB,EAAE,CAAU;IACxC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC;QAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;IAC/B,aAAa;IACb,kBAAkB;IAClB,eAAe;IACf,cAAc;IACd,cAAc;IACd,gBAAgB;IAChB,iBAAiB;CAClB,CAAC,CAAA;AAEF;;;;;GAKG;AACH,SAAgB,eAAe,CAAC,QAAa;IAC3C,MAAM,IAAI,GAAG,QAAQ,EAAE,SAAS,CAAA;IAChC,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACzC,MAAM,CAAC,GAAG,QAAQ,EAAE,IAAI,IAAI,QAAQ,CAAA;IACpC,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAE5C,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,GAAG,EAAE,CAAA;IAC9E,wCAAwC;IACxC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAA;IAE3C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IAC7C,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,IAAI,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IAC/C,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,IAAI,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IAClD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,aAAa,IAAI,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;IACnD,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAA;IAErB;;;OAGG;IACH,IAAI,CAAC,GAAG,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAE1B,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["/*\n * 한 사건이 **어느 물품을 건드렸나** — 브로드캐스팅이 바뀐 것만 만들 수 있게. 순수(import 없음).\n *\n * ── 왜 물품만인가 (2026-08-21 실측) ─────────────────────────────────────────\n * 브로드캐스팅 한 번이 만드는 payload 는 엔티티 수만큼이고, 그 대부분이 물품이다(실측: 엔티티 3,611 중\n * 물품 2,400). 자리·설비·인원·자산은 모델이 정한 수라 적고(자리 최대 200, 평균 19), 작업·오더는 수백이다.\n * 그래서 **물품 축만 걸러도** 만드는 양의 3분의 2가 사라진다.\n *\n * 나머지 축은 **매번 전부 만든다.** 걸러서 얻는 것이 적고, 잘못 걸러서 잃는 것이 크기 때문이다 —\n * 예를 들어 물품이 한 자리에서 다른 자리로 가면 **두 자리의 점유가 함께 바뀌는데** EPCIS 사건은 읽은\n * 지점 하나만 말한다. 그것으로 자리를 걸렀으면 떠난 자리의 점유가 낡은 값으로 남는다(오류 없이).\n *\n * ── 모르면 전부다 ───────────────────────────────────────────────────────────\n * 이 함수가 `null` 을 돌려주면 「이 사건이 어느 물품을 건드렸는지 말할 수 없다」는 뜻이고, 그때 호출자는\n * **전부 다시 만든다**(지금까지의 거동). 낯선 어휘가 오면 조용히 빠뜨리는 것이 아니라 비싸게 안전한\n * 쪽으로 떨어진다 — 화면이 낡는 것보다 계산을 한 번 더 하는 것이 낫다.\n */\n\n/** 물품 태그는 개체 식별자(epc)다 — 자리·설비와 달리 id 가 아니다(entity-delta 와 같은 규약). */\nfunction push(out: Set<string>, v: unknown): void {\n if (typeof v === 'string' && v) out.add(v)\n}\n\n/**\n * 물품을 건드리지 않는다고 **선언된** 사건들 — 커널의 운영 델타와 에너지 사건.\n *\n * 이 목록은 커널 계약(`OP_EVENT`·`ENERGY_EVENT`)에서 왔다. 여기 없는 낱말이 오면 「모른다」로 떨어져\n * 전부 다시 만든다 — 커널이 사건을 늘렸을 때 조용히 빠뜨리는 대신 비싸게 안전한 쪽을 고른다.\n *\n * 근거: 미러의 리듀서에서 이 사건들은 작업·설비·인원·자산·오더·계량만 바꾼다(물품 축을 만지지 않는다).\n * 자리 점유는 이 사건들도 바꾸지만, 자리는 **어차피 매번 전부 다시 만든다**(위 주석).\n */\nconst ITEM_FREE_EVENTS = new Set([\n 'task.status',\n 'equipment.status',\n 'person.status',\n 'asset.status',\n 'order.status',\n 'quality.output',\n 'attention.acked'\n])\n\n/**\n * 이 사건이 건드린 물품 태그들. **말할 수 없으면 `null`** — 호출자는 그때 전부 다시 만든다.\n *\n * 개체를 말하는 자리를 전부 본다: `epcList`(관측·커미셔닝) · `parentID`(집약의 부모) ·\n * `childEPCs`(집약의 자식) · `inputEPCList`·`outputEPCList`(변환의 입출력).\n */\nexport function touchedItemKeys(envelope: any): Set<string> | null {\n const type = envelope?.eventType\n if (typeof type !== 'string') return null\n const d = envelope?.data ?? envelope\n if (!d || typeof d !== 'object') return null\n\n if (type.startsWith('energy.') || ITEM_FREE_EVENTS.has(type)) return new Set()\n /* EPCIS 밖의 낯선 낱말 — 무엇을 바꿨는지 우리가 모른다. */\n if (!type.startsWith('epcis.')) return null\n\n const out = new Set<string>()\n for (const v of d.epcList ?? []) push(out, v)\n for (const v of d.childEPCs ?? []) push(out, v)\n for (const v of d.inputEPCList ?? []) push(out, v)\n for (const v of d.outputEPCList ?? []) push(out, v)\n push(out, d.parentID)\n\n /*\n * 개체를 하나도 말하지 않은 EPCIS 사건은 **부류 수량**으로 말한 것이다(`quantityList`). 그 부류의\n * 물품이 어느 것인지는 여기서 알 수 없으므로 전부 다시 만든다.\n */\n if (!out.size) return null\n\n return out\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/headless-twin",
3
- "version": "10.0.11",
3
+ "version": "10.0.13",
4
4
  "main": "dist-server/index.js",
5
5
  "things-factory": true,
6
6
  "author": "heartyoh <heartyoh@hatiolab.com>",
@@ -22,16 +22,16 @@
22
22
  "clean": "npm run clean:server && npm run clean:shared",
23
23
  "migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration",
24
24
  "typecheck": "tsc --noEmit && tsc -p tsconfig.shared.json --noEmit",
25
- "test": "node --test test/*.test.ts",
25
+ "test": "node --import ./test/resolve-ts-siblings.mjs --test test/*.test.ts",
26
26
  "build:shared": "tsc -p tsconfig.shared.json",
27
27
  "clean:shared": "rm -rf dist-shared tsconfig.shared.tsbuildinfo"
28
28
  },
29
29
  "dependencies": {
30
- "@operato/twin-kernel": "^0.7.30",
31
- "@things-factory/auth-base": "^10.0.10",
32
- "@things-factory/cache-service": "^10.0.10",
30
+ "@operato/twin-kernel": "^0.7.53",
31
+ "@things-factory/auth-base": "^10.0.13",
32
+ "@things-factory/cache-service": "^10.0.13",
33
33
  "@things-factory/env": "^10.0.8",
34
- "@things-factory/shell": "^10.0.10"
34
+ "@things-factory/shell": "^10.0.13"
35
35
  },
36
- "gitHead": "3d26d911797ba51b29402bf26ff7888a46fccad4"
36
+ "gitHead": "808b3b9e3bf15c058316ec4c73455108df7722aa"
37
37
  }
@@ -1,12 +1,16 @@
1
- import { ingest, ingestEnergyRecords, ingestEnergyEquipmentRecords, isEnergyRecord,
2
- isEnergyEquipmentRecord, type AdapterRule, type EnergyRecord,
3
- type EnergyEquipmentRecord, type IngestResult } from '@operato/twin-kernel'
1
+ import { ingest, ingestEnergyRecords, ingestEnergyEquipmentRecords, ingestOperationalRecords, isAggregationRecord, isTransformationRecord,
2
+ isEnergyRecord, isEnergyEquipmentRecord, isOperationalRecord, type AdapterRule, type EnergyRecord,
3
+ type EnergyEquipmentRecord, type OperationalRecord, type IngestResult } from '@operato/twin-kernel'
4
4
 
5
5
  /*
6
6
  * 정규 레코드 → 커널 검증 ACL → CanonicalEnvelope (P1 seam 코어).
7
7
  * 순수(커널만 의존; shell·integration-base 무의존 → node:test 검증 가능).
8
8
  * 매핑(레거시 스키마 → 정규)은 이 앞 단계(jsonata step 또는 커넥터 openLiveFeed)가 끝낸다.
9
- * 여기 도착하는 레코드는 이미 정규 필드다: { epc, action, bizStep, disposition?, readPoint?, bizLocation? }
9
+ * 여기 도착하는 레코드는 이미 정규 필드다 어휘마다 자기 필드 이름이 계약이다:
10
+ * EPCIS 품목 { epc, action, bizStep, disposition?, readPoint?, bizLocation? }
11
+ * 에너지 계량 { meterId, kW?, kWh?, … } · 설비 에너지 { equipmentId, generatedKW?, soc?, … }
12
+ * 담김(적재) { parentID, childEPCs[] | childQuantityList[], action, bizStep, … }
13
+ * 운영 사실 { taskId | moverId | personId | assetId | orderId, status, … } // vocabulary-guard: allow 저널 와이어 필드
10
14
  * 커널은 objectEvent 구성 + EPCIS 검증(위반=오염 차단) + 봉투 래핑만(방언 없음).
11
15
  *
12
16
  * 두 소비처가 이 단일 코어를 공유(중복 금지):
@@ -15,7 +19,7 @@ import { ingest, ingestEnergyRecords, ingestEnergyEquipmentRecords, isEnergyReco
15
19
  * SoT: design/integration/face2-adapters.md §5(seam)·§7(매핑=밖, 검증=커널).
16
20
  */
17
21
 
18
- /** 고정 정규 매핑(방언 아님 — 필드명 자체가 정규 계약). sourceType='twin' 으로 매칭. */
22
+ /** 고정 정규 매핑(방언 아님 — 필드명 자체가 정규 계약). `sourceType` 으로 어느 품목 사실인지 고른다. */
19
23
  const TWIN_INGEST_RULES: AdapterRule[] = [
20
24
  {
21
25
  sourceType: 'twin',
@@ -25,14 +29,66 @@ const TWIN_INGEST_RULES: AdapterRule[] = [
25
29
  bizStep: '$.bizStep',
26
30
  disposition: '$.disposition',
27
31
  epc: '$.epc',
32
+ /* 비직렬 수량 — 개체 없이 「이 자리에 이 품목이 얼마 남았다」만 오는 관측이 있다. */
33
+ quantityList: '$.quantityList',
34
+ readPoint: '$.readPoint',
35
+ bizLocation: '$.bizLocation'
36
+ }
37
+ },
38
+ {
39
+ /* **변환의 사실** — 무엇으로 무엇이 되었나. 이름은 표준의 것을 그대로 쓴다(갈래 판정이 그 이름을 본다). */
40
+ sourceType: 'twin-transformation',
41
+ mapping: {
42
+ type: 'TransformationEvent',
43
+ bizStep: '$.bizStep',
44
+ disposition: '$.disposition',
45
+ inputEPCList: '$.inputEPCList',
46
+ inputQuantityList: '$.inputQuantityList',
47
+ outputEPCList: '$.outputEPCList',
48
+ outputQuantityList: '$.outputQuantityList',
49
+ transformationID: '$.transformationID',
50
+ readPoint: '$.readPoint',
51
+ bizLocation: '$.bizLocation'
52
+ }
53
+ },
54
+ {
55
+ /*
56
+ * **담김의 사실** — 무엇이 무엇에 실렸나(팔레트·상자). 오래 비어 있던 자리다: 관측 리듀서는 이 사실을
57
+ * 받으면 자식 물품에 `parent` 를 붙이는데, 문이 `ObjectEvent` 하나만 알아서 원본은 적재를 말할 길이
58
+ * 없었다(시뮬 원본을 붙여 돌려 보고서야 로그로 드러났다).
59
+ */
60
+ sourceType: 'twin-aggregation',
61
+ mapping: {
62
+ type: 'AggregationEvent',
63
+ action: '$.action',
64
+ bizStep: '$.bizStep',
65
+ disposition: '$.disposition',
66
+ parentID: '$.parentID',
67
+ childEPCs: '$.childEPCs',
68
+ /* 낱개 식별자 없이 **수량으로** 담긴 자식(「이 품번 40개」) — 입고·포장이 이 모양으로 온다. */
69
+ childQuantityList: '$.childQuantityList',
28
70
  readPoint: '$.readPoint',
29
71
  bizLocation: '$.bizLocation'
30
72
  }
31
73
  }
32
74
  ]
33
75
 
76
+ /** 개체 관측 — 물건 하나가 어디서 무엇이 됐나. */
34
77
  export interface CanonicalRecord {
35
- epc: string
78
+ /**
79
+ * 개체 하나의 식별자. **`quantityList` 를 쓰면 없어도 된다** — 낱개 번호가 없는 자재(밀가루 3.5kg)는
80
+ * 개체가 없다. 둘 다 없으면 무엇을 관측했는지 말하지 않은 것이므로 커널 매핑이 거부한다.
81
+ */
82
+ epc?: string
83
+ /**
84
+ * 클래스와 수량 — `{ epcClass, quantity, uom? }`. **잔량 절대값을 싣는다**(「얼마 뺐다」가 아니라
85
+ * 「얼마 남았다」). 그래야 사건 하나를 놓쳐도 다음 사건이 정답을 다시 말해 주고 오차가 쌓이지 않는다.
86
+ *
87
+ * 담김(`childQuantityList`)이 하루 먼저 이 자리를 받았다 — 그때와 같은 이유다. 원본이 비직렬 수량을
88
+ * 말할 길이 없어서, 그것이 주된 경로인 시스템(식품 제조 MES)은 라이브를 낼 수 없었다. 커널의
89
+ * 관측 리듀서는 처음부터 이 값을 읽었고 문만 없었다.
90
+ */
91
+ quantityList?: { epcClass: string; quantity?: number; uom?: string }[]
36
92
  action: string // ADD | OBSERVE | DELETE
37
93
  bizStep: string
38
94
  disposition?: string
@@ -40,13 +96,66 @@ export interface CanonicalRecord {
40
96
  bizLocation?: string
41
97
  }
42
98
 
99
+ /**
100
+ * 담김 관측 — **부모와 자식**으로 말한다. 자리 필드는 개체 관측과 같은 이름이다(문자열).
101
+ *
102
+ * 이름은 표준의 것을 그대로 쓴다(`parentID`·`childEPCs`) — 여기서 다시 지으면 매핑이 번역이 되고,
103
+ * 같은 사실에 두 어휘가 생긴다.
104
+ */
105
+ /**
106
+ * 변환 관측 — **무엇으로 무엇이 되었나.** 입력과 출력이 **한 사실에 함께** 있어야 계보가 남는다.
107
+ *
108
+ * 따로 보내면(「이 로트가 없어졌다」와 「이 제품이 생겼다」) 그 둘을 이을 근거가 없다. 그래서 표준이
109
+ * 한 사건에 둘을 담고, 우리 레코드도 같은 모양이다.
110
+ *
111
+ * 이름은 **표준의 것을 그대로** 쓴다(`inputQuantityList` 등). 갈래 판정이 이 이름으로 이루어지므로
112
+ * 커넥터 낱말(`consumed`·`produced`)로 담으면 판정되지 않는다 — 커넥터가 정규 이름으로 담아 보내고
113
+ * 매핑은 그 안에서 값을 고른다.
114
+ *
115
+ * **없는 쪽은 필드를 만들지 않는다.** 빈 배열을 실으면 「없다」고 말하는 것이 된다. 소실(N→0)과
116
+ * 생성(0→N)도 표준이 인정하는 변환이다.
117
+ *
118
+ * `transformationID` 가 한 오더의 여러 단계를 잇는다 — 소비를 먼저 알고 산출을 나중에 알게 되는 원본에서
119
+ * 그 둘을 묶는 유일한 자리다.
120
+ */
121
+ export interface CanonicalTransformationRecord {
122
+ /** 개체로 소비된 것. */
123
+ inputEPCList?: string[]
124
+ /** 클래스와 수량으로 소비된 것 — 로트 단위 소비가 이 자리다. */
125
+ inputQuantityList?: { epcClass: string; quantity?: number; uom?: string }[]
126
+ outputEPCList?: string[]
127
+ outputQuantityList?: { epcClass: string; quantity?: number; uom?: string }[]
128
+ /** 한 오더의 여러 단계를 잇는 식별자. */
129
+ transformationID?: string
130
+ bizStep: string
131
+ disposition?: string
132
+ readPoint?: string
133
+ bizLocation?: string
134
+ }
135
+
136
+ export interface CanonicalAggregationRecord {
137
+ parentID: string
138
+ /** 개체로 담긴 자식. 수량으로만 담겼으면 없다(둘 중 하나는 있어야 한다). */
139
+ childEPCs?: string[]
140
+ /** 수량으로 담긴 자식 — `{ epcClass, quantity, uom? }`. 값의 옳고 그름은 커널 검증이 본다. */
141
+ childQuantityList?: { epcClass: string; quantity?: number; uom?: string }[]
142
+ action: string // ADD(담다) | DELETE(풀다) | OBSERVE
143
+ bizStep: string
144
+ disposition?: string
145
+ readPoint?: string
146
+ bizLocation?: string
147
+ }
148
+
43
149
  /** 정규 레코드[] → 검증된 CanonicalEnvelope[] (+ 거부분). 순수 함수. 단일→객체도 배열로 정규화. */
44
150
  export function ingestCanonicalRecords(
45
151
  records:
46
152
  | CanonicalRecord
153
+ | CanonicalAggregationRecord
154
+ | CanonicalTransformationRecord
47
155
  | EnergyRecord
48
156
  | EnergyEquipmentRecord
49
- | (CanonicalRecord | EnergyRecord | EnergyEquipmentRecord)[]
157
+ | OperationalRecord
158
+ | (CanonicalRecord | CanonicalAggregationRecord | CanonicalTransformationRecord | EnergyRecord | EnergyEquipmentRecord | OperationalRecord)[]
50
159
  | undefined
51
160
  | null,
52
161
  tenantId: string,
@@ -54,29 +163,69 @@ export function ingestCanonicalRecords(
54
163
  ): IngestResult {
55
164
  const arr = Array.isArray(records) ? records : records ? [records] : []
56
165
  /*
57
- * **어휘가 둘이다** — 물류(EPCIS) 에너지. 예전에는 이 함수가 무조건 `ObjectEvent` 를 만들었고,
58
- * 그래서 계측을 이 길로 넣으면 `epc` 가 없어 거부되거나(그나마 나은 쪽) 엉뚱한 물품 관측이 됐다.
166
+ * **어휘가 넷이다** — 물류(EPCIS)·에너지 계량·설비 에너지·운영 사실. 예전에는 이 함수가 무조건
167
+ * `ObjectEvent` 를 만들었고, 그래서 계측을 이 길로 넣으면 `epc` 가 없어 거부되거나(그나마 나은 쪽)
168
+ * 엉뚱한 물품 관측이 됐다.
169
+ *
170
+ * 물류 어휘 안에는 모양이 둘이다(개체 관측 `epc` / 담김 `parentID`+`childEPCs`).
59
171
  *
60
- * 판정은 커널이 한 곳에서 한다(`isEnergyRecord`) — 여기서 다시 짐작하면 소비처마다 답이 달라진다.
61
- * 봉투는 같은 것을 쓰므로 아래 소비처(미러·저널·방송)는 이 갈림을 알 필요가 없다.
172
+ * 판정은 커널이 한 곳에서 한다(`isEnergyRecord`·`isOperationalRecord`·`isAggregationRecord`) — 여기서 다시 짐작하면
173
+ * 소비처마다 답이 달라진다. 봉투는 같은 것을 쓰므로 아래 소비처(미러·저널·브로드캐스팅)는 이 갈림을 알
174
+ * 필요가 없다.
62
175
  */
63
176
  const energy = arr.filter(isEnergyRecord) as EnergyRecord[]
64
177
  /* 세 번째 어휘 — 설비가 낸 자기 에너지 상태(발전·저장·감축 여지·개폐 위치). 계량과 갈라 두는
65
178
  이유는 커널에 적혀 있다: 계량은 구간에 누적되고, 이것들은 그 설비의 지금이다. */
66
179
  const energyEquipment = arr.filter(isEnergyEquipmentRecord) as unknown as EnergyEquipmentRecord[]
67
- const epcis = arr.filter(r => !isEnergyRecord(r) && !isEnergyEquipmentRecord(r)) as CanonicalRecord[]
180
+ /*
181
+ * 네 번째 어휘 — **운영 사실**(작업·설비·사람·자산·오더·품질). 관측 리듀서는 이것을 오래전부터
182
+ * 접었는데 **들어올 문이 없었다**: 원본이 「이 작업이 끝났다」를 말할 길이 없어 시뮬 커널만 그것을
183
+ * 아는 상태가 남았다(같은 화면이 두 구동에서 다른 것을 말한다).
184
+ */
185
+ const operational = arr.filter(isOperationalRecord) as OperationalRecord[]
186
+ const epcis = arr.filter(r => !isEnergyRecord(r) && !isEnergyEquipmentRecord(r) && !isOperationalRecord(r)) as (CanonicalRecord | CanonicalAggregationRecord | CanonicalTransformationRecord)[]
68
187
 
188
+ /* 품목 어휘 안에서도 갈림이 있다(개체 관측 / 담김) — 그 판정도 커널의 것을 부른다. */
69
189
  const epcisResult = epcis.length
70
- ? ingest(epcis.map(r => ({ ...r, sourceType: 'twin' })), TWIN_INGEST_RULES, { tenantId, defaultEventTime })
190
+ ? ingest(
191
+ epcis.map(r => ({
192
+ ...r,
193
+ /* 판정은 커널이 한 곳에서 한다 — 여기서 다시 짐작하면 소비처마다 답이 달라진다. */
194
+ sourceType: isTransformationRecord(r)
195
+ ? 'twin-transformation'
196
+ : isAggregationRecord(r)
197
+ ? 'twin-aggregation'
198
+ : 'twin'
199
+ })),
200
+ TWIN_INGEST_RULES,
201
+ /*
202
+ * **레코드가 시각을 말하면 그것을 쓴다** (2026-08-23).
203
+ *
204
+ * `eventTimePath` 를 주지 않던 동안 이 경로는 **언제나 폴링 시각**을 썼다(`defaultEventTime`).
205
+ * 그래서 커넥터가 이미 싣고 있던 `eventTime`(변환 레코드의 `finishedAt` 등)이 **조용히
206
+ * 무시됐다** — 커넥터를 붙인 쪽은 그것이 먹는다고 믿고 있었고, 어디에도 오류가 나지 않았다.
207
+ *
208
+ * 무엇이 걸렸나: 계보(변환)의 시각이다. 4월에 만든 로트가 8월에 만들어진 것으로 저널에 남았다.
209
+ * 그리고 실측 소요를 재는 폴드가 봉투 시각을 쓰므로(§`kpi-fold`: `workMs = 완료 − 첫 착수`),
210
+ * 폴링 간격을 작업시간으로 배울 수 있었다 — 못 배우는 것보다 나쁘다.
211
+ *
212
+ * 레코드에 `eventTime` 이 없으면 커널이 조용히 기본값으로 떨어진다(§`resolve` — 없는 값은
213
+ * 오류가 아니다). 그래서 이 한 줄이 기존 경로를 막지 않는다: 시각을 말하는 레코드만 사실이 된다.
214
+ */
215
+ { tenantId, defaultEventTime, eventTimePath: 'eventTime' }
216
+ )
71
217
  : { accepted: [], rejected: [] }
72
218
  const energyResult = energy.length ? ingestEnergyRecords(energy, { tenantId, defaultEventTime }) : { accepted: [], rejected: [] }
73
219
  const equipmentResult = energyEquipment.length
74
220
  ? ingestEnergyEquipmentRecords(energyEquipment, { tenantId, defaultEventTime })
75
221
  : { accepted: [], rejected: [] }
222
+ const operationalResult = operational.length
223
+ ? ingestOperationalRecords(operational, { tenantId, defaultEventTime })
224
+ : { accepted: [], rejected: [] }
76
225
 
77
226
  /* 거부분을 합쳐 돌려준다 — 어느 어휘에서 거부됐는지는 이유 문구가 말한다. */
78
227
  return {
79
- accepted: [...epcisResult.accepted, ...energyResult.accepted, ...equipmentResult.accepted],
80
- rejected: [...epcisResult.rejected, ...energyResult.rejected, ...equipmentResult.rejected]
228
+ accepted: [...epcisResult.accepted, ...energyResult.accepted, ...equipmentResult.accepted, ...operationalResult.accepted],
229
+ rejected: [...epcisResult.rejected, ...energyResult.rejected, ...equipmentResult.rejected, ...operationalResult.rejected]
81
230
  }
82
231
  }
@@ -57,7 +57,7 @@ export function routeCommand(inst: RoutableInstance | undefined, ownedByCaller:
57
57
  if (!type) return { target: 'reject', errorCode: 'unknown-command' }
58
58
 
59
59
  if (inst.mode !== 'live') {
60
- /* 시뮬 — 커널이 세계다. 런타임이 없으면 예외를 던지지 않고 이유를 말한다. */
60
+ /* 시뮬 — 커널이 세계다. 런타임이 없으면 오류를 내지 않고 이유를 말한다. */
61
61
  return inst.hasRuntime ? { target: 'runtime' } : { target: 'reject', errorCode: 'no-runtime' }
62
62
  }
63
63
 
@@ -0,0 +1,66 @@
1
+ /*
2
+ * **자극은 원본이 선언한다** — 트윈이 아니라.
3
+ *
4
+ * ── 무엇이 났나 (2026-08-19) ────────────────────────────────────────────────
5
+ * 데모의 자극(시나리오)은 시드 코드가 **메모리에만** 실었다: `runtime.scenario.load(...)`. 그래서 트윈을
6
+ * 재기동하면 자극이 사라지고, 구조만 서 있는 트윈이 남는다 — 작업 0·오더 0. 「살아 있는 데모」가
7
+ * **첫 재기동까지만** 사는 것이다(오늘 서버를 여러 번 띄우는 동안 실제로 그 상태였다).
8
+ *
9
+ * ── 왜 트윈에 저장하지 않나 (ADR-0029 · plans/simulator-as-source.md §4) ────
10
+ * 자극은 **원본의 거동**이다: 무엇이 들어오고 무슨 주문이 나는지는 시뮬레이터(원본)가 정하는 사실이고,
11
+ * 트윈은 그것을 반영한다. 트윈에 새 축을 만들면 ADR-0029 가 나중에 걷어내야 하는 표면이 하나 늘고
12
+ * (그 계획서의 §2·§5 가 이미 그 부담을 적어 두었다), 원본에 두면 그 계획의 **첫 조각 앞자리**가 된다:
13
+ * 지금은 호스트가 읽어 커널에 싣고, 나중에 `openLiveFeed` 뒤로 옮기면 같은 선언이 그대로 쓰인다.
14
+ *
15
+ * 그래서 집은 `TwinReference.connectionConfig.scenario` — 어댑터별 설정 자리(이미 `simple-json`)다.
16
+ *
17
+ * ── 판정을 순수 함수로 (이 파일) ────────────────────────────────────────────
18
+ * 「이 트윈에 저장된 자극을 실을 것인가」에는 사람이 틀리기 쉬운 갈림이 셋 있다: 미러에 실으면 **현실을
19
+ * 오염**시키고, 잘못된 선언을 실으면 **다음 틱에서 서버가 내려가고**(실제로 그 이력이 있다), 시나리오
20
+ * 엔진이 없는 런타임에 실으면 조용히 아무 일도 없다. 그래서 판정을 값으로 만들고 시험한다.
21
+ */
22
+
23
+ /** 자극을 실을지 말지 — 그리고 왜. 부르는 쪽은 이 답만 보고 움직인다. */
24
+ export type StimulusPlan =
25
+ /** 실어라 — 검사를 통과한 선언. */
26
+ | { action: 'load'; scenario: any }
27
+ /**
28
+ * 싣지 않는다 — 그 이유.
29
+ * · `none` — 원본이 자극을 선언하지 않았다(정상: 정적 구조만 반영하는 트윈)
30
+ * · `mirror` — 미러다. 외부 실물이 진실인 트윈에 우리가 자극을 만들면 그것은 현실이 아니다
31
+ * · `no-engine` — 이 런타임에 시나리오 엔진이 없다(관측 구동 커널)
32
+ * · `invalid` — 선언이 커널 규약을 어긴다. **싣지 않고 말한다**(다음 틱에서 터지게 두지 않는다)
33
+ */
34
+ | { action: 'skip'; reason: 'none' | 'observed' | 'no-engine' | 'invalid'; detail?: string }
35
+
36
+ /**
37
+ * 저장된 자극 선언을 읽어 **실을지 판정한다** — 커널 검사기를 그대로 쓴다(규칙을 두 벌로 만들지 않는다).
38
+ *
39
+ * `validate` 는 커널의 `validateScenario` 를 넘긴다(호스트가 커널 타입에 묶이지 않게 함수로 받는다).
40
+ * 검사기가 없으면 **싣지 않는다**: 검사할 수 없는 선언을 태우는 것이 가장 비싼 선택이다(서버가 내려간
41
+ * 이력이 있다).
42
+ */
43
+ export function planStimulus(
44
+ config: any,
45
+ runtime: { hasScenarioEngine: boolean; mode?: 'sim' | 'live' },
46
+ validate?: (scenario: any) => { ok: boolean; errorCode?: string; errorParams?: any } | { ok: false; errorCode: string }
47
+ ): StimulusPlan {
48
+ const scenario = config?.scenario
49
+ if (!scenario || typeof scenario !== 'object') return { action: 'skip', reason: 'none' }
50
+ /* 관측 트윈을 먼저 막는다 — 「엔진이 없다」로 답하면 이유가 흐려진다(애초에 실을 수 없는 트윈이다). */
51
+ if (runtime.mode === 'live') return { action: 'skip', reason: 'observed' }
52
+ if (!runtime.hasScenarioEngine) return { action: 'skip', reason: 'no-engine' }
53
+ if (!validate) return { action: 'skip', reason: 'invalid', detail: 'no validator available' }
54
+ const v = validate(scenario)
55
+ if (!v?.ok) return { action: 'skip', reason: 'invalid', detail: (v as any)?.errorCode ?? 'rejected' }
56
+ return { action: 'load', scenario }
57
+ }
58
+
59
+ /**
60
+ * 선언을 원본 설정에 얹는다(순수) — **기존 설정을 지우지 않는다.**
61
+ *
62
+ * 어댑터 설정에는 접속 정보가 함께 산다. 자극을 넣으면서 그 옆을 덮으면 다음 재동기가 원본을 못 찾는다.
63
+ */
64
+ export function withStimulus(config: any, scenario: any): any {
65
+ return { ...(config ?? {}), scenario }
66
+ }
@@ -12,6 +12,7 @@ export * from '@things-factory/headless-twin/dist-shared/entity-delta.js'
12
12
  export * from '@things-factory/headless-twin/dist-shared/twin-level.js'
13
13
  /* 부하 계기판 — 어떤 작업이 이벤트 루프를 얼마나 쓰는지. */
14
14
  export * from './load-meter.js'
15
+ export * from './loop-lag.js'
15
16
  /* 런타임 레지스트리 키 규약 — 소비처(다른 키 캐시·테스트)가 `:` 를 다시 적지 않게 함께 내보낸다. */
16
17
  export * from './runtime-key.js'
17
18
  export * from './kpi-query.js'
@@ -20,6 +21,11 @@ export * from './travel-estimator.js'
20
21
  export * from './measured-estimator.js'
21
22
  export * from './model-basis.js'
22
23
  export * from './live-feed-registry.js'
24
+ /* 동기화 건강 — 「이 트윈이 현장과 맞춰지고 있나」. 판정·추이·사유·표본. */
25
+ export * from './ingest-health.js'
23
26
  /* 로컬 선언(현장·트윈) — 소비처(twin-ai 도구)가 명세·검사 규칙을 다시 적지 않게. */
24
27
  export * from './local-declarations.js'
25
28
  export * from './operation-basis.js'
29
+ export * from './measured-yield.js'
30
+ export * from './declared-stimulus.js'
31
+ export * from './integration-coverage.js'