@things-factory/headless-twin 10.1.277 → 10.1.279

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (614) hide show
  1. package/package.json +21 -7
  2. package/dist-server/engine/attention-digest.js.map +0 -1
  3. package/dist-server/engine/canonical-ingest.js.map +0 -1
  4. package/dist-server/engine/checkpoint-fold.js.map +0 -1
  5. package/dist-server/engine/command-routing.js.map +0 -1
  6. package/dist-server/engine/declared-stimulus.js.map +0 -1
  7. package/dist-server/engine/energy-topology.js.map +0 -1
  8. package/dist-server/engine/event-time.js.map +0 -1
  9. package/dist-server/engine/index.js.map +0 -1
  10. package/dist-server/engine/ingest-dedupe.js.map +0 -1
  11. package/dist-server/engine/ingest-health.js.map +0 -1
  12. package/dist-server/engine/integration-coverage.js.map +0 -1
  13. package/dist-server/engine/integration-probes.js.map +0 -1
  14. package/dist-server/engine/integration-runner.js.map +0 -1
  15. package/dist-server/engine/integration-target-profile.js.map +0 -1
  16. package/dist-server/engine/kpi-baseline.js.map +0 -1
  17. package/dist-server/engine/kpi-fold.js.map +0 -1
  18. package/dist-server/engine/kpi-query.js.map +0 -1
  19. package/dist-server/engine/kpi-target.js.map +0 -1
  20. package/dist-server/engine/live-attentions.js.map +0 -1
  21. package/dist-server/engine/live-feed-registry.js.map +0 -1
  22. package/dist-server/engine/load-meter.js.map +0 -1
  23. package/dist-server/engine/local-declarations.js.map +0 -1
  24. package/dist-server/engine/log.js.map +0 -1
  25. package/dist-server/engine/loop-lag.js.map +0 -1
  26. package/dist-server/engine/measured-estimator.js.map +0 -1
  27. package/dist-server/engine/measured-yield.js.map +0 -1
  28. package/dist-server/engine/model-basis.js.map +0 -1
  29. package/dist-server/engine/model-gap.js.map +0 -1
  30. package/dist-server/engine/model-refusal.js.map +0 -1
  31. package/dist-server/engine/model-vocabulary.js.map +0 -1
  32. package/dist-server/engine/oee-accumulator.js.map +0 -1
  33. package/dist-server/engine/operation-basis.js.map +0 -1
  34. package/dist-server/engine/producer-guard.js.map +0 -1
  35. package/dist-server/engine/property-effects.js.map +0 -1
  36. package/dist-server/engine/read-time.js.map +0 -1
  37. package/dist-server/engine/restart-policy.js.map +0 -1
  38. package/dist-server/engine/runtime-key.js.map +0 -1
  39. package/dist-server/engine/spec-coverage.js.map +0 -1
  40. package/dist-server/engine/stage-path.js.map +0 -1
  41. package/dist-server/engine/state-axes.js.map +0 -1
  42. package/dist-server/engine/structure-diff.js.map +0 -1
  43. package/dist-server/engine/travel-estimator.js.map +0 -1
  44. package/dist-server/engine/twin-engine.js.map +0 -1
  45. package/dist-server/engine/twin-state-channel.js.map +0 -1
  46. package/dist-server/engine/warm-start.js.map +0 -1
  47. package/dist-server/index.js.map +0 -1
  48. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +0 -1
  49. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +0 -1
  50. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +0 -1
  51. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +0 -1
  52. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +0 -1
  53. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +0 -1
  54. package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.js.map +0 -1
  55. package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.js.map +0 -1
  56. package/dist-server/migrations/1786800000000-CreateTwinReferenceReconciliations.js.map +0 -1
  57. package/dist-server/migrations/index.js.map +0 -1
  58. package/dist-server/routes.js.map +0 -1
  59. package/dist-server/service/actuation/actuation-advisor.js.map +0 -1
  60. package/dist-server/service/actuation/actuation-outcome.js.map +0 -1
  61. package/dist-server/service/actuation/actuation-rule-resolver.js.map +0 -1
  62. package/dist-server/service/actuation/actuation-rule.js.map +0 -1
  63. package/dist-server/service/actuation/approval-gateway.js.map +0 -1
  64. package/dist-server/service/actuation/catch-up-effects.js.map +0 -1
  65. package/dist-server/service/actuation/command-dispatcher.js.map +0 -1
  66. package/dist-server/service/actuation/command-store.js.map +0 -1
  67. package/dist-server/service/actuation/dispatch-after-approval.js.map +0 -1
  68. package/dist-server/service/actuation/effect-match.js.map +0 -1
  69. package/dist-server/service/actuation/index.js.map +0 -1
  70. package/dist-server/service/actuation/retry-plan.js.map +0 -1
  71. package/dist-server/service/actuation/rule-evaluate.js.map +0 -1
  72. package/dist-server/service/actuation/rule-loop.js.map +0 -1
  73. package/dist-server/service/actuation/rule-runner.js.map +0 -1
  74. package/dist-server/service/actuation/rule-wiring.js.map +0 -1
  75. package/dist-server/service/actuation/settle-effects.js.map +0 -1
  76. package/dist-server/service/actuation/sweep-dispatch.js.map +0 -1
  77. package/dist-server/service/actuation/twin-command-resolver.js.map +0 -1
  78. package/dist-server/service/actuation/twin-command.js.map +0 -1
  79. package/dist-server/service/actuation/twin-now.js.map +0 -1
  80. package/dist-server/service/index.js.map +0 -1
  81. package/dist-server/service/reference/actuation-routing.js.map +0 -1
  82. package/dist-server/service/reference/actuation-target.js.map +0 -1
  83. package/dist-server/service/reference/actuation-verification.js.map +0 -1
  84. package/dist-server/service/reference/attention-lane.js.map +0 -1
  85. package/dist-server/service/reference/connection-portability-resolver.js.map +0 -1
  86. package/dist-server/service/reference/connection-portability.js.map +0 -1
  87. package/dist-server/service/reference/control-routing.js.map +0 -1
  88. package/dist-server/service/reference/discovery-result.js.map +0 -1
  89. package/dist-server/service/reference/fill-cadence.js.map +0 -1
  90. package/dist-server/service/reference/fill-dedupe.js.map +0 -1
  91. package/dist-server/service/reference/fill-loop.js.map +0 -1
  92. package/dist-server/service/reference/fill-span.js.map +0 -1
  93. package/dist-server/service/reference/hook-contract.js.map +0 -1
  94. package/dist-server/service/reference/hook-rejected.js.map +0 -1
  95. package/dist-server/service/reference/hook-store.js.map +0 -1
  96. package/dist-server/service/reference/index.js.map +0 -1
  97. package/dist-server/service/reference/ingest-space.js.map +0 -1
  98. package/dist-server/service/reference/knob-defaults.js.map +0 -1
  99. package/dist-server/service/reference/live-cadence.js.map +0 -1
  100. package/dist-server/service/reference/live-feed-lease.js.map +0 -1
  101. package/dist-server/service/reference/live-ingest.js.map +0 -1
  102. package/dist-server/service/reference/reference-adapter.js.map +0 -1
  103. package/dist-server/service/reference/reference-assessment.js.map +0 -1
  104. package/dist-server/service/reference/reference-fill-run.js.map +0 -1
  105. package/dist-server/service/reference/reference-fill.js.map +0 -1
  106. package/dist-server/service/reference/reference-hook.js.map +0 -1
  107. package/dist-server/service/reference/reference-link-groups.js.map +0 -1
  108. package/dist-server/service/reference/reference-links.js.map +0 -1
  109. package/dist-server/service/reference/reference-live.js.map +0 -1
  110. package/dist-server/service/reference/reference-master.js.map +0 -1
  111. package/dist-server/service/reference/reference-probe.js.map +0 -1
  112. package/dist-server/service/reference/reference-progress-channel.js.map +0 -1
  113. package/dist-server/service/reference/reference-progress-subscription.js.map +0 -1
  114. package/dist-server/service/reference/reference-progress.js.map +0 -1
  115. package/dist-server/service/reference/reference-reconciliation-query.js.map +0 -1
  116. package/dist-server/service/reference/reference-reconciliation-writer.js.map +0 -1
  117. package/dist-server/service/reference/reference-reconciliation.js.map +0 -1
  118. package/dist-server/service/reference/reference-registry.js.map +0 -1
  119. package/dist-server/service/reference/reference-resolver.js.map +0 -1
  120. package/dist-server/service/reference/template-registry.js.map +0 -1
  121. package/dist-server/service/reference/twin-reference-reconciliation.js.map +0 -1
  122. package/dist-server/service/reference/twin-reference.js.map +0 -1
  123. package/dist-server/service/twin-attention/index.js.map +0 -1
  124. package/dist-server/service/twin-attention/twin-attention-query.js.map +0 -1
  125. package/dist-server/service/twin-audit/command-audit.js.map +0 -1
  126. package/dist-server/service/twin-audit/index.js.map +0 -1
  127. package/dist-server/service/twin-audit/twin-audit-event.js.map +0 -1
  128. package/dist-server/service/twin-audit/twin-audit-query.js.map +0 -1
  129. package/dist-server/service/twin-backfill/backfill-period-facts.js.map +0 -1
  130. package/dist-server/service/twin-backfill/backfill-shape.js.map +0 -1
  131. package/dist-server/service/twin-backfill/index.js.map +0 -1
  132. package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +0 -1
  133. package/dist-server/service/twin-control/index.js.map +0 -1
  134. package/dist-server/service/twin-control/twin-control-mutation.js.map +0 -1
  135. package/dist-server/service/twin-event/index.js.map +0 -1
  136. package/dist-server/service/twin-event/journal-count.js.map +0 -1
  137. package/dist-server/service/twin-event/twin-event-keys.js.map +0 -1
  138. package/dist-server/service/twin-event/twin-event-type.js.map +0 -1
  139. package/dist-server/service/twin-event/twin-event.js.map +0 -1
  140. package/dist-server/service/twin-forecast/forecast-metrics.js.map +0 -1
  141. package/dist-server/service/twin-forecast/forecast-tuning.js.map +0 -1
  142. package/dist-server/service/twin-forecast/gap-analytics.js.map +0 -1
  143. package/dist-server/service/twin-forecast/index.js.map +0 -1
  144. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +0 -1
  145. package/dist-server/service/twin-ingest-window/index.js.map +0 -1
  146. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +0 -1
  147. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +0 -1
  148. package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +0 -1
  149. package/dist-server/service/twin-instance/index.js.map +0 -1
  150. package/dist-server/service/twin-instance/twin-instance.js.map +0 -1
  151. package/dist-server/service/twin-journal/event-type-split-shape.js.map +0 -1
  152. package/dist-server/service/twin-journal/event-type-split.js.map +0 -1
  153. package/dist-server/service/twin-journal/index.js.map +0 -1
  154. package/dist-server/service/twin-journal/journal-order.js.map +0 -1
  155. package/dist-server/service/twin-journal/twin-journal-query.js.map +0 -1
  156. package/dist-server/service/twin-lifecycle/domain-catalog.js.map +0 -1
  157. package/dist-server/service/twin-lifecycle/index.js.map +0 -1
  158. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +0 -1
  159. package/dist-server/service/twin-metrics/index.js.map +0 -1
  160. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +0 -1
  161. package/dist-server/service/twin-model/axis-journal-evidence.js.map +0 -1
  162. package/dist-server/service/twin-model/choose-recipe.js.map +0 -1
  163. package/dist-server/service/twin-model/epcis-coverage.js.map +0 -1
  164. package/dist-server/service/twin-model/external-resolver.js.map +0 -1
  165. package/dist-server/service/twin-model/iec61850-coverage.js.map +0 -1
  166. package/dist-server/service/twin-model/index.js.map +0 -1
  167. package/dist-server/service/twin-model/isa95-coverage.js.map +0 -1
  168. package/dist-server/service/twin-model/item-ref.js.map +0 -1
  169. package/dist-server/service/twin-model/name-index.js.map +0 -1
  170. package/dist-server/service/twin-model/project-structure.js.map +0 -1
  171. package/dist-server/service/twin-model/standard-coverage.js.map +0 -1
  172. package/dist-server/service/twin-model/status-tally.js.map +0 -1
  173. package/dist-server/service/twin-model/twin-equipment.js.map +0 -1
  174. package/dist-server/service/twin-model/twin-lineage-query.js.map +0 -1
  175. package/dist-server/service/twin-model/twin-location.js.map +0 -1
  176. package/dist-server/service/twin-model/twin-model-item-query.js.map +0 -1
  177. package/dist-server/service/twin-model/twin-model-mutation.js.map +0 -1
  178. package/dist-server/service/twin-model/twin-model-query.js.map +0 -1
  179. package/dist-server/service/twin-model/twin-model-tree-query.js.map +0 -1
  180. package/dist-server/service/twin-model/twin-operation.js.map +0 -1
  181. package/dist-server/service/twin-readiness/index.js.map +0 -1
  182. package/dist-server/service/twin-readiness/twin-readiness-query.js.map +0 -1
  183. package/dist-server/service/twin-space/area-resolver.js.map +0 -1
  184. package/dist-server/service/twin-space/index.js.map +0 -1
  185. package/dist-server/service/twin-space/move-space.js.map +0 -1
  186. package/dist-server/service/twin-space/representation-role.js.map +0 -1
  187. package/dist-server/service/twin-space/space-integrity.js.map +0 -1
  188. package/dist-server/service/twin-space/twin-area.js.map +0 -1
  189. package/dist-server/service/twin-space/twin-space-area.js.map +0 -1
  190. package/dist-server/service/twin-space/twin-space-representation.js.map +0 -1
  191. package/dist-server/service/twin-space/twin-space-resolver.js.map +0 -1
  192. package/dist-server/service/twin-space/twin-space.js.map +0 -1
  193. package/dist-server/service/twin-state/index.js.map +0 -1
  194. package/dist-server/service/twin-state/twin-state-subscription.js.map +0 -1
  195. package/dist-server/service/twin-structure/index.js.map +0 -1
  196. package/dist-server/service/twin-structure/twin-structure.js.map +0 -1
  197. package/dist-server/service/twin-subject/event-subjects.js.map +0 -1
  198. package/dist-server/service/twin-subject/index.js.map +0 -1
  199. package/dist-server/service/twin-subject/item-resolver.js.map +0 -1
  200. package/dist-server/service/twin-subject/subject-query-shape.js.map +0 -1
  201. package/dist-server/service/twin-subject/twin-subject-backfill.js.map +0 -1
  202. package/dist-server/service/twin-subject/twin-subject-event.js.map +0 -1
  203. package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +0 -1
  204. package/dist-server/service/twin-subject/twin-subject-query.js.map +0 -1
  205. package/dist-server/service/twin-subject/twin-subject-writer.js.map +0 -1
  206. package/dist-server/service/twin-target/index.js.map +0 -1
  207. package/dist-server/service/twin-target/twin-target-resolver.js.map +0 -1
  208. package/dist-server/service/twin-target/twin-target.js.map +0 -1
  209. package/dist-shared/axis-read.js.map +0 -1
  210. package/dist-shared/entity-delta.js.map +0 -1
  211. package/dist-shared/kpi-broadcast.js.map +0 -1
  212. package/dist-shared/touched-items.js.map +0 -1
  213. package/dist-shared/twin-level.js.map +0 -1
  214. package/server/engine/attention-digest.ts +0 -102
  215. package/server/engine/canonical-ingest.ts +0 -511
  216. package/server/engine/checkpoint-fold.ts +0 -12
  217. package/server/engine/command-routing.ts +0 -67
  218. package/server/engine/declared-stimulus.ts +0 -66
  219. package/server/engine/energy-topology.ts +0 -189
  220. package/server/engine/event-time.ts +0 -96
  221. package/server/engine/index.ts +0 -51
  222. package/server/engine/ingest-dedupe.ts +0 -24
  223. package/server/engine/ingest-health.ts +0 -24
  224. package/server/engine/integration-coverage.ts +0 -147
  225. package/server/engine/integration-probes.ts +0 -144
  226. package/server/engine/integration-runner.ts +0 -95
  227. package/server/engine/integration-target-profile.ts +0 -103
  228. package/server/engine/kpi-baseline.ts +0 -202
  229. package/server/engine/kpi-fold.ts +0 -1516
  230. package/server/engine/kpi-query.ts +0 -1210
  231. package/server/engine/kpi-target.ts +0 -248
  232. package/server/engine/live-attentions.ts +0 -30
  233. package/server/engine/live-feed-registry.ts +0 -59
  234. package/server/engine/load-meter.ts +0 -393
  235. package/server/engine/local-declarations.ts +0 -779
  236. package/server/engine/log.ts +0 -72
  237. package/server/engine/loop-lag.ts +0 -120
  238. package/server/engine/measured-estimator.ts +0 -91
  239. package/server/engine/measured-yield.ts +0 -89
  240. package/server/engine/model-basis.ts +0 -162
  241. package/server/engine/model-gap.ts +0 -168
  242. package/server/engine/model-refusal.ts +0 -31
  243. package/server/engine/model-vocabulary.ts +0 -82
  244. package/server/engine/oee-accumulator.ts +0 -195
  245. package/server/engine/operation-basis.ts +0 -131
  246. package/server/engine/producer-guard.ts +0 -64
  247. package/server/engine/property-effects.ts +0 -216
  248. package/server/engine/read-time.ts +0 -77
  249. package/server/engine/restart-policy.ts +0 -55
  250. package/server/engine/runtime-key.ts +0 -58
  251. package/server/engine/spec-coverage.ts +0 -105
  252. package/server/engine/stage-path.ts +0 -172
  253. package/server/engine/state-axes.ts +0 -60
  254. package/server/engine/structure-diff.ts +0 -88
  255. package/server/engine/travel-estimator.ts +0 -138
  256. package/server/engine/twin-engine.ts +0 -4979
  257. package/server/engine/twin-state-channel.ts +0 -39
  258. package/server/engine/warm-start.ts +0 -265
  259. package/server/index.ts +0 -90
  260. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +0 -43
  261. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +0 -54
  262. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +0 -53
  263. package/server/migrations/1786300000000-IndexEventTypeByTime.ts +0 -48
  264. package/server/migrations/1786400000000-IndexEventCreatedAt.ts +0 -38
  265. package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +0 -93
  266. package/server/migrations/1786600000000-RenameOperatoMesAdapterType.ts +0 -66
  267. package/server/migrations/1786700000000-AddDispatchRetryColumns.ts +0 -82
  268. package/server/migrations/1786800000000-CreateTwinReferenceReconciliations.ts +0 -39
  269. package/server/migrations/index.ts +0 -25
  270. package/server/routes.ts +0 -75
  271. package/server/service/actuation/actuation-advisor.ts +0 -131
  272. package/server/service/actuation/actuation-outcome.ts +0 -97
  273. package/server/service/actuation/actuation-rule-resolver.ts +0 -147
  274. package/server/service/actuation/actuation-rule.ts +0 -139
  275. package/server/service/actuation/approval-gateway.ts +0 -44
  276. package/server/service/actuation/catch-up-effects.ts +0 -152
  277. package/server/service/actuation/command-dispatcher.ts +0 -474
  278. package/server/service/actuation/command-store.ts +0 -236
  279. package/server/service/actuation/dispatch-after-approval.ts +0 -71
  280. package/server/service/actuation/effect-match.ts +0 -124
  281. package/server/service/actuation/index.ts +0 -27
  282. package/server/service/actuation/retry-plan.ts +0 -145
  283. package/server/service/actuation/rule-evaluate.ts +0 -352
  284. package/server/service/actuation/rule-loop.ts +0 -160
  285. package/server/service/actuation/rule-runner.ts +0 -216
  286. package/server/service/actuation/rule-wiring.ts +0 -84
  287. package/server/service/actuation/settle-effects.ts +0 -115
  288. package/server/service/actuation/sweep-dispatch.ts +0 -179
  289. package/server/service/actuation/twin-command-resolver.ts +0 -387
  290. package/server/service/actuation/twin-command.ts +0 -232
  291. package/server/service/actuation/twin-now.ts +0 -97
  292. package/server/service/index.ts +0 -100
  293. package/server/service/reference/actuation-routing.ts +0 -192
  294. package/server/service/reference/actuation-target.ts +0 -163
  295. package/server/service/reference/actuation-verification.ts +0 -85
  296. package/server/service/reference/attention-lane.ts +0 -21
  297. package/server/service/reference/connection-portability-resolver.ts +0 -171
  298. package/server/service/reference/connection-portability.ts +0 -329
  299. package/server/service/reference/control-routing.ts +0 -62
  300. package/server/service/reference/discovery-result.ts +0 -95
  301. package/server/service/reference/fill-cadence.ts +0 -29
  302. package/server/service/reference/fill-dedupe.ts +0 -66
  303. package/server/service/reference/fill-loop.ts +0 -153
  304. package/server/service/reference/fill-span.ts +0 -31
  305. package/server/service/reference/hook-contract.ts +0 -364
  306. package/server/service/reference/hook-rejected.ts +0 -53
  307. package/server/service/reference/hook-store.ts +0 -28
  308. package/server/service/reference/index.ts +0 -18
  309. package/server/service/reference/ingest-space.ts +0 -104
  310. package/server/service/reference/knob-defaults.ts +0 -59
  311. package/server/service/reference/live-cadence.ts +0 -129
  312. package/server/service/reference/live-feed-lease.ts +0 -56
  313. package/server/service/reference/live-ingest.ts +0 -94
  314. package/server/service/reference/reference-adapter.ts +0 -985
  315. package/server/service/reference/reference-assessment.ts +0 -250
  316. package/server/service/reference/reference-fill-run.ts +0 -179
  317. package/server/service/reference/reference-fill.ts +0 -223
  318. package/server/service/reference/reference-hook.ts +0 -479
  319. package/server/service/reference/reference-link-groups.ts +0 -21
  320. package/server/service/reference/reference-links.ts +0 -77
  321. package/server/service/reference/reference-live.ts +0 -420
  322. package/server/service/reference/reference-master.ts +0 -999
  323. package/server/service/reference/reference-probe.ts +0 -264
  324. package/server/service/reference/reference-progress-channel.ts +0 -26
  325. package/server/service/reference/reference-progress-subscription.ts +0 -78
  326. package/server/service/reference/reference-progress.ts +0 -95
  327. package/server/service/reference/reference-reconciliation-query.ts +0 -28
  328. package/server/service/reference/reference-reconciliation-writer.ts +0 -38
  329. package/server/service/reference/reference-reconciliation.ts +0 -55
  330. package/server/service/reference/reference-registry.ts +0 -17
  331. package/server/service/reference/reference-resolver.ts +0 -969
  332. package/server/service/reference/template-registry.ts +0 -85
  333. package/server/service/reference/twin-reference-reconciliation.ts +0 -27
  334. package/server/service/reference/twin-reference.ts +0 -190
  335. package/server/service/twin-attention/index.ts +0 -3
  336. package/server/service/twin-attention/twin-attention-query.ts +0 -70
  337. package/server/service/twin-audit/command-audit.ts +0 -114
  338. package/server/service/twin-audit/index.ts +0 -5
  339. package/server/service/twin-audit/twin-audit-event.ts +0 -158
  340. package/server/service/twin-audit/twin-audit-query.ts +0 -72
  341. package/server/service/twin-backfill/backfill-period-facts.ts +0 -168
  342. package/server/service/twin-backfill/backfill-shape.ts +0 -86
  343. package/server/service/twin-backfill/index.ts +0 -3
  344. package/server/service/twin-backfill/twin-backfill-resolver.ts +0 -35
  345. package/server/service/twin-control/index.ts +0 -3
  346. package/server/service/twin-control/twin-control-mutation.ts +0 -150
  347. package/server/service/twin-event/index.ts +0 -3
  348. package/server/service/twin-event/journal-count.ts +0 -53
  349. package/server/service/twin-event/twin-event-keys.ts +0 -215
  350. package/server/service/twin-event/twin-event-type.ts +0 -68
  351. package/server/service/twin-event/twin-event.ts +0 -250
  352. package/server/service/twin-forecast/forecast-metrics.ts +0 -106
  353. package/server/service/twin-forecast/forecast-tuning.ts +0 -40
  354. package/server/service/twin-forecast/gap-analytics.ts +0 -139
  355. package/server/service/twin-forecast/index.ts +0 -3
  356. package/server/service/twin-forecast/twin-forecast-query.ts +0 -486
  357. package/server/service/twin-ingest-window/index.ts +0 -7
  358. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +0 -125
  359. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +0 -57
  360. package/server/service/twin-ingest-window/twin-ingest-window.ts +0 -113
  361. package/server/service/twin-instance/index.ts +0 -3
  362. package/server/service/twin-instance/twin-instance.ts +0 -199
  363. package/server/service/twin-journal/event-type-split-shape.ts +0 -112
  364. package/server/service/twin-journal/event-type-split.ts +0 -144
  365. package/server/service/twin-journal/index.ts +0 -3
  366. package/server/service/twin-journal/journal-order.ts +0 -20
  367. package/server/service/twin-journal/twin-journal-query.ts +0 -615
  368. package/server/service/twin-lifecycle/domain-catalog.ts +0 -51
  369. package/server/service/twin-lifecycle/index.ts +0 -3
  370. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +0 -285
  371. package/server/service/twin-metrics/index.ts +0 -3
  372. package/server/service/twin-metrics/twin-metrics-query.ts +0 -172
  373. package/server/service/twin-model/axis-journal-evidence.ts +0 -57
  374. package/server/service/twin-model/choose-recipe.ts +0 -31
  375. package/server/service/twin-model/epcis-coverage.ts +0 -251
  376. package/server/service/twin-model/external-resolver.ts +0 -173
  377. package/server/service/twin-model/iec61850-coverage.ts +0 -111
  378. package/server/service/twin-model/index.ts +0 -25
  379. package/server/service/twin-model/isa95-coverage.ts +0 -320
  380. package/server/service/twin-model/item-ref.ts +0 -80
  381. package/server/service/twin-model/name-index.ts +0 -94
  382. package/server/service/twin-model/project-structure.ts +0 -213
  383. package/server/service/twin-model/standard-coverage.ts +0 -35
  384. package/server/service/twin-model/status-tally.ts +0 -37
  385. package/server/service/twin-model/twin-equipment.ts +0 -191
  386. package/server/service/twin-model/twin-lineage-query.ts +0 -327
  387. package/server/service/twin-model/twin-location.ts +0 -147
  388. package/server/service/twin-model/twin-model-item-query.ts +0 -1029
  389. package/server/service/twin-model/twin-model-mutation.ts +0 -503
  390. package/server/service/twin-model/twin-model-query.ts +0 -792
  391. package/server/service/twin-model/twin-model-tree-query.ts +0 -211
  392. package/server/service/twin-model/twin-operation.ts +0 -157
  393. package/server/service/twin-readiness/index.ts +0 -3
  394. package/server/service/twin-readiness/twin-readiness-query.ts +0 -96
  395. package/server/service/twin-space/area-resolver.ts +0 -75
  396. package/server/service/twin-space/index.ts +0 -8
  397. package/server/service/twin-space/move-space.ts +0 -262
  398. package/server/service/twin-space/representation-role.ts +0 -15
  399. package/server/service/twin-space/space-integrity.ts +0 -293
  400. package/server/service/twin-space/twin-area.ts +0 -63
  401. package/server/service/twin-space/twin-space-area.ts +0 -66
  402. package/server/service/twin-space/twin-space-representation.ts +0 -99
  403. package/server/service/twin-space/twin-space-resolver.ts +0 -582
  404. package/server/service/twin-space/twin-space.ts +0 -148
  405. package/server/service/twin-state/index.ts +0 -3
  406. package/server/service/twin-state/twin-state-subscription.ts +0 -62
  407. package/server/service/twin-structure/index.ts +0 -3
  408. package/server/service/twin-structure/twin-structure.ts +0 -96
  409. package/server/service/twin-subject/event-subjects.ts +0 -162
  410. package/server/service/twin-subject/index.ts +0 -10
  411. package/server/service/twin-subject/item-resolver.ts +0 -92
  412. package/server/service/twin-subject/subject-query-shape.ts +0 -84
  413. package/server/service/twin-subject/twin-subject-backfill.ts +0 -196
  414. package/server/service/twin-subject/twin-subject-event.ts +0 -101
  415. package/server/service/twin-subject/twin-subject-fast-path.ts +0 -346
  416. package/server/service/twin-subject/twin-subject-query.ts +0 -210
  417. package/server/service/twin-subject/twin-subject-writer.ts +0 -189
  418. package/server/service/twin-target/index.ts +0 -5
  419. package/server/service/twin-target/twin-target-resolver.ts +0 -142
  420. package/server/service/twin-target/twin-target.ts +0 -97
  421. package/server/types/global.d.ts +0 -22
  422. package/shared/axis-read.ts +0 -28
  423. package/shared/entity-delta.ts +0 -335
  424. package/shared/kpi-broadcast.ts +0 -13
  425. package/shared/touched-items.ts +0 -73
  426. package/shared/twin-level.ts +0 -48
  427. package/test/ack-shape-alignment.test.ts +0 -97
  428. package/test/actuation-advisor.test.ts +0 -55
  429. package/test/actuation-approval-door.test.ts +0 -411
  430. package/test/actuation-dispatch.test.ts +0 -209
  431. package/test/actuation-effect.test.ts +0 -162
  432. package/test/actuation-outcome.test.ts +0 -152
  433. package/test/actuation-rule-runner.test.ts +0 -281
  434. package/test/actuation-rule.test.ts +0 -266
  435. package/test/actuation-seam.test.ts +0 -255
  436. package/test/actuation-settle.test.ts +0 -164
  437. package/test/actuation-target.test.ts +0 -145
  438. package/test/actuation-verification-state.test.ts +0 -139
  439. package/test/adopt-structure-live.test.ts +0 -133
  440. package/test/aggregate-not-findone.test.ts +0 -112
  441. package/test/area-resolver-db.test.ts +0 -110
  442. package/test/attention-digest.test.ts +0 -161
  443. package/test/axis-declaration-passthrough-db.test.ts +0 -152
  444. package/test/axis-journal-evidence.test.ts +0 -71
  445. package/test/axis-read.test.ts +0 -188
  446. package/test/backfill-shape.test.ts +0 -89
  447. package/test/boot-resume.test.ts +0 -287
  448. package/test/broadcast-cost-baseline.test.ts +0 -201
  449. package/test/broadcast-period.test.ts +0 -58
  450. package/test/broadcast-queue-overflow.test.ts +0 -244
  451. package/test/canonical-ingest-vocabularies.test.ts +0 -451
  452. package/test/canonical-quantity-door.test.ts +0 -61
  453. package/test/capability-mapping.test.ts +0 -89
  454. package/test/checkpoint-refuses-empty.test.ts +0 -103
  455. package/test/choose-recipe.test.ts +0 -117
  456. package/test/column-type-portability.test.ts +0 -122
  457. package/test/command-routing.test.ts +0 -61
  458. package/test/command-store-writes-columns.test.ts +0 -126
  459. package/test/connection-portability-doors.test.ts +0 -87
  460. package/test/connector-capability-declaration.test.ts +0 -195
  461. package/test/contract-layer-guard.test.ts +0 -48
  462. package/test/control-capability.test.ts +0 -98
  463. package/test/cursor-stall-not-read-failure.test.ts +0 -126
  464. package/test/declaration-reaches-model.test.ts +0 -226
  465. package/test/declared-location-types.test.ts +0 -89
  466. package/test/declared-stimulus.test.ts +0 -88
  467. package/test/dedupe-transformation.test.ts +0 -112
  468. package/test/discovery-result.test.ts +0 -75
  469. package/test/dispatch-after-approval-source.test.ts +0 -81
  470. package/test/duration-estimators.test.ts +0 -144
  471. package/test/energy-topology.test.ts +0 -113
  472. package/test/entity-delta.test.ts +0 -352
  473. package/test/equipment-identity-reaches.test.ts +0 -57
  474. package/test/event-subjects.test.ts +0 -148
  475. package/test/event-time-column.test.ts +0 -67
  476. package/test/event-type-split.test.ts +0 -178
  477. package/test/external-resolver.test.ts +0 -177
  478. package/test/fact-scope-wiring.test.ts +0 -96
  479. package/test/fill-dedupe.test.ts +0 -59
  480. package/test/fill-loop.test.ts +0 -35
  481. package/test/first-envelope.test.ts +0 -153
  482. package/test/fold-gap-speaks.test.ts +0 -53
  483. package/test/fold-shift-dst.test.ts +0 -110
  484. package/test/forecast-metrics.test.ts +0 -157
  485. package/test/forecast-tuning.test.ts +0 -42
  486. package/test/gap-analytics.test.ts +0 -113
  487. package/test/generated-not-counted.test.ts +0 -99
  488. package/test/guard-roots.ts +0 -76
  489. package/test/home-in-area.test.ts +0 -90
  490. package/test/hook-address.test.ts +0 -28
  491. package/test/hook-rejected-shape.test.ts +0 -86
  492. package/test/hook-response-sum.test.ts +0 -193
  493. package/test/hook-sequence.test.ts +0 -333
  494. package/test/ingest-bench.test.ts +0 -70
  495. package/test/ingest-dedupe.test.ts +0 -56
  496. package/test/ingest-expected-quiet.test.ts +0 -111
  497. package/test/ingest-health-engine.test.ts +0 -248
  498. package/test/ingest-health-wiring.test.ts +0 -120
  499. package/test/ingest-health.test.ts +0 -306
  500. package/test/ingest-history.test.ts +0 -247
  501. package/test/ingest-idempotent.test.ts +0 -211
  502. package/test/ingest-reconcile-four-ways.test.ts +0 -97
  503. package/test/ingest-rules-header.test.ts +0 -100
  504. package/test/ingest-running-guard.test.ts +0 -136
  505. package/test/ingest-space.test.ts +0 -78
  506. package/test/ingest-window-rolls-unobserved.test.ts +0 -72
  507. package/test/ingest-wiring-guard.test.ts +0 -197
  508. package/test/instance-cache-lifecycle.test.ts +0 -110
  509. package/test/intake-mapping-parity.test.ts +0 -175
  510. package/test/integration-probes.test.ts +0 -103
  511. package/test/integration-runner.test.ts +0 -95
  512. package/test/item-ref.test.ts +0 -78
  513. package/test/journal-order-axis.test.ts +0 -80
  514. package/test/journal-read-discipline.test.ts +0 -186
  515. package/test/journal-retention.test.ts +0 -275
  516. package/test/journal-sort-axis.test.ts +0 -142
  517. package/test/journal-write-door.test.ts +0 -110
  518. package/test/journal-write-trend.test.ts +0 -115
  519. package/test/kernel-kind-guard.test.ts +0 -98
  520. package/test/knob-defaults.test.ts +0 -72
  521. package/test/kpi-baseline-db.test.ts +0 -216
  522. package/test/kpi-baseline.test.ts +0 -196
  523. package/test/kpi-fold.test.ts +0 -1456
  524. package/test/kpi-query-bench.test.ts +0 -130
  525. package/test/lens-own-places.test.ts +0 -50
  526. package/test/lineage-survives-restart.test.ts +0 -234
  527. package/test/live-cadence.test.ts +0 -170
  528. package/test/live-cursor-wiring.test.ts +0 -87
  529. package/test/live-feed-lease.test.ts +0 -63
  530. package/test/live-feed-registry.test.ts +0 -75
  531. package/test/live-kernel-facts.test.ts +0 -161
  532. package/test/live-mirror-parity.test.ts +0 -286
  533. package/test/load-meter.test.ts +0 -328
  534. package/test/local-declarations.test.ts +0 -662
  535. package/test/log-stamp.test.ts +0 -59
  536. package/test/loop-lag.test.ts +0 -82
  537. package/test/master-to-twin.test.ts +0 -279
  538. package/test/measured-yield.test.ts +0 -90
  539. package/test/merge-streams.test.ts +0 -70
  540. package/test/mirror-resumes-from-checkpoint.test.ts +0 -192
  541. package/test/model-basis.test.ts +0 -141
  542. package/test/model-gap.test.ts +0 -153
  543. package/test/model-refusal.test.ts +0 -75
  544. package/test/model-vocabulary.test.ts +0 -114
  545. package/test/move-space.test.ts +0 -149
  546. package/test/mutation-gate.test.ts +0 -122
  547. package/test/oee-accumulator.test.ts +0 -204
  548. package/test/oee-site-calendar.test.ts +0 -146
  549. package/test/open-doors.test.ts +0 -197
  550. package/test/operation-basis.test.ts +0 -127
  551. package/test/operational-vocabulary.test.ts +0 -108
  552. package/test/operations-capability-db.test.ts +0 -165
  553. package/test/order-and-transaction-split.test.ts +0 -107
  554. package/test/order-fold-host.test.ts +0 -112
  555. package/test/producer-guard.test.ts +0 -66
  556. package/test/project-structure-db.test.ts +0 -226
  557. package/test/projection-reaches-screen.test.ts +0 -277
  558. package/test/projection-reads-declared.test.ts +0 -109
  559. package/test/property-effects.test.ts +0 -128
  560. package/test/provenance-promotion.test.ts +0 -99
  561. package/test/read-failure-ledger.test.ts +0 -106
  562. package/test/read-failure-visible.test.ts +0 -145
  563. package/test/read-time.test.ts +0 -118
  564. package/test/reference-fill.test.ts +0 -235
  565. package/test/reference-grounding.test.ts +0 -70
  566. package/test/reference-hook.test.ts +0 -177
  567. package/test/reference-links.test.ts +0 -56
  568. package/test/reference-reconciliation.test.ts +0 -39
  569. package/test/registry-key-guard.test.ts +0 -80
  570. package/test/rejection-carries-event-id.test.ts +0 -101
  571. package/test/rename-twin.test.ts +0 -89
  572. package/test/resolve-ts-siblings.mjs +0 -52
  573. package/test/restart-policy.test.ts +0 -111
  574. package/test/resync-origin-site.test.ts +0 -83
  575. package/test/retry-plan.test.ts +0 -139
  576. package/test/revision-axis.test.ts +0 -110
  577. package/test/runtime-key.test.ts +0 -66
  578. package/test/save-intake-collision.test.ts +0 -91
  579. package/test/scale-twin-bench.test.ts +0 -103
  580. package/test/secrets-at-rest.test.ts +0 -135
  581. package/test/snapshot-freshness.test.ts +0 -60
  582. package/test/source-outcome-audit.test.ts +0 -104
  583. package/test/space-integrity.test.ts +0 -223
  584. package/test/space-representation.test.ts +0 -66
  585. package/test/space-scope-includes-stopped.test.ts +0 -55
  586. package/test/spec-coverage.test.ts +0 -113
  587. package/test/stage-path.test.ts +0 -95
  588. package/test/standard-coverage.test.ts +0 -148
  589. package/test/state-axes.test.ts +0 -74
  590. package/test/status-tally.test.ts +0 -55
  591. package/test/streamline-e2e.test.ts +0 -76
  592. package/test/structure-revision-db.test.ts +0 -315
  593. package/test/subject-fast-path.test.ts +0 -148
  594. package/test/subject-read-model.test.ts +0 -175
  595. package/test/sweep-dispatch.test.ts +0 -191
  596. package/test/tenant-registry-db.test.ts +0 -149
  597. package/test/time-range.test.ts +0 -152
  598. package/test/touched-items.test.ts +0 -61
  599. package/test/twin-audit.test.ts +0 -83
  600. package/test/twin-event-keys.test.ts +0 -193
  601. package/test/twin-model-item-db.test.ts +0 -319
  602. package/test/twin-model-parent-relation-db.test.ts +0 -125
  603. package/test/twin-model-tree-db.test.ts +0 -209
  604. package/test/twin-origin-resync.test.ts +0 -133
  605. package/test/twin-state-channel.test.ts +0 -111
  606. package/test/vocabulary-guard.test.ts +0 -43
  607. package/test/warm-start-seam.test.ts +0 -162
  608. package/test/warm-start.test.ts +0 -297
  609. package/test/withheld-door.test.ts +0 -95
  610. package/test/yield-loop.test.ts +0 -199
  611. package/tsconfig.json +0 -15
  612. package/tsconfig.shared.json +0 -23
  613. package/tsconfig.shared.tsbuildinfo +0 -1
  614. package/tsconfig.tsbuildinfo +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"twin-state-channel.js","sourceRoot":"","sources":["../../server/engine/twin-state-channel.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AAgBH,4CAGC;AAGD,0CAEC;AAtBY,QAAA,gBAAgB,GAAG,YAAY,CAAA;AAc5C,SAAgB,gBAAgB,CAAC,QAAgB,EAAE,SAAgC;IACjF,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,0FAA0F,CAAC,CAAA;IAC1H,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAA;AAChC,CAAC;AAED,0DAA0D;AAC1D,SAAgB,eAAe,CAAC,QAAgB,EAAE,UAAkB;IAClE,OAAO,CAAC,CAA+B,EAAW,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,EAAE,UAAU,KAAK,UAAU,CAAA;AAC/H,CAAC","sourcesContent":["/*\n * The `twin-state` channel — **a message says whose twin it is, and the filter asks.** Pure.\n *\n * ── What went wrong (2026-09-24) ─────────────────────────────────────────────\n * A twin is `(domain, instanceId)` (`runtime-key.ts`), and the same instanceId in two tenants is the\n * expected case — reference source names such as `sap-ewm-1710` are vocabulary tenants share. The\n * channel had not caught up: publishers sent `{ twinState: { instanceId, … } }` with no domain, and the\n * subscription filtered on instanceId alone. Ownership was checked once, when subscribing. So a tenant\n * watching its own `sap-ewm-1710` also received the other tenant's deltas, and the snapshot sent to a\n * new subscriber went to every subscriber of that id in every tenant.\n *\n * The rule is shell's `data` channel's (the message carries its domain, the filter compares it with the\n * key), compared by id because the twin's key already is. Every sender builds its payload here, and\n * `twin-state-channel.test.ts` fails if a sender builds one anywhere else.\n */\n\nexport const TWIN_STATE_TOPIC = 'twin-state'\n\nexport interface TwinStateMessageShape {\n instanceId: string\n kind: string\n revision?: number\n payload?: unknown\n}\n\nexport interface TwinStatePayload {\n domainId: string\n twinState: TwinStateMessageShape\n}\n\nexport function twinStatePayload(domainId: string, twinState: TwinStateMessageShape): TwinStatePayload {\n if (!domainId) throw new Error('twinStatePayload: domainId is required — a twin state message always belongs to a tenant')\n return { domainId, twinState }\n}\n\n/** Passes only this tenant's messages about this twin. */\nexport function twinStateFilter(domainId: string, instanceId: string) {\n return (p: TwinStatePayload | undefined): boolean => !!p && p.domainId === domainId && p.twinState?.instanceId === instanceId\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"warm-start.js","sourceRoot":"","sources":["../../server/engine/warm-start.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 웜스타트 판정 — **순수**. \"기동하는 커널에 직전 상태를 주입할 것인가, 주입한다면 무엇을\" 만 정한다.\n * 실제 주입(hydrateObserved 호출)과 로그는 엔진이 한다.\n *\n * ── 왜 떼어냈나 ─────────────────────────────────────────────────────────────\n * 이 판정이 틀리면 증상이 정반대 두 방향으로 나온다: 주입해야 할 때 안 주입하면 **있는 재고가 0 으로**\n * 보이고(2026-07-31 hatiolab-wms: 저널에 입고 540·출고 94 인데 재고 화면이 비어 있었다), 심지\n * 말아야 할 벤치에 주입하면 **용량 측정이 오염된다.** 둘 다 알리지 않고 틀리는 종류라 규칙을 고정한다.\n *\n * ── 오더를 버리던 전제가 낡았다 (2026-08-05) ────────────────────────────────\n * 예전 주석은 \"스냅샷의 오더에는 progress 밖에 없어서 역산하면 없는 숫자를 지어내는 셈이므로\n * 넘기지 않는다\" 였다. 그때는 맞았다. 그러나 커널 `OrderState` 가 **원값을 되찾은 뒤**\n * (`requested`·`fulfilled`·`lines`) 이 판정만 그대로 남아, 재기동하면 재고는 맞는데 **진행 중\n * 주문이 통째로 사라진** 화면이 나왔다. 오류는 나지 않는다 — 그래서 오래 남아 있었다.\n *\n * 두 스냅샷 생산 경로가 모두 원값을 담는다: 시뮬은 커널 `snapshot()`, 라이브(미러)는\n * `StateProjector` 다. 사람·자산·작업도 같은 스냅샷에 있고 커널이 심을 수 있는데 넘기지 않고 있었다.\n * 그래서 씨앗을 **관측 스냅샷 전체**로 넓힌다.\n *\n * 다만 넓히면서 **들어온 것이 없는 누락을 만들지 않는다.** 원값이 없는 오더는 커널이 걸러 내므로(지어내지\n * 않는다), 그 수를 여기서 세어 호출부가 말할 수 있게 한다. 세지 않으면 \"주문이 없다\" 와 \"주문을\n * 못 심었다\" 가 화면에서 같아진다.\n */\n\n/**\n * 커널에 심을 관측 상태 — 구조가 아니라 \"무엇이 어디에 얼마나\".\n *\n * 커널 `hydrateObserved` 가 받는 스냅샷 모양을 그대로 따른다. 여기서 골라 담지 않는 이유: 무엇을\n * 심을 수 있는지는 커널이 알고, 그 규칙(고아 작업 제외·이행 완료 오더 제외 등)도 커널에 있다.\n * 호스트가 미리 골라내면 규칙이 두 곳으로 갈린다.\n */\nexport interface ObservedSeed {\n locations: unknown[]\n items: unknown[]\n equipment: unknown[]\n persons?: unknown[]\n assets?: unknown[]\n tasks?: unknown[]\n orders?: unknown[]\n /** 확인해 둔 주목 신호 id — 상태에서 파생되지 않아 반드시 함께 넘긴다. */\n acked?: string[]\n /**\n * 주목 신호가 **처음 성립한 시각** — 확인과 같은 성질이다(계산으로 되살릴 수 없다).\n *\n * 넘기지 않으면 재기동 뒤 세 시간째 지속된 조건이 「0초째」로 되살아난다. 방금 찬 자리와 세 시간째\n * 막힌 자리는 할 일이 다르므로, 그 값이 거짓이면 화면이 사람을 잘못된 순서로 움직인다.\n */\n attentionSince?: { id: string; since: string }[]\n}\n\n/** 심은 것의 내역 — 로그와 테스트가 같은 숫자를 본다. */\nexport interface WarmStartCounts {\n itemCount: number\n equipmentCount: number\n personCount: number\n assetCount: number\n taskCount: number\n /** 원값(requested)이 있어 커널이 실제로 심을 수 있는 오더. */\n orderCount: number\n /** 이어받은 확인 처리 수 — 잃으면 확인해 둔 신호가 다시 빨개진다. */\n ackedCount: number\n /** 이어받은 시작 시각 수 — 잃으면 지속된 조건이 모두 「방금」이 된다. */\n attentionSinceCount: number\n /**\n * 원값이 없어 심지 못하는 오더 — **0 이 아니면 반드시 말한다.**\n * progress 만 있는 오더는 남은 수량을 알 수 없어 재계획의 근거가 되지 못한다. 지어내지 않고 뺀다.\n */\n ordersWithoutDemand: number\n /** Finished orders carried folded (ADR-0092) — 0 when the twin had folded nothing. */\n foldedOrderCount: number\n}\n\nexport type WarmStartPlan =\n | ({ action: 'hydrate'; seed: ObservedSeed } & WarmStartCounts)\n /** 벤치 트윈 — 새 시작에서 용량을 재는 게 목적이라 현재 상태를 주입하면 측정이 오염된다. */\n | { action: 'skip'; reason: 'bench' }\n /** 심을 상태가 없다 — 처음 만든 트윈이거나 저널·체크포인트가 비었다. 정상이다. */\n | { action: 'skip'; reason: 'no-state' }\n /** 커널이 관측 주입을 지원하지 않는다 — 구조만으로 시작하므로 보유량은 0 으로 읽힌다(알려야 한다). */\n | { action: 'skip'; reason: 'unsupported' }\n\n/** 스냅샷이 들고 올 수 있는 관측 축 — 어느 것이든 있으면 주입할 가치가 있다. */\ninterface ObservedState {\n locations?: unknown[]\n items?: unknown[]\n equipment?: unknown[]\n persons?: unknown[]\n assets?: unknown[]\n tasks?: unknown[]\n orders?: unknown[]\n foldedOrders?: unknown\n acked?: string[]\n attentionSince?: { id: string; since: string }[]\n}\n\nfunction foldedTotal(f: { counts: Record<string, Record<string, number>> }): number {\n let n = 0\n for (const byStatus of Object.values(f.counts)) for (const c of Object.values(byStatus ?? {})) n += Number(c) || 0\n return n\n}\n\n/** 관측 상태가 실려 있는지 — 축 하나라도 있으면 상태다(봉투에는 없다). */\nconst looksLikeState = (x: any): boolean =>\n !!x && typeof x === 'object' && ['locations', 'items', 'equipment', 'orders', 'tasks', 'persons', 'assets', 'acked'].some(k => k in x)\n\n/**\n * 봉투를 벗긴다 — **체크포인트가 겹포장돼 복원이 알리지 않고 죽던 자리.**\n *\n * `TwinEngine.snapshot()` 은 라이브에서는 상태를, 시뮬에서는 `runtime.resync()` 봉투(`{revision,state}`)를\n * 돌려준다. 그 봉투를 다시 `{revision, state}` 로 감싸 캐시에 넣었으므로, 꺼낸 `state` 는 축이 하나도\n * 없는 껍데기였다 → `planWarmStart` 가 \"심을 것이 없다\" 로 판정하고 **알리지 않고** 넘어갔다. 저널에 수천 건이\n * 있고 캐시도 최신인데 트윈이 빈 채로 떴다(오류도 로그도 없다).\n *\n * 반환 모양을 통일하는 것이 뿌리 수정이지만, 그 값은 State 구독 페이로드로도 나가므로 클라이언트 계약을\n * 건드린다. 그래서 **복원 경로에서만** 벗긴다. 겹겹이 싸여 있어도 벗긴다(과거에 저장된 값이 남아 있다).\n */\nexport function unwrapState(x: any, depth = 4): any {\n if (!x || typeof x !== 'object') return x\n if (looksLikeState(x)) return x\n if (depth > 0 && x.state && typeof x.state === 'object') return unwrapState(x.state, depth - 1)\n return x\n}\n\nconst arr = (v: unknown[] | undefined): unknown[] => (Array.isArray(v) ? v : [])\n\n/** 원값이 있는 오더인가 — 커널 `hydrateObserved` 의 판정(`requested !== undefined`)과 같은 기준. */\nconst hasDemand = (o: unknown): boolean =>\n !!o && typeof o === 'object' && (o as { requested?: unknown }).requested !== undefined\n\n/**\n * 무엇을 할지 정한다.\n *\n * 오더·작업·사람·자산도 함께 주입한다. 골라내는 규칙은 커널이 들고 있고(이행 완료 오더, 고아 작업),\n * 여기서는 **심을 수 없는 오더의 수만 센다** — 호출부가 그 사실을 말할 수 있어야 하기 때문이다.\n */\nexport function planWarmStart(state: ObservedState | null | undefined, purpose: string | undefined, canHydrate: boolean): WarmStartPlan {\n /* 벤치 판정이 먼저다 — 상태가 있든 없든 벤치에는 주입하지 않는다는 사실이 바뀌지 않는다. */\n if (purpose === 'bench') return { action: 'skip', reason: 'bench' }\n if (!state) return { action: 'skip', reason: 'no-state' }\n /* 봉투가 들어오면 스스로 벗는다 — 호출부가 한 곳이라도 잊으면 그 경로가 알리지 않고 죽는다(실제로 그랬다). */\n state = unwrapState(state)\n\n const locations = arr(state.locations)\n const items = arr(state.items)\n const equipment = arr(state.equipment)\n const persons = arr(state.persons)\n const assets = arr(state.assets)\n const tasks = arr(state.tasks)\n const orders = arr(state.orders)\n /* 확인 처리는 문자열 id 목록이다 — 배열이 아니면 없는 것으로 본다(지어내지 않는다). */\n const acked = Array.isArray(state.acked) ? state.acked.filter((x): x is string => typeof x === 'string') : []\n /* 시작 시각은 {id, since} 쌍 목록이다 — 모양이 아니면 없는 것으로 본다(지어낸 시각을 심으면 화면이\n 「세 시간째」라고 거짓을 말한다. 모르면 그 신호는 이어붙는 순간이 새 시작이다). */\n const attentionSince = Array.isArray(state.attentionSince)\n ? state.attentionSince.filter((x: any): x is { id: string; since: string } => !!x && typeof x.id === 'string' && typeof x.since === 'string')\n : []\n\n /*\n * Folded orders (ADR-0092) — their counts are the only record of the finished orders taken out of the hot\n * list; nothing re-sends them. The seed used to leave them out, so a restart lost every folded count.\n * Carried as the kernel wrote them; a value without a `counts` object is not a folded side.\n */\n const f: any = (state as any).foldedOrders\n const foldedOrders = f && typeof f === 'object' && f.counts && typeof f.counts === 'object' ? f : undefined\n\n /* 어느 축도 없으면 심을 것이 없다 — 빈 주입으로 로그만 남기지 않는다. */\n if (!locations.length && !items.length && !equipment.length && !persons.length && !assets.length && !tasks.length && !orders.length && !foldedOrders && !acked.length && !attentionSince.length) {\n return { action: 'skip', reason: 'no-state' }\n }\n\n /* 지원 여부는 마지막에 본다 — 주입할 것이 있는데 주입하지 못하는 상황이라야 경고할 가치가 있다. */\n if (!canHydrate) return { action: 'skip', reason: 'unsupported' }\n\n const seedable = orders.filter(hasDemand)\n\n return {\n action: 'hydrate',\n seed: {\n locations,\n items,\n equipment,\n ...(persons.length ? { persons } : {}),\n ...(assets.length ? { assets } : {}),\n ...(tasks.length ? { tasks } : {}),\n /* 원값 없는 오더는 넘기지 않는다 — 커널도 걸러 내지만, 넘기지 않으면 세어 둔 수와 어긋날 일이 없다. */\n ...(seedable.length ? { orders: seedable } : {}),\n ...(foldedOrders ? { foldedOrders } : {}),\n /* 확인 처리는 조건이 사라진 id 가 섞여 있어도 해롭지 않다 — 그 신호가 없으면 표시할 대상이 없다. */\n ...(acked.length ? { acked } : {}),\n /* 조건이 사라진 id 가 섞여 있어도 해롭지 않다 — 커널이 그 신호가 없으면 함께 지운다. */\n ...(attentionSince.length ? { attentionSince } : {})\n },\n itemCount: items.length,\n equipmentCount: equipment.length,\n personCount: persons.length,\n assetCount: assets.length,\n taskCount: tasks.length,\n orderCount: seedable.length,\n foldedOrderCount: foldedOrders ? foldedTotal(foldedOrders) : 0,\n ordersWithoutDemand: orders.length - seedable.length,\n ackedCount: acked.length,\n attentionSinceCount: attentionSince.length\n }\n}\n\n/*\n * ── 미러(관측 구동) 재기동의 연속성 ─────────────────────────────────────────\n *\n * 미러의 진실은 원천이다. 그래서 재고·위치·설비 상태는 심지 않는다 — 다음 계측·다음 마스터 재읽기가\n * 곧 정정하므로, 심으면 **떠난 물건이 되살아나는** 위험만 남는다(그래서 `startLive` 는 오랫동안 아무것도\n * 심지 않았다).\n *\n * 그런데 원천이 **애초에 다시 말해 주지 않는 축**이 있다. 우리가 계산해 만든 것들이다:\n * · 확인(ack)·조건이 처음 성립한 시각 — 사람이 한 일과 우리가 기억한 시각\n * · 에너지의 열린 구간 누적·적산 기준점·관측 이후 최대 — SCADA 는 「이번 15분에 지금까지 얼마」를 모른다\n *\n * 잃으면 **오류 없이 값이 작아진다**(실측 2026-08-18: 재기동 뒤 그 구간의 전력량·피크가 작아지고, 세\n * 시간째 지속된 경보가 「0초째」가 됐다). 그래서 축을 갈라 되풀어 주지 않는 것만 이어받는다.\n *\n * 무엇을 이어받을지는 여기서 고르고, **어떻게 흡수할지는 커널이 정한다**(`hydrateContinuity`) — 도메인\n * 규칙이 호스트로 새면 트윈마다 다른 답이 된다.\n */\nexport interface ContinuitySeed {\n acked?: string[]\n attentionSince?: { id: string; since: string }[]\n energy?: unknown\n /** 접힌 오더(ADR-0092) — 모양은 커널이 안다(호스트는 있나만 본다). */\n foldedOrders?: unknown\n}\n\nexport interface ContinuityPlan {\n seed: ContinuitySeed\n /** 무엇을 이어받았나 — 로그와 시험이 같은 숫자를 본다(0 이면 이어받을 것이 없었다는 사실이다). */\n ackedCount: number\n attentionSinceCount: number\n /** 에너지 연속성을 이어받았나 — 열린 구간이 있으면 그 구간의 요금 근거가 살아난다. */\n energy: boolean\n /** 접힌 오더를 이어받았나(ADR-0092). */\n foldedOrders: boolean\n}\n\n/** 미러가 이어받을 축만 골라낸다 — 관측 축(재고·위치·설비)은 일부러 넣지 않는다. */\nexport function planLiveContinuity(raw: unknown): ContinuityPlan {\n const state = raw ? (unwrapState(raw as any) as any) : undefined\n const acked = Array.isArray(state?.acked) ? state.acked.filter((x: any): x is string => typeof x === 'string') : []\n const attentionSince = Array.isArray(state?.attentionSince)\n ? state.attentionSince.filter((x: any): x is { id: string; since: string } => !!x && typeof x.id === 'string' && typeof x.since === 'string')\n : []\n /* 에너지는 커널이 자기 모양을 안다 — 호스트는 「있나」만 본다(골라 담으면 규칙이 두 곳으로 갈린다). */\n const energy = state?.energy && typeof state.energy === 'object' ? state.energy : undefined\n /* 접힌 오더(ADR-0092) — 원천을 다시 읽는 미러도 세는 수와 경계를 잃지 않게(없으면 옛 종결이 뜨거운 줄로 되살아난다). */\n const foldedOrders = state?.foldedOrders && typeof state.foldedOrders === 'object' ? state.foldedOrders : undefined\n return {\n seed: {\n ...(acked.length ? { acked } : {}),\n ...(attentionSince.length ? { attentionSince } : {}),\n ...(energy ? { energy } : {}),\n ...(foldedOrders ? { foldedOrders } : {})\n },\n ackedCount: acked.length,\n attentionSinceCount: attentionSince.length,\n energy: !!energy,\n foldedOrders: !!foldedOrders\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,4DAAiC;AACjC,6DAAkC;AAClC,qEAAqE;AACrE,kDAAkD;AAAzC,sGAAA,UAAU,OAAA;AAEnB,4CAAoD;AACpD,uBAAoB;AAEpB,gDAA8C;AAC9C,6EAAgF;AAChF,oEAAiF;AACjF,+EAAqF;AACrF,4EAA2E;AAC3E,mEAAyE;AACzE,mEAAgE;AAEhE;;;;;;;;GAQG;AACH,OAAO,CAAC,EAAE,CAAC,wBAA+B,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAO,EAAE,EAAE;IACjF,IAAI,CAAC;QACH;;;WAGG;QACH,IAAA,oCAAyB,GAAE,CAAA;QAC3B;;;WAGG;QACH,IAAA,iDAA4B,GAAE,CAAA;QAC9B;;;WAGG;QACH,IAAA,sCAAmB,EAAC,CAAC,CAAC,EAAE,CAAC,IAAA,gBAAO,EAAC,CAAC,CAAC,CAAC,CAAA;QACpC;;;;;;;;WAQG;QACH;;;;;;;;WAQG;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,CAAC,CAAC,CAAA;QACpE,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,oBAAoB,IAAI,CAAC,CAAC,CAAA;QAC3E,MAAM,aAAa,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAA;QACxD,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;YACtB,qBAAU,CAAC,sBAAsB,GAAG,aAAa,CAAA;YACjD,IAAA,gBAAO,EAAC,4BAA4B,aAAa,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAA;QACxF,CAAC;QAED,MAAM,qBAAU,CAAC,SAAS,EAAE,CAAA;QAC5B,MAAM,IAAA,4CAAwB,GAAE,CAAA;QAChC;;;WAGG;QACH,IAAA,4BAAa,GAAE,CAAA;QAEf;;;;;;WAMG;QACH,IAAA,qCAAsB,GAAE,CAAA;QAExB,IAAA,gBAAO,EAAC,iCAAiC,CAAC,CAAA;IAC5C,CAAC;IAAC,OAAO,EAAE,EAAE,CAAC;QACZ,IAAA,kBAAS,EAAC,qCAAqC,EAAE,EAAE,CAAC,CAAA;IACtD,CAAC;AACH,CAAC,CAAC,CAAA","sourcesContent":["export * from './engine/index.js'\nexport * from './service/index.js'\n/* 스키마 이력 — 프레임워크가 `migrations` export 를 모아 실행한다(`run migration`). */\nexport { migrations } from './migrations/index.js'\n\nimport { twinLog, twinError } from './engine/log.js'\nimport './routes.js'\n\nimport { TwinEngine } from './engine/index.js'\nimport { resumeReferenceLiveFeeds } from './service/reference/reference-live.js'\nimport { installIngestWindowWriter } from './service/twin-ingest-window/index.js'\nimport { installReferenceLinkResolver } from './service/reference/reference-links.js'\nimport { installAreaResolver } from './service/twin-space/area-resolver.js'\nimport { startActuationRuleLoop } from './service/actuation/rule-loop.js'\nimport { startFillLoop } from './service/reference/fill-loop.js'\n\n/*\n * 모듈 부팅 — **도는 트윈을 되살린다.**\n *\n * 두 층이 순서대로다: ① 엔진이 등록부의 `running` 행을 선언된 `restartPolicy` 그대로 되살리고\n * (`bootstrap`), ② 레퍼런스 계층이 미러 트윈의 커넥터 피드를 다시 붙인다. 순서가 뒤집히면 피드가\n * 아직 없는 커널에 계측을 붓게 된다.\n *\n * 예전에는 ①도 ②도 없었다 — 재기동 한 번에 등록부만 「도는 중」이라 말하는 트윈이 남았다.\n */\nprocess.on('bootstrap-module-start' as any, async ({ app, config, client }: any) => {\n try {\n /*\n * 닫힌 인제스트 창을 표로 옮기는 문을 먼저 연다 — 트윈을 되살리기 **전에** 등록해야 첫 창부터\n * 남는다. 커널 층은 저장 계층을 모르므로 이 등록이 그 둘을 잇는 유일한 자리다.\n */\n installIngestWindowWriter()\n /*\n * 원본에만 있는 관계를 조회하는 문을 연다 — MES 의 생산 실적·정비 계획처럼 트윈이 담지 않는\n * 것들. 조회용 참조는 트윈으로 복제하지 않고 그때 물어본다.\n */\n installReferenceLinkResolver()\n /*\n * 구역 이름을 답하는 문 — 자리의 부모가 구역일 때 화면이 id 대신 이름을 보인다. 등록이 없던\n * 동안에는 모든 자리의 부모가 「풀 수 있는 부품이 없다」로 나왔다(2026-09-18 실측).\n */\n installAreaResolver(m => twinLog(m))\n /*\n * **저널 보존 기간은 선언에서 온다** — 코드에 박아 두지 않는다(2026-08-22).\n *\n * 실측: 개발 저널이 시간당 581,794행 · 0.85GB(하루 20GB) 로 자랐고 지우는 것이 없었다. 그런데\n * 「며칠까지 남기나」는 **배포마다 갈리는 사실**이다 — 개발은 짧아도 되고 실증은 길어야 한다.\n *\n * 선언이 없으면 **아무것도 지우지 않는다.** 알리지 않고 지우는 편이 알리지 않고 쌓는 것보다 나쁘다.\n * 값은 스냅샷 TTL(7일)보다 짧을 수 없다 — 엔진이 그것을 올려 주고 그 사실을 말한다.\n */\n /*\n * 읽는 순서: **환경변수 → 설정 → 없음.**\n *\n * 설정(`config.twin.journalRetentionDays`)이 배포의 선언이고, 환경변수\n * (`TWIN_JOURNAL_RETENTION_DAYS`)는 그것을 **그 자리에서** 덮는다. 컨테이너로 도는 배포에서는\n * 파일을 고치지 않고 값을 바꿀 수 있어야 하고, 개발에서도 그 편이 빠르다.\n *\n * 둘 다 없으면 **아무것도 지우지 않는다.**\n */\n const fromEnv = Number(process.env.TWIN_JOURNAL_RETENTION_DAYS ?? 0)\n const fromConfig = Number(config?.get?.('twin')?.journalRetentionDays ?? 0)\n const retentionDays = fromEnv > 0 ? fromEnv : fromConfig\n if (retentionDays > 0) {\n TwinEngine.JOURNAL_RETENTION_DAYS = retentionDays\n twinLog(`[twin] journal retention ${retentionDays}일 (${fromEnv > 0 ? '환경변수' : '설정'})`)\n }\n\n await TwinEngine.bootstrap()\n await resumeReferenceLiveFeeds()\n /*\n * 빠진 구간을 다시 받는 주기 — 기동에서 한 번, 그 뒤 10분마다(§`fill-loop.ts`).\n * 피드를 붙인 **뒤에** 세운다: 커널이 되살아나기 전에 부르면 도는 트윈이 0개다.\n */\n startFillLoop()\n\n /*\n * 조치 규칙 주기 — **트윈이 다 선 뒤에** 세운다. 앞에 세우면 첫 바퀴가 아직 안 선 트윈들을 돌고\n * 「상태를 읽을 수 없다」를 규칙 행에 적는다. 그것은 사실이지만 사람에게는 오해다.\n *\n * 커널 틱과 따로 돈다 — 틱은 예외가 나면 그 트윈을 멈추고, 규칙 하나가 잘못됐다고 공장이 멈추면\n * 안 된다(§`rule-loop.ts`).\n */\n startActuationRuleLoop()\n\n twinLog('Headless Twin host has started.')\n } catch (ex) {\n twinError('Headless Twin host failed to start.', ex)\n }\n})\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"1786000000000-RenameTwinInstanceBoardToModel.js","sourceRoot":"","sources":["../../server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,GAAG,gBAAgB,CAAA;AACnC,MAAM,GAAG,GAAG,OAAO,CAAA;AACnB,MAAM,GAAG,GAAG,OAAO,CAAA;AAEnB,MAAa,2CAA2C;IAC/C,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;QACpD,IAAI,CAAC,KAAK;YAAE,OAAM;QAClB,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;QACtE,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAM;QACjC,MAAM,WAAW,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IACjD,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;QACpD,IAAI,CAAC,KAAK;YAAE,OAAM;QAClB,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;QACtE,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAM;QACjC,MAAM,WAAW,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IACjD,CAAC;CACF;AAhBD,kGAgBC","sourcesContent":["import { MigrationInterface, QueryRunner } from 'typeorm'\n\n/**\n * `twin_instances.model` → `twin_instances.model`.\n *\n * ── 왜 개명하나 ────────────────────────────────────────────────────────────────\n * 이 컬럼이 담는 것은 커널이 `TwinModelDef` 라 부르는 **트윈 모델**이다. `board` 는 저장 위치의 옛\n * 이름이고, 이 앱에는 **진짜 board 가 따로 있다**(things-scene 보드 — 공간 표현의 `boardId`). 같은\n * 낱말이 두 가지를 뜻하니 읽는 사람이 매번 어느 쪽인지 판단해야 했고, 코드와 주석이 \"board(마스터\n * 청사진)\" 처럼 번역을 달아 설명해 왔다. 커널이 이미 옳은 이름을 갖고 있으므로 발명할 이름도 없다.\n *\n * ── 데이터를 잃지 않는다 ──────────────────────────────────────────────────────\n * `renameColumn` 은 값을 그대로 둔 채 이름만 바꾼다. 지우고 다시 만들면(엔티티 변경 + synchronize)\n * 기존 트윈의 모델이 통째로 사라지고, 원천이 없는 트윈은 되살릴 방법이 없다.\n *\n * `renameColumn` API 만 쓰므로 지원 대상 5개 드라이버가 각자의 DDL 로 처리한다(raw SQL 금지 규약).\n *\n * ── 멱등 ──────────────────────────────────────────────────────────────────────\n * 이미 `model` 이 있으면(엔티티 선언 + synchronize 가 먼저 지나간 경우) 아무것도 하지 않는다.\n * 그 경우 옛 컬럼이 남아 있을 수 있는데 **지우지 않는다** — 값이 든 컬럼을 마이그레이션이 알리지 않고\n * 버리면 안 된다. 남은 컬럼의 처분은 사람이 확인한 뒤의 일이다.\n */\nconst TABLE_NAME = 'twin_instances'\nconst OLD = 'board'\nconst NEW = 'model'\n\nexport class RenameTwinInstanceBoardToModel1786000000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n const table = await queryRunner.getTable(TABLE_NAME)\n if (!table) return\n const has = (name: string) => table.columns.some(c => c.name === name)\n if (has(NEW) || !has(OLD)) return\n await queryRunner.renameColumn(table, OLD, NEW)\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n const table = await queryRunner.getTable(TABLE_NAME)\n if (!table) return\n const has = (name: string) => table.columns.some(c => c.name === name)\n if (has(OLD) || !has(NEW)) return\n await queryRunner.renameColumn(table, NEW, OLD)\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"1786100000000-PromoteEventActionAndSyncWarnings.js","sourceRoot":"","sources":["../../server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts"],"names":[],"mappings":"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","sourcesContent":["import { MigrationInterface, QueryRunner, TableColumn, TableIndex } from 'typeorm'\n\n/**\n * 두 계측을 위한 컬럼 둘.\n *\n * ① `twin_events.action` — EPCIS 행위(ADD/OBSERVE/DELETE) 승격.\n * 되풀어 읽는 스냅샷은 원리적으로 `DELETE` 를 못 낸다. 그래서 「이 트윈이 반출을 한 번이라도\n * 계산했는가」가 진짜 delta 피드의 증거인데, 값이 `payload`(simple-json = TEXT) 안에 있는 동안에는\n * 5개 드라이버 어디에서도 그 질문을 할 수 없었다(DB별 JSON 연산자 금지).\n *\n * ② `twin_references.last_warning_count` — 마지막 동기에서 못 옮긴 값의 수.\n * 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도 성공이라 답하면서 아무 일도 하지 않는다.\n *\n * **둘 다 nullable, 기본값 없음.** 기본값 0 을 깔면 「없었다」와 「세지 않았다」가 같아지고, 그\n * 구분이 단계 판정의 전부다(옛 행은 모르는 것이 맞다 — 채워 넣지 않는다).\n *\n * 인덱스 `ix_twin_event_6` 는 저널이 큰 환경에서 생성에 시간이 걸린다. 그래도 만드는 이유:\n * 없으면 「반출을 계산했나」가 그 인스턴스 저널 전량 주사가 되고, 반출이 **없는** 경우(초기 상태이자\n * 가장 흔한 경우)가 항상 최악이다.\n *\n * 멱등: 이미 있으면 아무것도 하지 않는다. TypeORM API 만 쓴다(raw SQL 금지 — 드라이버 5종).\n */\nconst EVENTS = 'twin_events'\nconst REFS = 'twin_references'\nconst ACTION = 'action'\nconst WARN = 'last_warning_count'\nconst IX = 'ix_twin_event_6'\n\nexport class PromoteEventActionAndSyncWarnings1786100000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n const events = await queryRunner.getTable(EVENTS)\n if (events && !events.columns.some(c => c.name === ACTION)) {\n await queryRunner.addColumn(events, new TableColumn({ name: ACTION, type: 'varchar', length: '16', isNullable: true }))\n }\n const withColumn = await queryRunner.getTable(EVENTS)\n if (withColumn && withColumn.columns.some(c => c.name === ACTION) && !withColumn.indices.some(i => i.name === IX)) {\n await queryRunner.createIndex(withColumn, new TableIndex({ name: IX, columnNames: ['domain_id', 'instance_id', ACTION] }))\n }\n\n const refs = await queryRunner.getTable(REFS)\n if (refs && !refs.columns.some(c => c.name === WARN)) {\n await queryRunner.addColumn(refs, new TableColumn({ name: WARN, type: 'int', isNullable: true }))\n }\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n const events = await queryRunner.getTable(EVENTS)\n if (events?.indices.some(i => i.name === IX)) await queryRunner.dropIndex(events, IX)\n const events2 = await queryRunner.getTable(EVENTS)\n if (events2?.columns.some(c => c.name === ACTION)) await queryRunner.dropColumn(events2, ACTION)\n const refs = await queryRunner.getTable(REFS)\n if (refs?.columns.some(c => c.name === WARN)) await queryRunner.dropColumn(refs, WARN)\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"1786200000000-CarryLiveFeedCursor.js","sourceRoot":"","sources":["../../server/migrations/1786200000000-CarryLiveFeedCursor.ts"],"names":[],"mappings":";;;AAAA,qCAAsE;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,IAAI,GAAG,iBAAiB,CAAA;AAC9B,MAAM,MAAM,GAAG,aAAa,CAAA;AAE5B,MAAa,gCAAgC;IACpC,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC7C,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;YACvD,MAAM,WAAW,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,qBAAW,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACtG,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC7C,IAAI,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,CAAC;YACtD,MAAM,WAAW,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAC5C,CAAC;IACH,CAAC;CACF;AAdD,4EAcC","sourcesContent":["import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'\n\n/**\n * `twin_references.live_cursor` — **어디까지 읽었나**를 재기동 너머로 잇는다.\n *\n * ── 무엇이 오류 없이 사라지고 있었나 (2026-08-23 실측) ─────────────────────────\n * 라이브 어댑터가 붙을 때마다 `Date.now() − 되돌아볼 날수` 로 창을 새로 만들었다. 그래서 **재기동마다\n * 미러의 과거가 잘렸다** — 작업 2,855 → 2,820(그 사이 8시간이 흐른 만큼). 미러가 아는 것이 「원본의\n * 사실」이 아니라 「창의 함수」였고, 화면은 그 수를 보이면서 그 사실을 말하지 않았다.\n *\n * 같은 원본에서 잰 규모가 그 값을 말한다: 작업지시 77,594건 중 창이 읽는 것이 3,039건(3.9%)이다.\n *\n * ── 왜 표인가 (캐시가 아니라) ───────────────────────────────────────────────\n * 커널이 갈림을 이미 적어 두었다(§`hydrateContinuity`): 「원천이 **애초에 다시 말해 주지 않는 축**」은\n * 재기동 연속성으로 이어받는다. 「우리가 어디까지 읽었나」가 정확히 그 성질이다.\n *\n * 그리고 스냅샷 체크포인트와 성질이 다르다: 그쪽은 잃어도 원천이 정정해 준다(그래서 TTL 캐시로 족하다).\n * **이 값은 잃으면 원천에 묻지 않게 된다** — 창이 다시 미끄러지고, 그 손실은 오류 없이 들어온 것이 없다.\n * 만료되는 자리에 둘 수 없다.\n *\n * ── 모양 ────────────────────────────────────────────────────────────────────\n * `{ streams: { [흐름]: { since?, seen: string[] } }, firstAttachedAt? }`\n *\n * **흐름 이름은 어댑터가 정한다** — 원본마다 흐름 수와 뜻이 다르다(첫 실 원본은 재고·오더·로트·투입 넷).\n * 이 층은 열쇠로만 다룬다. 계약이 이름을 닫으면 새 원본마다 계약을 고치게 된다.\n *\n * `firstAttachedAt` 은 「언제부터 아는가」다 — 커널은 상한만 안다(`nowTime`). 둘이 함께 「이 트윈이\n * 아는 구간」이고, 화면이 수를 보일 때 그 구간을 말해야 한다.\n *\n * **nullable, 기본값 없음.** `null` = 한 번도 붙지 않았다는 사실이고, 그때만 되돌아볼 날수로 첫 창을\n * 만든다. 빈 객체를 기본으로 깔면 「첫 붙음」과 「이어 붙음」이 같아지고, 그 구분이 이 컬럼의 전부다.\n *\n * `simple-json` = TEXT 이므로 드라이버 다섯을 그대로 지난다(DB별 JSON 타입·연산자 금지).\n * 멱등: 이미 있으면 아무것도 하지 않는다. TypeORM API 만 쓴다(raw SQL 금지).\n */\nconst REFS = 'twin_references'\nconst CURSOR = 'live_cursor'\n\nexport class CarryLiveFeedCursor1786200000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n const refs = await queryRunner.getTable(REFS)\n if (refs && !refs.columns.some(c => c.name === CURSOR)) {\n await queryRunner.addColumn(refs, new TableColumn({ name: CURSOR, type: 'text', isNullable: true }))\n }\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n const refs = await queryRunner.getTable(REFS)\n if (refs && refs.columns.some(c => c.name === CURSOR)) {\n await queryRunner.dropColumn(refs, CURSOR)\n }\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"1786300000000-IndexEventTypeByTime.js","sourceRoot":"","sources":["../../server/migrations/1786300000000-IndexEventTypeByTime.ts"],"names":[],"mappings":";;;AAAA,qCAAqE;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,GAAG,aAAa,CAAA;AAC5B,MAAM,EAAE,GAAG,iBAAiB,CAAA;AAC5B,MAAM,OAAO,GAAG,CAAC,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,CAAC,CAAA;AAExE,MAAa,iCAAiC;IACrC,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACjD,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;YAAE,OAAM;QACnD,MAAM,WAAW,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,oBAAU,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IAC3F,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACjD,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;YAAE,OAAM;QACpD,MAAM,WAAW,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACzC,CAAC;CACF;AAdD,8EAcC","sourcesContent":["import { MigrationInterface, QueryRunner, TableIndex } from 'typeorm'\n\n/**\n * 「이 종류의 사실을 시각순으로」 — 그 축에 인덱스가 없었다.\n *\n * ── 무엇이 났나 (2026-08-25 실측 · 승화푸드) ─────────────────────────────────\n * 지난 기록 조회는 필터가 없을 때 `revision DESC`(적힌 순서)로 잘랐다. 연결된 시스템을 되읽는\n * 트윈에서는 그 순서가 일어난 순서와 달라서, 공정 타임라인이 4월 사실을 최근 것으로 그렸다. 그래서\n * 기본을 시각순으로 고쳤는데(`journal-order.ts`), 그때 이 인덱스가 없는 것이 드러났다.\n *\n * ix_2 (domain, instance, eventType, revision) 종류는 걸러지고 정렬이 적힌 순서다\n * ix_1 (domain, instance, eventTime) 정렬은 맞고 종류를 못 거른다\n *\n * 종류로 거르고 시각으로 정렬하면 `ix_1` 로 걷다가 행마다 테이블을 열어 종류를 확인한다. 찾는 종류가\n * 며칠 전에 멈춘 트윈에서는 그 사이 행을 전부 읽고 버린다. 실측: 200건에 1.7초(같은 조회가 옛 정렬로는\n * 0.001초). 승화푸드에서 작업 사실은 8월 22일에 멈춰 있고 그 뒤로 리비전이 110만 늘었다.\n *\n * ── 그 1.7초가 서버 전체를 막았다 ───────────────────────────────────────────\n * 공정 타임라인은 5초마다 세 종류를 부른다. sqlite 드라이버는 연결이 하나라서, 5초 주기에 5초가 넘는\n * 일감이 들어오면 나머지 질의가 전부 뒤에 선다. 39분 동안 느린 질의 3,152건이 쌓였고, 한 줄짜리\n * `domains` 조회가 중간 7.4초로 찍혔다 — 그 질의의 문제가 아니라 줄을 선 값이다.\n *\n * ── 왜 이제 얹나 ────────────────────────────────────────────────────────────\n * 엔티티 주석이 「저널 하나가 아주 커져서 이 축들의 조회가 느려지면 그때 측정을 근거로 인덱스를 추가할\n * 일이지, 지레 얹어 쓰기를 무겁게 할 일은 아니다」라고 적어 두었다. 그 측정이 나왔다.\n *\n * 큰 기록에서는 생성에 시간이 걸린다(47GB 개발 파일에서 수분). 멱등이므로 다시 돌려도 안전하고,\n * TypeORM API 만 쓴다(드라이버 다섯을 다 지나가야 하므로 raw SQL 금지).\n */\nconst EVENTS = 'twin_events'\nconst IX = 'ix_twin_event_7'\nconst COLUMNS = ['domain_id', 'instance_id', 'event_type', 'event_time']\n\nexport class IndexEventTypeByTime1786300000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n const events = await queryRunner.getTable(EVENTS)\n if (!events) return\n if (events.indices.some(i => i.name === IX)) return\n await queryRunner.createIndex(events, new TableIndex({ name: IX, columnNames: COLUMNS }))\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n const events = await queryRunner.getTable(EVENTS)\n if (!events) return\n if (!events.indices.some(i => i.name === IX)) return\n await queryRunner.dropIndex(events, IX)\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"1786400000000-IndexEventCreatedAt.js","sourceRoot":"","sources":["../../server/migrations/1786400000000-IndexEventCreatedAt.ts"],"names":[],"mappings":";;;AAAA,qCAAqE;AAErE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,GAAG,aAAa,CAAA;AAC5B,MAAM,EAAE,GAAG,iBAAiB,CAAA;AAC5B,MAAM,OAAO,GAAG,CAAC,WAAW,EAAE,aAAa,EAAE,YAAY,CAAC,CAAA;AAE1D,MAAa,gCAAgC;IACpC,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACjD,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;YAAE,OAAM;QACnD,MAAM,WAAW,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,oBAAU,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IAC3F,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACjD,IAAI,CAAC,MAAM;YAAE,OAAM;QACnB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;YAAE,OAAM;QACpD,MAAM,WAAW,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACzC,CAAC;CACF;AAdD,4EAcC","sourcesContent":["import { MigrationInterface, QueryRunner, TableIndex } from 'typeorm'\n\n/**\n * 「이 트윈의 오래된 기록」 — 보관 기간 정리가 쓰는 축에 인덱스가 없었다.\n *\n * ── 무엇이 났나 (2026-08-27 실측 · 개발 파일 51GB · 3,128만 행) ───────────────\n * 정리는 `created_at` 으로 자른다(보관은 저장 나이의 문제이고 저장 나이는 실제 시계다). 그런데 그 축에\n * 인덱스가 없어서, 매 배치마다 그 트윈의 저널을 전량 주사했다.\n *\n * hatio-mx2 의 7일 밖 행을 세기 208초 ← 1,029만 행을 전부 읽는다(7일 밖은 2,446건뿐이다)\n * revision 으로 한 행 짚기 0.004초\n *\n * 정리는 한 창에 2초만 쓰도록 예산이 걸려 있다. 그 2초를 주사에 다 쓰고 44건만 지우고 멈췄다. 지금은\n * 지울 것이 적어서 크게 드러나지 않지만, 보관 기간을 줄이거나 쓰는 양이 늘면 따라가지 못한다.\n *\n * ── 큰 기록에서는 만드는 데 시간이 걸린다 ───────────────────────────────────\n * 3,128만 행에 인덱스를 만드는 동안 쓰기가 막힌다(sqlite 는 쓰기가 하나다). 서버를 내린 뒤 돌린다.\n * 멱등이므로 다시 돌려도 안전하고, TypeORM API 만 쓴다(드라이버 다섯을 다 지나가야 하므로 raw SQL 금지).\n */\nconst EVENTS = 'twin_events'\nconst IX = 'ix_twin_event_8'\nconst COLUMNS = ['domain_id', 'instance_id', 'created_at']\n\nexport class IndexEventCreatedAt1786400000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n const events = await queryRunner.getTable(EVENTS)\n if (!events) return\n if (events.indices.some(i => i.name === IX)) return\n await queryRunner.createIndex(events, new TableIndex({ name: IX, columnNames: COLUMNS }))\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n const events = await queryRunner.getTable(EVENTS)\n if (!events) return\n if (!events.indices.some(i => i.name === IX)) return\n await queryRunner.dropIndex(events, IX)\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"1786500000000-CreateTwinSubjectEvents.js","sourceRoot":"","sources":["../../server/migrations/1786500000000-CreateTwinSubjectEvents.ts"],"names":[],"mappings":"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","sourcesContent":["import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm'\n\n/**\n * **대상별 사건 목록** — 조회용 파생 표를 만든다.\n *\n * ── 왜 (2026-08-27 실측) ────────────────────────────────────────────────────\n * 360 화면이 「이 대상의 이력」을 묻고 그 조회가 64초다. 「무엇으로 걸러 시각순으로 정렬해 앞에서\n * 30개」라는 모양에 맞는 목차가 저널에 없고, 얹어도 풀리지 않는다.\n *\n * 여러 값으로 거르면(IN) 정렬 때문에 목차를 못 쓴다\n * 축이 늘 때마다 저널의 목차가 늘어난다 — 360 의 축 넷이면 13개가 된다\n * 품목과 로트가 한 문자열에 붙어 있어 품목만으로는 찾을 수 없다\n *\n * ── 비어서 시작한다 ────────────────────────────────────────────────────────\n * 이 표는 **앞으로만 채운다.** 기존 3,128만 건을 소급해서 채우지 않는다 — 그래야 새 조회 축이 필요할\n * 때마다 전체를 다시 만드는 구조가 되지 않는다. 담지 않은 구간은 조회가 `coveredFrom` 으로 말한다.\n *\n * 그래서 이 마이그레이션은 큰 기록에서도 값싸다(표와 목차를 만들 뿐, 채우지 않는다).\n *\n * 멱등이고 TypeORM API 만 쓴다(드라이버 다섯을 다 지나가야 하므로 raw SQL 금지). 컬럼 타입 이름을\n * 적지 않는 규율은 엔티티와 같다 — `datetime` 은 Postgres 가 거절한다.\n *\n * 설계: `operato-twin/design/plans/read-models.md`\n */\nconst TABLE = 'twin_subject_events'\n\nexport class CreateTwinSubjectEvents1786500000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n /*\n * 표마다 따로 본다 — 하나가 이미 있다고 여기서 돌아가면 나머지가 만들어지지 않는다. 실제로 그렇게\n * 적어 두었다가, 사건 목록만 있고 진행 지점 표가 없는 상태를 만들었다.\n */\n if (!(await queryRunner.hasTable(TABLE))) {\n await this.createSubjectEvents(queryRunner)\n }\n }\n\n private async createSubjectEvents(queryRunner: QueryRunner): Promise<void> {\n await queryRunner.createTable(\n new Table({\n name: TABLE,\n columns: [\n { name: 'id', type: 'varchar', length: '36', isPrimary: true },\n { name: 'domain_id', type: 'varchar', length: '36', isNullable: true },\n { name: 'instance_id', type: 'varchar', isNullable: false },\n { name: 'subject_kind', type: 'varchar', isNullable: false },\n { name: 'subject_id', type: 'varchar', isNullable: false },\n /* 시각 컬럼의 타입 이름은 드라이버가 정한다 — `timestamp` 는 sqlite 가 모르고 `datetime` 은 Postgres 가 거절한다. */\n { name: 'event_time', type: queryRunner.connection.driver.mappedDataTypes.createDate as string, isNullable: false },\n { name: 'revision', type: 'int', isNullable: false },\n { name: 'event_type', type: 'varchar', isNullable: false },\n { name: 'event_id', type: 'varchar', length: '36', isNullable: true },\n {\n name: 'created_at',\n type: queryRunner.connection.driver.mappedDataTypes.createDate as string,\n isNullable: true\n }\n ]\n }),\n true\n )\n\n const table = await queryRunner.getTable(TABLE)\n if (!table) return\n\n /* 이 표의 존재 이유 — 「이 대상의 것을 최신순으로」가 목차를 위에서 읽고 끝난다(정렬 없음). */\n if (!table.indices.some(i => i.name === 'ix_twin_subject_event_0')) {\n await queryRunner.createIndex(\n table,\n new TableIndex({\n name: 'ix_twin_subject_event_0',\n columnNames: ['domain_id', 'instance_id', 'subject_kind', 'subject_id', 'event_time']\n })\n )\n }\n /* 보관 상한을 지우는 축 — 「이 트윈에서 오래된 줄」. */\n if (!table.indices.some(i => i.name === 'ix_twin_subject_event_1')) {\n await queryRunner.createIndex(\n table,\n new TableIndex({\n name: 'ix_twin_subject_event_1',\n columnNames: ['domain_id', 'instance_id', 'event_time']\n })\n )\n }\n\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n if (!(await queryRunner.hasTable(TABLE))) return\n await queryRunner.dropTable(TABLE)\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"1786600000000-RenameOperatoMesAdapterType.js","sourceRoot":"","sources":["../../server/migrations/1786600000000-RenameOperatoMesAdapterType.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,IAAI,GAAG,iBAAiB,CAAA;AAC9B,MAAM,MAAM,GAAG,cAAc,CAAA;AAC7B;;;;;;GAMG;AACH,MAAM,QAAQ,GAAG,aAAa,CAAA;AAC9B,MAAM,GAAG,GAAG,aAAa,CAAA;AACzB,MAAM,GAAG,GAAG,eAAe,CAAA;AAE3B,KAAK,UAAU,MAAM,CAAC,WAAwB,EAAE,IAAY,EAAE,EAAU;IACtE,mCAAmC;IACnC,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC9C,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;QAAE,OAAM;IAExD,oDAAoD;IACpD,MAAM,WAAW,CAAC,OAAO;SACtB,kBAAkB,EAAE;SACpB,MAAM,CAAC,IAAI,CAAC;SACZ,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;SAC3B,OAAO,EAAE,CAAA;AACd,CAAC;AAED,MAAa,wCAAwC;IAC5C,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,MAAM,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IACrC,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,MAAM,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IACrC,CAAC;CACF;AARD,4FAQC","sourcesContent":["import { MigrationInterface, QueryRunner } from 'typeorm'\n\n/**\n * `twin_references.adapter_type` — `operato-mes` → `operato-plant`.\n *\n * ── 왜 마이그레이션이 필요한가 ────────────────────────────────────────────────\n * 커넥터의 `type` 은 **저장되는 식별자**다. 트윈을 원본에 붙일 때 그 문자열이 이 컬럼에 적히고,\n * 넘길 때·라이브를 열 때 그 값으로 커넥터를 찾는다(§`getAdapter`).\n *\n * 그래서 제품 개명(`operato-mes` → `operato-plant`)이 코드만 바뀌면 **이미 붙어 있는 원본이 커넥터를\n * 못 찾는다.** 그때 나오는 것은 이렇다.\n *\n * 조치 넘김 `no-adapter` 로 거절\n * 라이브 커넥터가 없어 관측이 끊긴다\n * 화면 「등록되지 않은 커넥터입니다 (operato-mes)」 한 줄\n *\n * **오류는 안 나고, 원인이 개명이라는 것이 그 문장에 없다.** 트윈은 그대로 서 있고 값만 안 온다.\n * 이 저장소에서 개명이 저장된 식별자에 닿았을 때 늘 이 모양으로 났다(보드 키·씬 컴포넌트 타입).\n *\n * ── 왜 이 패키지인가 ─────────────────────────────────────────────────────────\n * 표를 든 쪽이 이 패키지다(`twin_references`). 커넥터는 `operato-twin` 이 등록하지만, 그 값이 사는\n * 자리는 여기다. 마이그레이션은 표를 든 패키지에 둔다.\n *\n * ── 되돌리기 ─────────────────────────────────────────────────────────────────\n * `down` 이 값을 되돌린다. 옛 코드로 되돌아가는 경우가 실제로 있고(배포 롤백), 그때 값이 새 이름으로\n * 남아 있으면 같은 결함이 반대 방향으로 난다.\n *\n * 멱등: 해당하는 행이 없으면 아무것도 하지 않는다. TypeORM 질의 빌더만 쓴다 — 드라이버마다 파라미터\n * 표기가 다르므로 raw SQL 을 쓰지 않는다.\n */\nconst REFS = 'twin_references'\nconst COLUMN = 'adapter_type'\n/**\n * 질의 빌더에는 **속성 이름**을 준다.\n *\n * 처음에 컬럼 이름(`adapter_type`)으로 썼더니 TypeORM 이 표 이름을 엔티티(`TwinReference`)로 풀고\n * 「그런 속성이 없다」로 거절했다 — 표 이름을 주더라도 매핑은 엔티티 기준이다. 컬럼 이름은 표가\n * 있는지 볼 때만 쓴다.\n */\nconst PROPERTY = 'adapterType'\nconst OLD = 'operato-mes'\nconst NEW = 'operato-plant'\n\nasync function rename(queryRunner: QueryRunner, from: string, to: string): Promise<void> {\n /* 표가 아직 없는 배포(첫 기동)에서는 할 일이 없다. */\n const table = await queryRunner.getTable(REFS)\n if (!table?.columns.some(c => c.name === COLUMN)) return\n\n /* 객체 형태로 준다 — 문자열 조건은 드라이버마다 식별자 인용이 달라 그대로 나간다. */\n await queryRunner.manager\n .createQueryBuilder()\n .update(REFS)\n .set({ [PROPERTY]: to })\n .where({ [PROPERTY]: from })\n .execute()\n}\n\nexport class RenameOperatoMesAdapterType1786600000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n await rename(queryRunner, OLD, NEW)\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n await rename(queryRunner, NEW, OLD)\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"1786700000000-AddDispatchRetryColumns.js","sourceRoot":"","sources":["../../server/migrations/1786700000000-AddDispatchRetryColumns.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qCAAsE;AAEtE,MAAM,KAAK,GAAG,sBAAsB,CAAA;AAEpC,4CAA4C;AAC5C,MAAM,YAAY,GAAG,eAAe,CAAA;AACpC,uBAAuB;AACvB,MAAM,WAAW,GAAG,aAAa,CAAA;AAEjC,MAAM,YAAY,GAAG,CAAC,oBAAoB,EAAE,OAAO,EAAE,iBAAiB,CAAU,CAAA;AAChF,MAAM,WAAW,GAAG,CAAC,UAAU,CAAU,CAAA;AAEzC,MAAa,oCAAoC;IACxC,KAAK,CAAC,EAAE,CAAC,WAAwB;QACtC,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QAC/C,IAAI,CAAC,KAAK;YAAE,OAAM,CAAC,qBAAqB;QAExC,MAAM,MAAM,GAAG,CAAC,OAAe,EAAsB,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,IAAI,CAAA;QAEzG,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;QACrC,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,CAAA;QAEnC;;;WAGG;QACH,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,uBAAuB,YAAY,IAAI,QAAQ,IAAI,IAAI,MAAM;gBACnE,GAAG,WAAW,IAAI,OAAO,IAAI,IAAI,qBAAqB,CACzD,CAAA;QACH,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YAChC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;gBAAE,SAAQ;YACtD,MAAM,WAAW,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,qBAAW,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACjG,CAAC;QAED,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;YAC/B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;gBAAE,SAAQ;YACtD,MAAM,WAAW,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,qBAAW,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAChG,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,WAAwB;QACxC,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QAC/C,IAAI,CAAC,KAAK;YAAE,OAAM;QAElB,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,YAAY,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC;YACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;gBAAE,SAAQ;YACvD,MAAM,WAAW,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAC3C,CAAC;IACH,CAAC;CACF;AAzCD,oFAyCC","sourcesContent":["/*\n * 조치 표에 **일꾼이 쓸 네 칸**을 더한다.\n *\n * ── 왜 표에 두나 ────────────────────────────────────────────────────────────\n * 물러섬을 메모리에 두면 재기동에 사라지고, 그러면 물러서던 것이 전부 즉시 재시도가 된다. 그리고\n * 일꾼이 여러 대가 되면 같은 답을 봐야 한다.\n *\n * dispatch_note_next 저쪽 말 중 「다음에 할 일」 한 줄 — 원인은 dispatch_note\n * retry 다시 해서 될 일인가 (again · after-fix · never)\n * attempts 몇 번 넘겨 봤나 — 목록의 「몇 번 해 봤나」\n * next_attempt_at 다음에 넘겨 볼 시각(ISO)\n *\n * ── 타입을 짐작하지 않는다 ──────────────────────────────────────────────────\n * 엔티티가 `@Column({ nullable: true })` 로 두면 그것이 어느 컬럼 타입이 되는지는 **드라이버마다\n * 다르다.** `'varchar'` 라고 적으면 sqlite·postgres·mysql 에서는 맞고 MSSQL·Oracle 에서 어긋난다.\n *\n * 그래서 같은 성질로 **이미 서 있는 형제 칸**의 타입을 읽어 그대로 쓴다 — 드라이버가 스스로 고른\n * 답이라 다섯 곳에서 다 맞는다. 형제가 없으면 짐작하지 않고 멈춘다.\n *\n * 글자 셋 dispatch_note 에서\n * 수 하나 effect_seen 에서\n *\n * plant 레인이 오늘 `equipmentClassIds` 마이그레이션에서 같은 방법을 썼다.\n *\n * ── 멱등 ────────────────────────────────────────────────────────────────────\n * 칸이 이미 있으면 아무것도 하지 않는다 — 개발 sqlite 는 엔티티 선언만으로 이미 만들어 둔다.\n * 표가 아직 없는 첫 기동에서도 할 일이 없다.\n */\nimport { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'\n\nconst TABLE = 'twin_command_records'\n\n/** 글자 칸의 형제 — 같은 성질(선택·자유 문자열)로 이미 서 있다. */\nconst TEXT_SIBLING = 'dispatch_note'\n/** 수 칸의 형제 — 선택·정수. */\nconst INT_SIBLING = 'effect_seen'\n\nconst TEXT_COLUMNS = ['dispatch_note_next', 'retry', 'next_attempt_at'] as const\nconst INT_COLUMNS = ['attempts'] as const\n\nexport class AddDispatchRetryColumns1786700000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n const table = await queryRunner.getTable(TABLE)\n if (!table) return /* 첫 기동 — 엔티티가 만든다 */\n\n const typeOf = (sibling: string): string | undefined => table.columns.find(c => c.name === sibling)?.type\n\n const textType = typeOf(TEXT_SIBLING)\n const intType = typeOf(INT_SIBLING)\n\n /*\n * 형제가 없으면 **멈춘다.** 짐작한 타입으로 칸을 세우면 어느 드라이버에서 어긋나는지 개발에서\n * 안 잡히고, 그 배포에서만 난다.\n */\n if (!textType || !intType) {\n throw new Error(\n `${TABLE} 에서 형제 칸을 못 찾았습니다 — ${TEXT_SIBLING}(${textType ?? '없음'}) · ` +\n `${INT_SIBLING}(${intType ?? '없음'}). 타입을 짐작하지 않고 멈춥니다`\n )\n }\n\n for (const name of TEXT_COLUMNS) {\n if (table.columns.some(c => c.name === name)) continue\n await queryRunner.addColumn(TABLE, new TableColumn({ name, type: textType, isNullable: true }))\n }\n\n for (const name of INT_COLUMNS) {\n if (table.columns.some(c => c.name === name)) continue\n await queryRunner.addColumn(TABLE, new TableColumn({ name, type: intType, isNullable: true }))\n }\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n const table = await queryRunner.getTable(TABLE)\n if (!table) return\n\n for (const name of [...TEXT_COLUMNS, ...INT_COLUMNS]) {\n if (!table.columns.some(c => c.name === name)) continue\n await queryRunner.dropColumn(TABLE, name)\n }\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"1786800000000-CreateTwinReferenceReconciliations.js","sourceRoot":"","sources":["../../server/migrations/1786800000000-CreateTwinReferenceReconciliations.ts"],"names":[],"mappings":"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","sourcesContent":["import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm'\n\n/* 번호 대사는 원본 범위를 실제로 읽은 뒤의 최신 상태를 재기동 뒤에도 보존한다. */\nconst TABLE = 'twin_reference_reconciliations'\n\nexport class CreateTwinReferenceReconciliations1786800000000 implements MigrationInterface {\n public async up(queryRunner: QueryRunner): Promise<void> {\n if (await queryRunner.hasTable(TABLE)) return\n const dateType = queryRunner.connection.driver.mappedDataTypes.createDate as string\n await queryRunner.createTable(new Table({\n name: TABLE,\n columns: [\n { name: 'id', type: 'varchar', length: '36', isPrimary: true },\n { name: 'domain_id', type: 'varchar', length: '36', isNullable: true },\n { name: 'instance_id', type: 'varchar', isNullable: false },\n { name: 'scope', type: 'varchar', isNullable: false },\n { name: 'status', type: 'varchar', isNullable: false },\n { name: 'source_first', type: 'int', isNullable: true },\n { name: 'source_last', type: 'int', isNullable: true },\n { name: 'cursor_seq', type: 'int', isNullable: true },\n { name: 'first_seq_seen', type: 'int', isNullable: true },\n { name: 'head_missing', type: 'int', isNullable: true },\n { name: 'behind', type: 'int', isNullable: true },\n { name: 'reason', type: 'varchar', isNullable: true },\n { name: 'checked_at', type: dateType, isNullable: false },\n { name: 'created_at', type: dateType, isNullable: true },\n { name: 'updated_at', type: dateType, isNullable: true }\n ]\n }), true)\n const table = await queryRunner.getTable(TABLE)\n if (!table) return\n await queryRunner.createIndex(table, new TableIndex({ name: 'uq_twin_reference_reconciliation_0', columnNames: ['domain_id', 'instance_id', 'scope'], isUnique: true }))\n await queryRunner.createIndex(table, new TableIndex({ name: 'ix_twin_reference_reconciliation_0', columnNames: ['domain_id', 'instance_id', 'checked_at'] }))\n }\n\n public async down(queryRunner: QueryRunner): Promise<void> {\n if (await queryRunner.hasTable(TABLE)) await queryRunner.dropTable(TABLE)\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/migrations/index.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,wHAA+G;AAC/G,8HAAqH;AACrH,kGAAyF;AACzF,oGAA2F;AAC3F,kGAAyF;AACzF,0GAAiG;AACjG,0GAAiG;AACjG,gIAAuH;AACvH,kHAAyG;AAE5F,QAAA,UAAU,GAAG;IACxB,8FAA2C;IAC3C,oGAA8C;IAC9C,wEAAgC;IAChC,0EAAiC;IACjC,wEAAgC;IAChC,gFAAoC;IACpC,wFAAwC;IACxC,gFAAoC;IACpC,sGAA+C;CAChD,CAAA","sourcesContent":["/*\n * 마이그레이션 묶음 — 프레임워크 규약(`@things-factory/shell` · `auth-base` 와 같은 방식).\n * 파일마다 하나씩 export 하고 여기서 모아 `server/index.ts` 가 내보낸다.\n */\nimport { RenameTwinInstanceBoardToModel1786000000000 } from './1786000000000-RenameTwinInstanceBoardToModel.js'\nimport { PromoteEventActionAndSyncWarnings1786100000000 } from './1786100000000-PromoteEventActionAndSyncWarnings.js'\nimport { CarryLiveFeedCursor1786200000000 } from './1786200000000-CarryLiveFeedCursor.js'\nimport { IndexEventTypeByTime1786300000000 } from './1786300000000-IndexEventTypeByTime.js'\nimport { IndexEventCreatedAt1786400000000 } from './1786400000000-IndexEventCreatedAt.js'\nimport { CreateTwinSubjectEvents1786500000000 } from './1786500000000-CreateTwinSubjectEvents.js'\nimport { AddDispatchRetryColumns1786700000000 } from './1786700000000-AddDispatchRetryColumns.js'\nimport { CreateTwinReferenceReconciliations1786800000000 } from './1786800000000-CreateTwinReferenceReconciliations.js'\nimport { RenameOperatoMesAdapterType1786600000000 } from './1786600000000-RenameOperatoMesAdapterType.js'\n\nexport const migrations = [\n RenameTwinInstanceBoardToModel1786000000000,\n PromoteEventActionAndSyncWarnings1786100000000,\n CarryLiveFeedCursor1786200000000,\n IndexEventTypeByTime1786300000000,\n IndexEventCreatedAt1786400000000,\n CreateTwinSubjectEvents1786500000000,\n RenameOperatoMesAdapterType1786600000000,\n AddDispatchRetryColumns1786700000000,\n CreateTwinReferenceReconciliations1786800000000\n]\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"routes.js","sourceRoot":"","sources":["../server/routes.ts"],"names":[],"mappings":";;AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,uEAA+D;AAC/D,mEAA8D;AAC9D,6EAAkE;AAClE,6EAAwE;AACxE,qEAAiE;AAEjE,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,IAAS,EAAE,MAAW,EAAE,EAAE;IACnF;;;;;OAKG;IACH,MAAM,CAAC,IAAI,CAAC,gCAAgC,EAAE,KAAK,EAAE,GAAQ,EAAE,EAAE;QAC/D,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,MAAM,CAAA;QAChC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,GAAG,CAAC,MAAM,GAAG,GAAG,CAAA;YAChB,GAAG,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAA;YACpD,OAAM;QACR,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,IAAA,8BAAU,EAAC;YAC/B,QAAQ,EAAE,MAAM,CAAC,EAAE;YACnB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;YACvC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;YAC/C,OAAO,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAA4B;YAChE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI;YACvB;;;eAGG;YACH,OAAO,EAAE,OAAO,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;YACnF,KAAK,EAAE,IAAA,6BAAa,GAAE;YACtB;;;eAGG;YACH;;;eAGG;YACH,MAAM,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,OAAO;gBACrD,CAAC,CAAC,IAAA,2BAAU,EAAC,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC;gBACpD,CAAC,CAAC,IAAA,oCAAgB,EAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;YAC3G;;;eAGG;YACH,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,IAAA,0BAAW,EAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC;SACnF,CAAC,CAAA;QACF,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QAC3B,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;IACzB,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,2DAA2D;AAC3D,OAAO,CAAC,EAAE,CAAC,sCAA6C,EAAE,CAAC,IAAS,EAAE,OAAY,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA;AAC1F,OAAO,CAAC,EAAE,CAAC,uCAA8C,EAAE,CAAC,IAAS,EAAE,OAAY,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA;AAC3F,OAAO,CAAC,EAAE,CAAC,uCAA8C,EAAE,CAAC,IAAS,EAAE,OAAY,EAAE,EAAE,GAAE,CAAC,CAAC,CAAA","sourcesContent":["/*\n * 이 패키지가 여는 HTTP 자리 — 지금은 웹훅 하나다.\n *\n * ── 왜 여기인가 ─────────────────────────────────────────────────────────────\n * 커넥터는 주소를 만들 수 없다. 어댑터 계약에 주소를 등록하는 자리가 없고, 있어서도 안 된다 — 주소는\n * 앱의 것이고 커넥터마다 제각기 열면 인증도 제각기가 된다.\n *\n * 그래서 입구는 **하나**다. 어느 연결의 것인지는 주소의 이름으로 가려내고, 본문을 우리 어휘로 옮기는\n * 것은 그 연결의 커넥터가 한다(§`reference-hook`).\n *\n * ── 왜 도메인 공개 라우터인가 ───────────────────────────────────────────────\n * 밀어 주는 쪽은 사람이 아니다. 사람 인증(JWT)을 요구하면 연결된 시스템이 우리 토큰을 발급받아\n * 갱신하며 관리해야 하고, 대부분의 현장에서 그것 때문에 연동이 서지 않는다.\n *\n * 대신 **연결마다 비밀값**을 두고 그것으로 확인한다. 비밀값을 선언하지 않은 연결은 훅을 받지 않는다 —\n * 인증 없는 입구를 열어 두는 것보다 훅을 못 쓰는 것이 낫다.\n */\nimport { liveIngest } from './service/reference/live-ingest.js'\nimport { requestFill } from './service/reference/fill-loop.js'\nimport { handleHook } from './service/reference/reference-hook.js'\nimport { deliverAttention } from './service/reference/attention-lane.js'\nimport { databaseStore } from './service/reference/hook-store.js'\n\nprocess.on('bootstrap-module-domain-public-route' as any, (_app: any, router: any) => {\n /*\n * 주소에 트윈까지 적는다 — 어느 트윈에 넣을지 짐작하지 않는다.\n *\n * 한 연결이 여러 사이트를 만들면 트윈도 여럿이다. 본문을 보고 고르게 하면 그 판단이 커넥터마다\n * 달라지고, 틀리면 다른 공장의 사실이 이 공장에 들어간다. 주소가 말하게 하면 그 일이 없다.\n */\n router.post('/twin/hook/:source/:instanceId', async (ctx: any) => {\n const domain = ctx.state?.domain\n if (!domain) {\n ctx.status = 400\n ctx.body = { ok: false, error: 'no domain context' }\n return\n }\n const outcome = await handleHook({\n domainId: domain.id,\n source: String(ctx.params.source ?? ''),\n instanceId: String(ctx.params.instanceId ?? ''),\n headers: (ctx.request?.headers ?? {}) as Record<string, unknown>,\n body: ctx.request?.body,\n /*\n * 받은 바이트 그대로 — 서명을 확인하려면 이것이 있어야 한다. `koa-bodyparser` 가 파싱한 본문과\n * 함께 남겨 준다. 파싱본을 다시 문자열로 만들면 키 순서와 공백이 달라져 서명이 맞지 않는다.\n */\n rawBody: typeof ctx.request?.rawBody === 'string' ? ctx.request.rawBody : undefined,\n store: databaseStore(),\n /*\n * 폴링이 쓰는 길을 그대로 쓴다 — 검증도 유입도 같은 자리다. 그래서 트윈 쪽에서는 물어서 받은\n * 것과 밀어 받은 것이 구별되지 않는다. 구별되는 것은 유입 장부의 `sources` 한 줄뿐이다.\n */\n /*\n * 유입 몸통은 `live-ingest.ts` 한 곳에 있다 — backfill 이 같은 것을 쓴다. 여기에 두면 두 길이\n * 서로 다른 규칙으로 받게 되고, 한쪽만 수정되는 날이 온다.\n */\n ingest: (instanceId, records, lane) => lane === 'FACTS'\n ? liveIngest(domain.id, instanceId, records, 'hook')\n : deliverAttention({ domainId: domain.id, source: String(ctx.params.source ?? ''), instanceId, records }),\n /*\n * 구멍을 본 그 자리에서 바로 받는다 — 번호가 비었다는 것을 아는 가장 이른 순간이다.\n * 기다리지 않는다(§`requestFill`): 훅의 응답이 늦으면 보내는 쪽이 되풀이한다.\n */\n onGap: scope => requestFill(domain.id, String(ctx.params.instanceId ?? ''), scope)\n })\n ctx.status = outcome.status\n ctx.body = outcome.body\n })\n})\n\n/* 나머지 셋은 이 패키지가 쓰지 않는다 — 자리는 남겨 둔다(다른 모듈이 같은 신호를 기다린다). */\nprocess.on('bootstrap-module-global-public-route' as any, (_app: any, _router: any) => {})\nprocess.on('bootstrap-module-global-private-route' as any, (_app: any, _router: any) => {})\nprocess.on('bootstrap-module-domain-private-route' as any, (_app: any, _router: any) => {})\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"actuation-advisor.js","sourceRoot":"","sources":["../../../server/service/actuation/actuation-advisor.ts"],"names":[],"mappings":";;;AAkEA,kDAEC;AAED,4CAEC;AAwBD,gCA+BC;AAhED,IAAI,OAAqC,CAAA;AAEzC,uBAAuB;AACvB,SAAgB,mBAAmB,CAAC,IAAsB;IACxD,OAAO,GAAG,IAAI,CAAA;AAChB,CAAC;AAED,SAAgB,gBAAgB;IAC9B,OAAO,OAAO,CAAA;AAChB,CAAC;AAeD,6CAA6C;AAChC,QAAA,iBAAiB,GAAG,KAAK,CAAA;AAEtC;;;;;GAKG;AACI,KAAK,UAAU,UAAU,CAAC,IAMhC;IACC,MAAM,CAAC,GAAG,OAAO,CAAA;IACjB,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAA;IAErD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,IAAI,yBAAiB,CAAA;IACjD,IAAI,KAAU,CAAA;IACd,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YACjC,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;YACpI,IAAI,OAAO,CAAY,OAAO,CAAC,EAAE;gBAC/B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,SAAgB,CAAC,EAAE,MAAM,CAAC,CAAA;YAC7D,CAAC,CAAC;SACH,CAAC,CAAA;QAEF,IAAK,OAAe,KAAK,SAAS,EAAE,CAAC;YACnC,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAA;QAChF,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAA;QAEjF,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,CAAA;IACjC,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,kBAAkB,MAAM,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,EAAE,EAAE,CAAA;IAC7E,CAAC;YAAS,CAAC;QACT,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAA;IAChC,CAAC;AACH,CAAC;AAED,uDAAuD;AACvD,MAAM,SAAS,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAA","sourcesContent":["/*\n * **AI 가 의견을 내는 자리** — 판정이 아니라 발언.\n *\n * ── 왜 필요한가 (2026-09-01, 사용자 지적) ────────────────────────────────\n * 사용자가 물었다 — 「AI 자신은 결재/승인의 담당이 될 수 없나. 최소한 의견제시자라도 될 수 있지 않나.」\n *\n * 승인자는 안 된다. 승인 기록의 값어치는 사고 뒤에 **책임을 물을 수 있는 것**이고, 「AI 가 승인했다」로는\n * 아무에게도 묻지 못한다. 그리고 낸 쪽이 승인하면 두 사람이 아니다 — 자기 승인을 막는 규칙과 같은 이유다.\n *\n * 의견은 내야 한다. `twinNow` 로 「도금 대기가 여섯 배다」는 사실을 승인 화면에 실었지만, 그건 계산값이다.\n * 「그러니 더 만들면 대기가 늘어난다」가 의견이고 그 자리가 없었다.\n *\n * ── 언제 묻나 — 승인 화면을 열 때다 ─────────────────────────────────────\n * 결재선에 올릴 때 묻지 않는다. 그 자리는 트랜잭션 안이고(`raiseTwinCommand` 의 `@transaction`),\n * 거기서 LLM 을 기다리면 **DB 연결 하나를 몇 초 붙잡는다**(sqlite 는 연결이 하나다).\n *\n * 그리고 화면을 열 때 묻는 것이 더 옳다. 승인하는 사람은 **지금** 판단하고, 올린 시각의 의견은 낡았다.\n *\n * ── 판정에 쓰지 않는다 ───────────────────────────────────────────────────\n * 이 값을 읽어 무엇을 막는 코드가 없다. 승인·거절은 사람이 한다. AI 가 반대해도 승인되고, 동의해도\n * 거절된다. 그 사실이 코드에 드러나 있어야 한다 — 판정에 쓰면 AI 가 사실상 승인자가 된다.\n */\nimport type { StoredCommand } from './command-dispatcher.js'\nimport type { TwinNow } from './twin-now.js'\n\n/**\n * AI 가 어떤 태도를 냈나 — **세 갈래여야 한다.**\n *\n * 「모르겠다」가 없으면 모를 때 동의로 답한다. 그리고 승인자가 그것을 「AI 가 괜찮다고 했다」로 읽는다.\n * 자동 승인 편향이 거기서 시작한다.\n */\nexport type AdvisorStance = 'object' | 'concur' | 'unsure'\n\nexport interface AdvisorOpinion {\n stance: AdvisorStance\n /** 사람이 읽는 문장. AI 가 쓴 그대로 — 우리가 다듬지 않는다(다듬으면 누가 말한 것인지 흐려진다). */\n text: string\n /** 무엇을 보고 말했나 — 주의 식별자·값. 승인자가 근거를 되짚을 수 있게. */\n basis?: string[]\n}\n\n/** 의견을 내는 자리 — 앱이 채운다(headless-twin 은 `twin-ai` 를 부르지 않는다). */\nexport interface ActuationAdvisor {\n /** 화면이 출처를 말해야 한다 — 「AI 의견」. 사람의 의견과 섞이면 안 된다. */\n label: string\n /**\n * 이 조치에 대해 한마디.\n *\n * 실패·시간초과면 `undefined` 를 돌려준다. 지어내지 않는다.\n */\n advise(input: {\n /*\n * The tenant the command belongs to. Required: the advisor's tools read the twin through it, and without it\n * every tool answers \"no domain context\" while the opinion is still written from the prompt alone\n * (2026-09-17 — the twin app passed nothing because this input had no place for it).\n */\n domainId: string\n command: StoredCommand\n twinNow: TwinNow\n locale?: string\n }): Promise<AdvisorOpinion | undefined>\n}\n\nlet advisor: ActuationAdvisor | undefined\n\n/** 앱이 부팅에서 한 번 부른다. */\nexport function setActuationAdvisor(next: ActuationAdvisor): void {\n advisor = next\n}\n\nexport function actuationAdvisor(): ActuationAdvisor | undefined {\n return advisor\n}\n\n/** 의견을 물은 결과 — **없음도 사실이다.** */\nexport interface AdvisorResult {\n /** 누가 말했나. 없으면 아무도 말하지 않았다. */\n by?: string\n opinion?: AdvisorOpinion\n /**\n * 의견이 없으면 왜 없나.\n *\n * 빈칸을 「AI 가 반대하지 않았다」로 읽으면 안 된다. 화면이 이 문장을 그대로 보여야 한다.\n */\n absent?: string\n}\n\n/** 몇 초까지 기다리나 — 승인 화면이 이것 때문에 안 열리면 안 된다. */\nexport const ADVISOR_BUDGET_MS = 8_000\n\n/**\n * 의견을 묻는다 — **없으면 없다고 답한다.**\n *\n * 실패를 삼키지 않는다. AI 가 죽어 있어서 의견이 없는 것과 AI 가 반대할 것이 없어서 조용한 것은 다른\n * 사실이고, 승인하는 사람에게 그 차이가 크다.\n */\nexport async function askAdvisor(args: {\n domainId: string\n command: StoredCommand\n twinNow: TwinNow\n locale?: string\n budgetMs?: number\n}): Promise<AdvisorResult> {\n const a = advisor\n if (!a) return { absent: 'AI 의견을 내는 자리가 설치되지 않았습니다' }\n\n const budget = args.budgetMs ?? ADVISOR_BUDGET_MS\n let timer: any\n try {\n const opinion = await Promise.race([\n a.advise({ domainId: args.domainId, command: args.command, twinNow: args.twinNow, ...(args.locale ? { locale: args.locale } : {}) }),\n new Promise<undefined>(resolve => {\n timer = setTimeout(() => resolve(TIMED_OUT as any), budget)\n })\n ])\n\n if ((opinion as any) === TIMED_OUT) {\n return { by: a.label, absent: `${Math.round(budget / 1000)}초 안에 답이 오지 않았습니다` }\n }\n if (!opinion?.text?.trim()) return { by: a.label, absent: 'AI 가 할 말이 없다고 답했습니다' }\n\n return { by: a.label, opinion }\n } catch (e: any) {\n return { by: a.label, absent: `의견을 받지 못했습니다 — ${String(e?.message ?? e)}` }\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n\n/** 시간초과를 값으로 구별한다 — `undefined` 로 두면 「할 말 없음」과 섞인다. */\nconst TIMED_OUT = Symbol('advisor-timeout')\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"actuation-outcome.js","sourceRoot":"","sources":["../../../server/service/actuation/actuation-outcome.ts"],"names":[],"mappings":";;AAyEA,gDAuBC;AAhCD,sDAAsD;AACtD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;AAEzD;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,OAAsB,EAAE,YAAqB;IAC9E,MAAM,IAAI,GAAG,OAAO,CAAC,YAAY,CAAA;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;IAE7B;;;OAGG;IACH,MAAM,eAAe,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;IAE/F,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3E,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;QAC/E,GAAG,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC;QAC7D,YAAY;KACb,CAAA;AACH,CAAC","sourcesContent":["/*\n * **조치가 어떻게 됐나** — 승인한 사람에게 돌려줄 사실 한 묶음.\n *\n * ── 왜 판정을 커널이 하나 (2026-09-04) ─────────────────────────────────────\n * 「저쪽에서 사람이 할 일이 남았나」의 답이 한 필드에 없다. `dispatchNote` 만 보면 틀린다 —\n * 그 문장은 **넘길 때 저쪽이 한 말**이고, 사람이 나중에 지시서를 골라도 그대로 남는다. 저쪽에\n * 다시 물을 일이 없으니 갱신되지 않는다(인티그레이션 레인이 MES 코드를 읽어 찾았다: 다시 보내면\n * 지금 상태를 답하는데, `acked` 가 종착이라 다시 보낼 일이 없다).\n *\n * 그러니 답은 **「그 문장이 있고 효과가 아직 없을 때」**다. 그 겹침을 화면마다 하게 두면 목록·\n * 상세·알림이 각자 겹치고, 한 곳이 빠진 날 그 화면만 다른 말을 한다. 오늘 설비 등급 소속에서\n * 같은 이유로 합침을 계약 한 자리에 뒀다.\n *\n * ── 커널은 저쪽 말을 해석하지 않는다 ───────────────────────────────────────\n * `effectStatus` 는 원천이 보낸 낱말 그대로다(§`QualityDelta` 와 같은 규율). 여기서 보는 것은\n * **그 값이 있는가**뿐이고, `released` 인지 `closed` 인지는 도메인의 뜻이라 커널이 가르지 않는다.\n */\nimport type { StoredCommand } from './command-dispatcher.js'\n\n/** 승인한 사람에게 돌려줄 것 — 화면이 겹쳐 볼 것이 없게 판정까지 낸다. */\nexport interface ActuationOutcome {\n id: string\n /** proposed · approved · dispatching · acked · failed · rejected. */\n state: string\n /** 저쪽이 준 이름 — 나갔을 때만. 없으면 무엇을 되돌릴지 지목할 수 없다. */\n dispatchRef?: string\n /** 못 나간 사유 — 저쪽 문장 그대로. */\n error?: string\n /** 저쪽이 받으면서 덧붙인 말 — **실패가 아니다.** 원인 쪽 문장. */\n note?: string\n /**\n * 그 말 중 **다음에 할 일** 한 줄.\n *\n * 왜 둘로 오나: 읽는 사람은 「그래서 내가 뭘 해야 하나」를 먼저 찾는다(UX 레인). 한 문장으로\n * 주면 화면이 자르게 되고, **자르는 규칙이 화면마다 생긴다.** 어댑터가 이미 둘로 알고 있으니\n * 붙이지 않고 그대로 나른다.\n *\n * 어댑터가 아직 갈라 주지 않으면 **비어 있다.** 그때 `note` 는 두 문장이 붙은 한 덩이이고,\n * 화면은 그것을 통째로 그려야 한다 — 파싱해서 나누면 어댑터가 갈라 주는 날 조용히 깨진다.\n */\n noteNext?: string\n /** 원천이 보고한 상태 — 낱말 그대로. */\n effectStatus?: string\n /** 그 보고를 본 시각. */\n effectAt?: string\n /**\n * 되돌릴 수 있나 — 화면은 **참일 때만** 한 줄 낸다.\n *\n * 지금 규칙에서 결재함에 올라온 조치는 거의 다 되돌릴 수 없다(되돌릴 수 없는 것만 승인을\n * 요구한다). 그래서 「되돌릴 수 없습니다」를 매번 적으면 늘 참인 경고가 되고, 늘 참인 경고는\n * 사흘이면 안 읽힌다 — 그러면 그 자리에 다른 말이 필요한 날 자리가 이미 죽어 있다(UX 레인 판단).\n */\n reversible?: boolean\n /**\n * **저쪽에서 사람이 할 일이 남았나** — 이 판정이 이 타입의 존재 이유다(머리말).\n *\n * 「모른다」가 있다: 아직 넘기지 않았으면 물어볼 것이 없다. `false` 로 접으면 화면이 「할 일\n * 없음」으로 그리고, 그것은 넘긴 뒤와 구별되지 않는다.\n */\n pendingAtSource?: boolean\n /** 넘길 수 있는 상태인가 — `acked` 는 종착이라 저쪽이 준비돼 있어도 다시 넘기지 않는다. */\n dispatchable: boolean\n}\n\n/** 넘김을 시도해 본 상태인가 — 이 뒤에야 「저쪽에 할 일이 남았나」를 물을 수 있다. */\nconst TRIED = new Set(['dispatching', 'acked', 'failed'])\n\n/**\n * 저장된 조치 하나를 사람이 읽을 사실로 옮긴다 — **순수하다.**\n *\n * DB 도 어댑터도 보지 않는다. 그래서 이 판정은 시험이 값만 바꿔 가며 확인할 수 있고, 화면이\n * 같은 규칙을 다시 쓰지 않아도 된다.\n */\nexport function actuationOutcomeOf(command: StoredCommand, dispatchable: boolean): ActuationOutcome {\n const note = command.dispatchNote\n const effect = command.effect\n\n /*\n * 넘겨 보지 않았으면 판정하지 않는다. 넘긴 뒤라면 「말이 있는데 효과가 아직 없다」가 곧\n * 「저쪽에서 사람이 할 일이 남았다」다.\n */\n const pendingAtSource = TRIED.has(command.state) ? Boolean(note) && !effect?.status : undefined\n\n return {\n id: command.id,\n state: command.state,\n ...(command.dispatchRef ? { dispatchRef: command.dispatchRef } : {}),\n ...(command.error ? { error: command.error } : {}),\n ...(note ? { note } : {}),\n ...(command.dispatchNoteNext ? { noteNext: command.dispatchNoteNext } : {}),\n ...(effect?.status ? { effectStatus: effect.status } : {}),\n ...(effect?.at ? { effectAt: effect.at } : {}),\n ...(command.reversible === undefined ? {} : { reversible: command.reversible }),\n ...(pendingAtSource === undefined ? {} : { pendingAtSource }),\n dispatchable\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"actuation-rule-resolver.js","sourceRoot":"","sources":["../../../server/service/actuation/actuation-rule-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 규칙을 만들고 끄는 자리, 그리고 **지금 한 번 돌려 보는 자리**.\n *\n * ── 왜 「지금 돌린다」가 있나 ────────────────────────────────────────────\n * 규칙을 만든 사람이 가장 먼저 묻는 것은 「이게 걸리나」다. 주기를 기다려 보라고 하면 그 사이에 무엇이\n * 잘못됐는지 알 수 없고, 조건이 그때까지 살아 있을지도 모른다. 돌려 보면 **무엇을 냈고 무엇을 왜 안\n * 냈는지**가 그 자리에서 답으로 온다.\n */\nimport { Arg, Args, Ctx, Directive, Mutation, Query, Resolver } from 'type-graphql'\n\nimport { CMD, MES_COMMANDS } from '@operato/ops-contract'\n\nimport { ListParam, ScalarObject, getQueryBuilderFromListParams, getRepository } from '@things-factory/shell'\n\nimport { TwinEngine } from '../../engine/twin-engine.js'\n\nimport { TwinActuationRule } from './actuation-rule.js'\nimport { commandTypeGap } from './rule-evaluate.js'\nimport { runRulesOnce } from './rule-runner.js'\nimport { databaseCommandStore } from './command-store.js'\nimport { rulesOf, noteRun, attentionsOf, modeOf } from './rule-wiring.js'\n\n@Resolver()\nexport class TwinActuationRuleResolver {\n @Query(returns => ScalarObject, {\n nullable: true,\n description: 'List actuation rules with the standard list contract.'\n })\n async twinActuationRules(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domain = context.state.domain\n if (!domain?.id) return { items: [], total: 0 }\n\n const qb = getQueryBuilderFromListParams({\n repository: getRepository(TwinActuationRule),\n params,\n domain,\n alias: 'r',\n searchables: ['instanceId', 'name', 'attentionKind', 'commandType', 'lastSkipReason'],\n sortables: ['createdAt', 'updatedAt', 'instanceId', 'attentionKind', 'commandType', 'lastRunAt'],\n defaultLimit: 50,\n maxLimit: 200\n })\n\n const [items, total] = await qb.getManyAndCount()\n return { items, total }\n }\n\n /**\n * 규칙을 세우거나 고친다.\n *\n * **낼 수 없는 종류는 저장하지 않는다.** 저장은 되는데 안 도는 규칙이 생기면 사람은 규칙이 걸린 줄로\n * 읽는다. 판정과 같은 검사를 지난다(§`commandTypeGap`) — 두 벌이면 어긋난다.\n */\n @Directive('@transaction')\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Create or update one actuation rule.' })\n async saveTwinActuationRule(\n @Arg('patch', type => ScalarObject) patch: any,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const { domain, user, tx } = context.state\n\n const instanceId = String(patch?.instanceId ?? '').trim()\n if (!instanceId) throw new Error('어느 트윈의 규칙인지 말해야 합니다 — 전역 규칙은 두지 않습니다')\n\n const attentionKind = String(patch?.attentionKind ?? '').trim()\n if (!attentionKind) throw new Error('어느 관측에 걸 것인지 말해야 합니다')\n\n const commandType = String(patch?.commandType ?? '').trim()\n const gap = commandTypeGap(commandType, MES_COMMANDS, Object.values(CMD) as string[])\n if (gap) throw new Error(gap)\n\n const repo = getRepository(TwinActuationRule, tx)\n const saved = await repo.save({\n ...(patch.id ? { id: patch.id } : {}),\n domain,\n instanceId,\n name: patch.name ?? null,\n attentionKind,\n minSeverity: patch.minSeverity ?? null,\n minHeldMs: patch.minHeldMs ?? null,\n /* 비워 두면 기본값(한 시간)이 걸린다. 0 은 「기다리지 않는다」는 뜻이라 그대로 존중한다. */\n cooldownMs: patch.cooldownMs ?? null,\n commandType,\n payloadTemplate: patch.payloadTemplate ?? null,\n /* 비어 있음을 「끔」으로 읽지 않는다 — 새로 만든 규칙은 켜져 있는 것이 사람의 뜻이다. */\n enabled: patch.enabled === false ? false : true,\n ...(patch.id ? {} : { creator: user }),\n updater: user\n } as any)\n\n return saved\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Delete one actuation rule.' })\n async deleteTwinActuationRule(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<any> {\n const { domain, tx } = context.state\n const result = await getRepository(TwinActuationRule, tx).delete({ id, domain: { id: domain.id } } as any)\n if (!result.affected) throw new Error(`모르는 규칙입니다 (${id})`)\n return { deleted: id }\n }\n\n /**\n * **지금 한 번 돌린다** — 무엇을 냈고 무엇을 왜 안 냈는지 그대로 답한다.\n *\n * 낸 조치는 제안 상태로 남는다. 결재선에 올리는 것은 사람이 따로 한다(§`raiseTwinCommand`) — 규칙이\n * 올리지 않는 이유가 여기에도 그대로 적용된다(결재선은 올린 사람 기준으로 풀린다).\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Evaluate actuation rules once now and report what happened.' })\n async runTwinActuationRules(\n @Arg('instanceId', { nullable: true }) instanceId: string,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain?.id\n if (!domainId) throw new Error('테넌트를 알 수 없습니다')\n\n /*\n * 트랜잭션을 넘기지 않는다 — 이 자리는 여러 트윈을 돌고, 한 트윈이 실패해도 앞의 트윈이 낸 것은\n * 남아야 한다. 한 덩어리로 묶으면 마지막 트윈의 실패가 앞의 결과를 전부 물린다.\n */\n const store = databaseCommandStore()\n\n const owned = TwinEngine.ownedTwins(domainId).filter(t => !instanceId || t.instanceId === instanceId)\n if (instanceId && !owned.length) {\n throw new Error(`이 서버가 들고 있지 않은 트윈입니다 (${instanceId}) — 멈춰 있거나 다른 서버가 돌고 있습니다`)\n }\n\n const reports = await runRulesOnce({\n ownedTwins: () => owned,\n rulesOf: twin => rulesOf(twin),\n attentionsOf,\n modeOf,\n store,\n specs: MES_COMMANDS,\n nowMs: () => Date.now(),\n noteRun\n })\n\n return { reports, proposed: reports.reduce((n, r) => n + r.proposed, 0) }\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"actuation-rule.js","sourceRoot":"","sources":["../../../server/service/actuation/actuation-rule.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **어느 관측에 어느 지시를 붙이는가** — 선언 한 행.\n *\n * ── 왜 표인가 (2026-09-01) ───────────────────────────────────────────────\n * 규칙은 현장이 정한다. 「병목이 세 시간 넘으면 추가 생산을 올린다」는 그 공장의 정책이고, 코드에 박으면\n * 공장마다 포크가 생긴다. 그리고 **끌 수 있어야 한다** — 규칙이 잘못 낸 것을 본 사람이 가장 먼저 하려는\n * 일이 그것이다.\n *\n * ── 판정은 여기 없다 ─────────────────────────────────────────────────────\n * 무엇을 올릴지는 `rule-evaluate.ts` 가 든다. 이 파일은 값을 담기만 한다 — 판정이 두 벌이 되면 화면과\n * 서버가 다른 답을 낸다.\n *\n * ── 계약에 넣지 않았다 ───────────────────────────────────────────────────\n * 이 규칙이 가리키는 두 값(`Attention.kind` · `CommandTypeSpec.type`)은 계약의 어휘다. 그러나 「그 둘을\n * 어떻게 잇는가」는 이 호스트의 배선이다 — 커널은 규칙을 읽지 않고, 커널을 쓰는 다른 소비처가 이 표를\n * 알 이유가 없다.\n */\nimport { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, UpdateDateColumn } from 'typeorm'\nimport { Field, ID, ObjectType } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\n@Entity()\n/* 판정이 이 두 칸으로 묻는다 — 이 테넌트의 · 이 트윈의 규칙. */\n@Index('ix_twin_actuation_rule_0', (e: TwinActuationRule) => [e.domain, e.instanceId])\n@ObjectType({ description: 'One rule mapping an observed condition to a field instruction that needs approval.' })\nexport class TwinActuationRule {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the rule.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Tenant this rule belongs to.' })\n domain?: Domain\n\n @RelationId((e: TwinActuationRule) => e.domain)\n domainId?: string\n\n /**\n * **어느 트윈인가** — 전역 규칙을 두지 않는다.\n *\n * 라인이 공장마다 다르므로 한 규칙이 모든 트윈에 걸리면 어느 공장에서는 반드시 틀린다. 트윈을 지목하면\n * 그 사실이 행에 드러나고, 화면이 「이 공장에 걸린 규칙」을 그대로 보여 줄 수 있다.\n */\n @Column()\n @Field({ description: 'Twin instance this rule applies to. There are no global rules.' })\n instanceId: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'What a person calls this rule.' })\n name?: string\n\n /** 어느 관측인가 — 커널의 안정 코드(`Attention.kind`). */\n @Column()\n @Field({ description: 'Observed condition kind in kernel vocabulary (Attention.kind).' })\n attentionKind: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Only at this severity or above: low, medium, high, critical.' })\n minSeverity?: string\n\n /**\n * **조건이 이만큼 이어졌을 때** 올린다.\n *\n * 없으면 처음 본 주기에 올린다. 깜빡이는 조건에 현장 지시가 나가는 것을 막는 유일한 칸이다.\n */\n @Column({ type: 'int', nullable: true })\n @Field({ nullable: true, description: 'Propose only after the condition has held this long (ms).' })\n minHeldMs?: number\n\n /**\n * 무엇을 내는가 — **선언된 조치 종류**(`CommandTypeSpec.type`).\n *\n * 커널 자극(`CMD`)이 아니다. 자극을 적으면 판정이 「선언되지 않은 조치 종류」로 거절한다 — 그 값은\n * 모델 상태를 바꾸는 것이고 현장에 나가는 지시가 아니다.\n */\n @Column()\n @Field({ description: 'Declared command type to propose. Not a kernel stimulus.' })\n commandType: string\n\n /**\n * 지시의 내용 — `{이름}` 을 관측의 값으로 바꿔 넣는다.\n *\n * 바꿀 값은 `Attention.params` 와 `anchor` 에서 온다. 바꿀 것이 없으면 그 칸을 비우고, 요구하는 칸이\n * 비면 판정이 거절한다 — 지어낸 값으로 현장 지시를 만들지 않는다.\n */\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Command payload template. {name} is filled from the observation.' })\n payloadTemplate?: any\n\n /**\n * **한 번 낸 근거에 다시 내기까지 기다리는 시간(ms).**\n *\n * 비워 두면 기본값이 걸린다(한 시간). 0 으로 두면 기다리지 않는데, 그러면 승인하는 순간 같은 것이\n * 다시 올라온다 — 걸어 보고 실제로 그 상태를 만들었다.\n */\n @Column({ type: 'int', nullable: true })\n @Field({ nullable: true, description: 'Wait this long before proposing again for the same cause (ms).' })\n cooldownMs?: number\n\n /** 지우지 않고 끌 수 있어야 한다 — 잘못 낸 것을 본 사람이 가장 먼저 하려는 일이다. */\n @Column({ default: true })\n @Field({ nullable: true, description: 'A disabled rule is not evaluated. Turning off is not deleting.' })\n enabled?: boolean\n\n /* ── 마지막 판정의 기록 ─────────────────────────────────────────────────\n * 규칙이 **아무것도 올리지 않을 때** 사람이 알 수 있어야 한다. 조용히 안 도는 규칙과 조건이 없어서 안\n * 올리는 규칙은 화면에서 똑같이 보이고, 그 둘을 구별하지 못하면 사람은 규칙이 도는 줄 안다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When this rule was last evaluated (ISO 8601).' })\n lastRunAt?: string\n\n @Column({ type: 'int', nullable: true })\n @Field({ nullable: true, description: 'How many commands this rule has proposed in total.' })\n proposedCount?: number\n\n /** 마지막에 왜 안 올렸나 — 사람 말로. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Why the last evaluation proposed nothing, when it did not.' })\n lastSkipReason?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n creator?: User\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"approval-gateway.js","sourceRoot":"","sources":["../../../server/service/actuation/approval-gateway.ts"],"names":[],"mappings":";;AAoCA,gDAEC;AAGD,0CAEC;AAVD,IAAI,OAAoC,CAAA;AAExC,uBAAuB;AACvB,SAAgB,kBAAkB,CAAC,IAAqB;IACtD,OAAO,GAAG,IAAI,CAAA;AAChB,CAAC;AAED,6CAA6C;AAC7C,SAAgB,eAAe;IAC7B,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["/*\n * **승인을 누가 받는가** — 그 자리를 밖으로 낸다.\n *\n * ── 왜 시임인가 (2026-08-31) ─────────────────────────────────────────────\n * 승인은 이미 이 저장소에 있다. `@things-factory/worklist` 가 결재선·위임·상신·「내가 승인할 것」 목록과\n * 승인 화면까지 들고 있다. 그것을 다시 만들면 두 벌이 되고, 사고 뒤에 「어느 쪽 기록이 진짜인가」를\n * 묻게 된다.\n *\n * 그런데 headless-twin 은 worklist 를 부를 수 없다 — worklist 는 조직도(Employee·Department·Role)와\n * 화면 27쪽을 데리고 오는 업무 모듈이고, 이 패키지는 커널을 안는 헤드리스 서버다. 의존을 그 방향으로\n * 걸면 커널을 쓰는 모든 배포가 조직도를 지게 된다.\n *\n * 그래서 **이 패키지는 자리만 만들고, 앱이 채운다**(operato-twin). 채우지 않은 배포에서는 게이트가\n * 사라지지 않는다 — 승인 없이 지나가는 것이 아니라, 아래 §`twinCommands` 의 직접 결정이 유일한 문이 된다.\n */\nimport type { StoredCommand } from './command-dispatcher.js'\n\n/** 결재선에 올리는 자리 — 앱이 구현한다. */\nexport interface ApprovalGateway {\n /** 화면과 오류 문장에 쓰는 이름. 「어디서 승인하는가」를 사람에게 말해야 한다. */\n label: string\n /**\n * 이 커맨드를 결재선에 올린다.\n *\n * 돌려주는 것은 **올라간 자리의 식별자**다. 이 값이 커맨드 행에 적히고, 그 뒤로는 직접 결정이 막힌다\n * — 문이 두 벌이면 한쪽 기록에만 남는 승인이 생긴다.\n *\n * `context` 는 리졸버 문맥 그대로 넘긴다(트랜잭션·로그인한 사람·테넌트). 이 패키지가 그 모양을 알면\n * 앱의 인증 구조를 여기서 다시 정의하게 된다.\n */\n request(command: StoredCommand, context: any): Promise<string>\n}\n\nlet gateway: ApprovalGateway | undefined\n\n/** 앱이 부팅에서 한 번 부른다. */\nexport function setApprovalGateway(next: ApprovalGateway): void {\n gateway = next\n}\n\n/** 등록된 것이 없으면 `undefined` — 없음을 없음으로 답한다. */\nexport function approvalGateway(): ApprovalGateway | undefined {\n return gateway\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"catch-up-effects.js","sourceRoot":"","sources":["../../../server/service/actuation/catch-up-effects.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **놓친 보고를 backfill 한다** — 유입 자리에서 잇는 길(§`settle-effects.ts`)의 짝.\n *\n * ── 왜 필요한가 (2026-09-02, 실제로 하나 놓쳤다) ──────────────────────────────\n * 유입 자리에서만 이으면, **우리가 못 듣고 있던 동안 온 보고는 영원히 사라진다.**\n *\n * 10:13 MES 가 WO-20260902-005 를 현장에 냄. order.status 가 트윈에 도착\n * 19:21 잇는 배선을 산출물에 넣음\n * → 그 보고는 저널에 남았는데 조치에는 안 이어졌다. 다시 오지 않는다\n *\n * 같은 일이 트윈이 내려가 있는 사이, 커넥터를 고치는 사이, 프로세스가 죽었다 서는 사이에 난다.\n * 관측은 되풀이해 주지 않는다.\n *\n * ── 왜 저널을 읽나 ───────────────────────────────────────────────────────────\n * 저널에 `orderId` 가 **컬럼으로** 있고 인덱스도 있다(`ix_twin_event_4 (domain, instanceId, orderId)`\n * — 「이 오더가 어디까지 갔나」). 그래서 「우리가 낸 지시들의 마지막 보고」를 값싸게 물을 수 있다.\n * JSON 안을 뒤지지 않으므로 드라이버 다섯에서 같이 돈다.\n *\n * ── 왜 방향이 반대인가 ───────────────────────────────────────────────────────\n * 유입 쪽은 **보고에서 조치를 찾는다**(보고가 많고 조치가 적다 — 그래서 식별자를 모아 한 번 묻는다).\n * 이쪽은 **조치에서 보고를 찾는다.** 효과를 기다리는 조치는 몇 건이고, 그 몇 건의 식별자로 저널을\n * 물으면 끝난다. 저널 전체를 주사하지 않는다.\n */\nimport { OP_EVENT } from '@operato/ops-contract'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { twinWarn } from '../../engine/log.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\n\nimport type { CommandStore, StoredCommand } from './command-dispatcher.js'\nimport { matchEffects, type OrderObservation } from './effect-match.js'\n\nexport interface CatchUpResult {\n /** 효과를 기다리던 조치 수. */\n awaiting: number\n /** 이번에 효과를 적은 조치 수. */\n recorded: number\n /** 저널에 보고가 아직 없던 조치 수 — 저쪽이 아직 아무 말도 안 한 것이다. */\n silent: number\n}\n\n/**\n * 이 도메인에서 놓친 보고를 한 번 backfill 한다.\n *\n * **던지지 않는다.** 주기에서 부르고, 여기서 터지면 그 주기가 멈춘다. 그리고 이 일은 다음 바퀴에\n * 다시 해도 같은 결과가 나온다 — 급할 것이 없다.\n */\nexport async function catchUpEffects(domainId: string, store: CommandStore): Promise<CatchUpResult> {\n const none: CatchUpResult = { awaiting: 0, recorded: 0, silent: 0 }\n\n if (!store.awaitingEffect || !store.recordEffects) {\n twinWarn(\n '[actuation] 놓친 보고를 backfill 할 수 없다 — 저장소에 awaitingEffect 나 recordEffects 가 없다. ' +\n '못 듣고 있던 동안 온 보고는 영원히 이어지지 않는다.'\n )\n return none\n }\n\n try {\n const commands = await store.awaitingEffect(domainId)\n if (!commands.length) return none\n\n /* 트윈마다 묶는다 — 저널 질의가 트윈 단위로 인덱스를 탄다. */\n const byTwin = new Map<string, StoredCommand[]>()\n for (const c of commands) {\n if (!c.dispatchRef) continue\n const list = byTwin.get(c.instanceId) ?? []\n list.push(c)\n byTwin.set(c.instanceId, list)\n }\n\n let recorded = 0\n let silent = 0\n\n for (const [instanceId, list] of byTwin) {\n const observations = await latestOrderReports(\n domainId,\n instanceId,\n list.map(c => c.dispatchRef!)\n )\n\n const seen = new Set(observations.map(o => o.orderId))\n silent += list.filter(c => !seen.has(c.dispatchRef!)).length\n\n const { updates } = matchEffects({\n observations,\n byRef: new Map(\n list.map(c => [c.dispatchRef!, { id: c.id, dispatchRef: c.dispatchRef!, ...(c.effect ? { effect: c.effect } : {}) }])\n )\n })\n\n /*\n * **이미 적힌 것과 같으면 적지 않는다.** 주기마다 부르므로, 안 걸러 내면 같은 보고로 `seen` 이\n * 영원히 는다 — 「몇 번 보고됐나」가 「몇 번 backfill 했나」가 된다.\n */\n const changed = updates.filter(u => {\n const before = list.find(c => c.id === u.commandId)?.effect\n return !before || before.status !== u.status || before.at !== u.at\n })\n\n if (changed.length) {\n await store.recordEffects(domainId, changed)\n recorded += changed.length\n }\n }\n\n return { awaiting: commands.length, recorded, silent }\n } catch (e: any) {\n twinWarn(`[actuation] 놓친 보고를 backfill하다 실패했다 — ${String(e?.message ?? e).slice(0, 200)}`)\n return none\n }\n}\n\n/**\n * 이 트윈에서 그 오더들의 **마지막** 상태 보고를 읽는다.\n *\n * ── 왜 오더마다 따로 묻나 ──────────────────────────────────────────────────\n * 한 번에 묶어 물으면 그 오더들의 보고를 **전부** 받게 된다. 오더 하나가 수백 번 보고될 수 있고\n * (초안 → 나감 → 진행 → 완료, 그리고 수량이 바뀔 때마다), 그러면 상한 없이 저널을 읽는 자리가 된다.\n *\n * 그래서 오더마다 `take: 1` 로 최신 하나만 읽는다. 질의 수가 조치 수만큼 늘지만, **효과를 기다리는\n * 조치는 몇 건**이다(넘긴 것만, 그것도 식별자가 있는 것만). 몇 건에 몇 질의는 값이 아니고, 상한 없이\n * 원장을 읽는 것은 값이다.\n *\n * `revision` 내림차순이 「나중」의 정본이다 — 시각이 같은 보고가 여럿 올 수 있고(같은 밀리초에 둘이\n * 실제로 있었다), 그때 무엇이 나중인지는 원장의 순서가 답한다.\n */\nasync function latestOrderReports(\n domainId: string,\n instanceId: string,\n refs: readonly string[]\n): Promise<OrderObservation[]> {\n const out: OrderObservation[] = []\n const repo = getRepository(TwinEvent)\n\n for (const orderId of new Set(refs)) {\n const rows = await repo.find({\n where: { domain: { id: domainId }, instanceId, eventType: OP_EVENT.order, orderId },\n order: { revision: 'DESC' },\n take: 1\n } as any)\n\n const row: any = rows[0]\n const data = row?.payload?.data\n if (!data) continue\n\n /* 봉투에 적힌 시각을 쓴다 — 저장 시각이 아니다. 없으면 판정이 거른다. */\n out.push({ orderId, status: String(data.status ?? ''), at: row.payload?.eventTime })\n }\n return out\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"command-dispatcher.js","sourceRoot":"","sources":["../../../server/service/actuation/command-dispatcher.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **승인을 지난 커맨드만 어댑터에 넘긴다** — 게이트가 서는 자리.\n *\n * ── 왜 이 자리인가 (2026-08-31) ──────────────────────────────────────────\n * 설계가 「기본 = propose-only」를 정해 두었는데 코드에 그 규율이 없었다. 인티그레이션 레인이 어댑터에\n * 조치를 붙이려다 그것을 보고 멈췄다 — 게이트 없이 붙이면 트윈이 사람 승인 없이 현장에 작업지시를\n * 내린다. 작업지시는 취소해도 이미 만든 것이 남는다.\n *\n * ── 확인을 여기서 한 번만 한다 ───────────────────────────────────────────\n * 어댑터는 승인을 확인하지 않는다. 그 확인은 이 파일이 했고 **타입이 그것을 증명한다**\n * (`ApprovedCommand` 는 `approveCommand`·`asApproved` 로만 만들어진다). 어댑터가 또 확인하면 규칙이\n * 두 벌이 되고, 한쪽만 고쳐지는 날이 온다.\n *\n * ── 저장소에 닿는 자리를 밖으로 낸다 ─────────────────────────────────────\n * 훅에서와 같은 이유다(§`HookStore`). 시험이 **재기동을 넣을 수 있어야** 한다 — 승인이 프로세스 안에만\n * 살아 있으면 「승인된다」는 보이지만 「다시 세운 뒤에도 승인이 남아 있나」는 보이지 않는다.\n */\nimport {\n CMD,\n approveCommand,\n asApproved,\n commandFromSpec,\n nextCommandState,\n passThroughReversible,\n requiresApproval,\n type ApprovedCommand,\n type CommandApproval,\n type CommandOrigin,\n type CommandTypeSpec,\n type ISOTime,\n type TwinCommand\n} from '@operato/ops-contract'\n\n/**\n * 조치의 결과 — 어댑터가 돌려준다.\n *\n * 계약에도 같은 이름의 타입이 있는데(`contract.ts:3074`) **다른 것**이다. 그쪽은 커널 자극의 답이고\n * 이쪽은 현장 지시의 답이다. 자극과 조치를 가른 것과 같은 갈림이다(§`propose` 의 거절).\n */\nimport type { RetryOutlook } from './retry-plan.js'\n\nexport interface CommandAck {\n ok: boolean\n /** 저쪽 문서 · 작업 식별자. 되돌릴 때 무엇을 되돌릴지 말하려면 이것이 있어야 한다. */\n ref?: string\n /**\n * **받아들였으나 저쪽이 덧붙인 말** — 실패가 아니고, 「끝났다」도 아니다.\n *\n * ── 왜 필요한가 (2026-09-02 실측) ──────────────────────────────────────\n * MES 에 조치 둘을 넘겼더니 트윈 쪽 두 행이 구별되지 않았다.\n *\n * WO-20260902-004 지시서 붙음 acked · ref 있음 · error 없음\n * WO-20260902-005 지시서 못 붙음 acked · ref 있음 · error 없음 ← 같다\n *\n * 005 는 사람이 MES 에서 지시서를 골라야 현장에 간다. 커넥터는 그 사실을 받았는데 **적을 칸이\n * 없어서 로그로만** 남겼다. `error` 에 적으면 실패로 읽혀 커맨드가 `failed` 로 앉는다.\n *\n * **해석하지 않고 그대로 적는다.** 무엇이 남았는지는 저쪽 어휘이고, 커널이 그 방언을 알면 보편\n * 계약이 깨진다 — 효과의 상태를 그대로 적는 것과 같은 규율(§`effect-match.ts`).\n */\n note?: string\n /**\n * 그 말 중 **다음에 할 일** 한 줄 — 원인은 `note` 다.\n *\n * 왜 둘인가: 읽는 사람은 「그래서 내가 뭘 해야 하나」를 먼저 찾는다(UX 레인). 한 문장으로 주면\n * 화면이 자르게 되고, **자르는 규칙이 화면마다 생긴다.** 어댑터는 이미 둘로 알고 있으니 붙이지\n * 않고 따로 준다.\n */\n noteNext?: string\n error?: string\n /**\n * **다시 해서 될 일인가** — 실패에만 답한다(§`RetryOutlook`).\n *\n * again 그대로 다시 해 볼 만하다 못 닿았거나 저쪽이 잠깐 흔들렸다\n * after-fix 사람이 고친 뒤 그대로 나간다 설정 문제\n * never 이 조치로는 영원히 안 된다 지시 내용이 틀렸다\n *\n * **문장에 담지 않는 이유**: 일꾼이 그것을 쓰려면 문장을 파싱해야 하고, 번역되면 깨진다.\n * `code` 를 따로 둔 것과 같은 이유다.\n *\n * 비어 있으면 **말하지 않은 것**이고 `never` 가 아니다. 그때 일꾼은 집지 않고 목록에 남긴다 —\n * 모르는 것을 `never` 로 접으면 고칠 수 있는 것을 사람이 포기한다.\n *\n * 타입을 좁게 둔다 — **어댑터가 답하는 값**이고, 여기서 넓히면 커넥터가 모르는 낱말을 보내도\n * 타입이 통과시킨다. 표에서 읽는 쪽은 `string` 이다(DB 는 무엇이든 들 수 있으므로\n * `isRetryOutlook` 으로 걸러 읽는다).\n */\n retry?: RetryOutlook\n}\n\n/**\n * 저장소에서 되세운 커맨드 — 계약의 커맨드에 **호스트가 아는 한 칸**이 붙는다.\n *\n * `approvalRef` 를 계약에 넣지 않는다. 계약은 운영 도메인의 어휘(EPCIS·ISA-95)를 들고, 「승인 기록이\n * 어느 업무 시스템의 몇 번 문서인가」는 이 저장소의 배선이다. 계약에 넣으면 커널을 쓰는 모든 소비처가\n * 우리 업무 모듈의 존재를 알게 된다.\n */\nexport type StoredCommand = TwinCommand & {\n approvalRef?: string\n proposedBy?: string\n /** 규칙이 낸 것이면 그 근거가 된 관측(`Attention.id`) — 중복을 막는 열쇠다. */\n causeRef?: string\n ruleRef?: string\n /**\n * 저쪽에서 실제로 무엇이 되었나 — **상태가 아니라 별개 사실이다.**\n *\n * `acked` 는 「저쪽이 답했다」이고 종착 상태다. 여기에 「현장에 나갔다」를 상태로 이어 붙이면\n * `acked` 가 종착이 아니게 되고, 되돌아오는 관측이 없는 커넥터의 조치가 **영원히 끝나지 않은\n * 것으로 보인다.** 그런 커넥터가 정상이다 — 관측을 되돌려 주는 능력은 커넥터의 성질이지 조치의\n * 수명이 아니다.\n *\n * `status` 는 **해석하지 않고 그대로** 적는다(§`effect-match.ts`). 오더 상태 어휘는 도메인\n * 소유이고, 어느 값이 「현장에 나갔다」인지는 현장마다 다르다.\n *\n * 이 칸이 비어 있는 것은 「효과가 없었다」가 아니라 **「모른다」**다. 넘기다 닿지 못한 조치는\n * 식별자를 못 받으므로 이을 길이 아예 없다.\n *\n * ── 계약에도 같은 칸을 두었다 ──────────────────────────────────────────\n * `ops-contract` 의 `TwinCommand.effect` 가 이것이다. 다만 이 저장소는 그 패키지를 레지스트리에서\n * 받으므로(`^0.9.0`), 다음 버전이 올라올 때까지 여기에 산다 — 위의 `approvalRef`·`causeRef` 와 같다.\n */\n effect?: { status: string; at: string; seen: number }\n /** 받아들였으나 저쪽이 덧붙인 말 — 해석하지 않고 그대로(§`CommandAck.note`). */\n dispatchNote?: string\n /** 그 말 중 다음에 할 일 한 줄 — 원인은 위 칸이다(§`CommandAck.noteNext`). */\n dispatchNoteNext?: string\n /**\n * 다시 해서 될 일인가 — 어댑터가 실패에 답한 갈래(§`RetryOutlook`). 비면 말하지 않은 것이다.\n *\n * **여기는 `string` 이다** — 표에서 읽은 값이고 DB 는 무엇이든 들 수 있다. 좁은 타입으로 두면\n * 낡은 행이나 손으로 고친 값이 타입상 참인 척한다. 판정하는 쪽이 `isRetryOutlook` 으로 걸러\n * 읽고, 모르는 낱말은 「말하지 않은 것」과 같이 다룬다.\n */\n retry?: string\n /** 몇 번 넘겨 봤나. 첫 시도가 1. */\n attempts?: number\n /** 다음에 넘겨 볼 시각(ISO) — 물러섬을 표에 든다. 메모리에 두면 재기동에 사라진다. */\n nextAttemptAt?: string\n}\n\n/** 커맨드가 사는 자리 — 재기동을 넘어야 한다. */\nexport interface CommandStore {\n load(domainId: string, id: string): Promise<StoredCommand | null>\n save(domainId: string, command: StoredCommand): Promise<void>\n /** 새로 낸다 — `save` 는 있는 행만 고친다. 낼 길이 없으면 대기 줄은 영원히 비어 있다. */\n create(domainId: string, command: StoredCommand): Promise<StoredCommand>\n /**\n * 결정을 기다리는 것들의 근거 — 규칙 판정이 중복을 피하려면 이것이 있어야 한다.\n *\n * 규칙을 쓰지 않는 소비처도 있으므로 없어도 된다. 없으면 판정에 빈 집합이 가고, **그때는 중복이 난다**\n * — 그 사실을 부르는 쪽이 알아야 하므로 조용히 빈 집합을 만들어 주지 않는다.\n */\n openCauses?(domainId: string, instanceId: string): Promise<ReadonlySet<string>>\n /**\n * 넘긴 지시의 식별자로 조치를 찾는다 — **폐루프가 이 하나에 걸린다.**\n *\n * 없으면 나간 지시와 돌아온 관측을 이을 수 없다. 그 사실을 부르는 쪽이 알아야 하므로 조용히 빈\n * 지도를 만들어 주지 않는다(§`openCauses` 와 같은 규율).\n */\n byDispatchRef?(domainId: string, refs: readonly string[]): Promise<ReadonlyMap<string, StoredCommand>>\n /**\n * 효과를 기다리는 조치들 — **backfill 하는 길이 이것에 걸린다.**\n *\n * 유입 자리에서 잇는 것만으로는 부족하다. 우리가 내려가 있던 동안, 또는 배선이 아직 없던 동안 온\n * 보고는 **영원히 사라진다.** 실제로 그렇게 됐다 — 배선을 만든 날 그 앞에 온 보고 하나를 못 이었다.\n *\n * 넘긴 식별자가 있는 것만 낸다. 없는 것은 이을 열쇠가 없다.\n */\n awaitingEffect?(domainId: string): Promise<readonly StoredCommand[]>\n /**\n * **넘길 차례를 기다리는 것들** — 승인만 되고 안 나간 것과, 실패해서 다시 해 볼 것.\n *\n * 일꾼이 이것으로 표를 읽는다. 없으면 일꾼이 아무것도 하지 않는다(옛 거동) — 저장소가\n * 이 능력을 갖추지 않은 구현도 그대로 돌아야 한다.\n */\n pendingDispatch?(domainId: string): Promise<readonly StoredCommand[]>\n /**\n * 효과만 적는다 — **상태를 건드리지 않는다.**\n *\n * `save` 로 적으면 상태·승인·오류까지 함께 쓰이고, 유입 경로가 조치의 수명에 손을 대게 된다.\n * 관측이 조치를 되돌리거나 다시 넘기게 하는 길은 두지 않는다.\n */\n recordEffects?(domainId: string, updates: readonly { commandId: string; status: string; at: string; seen: number }[]): Promise<void>\n /**\n * 근거마다 마지막으로 조치를 낸 시각 — 승인 직후 같은 것이 다시 올라오는 것을 막는다.\n *\n * 없어도 된다. 없으면 판정이 결정 대기만 보고, **승인하는 순간 같은 조건이 다시 올라온다.**\n */\n lastProposedAt?(domainId: string, instanceId: string): Promise<ReadonlyMap<string, number>>\n}\n\n/**\n * 조치를 낸다 — **대기 줄에 무언가가 들어오는 유일한 자리.**\n *\n * ── 왜 뒤늦게 생겼나 (2026-08-31) ────────────────────────────────────────\n * 게이트와 승인 화면을 만들고 나서 물었다 — 「무엇을 승인하는 화면인가」. 커맨드 행을 **적는 코드가\n * 하나도 없었다.** 자리는 있고 길이 없는 그 부류다.\n *\n * 종류를 선언된 것으로 제한한다. 모르는 종류를 받으면 되돌릴 수 있는지도 모르는 채로 게이트를 지나게\n * 되고, 그러면 게이트가 시늉이 된다.\n */\nexport async function propose(\n store: CommandStore,\n domainId: string,\n specs: readonly CommandTypeSpec[],\n seed: {\n id: string\n instanceId: string\n type: string\n origin: CommandOrigin\n proposedAt: ISOTime\n payload?: Record<string, unknown>\n /** 낸 사람. 사람이 냈으면 반드시 있어야 한다 — 없으면 자기 승인을 막을 수 없다. */\n proposedBy?: string\n /** 규칙이 낸 것이면 그 근거. 이 값이 없으면 다음 주기가 같은 것을 또 올린다. */\n causeRef?: string\n ruleRef?: string\n }\n): Promise<StoredCommand> {\n const spec = specs.find(s => s.type === seed.type)\n if (!spec) {\n /*\n * **자극과 조치는 다른 어휘다.**\n *\n * 계약에 두 벌이 있다. `CMD` 는 커널 안에서 도는 자극이고(`resource.repair` = 모델에서 수리된 것으로\n * 둔다), 여기서 다루는 것은 현장에 나가는 지시다(`production.order` = 실제로 만든다). 주의 신호가\n * 권하는 것(`Attention.suggestedAction.command`)은 앞엣것이다.\n *\n * 둘이 섞이는 길이 실재한다 — 주의를 읽어 조치를 내는 코드를 쓰다 보면 그 값을 그대로 넘기게 된다.\n * 막히기는 하는데 **왜 막히는지 말하지 않으면** 부르는 쪽은 선언이 빠진 줄로 읽고 선언을 추가한다.\n * 그 순간 모델 안의 자극이 현장 지시가 된다.\n */\n if ((Object.values(CMD) as string[]).includes(seed.type)) {\n throw new Error(\n `그것은 커널 안에서 도는 자극이다 (${seed.type}) — 현장에 나가는 지시가 아니다. ` +\n '모델 상태를 바꾸려면 커널에 자극을 넣고, 현장에 무언가를 시키려면 그 지시를 조치 종류로 선언한다'\n )\n }\n\n throw new Error(\n `선언되지 않은 조치 종류다 (${seed.type}) — 선언된 것: ${specs.map(s => s.type).join(', ') || '(없음)'}. ` +\n '되돌릴 수 있는지를 모르는 채로 승인 게이트를 지나게 할 수 없다'\n )\n }\n\n /* 사람이 낸 것이면 누구인지 있어야 한다 — 되돌릴 수 없는 조치는 누가 냈는지가 기록에 남는다. */\n if (seed.origin === 'operator' && !seed.proposedBy?.trim()) {\n throw new Error('사람이 내는 조치는 낸 사람이 있어야 한다 — 누가 냈는지 남지 않는 조치는 받지 않는다')\n }\n\n /* 요구하는 칸이 없으면 계약이 던진다 — 빈 지시가 대기 줄에 쌓이면 승인하는 사람이 판단할 근거가 없다. */\n return await store.create(domainId, {\n ...commandFromSpec(spec, seed),\n ...(seed.proposedBy?.trim() ? { proposedBy: seed.proposedBy.trim() } : {}),\n ...(seed.causeRef ? { causeRef: seed.causeRef } : {}),\n ...(seed.ruleRef ? { ruleRef: seed.ruleRef } : {})\n })\n}\n\n/**\n * 결재선에 올라간 자리를 커맨드에 적는다.\n *\n * 이것이 적힌 뒤로 직접 결정은 막힌다. 그래서 **올리기와 적기가 한 트랜잭션에 있어야 한다** — 올렸는데\n * 적지 못하면 결재선에는 있고 커맨드는 직접 결정도 되는 상태가 남는다.\n */\nexport async function linkApproval(\n store: CommandStore,\n domainId: string,\n id: string,\n approvalRef: string\n): Promise<StoredCommand> {\n const command = await mustLoad(store, domainId, id)\n const next: StoredCommand = { ...command, approvalRef }\n await store.save(domainId, next)\n return next\n}\n\n/**\n * 승인한다 — **직접 문**.\n *\n * 승인할 수 없는 상태면 계약이 던진다 — 여기서 답으로 돌려주지 않는 이유는, 실패를 답으로 주면 부르는\n * 쪽이 그것을 무시할 수 있기 때문이다.\n */\nexport async function approve(\n store: CommandStore,\n domainId: string,\n id: string,\n approval: CommandApproval\n): Promise<StoredCommand> {\n const command = await mustLoad(store, domainId, id)\n assertDirectDoorOpen(command)\n\n const approved = approveCommand(command, approval)\n await store.save(domainId, { ...approved, ...carried(command) })\n return approved\n}\n\n/** 거절한다 — 여기서 끝난다. 되살아나지 않는다. */\nexport async function reject(\n store: CommandStore,\n domainId: string,\n id: string,\n approval: CommandApproval\n): Promise<StoredCommand> {\n const command = await mustLoad(store, domainId, id)\n assertDirectDoorOpen(command)\n return await settle(store, domainId, command, false, approval)\n}\n\n/**\n * 결재선의 판정을 커맨드에 옮긴다 — **결재선 문**.\n *\n * 부르는 쪽은 앱의 브리지다. 올라간 자리를 함께 넘기게 해서, 다른 결재 건의 판정이 이 커맨드에 적히는\n * 길을 막는다.\n *\n * 거절을 여기서 **끝으로** 다룬다. 업무 목록에서 「거절」은 되돌려 보내 고쳐 올리라는 뜻이지만, 조치에는\n * 고칠 것이 없다 — 지시 내용은 제안될 때 이미 정해져 있다. 그러니 되돌려 받을 것이 없고, 게이트를 닫는\n * 것이 맞다. 커맨드가 닫힌 뒤에 결재선에서 누가 승인해도 계약이 `rejected → approved` 를 허용하지 않는다.\n */\nexport async function settleByApproval(\n store: CommandStore,\n domainId: string,\n id: string,\n decision: { approvalRef: string; approved: boolean; by: string; at: string; note?: string }\n): Promise<StoredCommand> {\n const command = await mustLoad(store, domainId, id)\n\n if (!command.approvalRef) {\n throw new Error(`결재선에 올라가지 않은 커맨드다 (${id}) — 이 판정이 어디서 왔는지 확인할 수 없다`)\n }\n if (command.approvalRef !== decision.approvalRef) {\n throw new Error(\n `다른 결재 건의 판정이다 (커맨드 ${id} 는 ${command.approvalRef} 를 기다리는데 판정은 ${decision.approvalRef} 에서 왔다)`\n )\n }\n\n const approval: CommandApproval = {\n by: decision.by,\n at: decision.at,\n ...(decision.note ? { note: decision.note } : {})\n }\n\n if (!decision.approved) return await settle(store, domainId, command, false, approval)\n\n const approved = approveCommand(command, approval)\n await store.save(domainId, { ...approved, ...carried(command) })\n return approved\n}\n\nasync function mustLoad(store: CommandStore, domainId: string, id: string): Promise<StoredCommand> {\n const command = await store.load(domainId, id)\n if (!command) throw new Error(`모르는 커맨드다 (${id})`)\n return command\n}\n\n/** 결재선에 올라간 커맨드는 직접 결정하지 않는다 — 문은 한 벌이다. */\nfunction assertDirectDoorOpen(command: StoredCommand): void {\n if (!command.approvalRef) return\n throw new Error(\n `이 조치는 결재선에 올라가 있다 (${command.approvalRef}) — 승인·거절은 업무 목록에서 한다. ` +\n '여기서 결정하면 결재선에는 대기로 남고 조치는 나간 상태가 된다'\n )\n}\n\n/** 승인 기록만 바뀌고 나머지는 그대로 간다 — 하나라도 흘리면 그 사실이 사라진다. */\nfunction carried(command: StoredCommand): Partial<StoredCommand> {\n return {\n ...(command.approvalRef ? { approvalRef: command.approvalRef } : {}),\n ...(command.proposedBy ? { proposedBy: command.proposedBy } : {}),\n ...(command.causeRef ? { causeRef: command.causeRef } : {}),\n ...(command.ruleRef ? { ruleRef: command.ruleRef } : {})\n }\n}\n\nasync function settle(\n store: CommandStore,\n domainId: string,\n command: StoredCommand,\n approved: boolean,\n approval: CommandApproval\n): Promise<StoredCommand> {\n const to = nextCommandState(command.state, approved ? 'approve' : 'reject')\n if (!to) throw new Error(`${command.state} 상태의 커맨드는 ${approved ? '승인' : '거절'}할 수 없다 (${command.id})`)\n\n const next: StoredCommand = { ...command, state: to, approval, ...carried(command) }\n await store.save(domainId, next)\n return next\n}\n\n/**\n * 어댑터에 넘긴다 — **승인을 지나지 않은 것은 여기서 막힌다.**\n *\n * `actuate` 는 `ApprovedCommand` 만 받으므로, 승인 없이 이 함수를 지나가는 길이 없다. 저장소에서\n * 되세운 커맨드는 타입이 지워진 채로 오므로 `asApproved` 로 다시 확인한다 — 확인 없이 표식을 붙이면\n * **재기동이 게이트를 여는 길**이 된다.\n *\n * 되돌릴 수 있는 커맨드는 사람 없이 지난다(시뮬·what-if). 그 판단도 계약이 한다.\n */\nexport async function dispatch(\n store: CommandStore,\n domainId: string,\n id: string,\n actuate: (command: ApprovedCommand) => Promise<CommandAck>\n): Promise<StoredCommand> {\n const command = await mustLoad(store, domainId, id)\n const ref = carried(command)\n\n /*\n * 되돌릴 수 있는 것은 제안 상태에서 바로 지난다. 되돌릴 수 없는 것은 승인 기록이 있어야 하고,\n * 없으면 여기서 던진다 — 조용히 건너뛰면 그 커맨드가 승인 없이 나간 것이 된다.\n */\n const approved: ApprovedCommand =\n command.state === 'proposed' && !requiresApproval(command) ? passThroughReversible(command) : asApproved(command)\n\n const to = nextCommandState(approved.state, 'dispatch')\n if (!to) throw new Error(`${approved.state} 상태의 커맨드는 넘길 수 없다 (${id})`)\n\n /*\n * **넘기기 전에 「넘기는 중」을 먼저 적는다.** 넘기고 나서 적으면, 그 사이에 프로세스가 죽었을 때\n * 저쪽은 받았는데 우리는 안 보낸 줄 알고 다시 보낸다. 현장이 두 번 움직인다.\n *\n * ── 지난 시도의 자국을 지운다 (2026-09-03, 인티그레이션 레인이 걸어서 찾음) ──\n * `failed → dispatch` 를 열어 둔 것의 짝이다. 다시 넘겨 성공했는데 **지난 실패의 `error` 가 그대로\n * 남아** 한 행이 두 가지를 동시에 말했다.\n *\n * state acked 받았다\n * error 「그쪽에 비밀값이 설정되지 않았습니다」 안 받는다 ← 지난 시도의 것\n *\n * 화면이 그 행을 그리면 어느 쪽을 믿어야 하는지 알 수 없다. `dispatchNote` 도 같은 부류였다 —\n * 새 응답에 덧붙일 말이 없으면 옛 말이 남는다.\n *\n * 그래서 「넘기는 중」이 **이번 시도의 빈 종이**여야 한다. 셋 다 이번 응답이 채우는 값이다.\n *\n * `dispatchRef` 를 지워도 효과가 고아가 되지 않는다 — 식별자는 성공할 때만 붙고(§아래), 성공은\n * `acked` 로 끝난다(종착). 그러니 다시 넘길 수 있는 커맨드에는 식별자가 없고 효과도 없다.\n */\n const dispatching: StoredCommand = {\n ...approved,\n state: to,\n ...ref,\n dispatchRef: undefined,\n dispatchNote: undefined,\n error: undefined\n }\n await store.save(domainId, dispatching)\n\n let ack: CommandAck\n try {\n ack = await actuate(approved)\n } catch (e: any) {\n /* 던진 실패는 어댑터가 갈래를 말할 기회가 없었다 — `retry` 를 비워 두고 일꾼이 「모름」으로 읽는다. */\n const failed: StoredCommand = { ...dispatching, state: 'failed', error: String(e?.message ?? e) }\n await store.save(domainId, failed)\n return failed\n }\n\n const settled: StoredCommand = ack?.ok\n ? {\n ...dispatching,\n state: 'acked',\n dispatchRef: ack.ref,\n ...(ack.note ? { dispatchNote: ack.note } : {}),\n ...(ack.noteNext ? { dispatchNoteNext: ack.noteNext } : {})\n }\n : {\n ...dispatching,\n state: 'failed',\n error: ack?.error ?? '어댑터가 이유를 말하지 않았다',\n /* 다시 될 일인지 어댑터가 답했으면 적는다 — 일꾼이 다음 주기에 이 값을 본다. */\n ...(ack?.retry ? { retry: ack.retry } : {})\n }\n await store.save(domainId, settled)\n return settled\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"command-store.js","sourceRoot":"","sources":["../../../server/service/actuation/command-store.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 커맨드가 저장소에 닿는 두 자리 — 찾는 것과 적는 것.\n *\n * 디스패처가 엔티티를 직접 가져오면 그 파일을 부르는 시험이 TypeORM 데코레이터를 지나게 되고, 시험이\n * 서지 않는다. 그러면 **게이트의 배선을 아무 시험도 걷지 않게 된다** — 훅에서 이미 겪은 부류다\n * (§`hook-store.ts`).\n */\nimport { In, IsNull, Not } from 'typeorm'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport type { TwinCommand } from '@operato/ops-contract'\n\nimport { hydratedDate } from '../../engine/read-time.js'\n\nimport { TwinCommandRecord } from './twin-command.js'\nimport type { CommandStore, StoredCommand } from './command-dispatcher.js'\n\n/**\n * 행 → 커맨드. 상태 어휘는 계약이 정한 문자열 그대로다.\n *\n * `createdAt` 은 드라이버가 준 값이라 **직접 날짜로 만들지 않는다** — 드라이버마다 모양이 다르고,\n * 문자열로 오는 드라이버에서는 시간대가 없는 값이 된다. `hydratedDate` 가 그 컬럼의 정의를 보고 푼다.\n */\nfunction toCommand(repo: any, row: TwinCommandRecord): StoredCommand {\n return {\n ...(row.approvalRef ? { approvalRef: row.approvalRef } : {}),\n ...(row.proposedBy ? { proposedBy: row.proposedBy } : {}),\n ...(row.causeRef ? { causeRef: row.causeRef } : {}),\n ...(row.ruleRef ? { ruleRef: row.ruleRef } : {}),\n id: row.id,\n instanceId: row.instanceId,\n type: row.type,\n ...(row.payload ? { payload: row.payload } : {}),\n origin: row.origin as TwinCommand['origin'],\n proposedAt: hydratedDate(repo, 'createdAt', row.createdAt)?.toISOString() ?? '',\n /* 없는 것을 거짓으로 채우지 않는다 — 비어 있음이 「되돌릴 수 없다」의 뜻이다. */\n ...(row.reversible === null || row.reversible === undefined ? {} : { reversible: row.reversible }),\n state: row.state as TwinCommand['state'],\n ...(row.decidedBy ? { approval: { by: row.decidedBy, at: row.decidedAt ?? '', ...(row.decisionNote ? { note: row.decisionNote } : {}) } } : {}),\n ...(row.dispatchRef ? { dispatchRef: row.dispatchRef } : {}),\n ...(row.dispatchNote ? { dispatchNote: row.dispatchNote } : {}),\n /* 셋이 다 있어야 효과다 — 하나라도 비면 적지 않는다(빈 값을 사실로 만들지 않는다). */\n ...(row.effectStatus && row.effectAt\n ? { effect: { status: row.effectStatus, at: row.effectAt, seen: row.effectSeen ?? 1 } }\n : {}),\n ...(row.error ? { error: row.error } : {})\n }\n}\n\n/**\n * 실제 저장소를 쓰는 구현 — 리졸버가 이것을 넣는다.\n *\n * **트랜잭션을 받는다.** `getRepository(target)` 를 인자 없이 부르면 요청의 트랜잭션에 붙지 않고\n * 별도로 커밋된다(`shell/initializers/database.ts:47`). 그러면 「올리고 적기」가 한 덩어리가 아니게 되고,\n * 뒤가 실패했을 때 앞이 남는다 — 결재선에는 없는데 커맨드는 적혀 직접 결정이 열린 상태다.\n *\n * 콜백에서 부를 때도 넘긴다 — 업무 목록의 판정과 우리 커맨드가 같이 커밋되거나 같이 물러나야 한다.\n */\nexport function databaseCommandStore(tx?: any): CommandStore {\n return {\n load: async (domainId: string, id: string) => {\n const repo = await getRepository(TwinCommandRecord, tx)\n const row = await repo.findOne({ where: { domain: { id: domainId }, id } }).catch(() => null)\n return row ? toCommand(repo, row) : null\n },\n\n /**\n * 결정을 기다리는 것들의 **근거** — 규칙이 같은 조건을 두 번 올리지 않게.\n *\n * 저장소에서 읽는다. 프로세스 기억에 두면 재기동이 곧 중복이 된다 — 다시 선 트윈이 같은 조건을 처음\n * 보는 것으로 읽고 대기 줄에 같은 지시를 또 올린다.\n *\n * `proposed` 만 본다 — 결정을 기다리는 것은 시간과 무관하게 막는다. 결정이 끝난 것을 언제 다시 낼지는\n * 이것이 아니라 **기다리는 시간**이 정한다(§`lastProposedAt`).\n */\n openCauses: async (domainId: string, instanceId: string) => {\n const repo = await getRepository(TwinCommandRecord, tx)\n const rows = await repo\n .find({ where: { domain: { id: domainId }, instanceId, state: 'proposed' }, select: ['causeRef'] as any })\n .catch(() => [] as TwinCommandRecord[])\n\n return new Set(rows.map(r => r.causeRef).filter((v): v is string => !!v))\n },\n\n /**\n * 근거마다 **마지막으로 조치를 낸 시각** — 결정이 끝난 것까지 본다.\n *\n * `openCauses` 만으로는 부족하다. 승인하는 순간 그 커맨드가 대기에서 빠지고 조건은 그대로 서 있어서\n * 다음 바퀴가 같은 것을 또 낸다. 실제로 그렇게 됐다 — 승인 한 번에 대기 줄이 다시 찼다.\n */\n lastProposedAt: async (domainId: string, instanceId: string) => {\n const repo = await getRepository(TwinCommandRecord, tx)\n const rows = await repo\n .find({ where: { domain: { id: domainId }, instanceId }, select: ['causeRef', 'createdAt'] as any })\n .catch(() => [] as TwinCommandRecord[])\n\n const out = new Map<string, number>()\n for (const row of rows) {\n if (!row.causeRef) continue\n /* 드라이버가 준 값을 직접 날짜로 만들지 않는다 — 컬럼 정의를 보고 푼다. */\n const at = hydratedDate(repo, 'createdAt', row.createdAt)?.getTime()\n if (at === undefined || !Number.isFinite(at)) continue\n const prev = out.get(row.causeRef)\n if (prev === undefined || at > prev) out.set(row.causeRef, at)\n }\n return out\n },\n\n /**\n * 넘긴 지시의 식별자로 찾는다 — 한 번에 묶어서 묻는다.\n *\n * 유입은 초당 수천 건이 지나는 자리다. 관측 하나마다 질의를 붙이면 그 경로가 느려지고, 느려지면\n * 훅이 밀린다. 그래서 묶음 하나에 질의 하나다.\n */\n byDispatchRef: async (domainId: string, refs: readonly string[]) => {\n const out = new Map<string, StoredCommand>()\n if (!refs.length) return out\n\n const repo = await getRepository(TwinCommandRecord, tx)\n const rows = await repo\n .find({ where: { domain: { id: domainId }, dispatchRef: In([...new Set(refs)]) } })\n .catch(() => [] as TwinCommandRecord[])\n\n for (const row of rows) {\n if (row.dispatchRef) out.set(row.dispatchRef, toCommand(repo, row))\n }\n return out\n },\n\n /**\n * 효과를 기다리는 조치들 — 넘긴 식별자가 있는 것만.\n *\n * 효과가 이미 적힌 것도 낸다. 상태는 여러 번 바뀌고(초안 → 나감 → 진행 중), 마지막 것이 남아야\n * 화면이 「지금 저쪽에서 무엇인가」에 답할 수 있다.\n *\n * 결정이 안 끝난 것과 실패한 것은 빼지 않는다 — 실패로 적혔는데 저쪽은 받았을 수 있고, 그때\n * 식별자가 있으면 그 사실이 이 길로 드러난다.\n */\n awaitingEffect: async (domainId: string) => {\n const repo = await getRepository(TwinCommandRecord, tx)\n const rows = await repo\n .find({ where: { domain: { id: domainId }, dispatchRef: Not(IsNull()) } })\n .catch(() => [] as TwinCommandRecord[])\n return rows.map(row => toCommand(repo, row))\n },\n\n /*\n * 넘길 차례를 기다리는 것들 — **두 상태만** 본다.\n *\n * `approved` 는 승인 직후 발신이 놓친 것(프로세스가 그 사이에 죽었다), `failed` 는 다시 해\n * 볼 것이다. 그 밖의 상태는 일꾼의 일이 아니다 — `proposed` 는 승인을 기다리고 `acked` 는\n * 종착이다.\n *\n * 물러섬은 여기서 걸러내지 않는다. 걸러내려면 시각 비교를 SQL 방언으로 써야 하고(이 저장소는\n * 다섯 드라이버를 지난다), 판정은 이미 순수 함수가 한다(§`retryDecisionOf`). 대기 줄이 큰\n * 현장이 오면 그때 인덱스와 함께 다시 본다.\n */\n pendingDispatch: async (domainId: string) => {\n const repo = await getRepository(TwinCommandRecord, tx)\n const rows = await repo\n .find({ where: { domain: { id: domainId }, state: In(['approved', 'failed']) } })\n .catch(() => [] as TwinCommandRecord[])\n return rows.map(row => toCommand(repo, row))\n },\n\n /*\n * 효과만 적는다 — 상태·승인·오류는 건드리지 않는다. 관측이 조치의 수명에 손을 대는 길은 두지\n * 않는다. 그리고 도메인으로 좁힌다 — 식별자는 시스템마다 겹칠 수 있다.\n */\n recordEffects: async (domainId: string, updates) => {\n const repo = await getRepository(TwinCommandRecord, tx)\n for (const u of updates) {\n await repo.update(\n { id: u.commandId, domain: { id: domainId } },\n { effectStatus: u.status, effectAt: u.at, effectSeen: u.seen } as any\n )\n }\n },\n\n /*\n * **근거는 여기서 안 적는다.** `causeRef`·`ruleRef` 는 낼 때 정해지고 바뀌지 않는다. 이 목록에 넣으면\n * 상태가 바뀔 때마다 다시 쓰이고, 부르는 쪽이 그 칸을 빼먹은 날 **근거가 지워진다** — 그러면 중복\n * 판정이 그 커맨드를 못 보고 같은 지시를 또 올린다.\n */\n save: async (domainId: string, command: StoredCommand) => {\n await getRepository(TwinCommandRecord, tx).update(\n { id: command.id, domain: { id: domainId } },\n {\n state: command.state,\n approvalRef: command.approvalRef ?? null,\n decidedBy: command.approval?.by ?? null,\n decidedAt: command.approval?.at ?? null,\n decisionNote: command.approval?.note ?? null,\n dispatchRef: command.dispatchRef ?? null,\n dispatchNote: command.dispatchNote ?? null,\n dispatchNoteNext: command.dispatchNoteNext ?? null,\n error: command.error ?? null,\n /* 일꾼이 쓰는 셋 — 없으면 물러섬이 표에 안 남고 재기동마다 즉시 재시도가 된다. */\n retry: command.retry ?? null,\n attempts: command.attempts ?? null,\n nextAttemptAt: command.nextAttemptAt ?? null\n } as any\n )\n },\n\n /*\n * 낼 때는 커맨드가 든 것을 그대로 적는다.\n *\n * `proposedAt` 은 옮기지 않는다 — 그 값은 `createdAt` 에서 읽으므로(§`toCommand`), 여기서 또 적으면\n * 같은 사실이 두 칸에 살고 하나만 고쳐지는 날이 온다. 그리고 되돌려 줄 때는 **적힌 행을 다시 읽는다**\n * — 낸 값을 그대로 답하면 드라이버가 실제로 무엇을 적었는지 모른 채로 사람에게 보여 준다.\n */\n create: async (domainId: string, command: StoredCommand) => {\n const repo = await getRepository(TwinCommandRecord, tx)\n const saved = await repo.save({\n id: command.id,\n domain: { id: domainId },\n instanceId: command.instanceId,\n type: command.type,\n payload: command.payload ?? null,\n origin: command.origin,\n proposedBy: command.proposedBy ?? null,\n causeRef: command.causeRef ?? null,\n ruleRef: command.ruleRef ?? null,\n reversible: command.reversible ?? null,\n state: command.state,\n approvalRef: command.approvalRef ?? null\n } as any)\n\n const row = await repo.findOne({ where: { domain: { id: domainId }, id: saved.id } })\n if (!row) throw new Error(`커맨드를 적었는데 다시 읽을 수 없다 (${saved.id})`)\n return toCommand(repo, row)\n }\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"dispatch-after-approval.js","sourceRoot":"","sources":["../../../server/service/actuation/dispatch-after-approval.ts"],"names":[],"mappings":";;AA4CA,sDA0BC;AAtED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,gDAA8C;AAC9C,yDAAyD;AACzD,mEAAkD;AAClD,4EAAkE;AAElE;;;;;;;;;GASG;AACH,SAAgB,qBAAqB,CAAC,QAAgB,EAAE,SAAiB;IACvE;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE;QAChB,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,IAAA,gCAAQ,EAAC,IAAA,uCAAoB,GAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,CACpF,IAAA,qCAAc,EAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,CACtD,CAAA;gBACD,IAAI,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;oBAC/B,qDAAqD;oBACrD,IAAA,iBAAQ,EAAC,uBAAuB,SAAS,eAAe,OAAO,CAAC,KAAK,IAAI,SAAS,EAAE,CAAC,CAAA;gBACvF,CAAC;YACH,CAAC;YAAC,OAAO,CAAM,EAAE,CAAC;gBAChB;;;mBAGG;gBACH,IAAA,iBAAQ,EAAC,uBAAuB,SAAS,eAAe,MAAM,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,CAAA;YACpF,CAAC;QACH,CAAC,CAAC,EAAE,CAAA;IACN,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["/*\n * **승인 다음에 넘긴다** — 승인 도장과 실제 발신 사이의 한 걸음.\n *\n * ── 무엇이 없었나 (2026-09-04) ──────────────────────────────────────────────\n * 사람이 결재함에서 승인하면 커맨드가 `approved` 로 앉고 **거기서 멈췄다.** 그것을 집어 현장에\n * 보내는 것이 아무것도 없었다 — 규칙 타이머는 조치를 *내는* 일만 하고, 화면에는 넘기기 단추가\n * 없다. 폐루프를 라이브로 확인한 것도 사람이 뮤테이션을 손으로 불러서였다.\n *\n * 그래서 승인은 아무 데도 닿지 않았고, 현장은 오지 않는 지시를 기다렸다. 오류는 나지 않는다.\n *\n * ── 왜 승인 트랜잭션 안에서 보내지 않나 ─────────────────────────────────────\n * 승인은 업무 목록의 트랜잭션 안에서 일어난다. 그 안에서 현장에 요청을 쏘면 두 가지가 깨진다.\n *\n * ① 롤백이 되돌리지 못한다. 공장에는 작업지시가 생겼는데 우리 장부는 승인을 취소한 상태가 된다.\n * **네트워크로 나간 것은 트랜잭션이 못 되돌린다.**\n * ② 트랜잭션이 원격 응답을 기다리는 동안 열려 있다. 저쪽이 느리면 결재 화면이 함께 멈춘다.\n *\n * 그래서 **커밋 다음으로 미룬다.** 미루면 「미룬 사이에 롤백됐으면?」이 남는데, 그것은\n * `dispatch` 가 스스로 막는다 — 새 연결로 커맨드를 **다시 읽고** 상태를 확인한다. 롤백됐으면\n * 승인 기록이 없어 그 자리에서 던진다(§`asApproved`). 우리가 따로 검사할 것이 없다.\n *\n * ── 정직한 한계 ─────────────────────────────────────────────────────────────\n * 커밋과 발신 사이에 프로세스가 죽으면 그 커맨드는 `approved` 로 남는다. **잃지는 않지만 스스로\n * 나가지도 않는다.** 그것을 사람이 볼 자리(「넘기지 못한 조치」 목록)와 다시 넘기는 길이 아직\n * 없다 — 이 파일이 그 빈 자리를 메우지 않는다는 것을 여기 적어 둔다.\n *\n * 그리고 이 미룸은 **한 프로세스 안의 약속**이다. 여러 대로 늘리면 승인을 받은 프로세스가\n * 보내는 것이 되고, 그 프로세스가 죽으면 위와 같다. 그때는 표를 보고 집어 가는 일꾼이 필요하다.\n */\nimport { twinWarn } from '../../engine/log.js'\nimport { databaseCommandStore } from './command-store.js'\nimport { dispatch } from './command-dispatcher.js'\nimport { routeActuation } from '../reference/actuation-routing.js'\n\n/**\n * 승인이 확정된 조치를 **커밋 뒤에** 현장으로 넘긴다.\n *\n * 부르는 쪽은 기다리지 않는다(`void`). 결재의 성공은 넘김의 성공에 달려 있지 않다 — 어댑터가\n * 죽어 있어도 승인은 남아 있어야 하고, 그것이 승인과 넘김을 가른 이유다.\n *\n * **던지지 않는다.** 이 함수는 요청 밖에서 돌기 때문에, 던지면 처리되지 않은 거절이 되어\n * 프로세스를 내린다. 넘기다 생긴 실패는 `dispatch` 가 `failed` 로 앉히고, 그 위의 예외(상태가\n * 맞지 않음·커맨드가 사라짐)만 여기서 받아 적는다.\n */\nexport function dispatchAfterApproval(domainId: string, commandId: string): void {\n /*\n * `setImmediate` 로 미룬다 — 지금 열린 트랜잭션이 커밋될 기회를 준다.\n *\n * 이것이 커밋을 **보장하지는 않는다.** 보장은 `dispatch` 의 다시 읽기가 한다(머리말). 여기서\n * 미루는 것은 「대개 커밋된 뒤에 읽게 하려는」 것이고, 아니면 다시 읽기가 거절한다.\n */\n setImmediate(() => {\n void (async () => {\n try {\n const settled = await dispatch(databaseCommandStore(), domainId, commandId, command =>\n routeActuation(domainId, command.instanceId, command)\n )\n if (settled.state === 'failed') {\n /* 실패는 결함이 아니다 — 저쪽이 안 받았거나 설정이 비었다는 사실이고, 행에 남는다. */\n twinWarn(`[twin-actuation] 조치 ${commandId} 를 넘기지 못했다: ${settled.error ?? '(사유 없음)'}`)\n }\n } catch (e: any) {\n /*\n * 여기 오는 것은 「넘길 수 없는 상태」다. 가장 흔한 원인은 **승인 트랜잭션이 롤백된 것**이고,\n * 그때는 넘기지 않는 것이 맞다. 조용히 지나가지 않고 적는다 — 다른 원인이면 그것이 결함이다.\n */\n twinWarn(`[twin-actuation] 조치 ${commandId} 를 넘기지 않았다: ${String(e?.message ?? e)}`)\n }\n })()\n })\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"effect-match.js","sourceRoot":"","sources":["../../../server/service/actuation/effect-match.ts"],"names":[],"mappings":";;AA4DA,oCAgDC;AAQD,oCAOC;AAtED;;;;;;GAMG;AACH,SAAgB,YAAY,CAAC,IAI5B;IACC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,GAAG,IAAI,CAAA;IAEpC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAA;IAC/C,MAAM,KAAK,GAA0C,EAAE,CAAA;IAEvD,KAAK,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QAChD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC,CAAA;YAC9D,SAAQ;QACV,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAClC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb;;;;eAIG;YACH,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAA;YAChD,SAAQ;QACV,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,sDAAsD;YACtD,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,iCAAiC,EAAE,CAAC,CAAA;YAClE,SAAQ;QACV,CAAC;QAED,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACtC,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,4DAA4D;YAC5D,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAC,CAAA;YAC3D,SAAQ;QACV,CAAC;QAED,sDAAsD;QACtD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,CAAA;QACxE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAA;IACjF,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAA;AAClD,CAAC;AAED;;;;;GAKG;AACH,SAAgB,YAAY,CAAC,YAAyC;IACpE,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,KAAK,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QAChD,IAAI,OAAO;YAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IAC/B,CAAC;IACD,OAAO,CAAC,GAAG,GAAG,CAAC,CAAA;AACjB,CAAC","sourcesContent":["/*\n * **나간 지시와 돌아온 관측을 잇는다** — 폐루프의 마지막 칸. 판단만, 저장소는 여기 없다.\n *\n * ── 왜 이 칸이 비어 있었나 (2026-09-02) ───────────────────────────────────────\n * `dispatchRef` 를 **쓰는 곳만 있고 읽는 곳이 없었다.** 어댑터가 답한 작업지시 이름을 적어 두는데,\n * 그 이름으로 무엇도 찾지 않았다. 그래서 조치를 넘긴 뒤 저쪽에서 그것이 어떻게 됐는지 트윈은 몰랐다.\n *\n * 자리는 있고 길이 없는 상태다. 조치 경로에서 이 부류가 나면 결과가 하나다 — **넘긴 것이 현장에서\n * 무엇이 되었는지 아무도 모르는데, 화면은 「넘겼다」까지만 보여 주고 끝난 것처럼 읽힌다.**\n *\n * ── 커널은 무엇이 효과인지 정하지 않는다 ─────────────────────────────────────\n * 오더 상태 어휘는 도메인 소유다(§`OrderStatusDelta.status` — `status: string`, 코어는 강제하지\n * 않는다). 어느 값이 「현장에 나갔다」인지는 현장마다 다르고, 커널이 그 방언을 알면 보편 계약이\n * 깨진다 — 흐름 판정이 공정 이름을 설비 종류로 환산하지 않는 것과 같은 이유다.\n *\n * 그래서 여기서 판단하는 것은 **이어지나** 하나다.\n *\n * 이 관측의 오더 식별자가 우리가 낸 지시의 식별자와 같은가\n *\n * 같으면 그 상태를 **해석하지 않고 그대로** 적는다. 무엇을 뜻하는지는 그 어휘를 아는 쪽이 읽는다.\n */\nimport type { ISOTime } from '@operato/ops-contract'\n\n/** 관측 하나 — 오더 상태 보고에서 필요한 것만. */\nexport interface OrderObservation {\n orderId: string\n status: string\n /** 이 보고를 관측한 시각. 없으면 이을 수 없다 — 언제인지 모르는 효과는 적지 않는다. */\n at?: ISOTime\n}\n\n/** 이을 대상 — 저장소가 `dispatchRef` 로 찾아 준 조치. */\nexport interface DispatchedCommand {\n id: string\n dispatchRef: string\n /** 이미 적힌 효과가 있나 — 몇 번 보고됐는지를 이어서 센다. */\n effect?: { status: string; at: ISOTime; seen: number }\n}\n\n/** 적을 것 하나. */\nexport interface EffectUpdate {\n commandId: string\n status: string\n at: ISOTime\n seen: number\n}\n\nexport interface EffectMatch {\n updates: EffectUpdate[]\n /** 이을 수 없던 관측과 그 이유 — 조용히 버리지 않는다. */\n skips: { orderId: string; reason: string }[]\n}\n\n/**\n * 한 번에 여러 관측을 잇는다.\n *\n * **같은 오더가 한 묶음에 여러 번 올 수 있다.** 그때는 마지막 것이 남고 `seen` 이 그만큼 는다 —\n * 중간 상태를 덮어쓰는 것이 맞다(화면이 묻는 것은 「지금 저쪽에서 무엇인가」다). 다만 몇 번 바뀌었는지는\n * 남긴다. 한 번도 안 바뀐 것과 다섯 번 바뀐 것은 다른 일이다.\n */\nexport function matchEffects(args: {\n observations: readonly OrderObservation[]\n /** `dispatchRef` → 그 지시를 낸 조치. 저장소가 채운다. */\n byRef: ReadonlyMap<string, DispatchedCommand>\n}): EffectMatch {\n const { observations, byRef } = args\n\n const updates = new Map<string, EffectUpdate>()\n const skips: { orderId: string; reason: string }[] = []\n\n for (const ob of observations) {\n const orderId = String(ob?.orderId ?? '').trim()\n if (!orderId) {\n skips.push({ orderId: '', reason: '오더 식별자가 없는 보고 — 이을 수 없다' })\n continue\n }\n\n const command = byRef.get(orderId)\n if (!command) {\n /*\n * 대개 이것이다 — 우리가 내지 않은 오더의 상태 보고. 저쪽 시스템은 자기 오더를 다 보고하고,\n * 그중 우리가 낸 것은 일부다. **이유로 남기되 흔한 것임을 적어 둔다** — 이 줄이 쌓이는 것을\n * 결함으로 읽으면 안 된다.\n */\n skips.push({ orderId, reason: '우리가 낸 지시가 아니다' })\n continue\n }\n\n const status = String(ob?.status ?? '').trim()\n if (!status) {\n /* 빈 상태를 적으면 「보고는 왔는데 아무 말도 안 했다」가 「효과 없음」과 같아 보인다. */\n skips.push({ orderId, reason: '상태를 말하지 않은 보고 — 빈 값을 효과로 적지 않는다' })\n continue\n }\n\n const at = String(ob?.at ?? '').trim()\n if (!at) {\n /* 언제인지 모르는 효과는 적지 않는다 — 「지금」으로 채우면 관측 시각이 아니라 처리 시각이 된다. */\n skips.push({ orderId, reason: '시각이 없는 보고 — 지금으로 채우지 않는다' })\n continue\n }\n\n /* 같은 묶음 안에서 앞서 센 것이 있으면 그것을 잇는다 — 없으면 적힌 효과에서 잇는다. */\n const prior = updates.get(command.id)?.seen ?? command.effect?.seen ?? 0\n updates.set(command.id, { commandId: command.id, status, at, seen: prior + 1 })\n }\n\n return { updates: [...updates.values()], skips }\n}\n\n/**\n * 이 묶음에서 **찾아봐야 할 오더 식별자**만 골라낸다 — 저장소에 물을 것을 줄인다.\n *\n * 관측 한 묶음이 수백 건일 수 있고 그중 우리가 낸 것은 몇 건이다. 전부를 하나씩 물으면 유입 경로에\n * 질의가 그만큼 붙는다 — 유입은 초당 수천 건이 지나는 자리다.\n */\nexport function refsToLookUp(observations: readonly OrderObservation[]): string[] {\n const out = new Set<string>()\n for (const ob of observations) {\n const orderId = String(ob?.orderId ?? '').trim()\n if (orderId) out.add(orderId)\n }\n return [...out]\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/actuation/index.ts"],"names":[],"mappings":";;;;AAAA,2DAAuD;AACvD,6EAAwE;AACxE,uDAAqD;AACrD,yEAAgE;AAEnD,QAAA,QAAQ,GAAG,CAAC,mCAAiB,EAAE,qCAAiB,CAAC,CAAA;AACjD,QAAA,SAAS,GAAG,CAAC,8CAAmB,EAAE,sDAAyB,CAAC,CAAA;AAEzE,4DAAiC;AACjC,8DAAmC;AACnC,gEAAqC;AACrC,uEAA4C;AAC5C,iEAAsC;AACtC,0DAA+B;AAC/B,8DAAmC;AACnC,iEAAsC;AACtC,kEAAuC;AACvC,6DAAkC;AAClC,wDAA6B;AAC7B,4DAAiC;AACjC,8DAAmC;AACnC,gEAAqC;AACrC,6DAAkC;AAClC,2DAAgC;AAChC,yDAA8B;AAC9B,qEAA0C;AAC1C,uEAA4C","sourcesContent":["import { TwinActuationRule } from './actuation-rule.js'\nimport { TwinActuationRuleResolver } from './actuation-rule-resolver.js'\nimport { TwinCommandRecord } from './twin-command.js'\nimport { TwinCommandResolver } from './twin-command-resolver.js'\n\nexport const entities = [TwinCommandRecord, TwinActuationRule]\nexport const resolvers = [TwinCommandResolver, TwinActuationRuleResolver]\n\nexport * from './twin-command.js'\nexport * from './actuation-rule.js'\nexport * from './approval-gateway.js'\nexport * from './dispatch-after-approval.js'\nexport * from './actuation-outcome.js'\nexport * from './retry-plan.js'\nexport * from './sweep-dispatch.js'\nexport * from './actuation-advisor.js'\nexport * from './command-dispatcher.js'\nexport * from './command-store.js'\nexport * from './twin-now.js'\nexport * from './effect-match.js'\nexport * from './settle-effects.js'\nexport * from './catch-up-effects.js'\nexport * from './rule-evaluate.js'\nexport * from './rule-runner.js'\nexport * from './rule-loop.js'\nexport * from './twin-command-resolver.js'\nexport * from './actuation-rule-resolver.js'\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"retry-plan.js","sourceRoot":"","sources":["../../../server/service/actuation/retry-plan.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;;;AAKH,wCAEC;AAoED,0CAoCC;AA1GD,SAAgB,cAAc,CAAC,CAAU;IACvC,OAAO,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,KAAK,OAAO,CAAA;AAC5D,CAAC;AAED;;;;GAIG;AACU,QAAA,gBAAgB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAU,CAAA;AAEnE;;;;GAIG;AACU,QAAA,mBAAmB,GAAG,OAAO,CAAA;AA6C1C,MAAM,EAAE,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;AAE7D;;;;;;GAMG;AACH,SAAgB,eAAe,CAAC,KAAiB;IAC/C,IAAI,CAAC,KAAK,CAAC,YAAY;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAA;IAEzE,MAAM,QAAQ,GAAG,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;IAE9F;;;;;OAKG;IACH,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;QACnB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,GAAG,wBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACzF,CAAC;IAED,uCAAuC;IACvC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAA;IACjF,IAAI,KAAK,CAAC,KAAK,KAAK,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;IAClE,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAA;IAE7E,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;QAC7C,4CAA4C;QAC5C,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,GAAG,KAAK;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,CAAA;IAChG,CAAC;IAED,IAAI,KAAK,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;QAChC,kCAAkC;QAClC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,GAAG,CAAC,EAAE,aAAa,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,GAAG,2BAAmB,CAAC,EAAE,CAAA;IACpG,CAAC;IAED,gCAAgC;IAChC,IAAI,QAAQ,IAAI,wBAAgB,CAAC,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAA;IAEvF,MAAM,IAAI,GAAG,wBAAgB,CAAC,QAAQ,CAAC,CAAA;IACvC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,GAAG,CAAC,EAAE,aAAa,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,CAAA;AACrF,CAAC","sourcesContent":["/*\n * **다시 넘길 것인가, 언제인가** — 일꾼이 묻는 것 하나.\n *\n * ── 왜 순수한가 ─────────────────────────────────────────────────────────────\n * 일꾼의 판단이 전부 여기 있다. DB 도 어댑터도 시계도 보지 않는다 — 시각은 받는다. 그래서 「10분\n * 뒤에 다시」 같은 규칙을 기다리지 않고 값만 바꿔 가며 잴 수 있다.\n *\n * ── 세 갈래는 어댑터가 답한다 ───────────────────────────────────────────────\n * 실패가 다 같지 않다. 2026-09-02 에 둘을 겪었다 — 저쪽 인증 정보가 없어 503(고치면 됨)이었고,\n * `ITEM-1` 은 400(고쳐도 이 조치로는 안 됨)이었다. 답만 보면 구별되지 않았다.\n *\n * again 그대로 다시 해 볼 만하다 못 닿았거나 저쪽이 잠깐 흔들렸다\n * after-fix 사람이 고친 뒤 그대로 나간다 설정 문제\n * never 이 조치로는 영원히 안 된다 지시 내용이 틀렸다\n *\n * 가르는 자리는 **「조치를 다시 낼 필요가 있나」**다(인티그레이션 레인 판단).\n *\n * ── `after-fix` 를 횟수로 접지 않는다 ───────────────────────────────────────\n * 이것이 이 파일에서 가장 중요한 결정이다. `after-fix` 는 **재시도가 아니라 기다리는 것**이다.\n * 두드려서 풀릴 일이 아니고, 사람이 설정을 고치는 순간 다음 주기에 나가면 된다.\n *\n * 횟수로 접으면 **사람이 고쳐 놓고도 다시 손으로 눌러야** 한다. 2026-09-02 사고가 정확히 그\n * 모양이었다 — 저쪽이 고쳐진 뒤 사람이 손으로 다시 넘겨서 나갔다.\n *\n * `again` 만 접는다. 끝없이 두드리는 것은 소음이고, 짧게 세 번 해서 안 되면 망이 흔들린 것이\n * 아니라 다른 것이다 — 그때는 사람이 봐야 한다.\n *\n * ── 멱등이 첫 시도와 재시도를 가른다 ────────────────────────────────────────\n * approved → 넘김 **첫 시도**다. 멱등 선언이 없어도 한다 — 한 번뿐이라 위험이 없다\n * failed → 넘김 **재시도**다. 두 번 보내도 지시가 하나라고 선언한 커넥터만 한다\n *\n * 선언하지 않은 커넥터의 실패는 목록에 남는다. 사람이 저쪽 상태를 보고 결정해야 한다 — 우리가\n * 「아마 괜찮겠지」로 두 번 보내면 현장에 지시가 두 건 선다.\n */\n\n/** 어댑터가 답하는 세 갈래. 비어 있으면 **말하지 않은 것**이고, `never` 가 아니다. */\nexport type RetryOutlook = 'again' | 'after-fix' | 'never'\n\nexport function isRetryOutlook(v: unknown): v is RetryOutlook {\n return v === 'again' || v === 'after-fix' || v === 'never'\n}\n\n/**\n * `again` 의 물러섬 — 30초 · 2분 · 10분, 그다음 접는다.\n *\n * 첫 값이 일꾼의 주기와 같다(§`RULE_PERIOD_MS`). 새 박자를 만들지 않는다.\n */\nexport const AGAIN_BACKOFF_MS = [30_000, 120_000, 600_000] as const\n\n/**\n * `after-fix` 는 **10분마다 계속** 본다 — 접지 않는다(머리말).\n *\n * 사람이 설정을 고치는 것을 기다리는 간격이다. 짧게 두면 헛되고, 길게 두면 고쳐 놓고 기다린다.\n */\nexport const AFTER_FIX_PERIOD_MS = 600_000\n\n/** 왜 집지 않는가 — 목록이 「고칠 것」과 「우리 쪽을 보지 말 것」을 갈라 보이게 한다. */\nexport type SkipReason =\n /** 종착이거나 넘길 수 없는 상태다. */\n | 'not-dispatchable'\n /** 아직 물러섬이 끝나지 않았다 — 다음 기회를 기다린다. */\n | 'backing-off'\n /** 어댑터가 「영원히 안 된다」고 답했다 — 사람이 **새 조치**를 내야 한다. 우리 쪽에 고칠 것이 없다. */\n | 'never'\n /** `again` 을 세 번 다 썼다 — 사람이 봐야 한다. */\n | 'attempts-spent'\n /** 어댑터가 다시 될 일인지 말하지 않았다 — 모르는 것을 재시도로 밀지 않는다. */\n | 'outlook-unknown'\n /** 재시도인데 커넥터가 멱등을 선언하지 않았다 — 두 건이 설 위험을 우리가 지지 않는다. */\n | 'not-idempotent'\n\nexport interface RetryDecision {\n /** 지금 넘길 것인가. */\n take: boolean\n /** 안 넘기면 왜 — 목록이 그대로 보인다. */\n skip?: SkipReason\n /** 넘길 것이면 이번이 몇 번째 시도인가(첫 시도가 1). */\n attempt?: number\n /** 이번 시도가 실패하면 다음은 언제인가(ISO). 없으면 더 안 본다. */\n nextAttemptAt?: string\n}\n\nexport interface RetryInput {\n /** 지금 상태 — `approved` 는 첫 시도, `failed` 는 재시도. */\n state: string\n /** 넘길 수 있는 상태인가 — 상태 기계가 답한다(`nextCommandState(state,'dispatch')`). */\n dispatchable: boolean\n /** 어댑터가 지난 실패에 답한 갈래. 첫 시도에는 없다. */\n retry?: string\n /** 지금까지 몇 번 넘겨 봤나. 없으면 0. */\n attempts?: number\n /** 다음에 볼 시각(ISO). 없으면 지금 봐도 된다. */\n nextAttemptAt?: string\n /** 이 커넥터가 두 번 보내도 지시가 하나인가 — **선언만 참으로 읽는다.** */\n idempotent?: boolean\n /** 지금(ms). */\n nowMs: number\n}\n\nconst at = (ms: number): string => new Date(ms).toISOString()\n\n/**\n * 이 조치를 지금 넘길 것인가.\n *\n * **모르면 넘기지 않는다.** 어댑터가 갈래를 말하지 않았거나 멱등을 선언하지 않았으면 재시도하지\n * 않고 목록에 남긴다 — 오늘 커널의 다른 축에서 지킨 것과 같은 규율이다(정격 사용량·품질 계수기·\n * 조용한 시각: 모르면 단언하지 않는다).\n */\nexport function retryDecisionOf(input: RetryInput): RetryDecision {\n if (!input.dispatchable) return { take: false, skip: 'not-dispatchable' }\n\n const attempts = typeof input.attempts === 'number' && input.attempts > 0 ? input.attempts : 0\n\n /*\n * **첫 시도** — 승인됐는데 한 번도 안 나간 것. 물러섬도 멱등도 묻지 않는다: 한 번뿐이라 두 건이\n * 설 수 없고, 어댑터는 아직 아무 답도 하지 않았다.\n *\n * 이 갈래가 승인 직후 발신이 놓친 것을 줍는다 — 커밋과 발신 사이에 프로세스가 죽은 경우다.\n */\n if (attempts === 0) {\n return { take: true, attempt: 1, nextAttemptAt: at(input.nowMs + AGAIN_BACKOFF_MS[0]) }\n }\n\n /* 재시도다. 여기서부터 어댑터의 답과 커넥터의 성질을 묻는다. */\n if (!isRetryOutlook(input.retry)) return { take: false, skip: 'outlook-unknown' }\n if (input.retry === 'never') return { take: false, skip: 'never' }\n if (input.idempotent !== true) return { take: false, skip: 'not-idempotent' }\n\n if (input.nextAttemptAt) {\n const dueMs = Date.parse(input.nextAttemptAt)\n /* 읽을 수 없는 값으로 막지 않는다 — 막으면 그 조치가 표에 갇힌다. */\n if (Number.isFinite(dueMs) && input.nowMs < dueMs) return { take: false, skip: 'backing-off' }\n }\n\n if (input.retry === 'after-fix') {\n /* 접지 않는다(머리말) — 같은 간격으로 계속 본다. */\n return { take: true, attempt: attempts + 1, nextAttemptAt: at(input.nowMs + AFTER_FIX_PERIOD_MS) }\n }\n\n /* `again` — 물러섬 표를 다 쓰면 접는다. */\n if (attempts >= AGAIN_BACKOFF_MS.length) return { take: false, skip: 'attempts-spent' }\n\n const next = AGAIN_BACKOFF_MS[attempts]\n return { take: true, attempt: attempts + 1, nextAttemptAt: at(input.nowMs + next) }\n}\n"]}