@things-factory/headless-twin 10.0.19 → 10.1.0

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 (327) hide show
  1. package/dist-server/engine/canonical-ingest.d.ts +10 -1
  2. package/dist-server/engine/canonical-ingest.js +110 -33
  3. package/dist-server/engine/canonical-ingest.js.map +1 -1
  4. package/dist-server/engine/event-time.d.ts +25 -0
  5. package/dist-server/engine/event-time.js +98 -0
  6. package/dist-server/engine/event-time.js.map +1 -0
  7. package/dist-server/engine/index.d.ts +2 -0
  8. package/dist-server/engine/index.js +23 -0
  9. package/dist-server/engine/index.js.map +1 -1
  10. package/dist-server/engine/ingest-dedupe.d.ts +18 -6
  11. package/dist-server/engine/ingest-dedupe.js +201 -15
  12. package/dist-server/engine/ingest-dedupe.js.map +1 -1
  13. package/dist-server/engine/ingest-health.d.ts +60 -1
  14. package/dist-server/engine/ingest-health.js +43 -8
  15. package/dist-server/engine/ingest-health.js.map +1 -1
  16. package/dist-server/engine/kpi-fold.d.ts +19 -0
  17. package/dist-server/engine/kpi-fold.js +4 -1
  18. package/dist-server/engine/kpi-fold.js.map +1 -1
  19. package/dist-server/engine/kpi-query.js +23 -3
  20. package/dist-server/engine/kpi-query.js.map +1 -1
  21. package/dist-server/engine/oee-accumulator.d.ts +35 -2
  22. package/dist-server/engine/oee-accumulator.js +56 -4
  23. package/dist-server/engine/oee-accumulator.js.map +1 -1
  24. package/dist-server/engine/twin-engine.d.ts +72 -3
  25. package/dist-server/engine/twin-engine.js +118 -5
  26. package/dist-server/engine/twin-engine.js.map +1 -1
  27. package/dist-server/index.js +21 -0
  28. package/dist-server/index.js.map +1 -1
  29. package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.d.ts +5 -0
  30. package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.js +66 -0
  31. package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.js.map +1 -0
  32. package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.d.ts +5 -0
  33. package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.js +79 -0
  34. package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.js.map +1 -0
  35. package/dist-server/migrations/index.js +5 -1
  36. package/dist-server/migrations/index.js.map +1 -1
  37. package/dist-server/routes.js +12 -20
  38. package/dist-server/routes.js.map +1 -1
  39. package/dist-server/service/actuation/actuation-advisor.d.ts +60 -0
  40. package/dist-server/service/actuation/actuation-advisor.js +53 -0
  41. package/dist-server/service/actuation/actuation-advisor.js.map +1 -0
  42. package/dist-server/service/actuation/actuation-outcome.d.ts +52 -0
  43. package/dist-server/service/actuation/actuation-outcome.js +34 -0
  44. package/dist-server/service/actuation/actuation-outcome.js.map +1 -0
  45. package/dist-server/service/actuation/actuation-rule-resolver.d.ts +19 -0
  46. package/dist-server/service/actuation/actuation-rule-resolver.js +161 -0
  47. package/dist-server/service/actuation/actuation-rule-resolver.js.map +1 -0
  48. package/dist-server/service/actuation/actuation-rule.d.ts +55 -0
  49. package/dist-server/service/actuation/actuation-rule.js +130 -0
  50. package/dist-server/service/actuation/actuation-rule.js.map +1 -0
  51. package/dist-server/service/actuation/approval-gateway.d.ts +20 -0
  52. package/dist-server/service/actuation/approval-gateway.js +14 -0
  53. package/dist-server/service/actuation/approval-gateway.js.map +1 -0
  54. package/dist-server/service/actuation/catch-up-effects.d.ts +16 -0
  55. package/dist-server/service/actuation/catch-up-effects.js +120 -0
  56. package/dist-server/service/actuation/catch-up-effects.js.map +1 -0
  57. package/dist-server/service/actuation/command-dispatcher.d.ts +206 -8
  58. package/dist-server/service/actuation/command-dispatcher.js +191 -14
  59. package/dist-server/service/actuation/command-dispatcher.js.map +1 -1
  60. package/dist-server/service/actuation/command-store.d.ts +10 -2
  61. package/dist-server/service/actuation/command-store.js +168 -5
  62. package/dist-server/service/actuation/command-store.js.map +1 -1
  63. package/dist-server/service/actuation/dispatch-after-approval.d.ts +11 -0
  64. package/dist-server/service/actuation/dispatch-after-approval.js +73 -0
  65. package/dist-server/service/actuation/dispatch-after-approval.js.map +1 -0
  66. package/dist-server/service/actuation/effect-match.d.ts +53 -0
  67. package/dist-server/service/actuation/effect-match.js +65 -0
  68. package/dist-server/service/actuation/effect-match.js.map +1 -0
  69. package/dist-server/service/actuation/index.d.ts +21 -2
  70. package/dist-server/service/actuation/index.js +21 -2
  71. package/dist-server/service/actuation/index.js.map +1 -1
  72. package/dist-server/service/actuation/retry-plan.d.ts +63 -0
  73. package/dist-server/service/actuation/retry-plan.js +99 -0
  74. package/dist-server/service/actuation/retry-plan.js.map +1 -0
  75. package/dist-server/service/actuation/rule-evaluate.d.ts +127 -0
  76. package/dist-server/service/actuation/rule-evaluate.js +205 -0
  77. package/dist-server/service/actuation/rule-evaluate.js.map +1 -0
  78. package/dist-server/service/actuation/rule-loop.d.ts +5 -0
  79. package/dist-server/service/actuation/rule-loop.js +156 -0
  80. package/dist-server/service/actuation/rule-loop.js.map +1 -0
  81. package/dist-server/service/actuation/rule-runner.d.ts +52 -0
  82. package/dist-server/service/actuation/rule-runner.js +135 -0
  83. package/dist-server/service/actuation/rule-runner.js.map +1 -0
  84. package/dist-server/service/actuation/rule-wiring.d.ts +24 -0
  85. package/dist-server/service/actuation/rule-wiring.js +80 -0
  86. package/dist-server/service/actuation/rule-wiring.js.map +1 -0
  87. package/dist-server/service/actuation/settle-effects.d.ts +21 -0
  88. package/dist-server/service/actuation/settle-effects.js +101 -0
  89. package/dist-server/service/actuation/settle-effects.js.map +1 -0
  90. package/dist-server/service/actuation/sweep-dispatch.d.ts +37 -0
  91. package/dist-server/service/actuation/sweep-dispatch.js +125 -0
  92. package/dist-server/service/actuation/sweep-dispatch.js.map +1 -0
  93. package/dist-server/service/actuation/twin-command-resolver.d.ts +120 -0
  94. package/dist-server/service/actuation/twin-command-resolver.js +406 -0
  95. package/dist-server/service/actuation/twin-command-resolver.js.map +1 -0
  96. package/dist-server/service/actuation/twin-command.d.ts +81 -0
  97. package/dist-server/service/actuation/twin-command.js +60 -0
  98. package/dist-server/service/actuation/twin-command.js.map +1 -1
  99. package/dist-server/service/actuation/twin-now.d.ts +41 -0
  100. package/dist-server/service/actuation/twin-now.js +65 -0
  101. package/dist-server/service/actuation/twin-now.js.map +1 -0
  102. package/dist-server/service/index.d.ts +5 -2
  103. package/dist-server/service/index.js +8 -0
  104. package/dist-server/service/index.js.map +1 -1
  105. package/dist-server/service/reference/actuation-routing.d.ts +60 -0
  106. package/dist-server/service/reference/actuation-routing.js +126 -0
  107. package/dist-server/service/reference/actuation-routing.js.map +1 -0
  108. package/dist-server/service/reference/actuation-target.d.ts +65 -0
  109. package/dist-server/service/reference/actuation-target.js +94 -0
  110. package/dist-server/service/reference/actuation-target.js.map +1 -0
  111. package/dist-server/service/reference/connection-portability-resolver.d.ts +29 -0
  112. package/dist-server/service/reference/connection-portability-resolver.js +181 -0
  113. package/dist-server/service/reference/connection-portability-resolver.js.map +1 -0
  114. package/dist-server/service/reference/connection-portability.d.ts +138 -0
  115. package/dist-server/service/reference/connection-portability.js +230 -0
  116. package/dist-server/service/reference/connection-portability.js.map +1 -0
  117. package/dist-server/service/reference/fill-cadence.d.ts +17 -0
  118. package/dist-server/service/reference/fill-cadence.js +31 -0
  119. package/dist-server/service/reference/fill-cadence.js.map +1 -0
  120. package/dist-server/service/reference/fill-dedupe.d.ts +16 -0
  121. package/dist-server/service/reference/fill-dedupe.js +63 -0
  122. package/dist-server/service/reference/fill-dedupe.js.map +1 -0
  123. package/dist-server/service/reference/fill-loop.d.ts +35 -0
  124. package/dist-server/service/reference/fill-loop.js +187 -0
  125. package/dist-server/service/reference/fill-loop.js.map +1 -0
  126. package/dist-server/service/reference/fill-span.d.ts +10 -0
  127. package/dist-server/service/reference/fill-span.js +37 -0
  128. package/dist-server/service/reference/fill-span.js.map +1 -0
  129. package/dist-server/service/reference/hook-contract.d.ts +88 -4
  130. package/dist-server/service/reference/hook-contract.js +105 -4
  131. package/dist-server/service/reference/hook-contract.js.map +1 -1
  132. package/dist-server/service/reference/hook-rejected.d.ts +25 -0
  133. package/dist-server/service/reference/hook-rejected.js +48 -0
  134. package/dist-server/service/reference/hook-rejected.js.map +1 -0
  135. package/dist-server/service/reference/index.d.ts +4 -1
  136. package/dist-server/service/reference/index.js +5 -1
  137. package/dist-server/service/reference/index.js.map +1 -1
  138. package/dist-server/service/reference/live-cadence.d.ts +68 -0
  139. package/dist-server/service/reference/live-cadence.js +44 -0
  140. package/dist-server/service/reference/live-cadence.js.map +1 -0
  141. package/dist-server/service/reference/live-feed-lease.d.ts +15 -0
  142. package/dist-server/service/reference/live-feed-lease.js +56 -0
  143. package/dist-server/service/reference/live-feed-lease.js.map +1 -0
  144. package/dist-server/service/reference/live-ingest.d.ts +21 -0
  145. package/dist-server/service/reference/live-ingest.js +66 -0
  146. package/dist-server/service/reference/live-ingest.js.map +1 -0
  147. package/dist-server/service/reference/reference-adapter.d.ts +291 -2
  148. package/dist-server/service/reference/reference-adapter.js +19 -1
  149. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  150. package/dist-server/service/reference/reference-fill-run.d.ts +50 -0
  151. package/dist-server/service/reference/reference-fill-run.js +142 -0
  152. package/dist-server/service/reference/reference-fill-run.js.map +1 -0
  153. package/dist-server/service/reference/reference-fill.d.ts +90 -0
  154. package/dist-server/service/reference/reference-fill.js +141 -0
  155. package/dist-server/service/reference/reference-fill.js.map +1 -0
  156. package/dist-server/service/reference/reference-hook.d.ts +22 -0
  157. package/dist-server/service/reference/reference-hook.js +135 -55
  158. package/dist-server/service/reference/reference-hook.js.map +1 -1
  159. package/dist-server/service/reference/reference-link-groups.d.ts +10 -0
  160. package/dist-server/service/reference/reference-link-groups.js +15 -0
  161. package/dist-server/service/reference/reference-link-groups.js.map +1 -0
  162. package/dist-server/service/reference/reference-links.d.ts +14 -0
  163. package/dist-server/service/reference/reference-links.js +73 -0
  164. package/dist-server/service/reference/reference-links.js.map +1 -0
  165. package/dist-server/service/reference/reference-live.d.ts +1 -0
  166. package/dist-server/service/reference/reference-live.js +32 -6
  167. package/dist-server/service/reference/reference-live.js.map +1 -1
  168. package/dist-server/service/reference/reference-master.d.ts +56 -2
  169. package/dist-server/service/reference/reference-master.js +33 -3
  170. package/dist-server/service/reference/reference-master.js.map +1 -1
  171. package/dist-server/service/reference/reference-resolver.d.ts +27 -0
  172. package/dist-server/service/reference/reference-resolver.js +79 -1
  173. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  174. package/dist-server/service/twin-event/twin-event-keys.d.ts +31 -3
  175. package/dist-server/service/twin-event/twin-event-keys.js +36 -4
  176. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  177. package/dist-server/service/twin-event/twin-event.d.ts +1 -0
  178. package/dist-server/service/twin-event/twin-event.js +11 -2
  179. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  180. package/dist-server/service/twin-model/choose-recipe.d.ts +15 -0
  181. package/dist-server/service/twin-model/choose-recipe.js +33 -0
  182. package/dist-server/service/twin-model/choose-recipe.js.map +1 -0
  183. package/dist-server/service/twin-model/external-resolver.d.ts +76 -0
  184. package/dist-server/service/twin-model/external-resolver.js +109 -0
  185. package/dist-server/service/twin-model/external-resolver.js.map +1 -0
  186. package/dist-server/service/twin-model/project-structure.js +16 -3
  187. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  188. package/dist-server/service/twin-model/twin-equipment.d.ts +17 -0
  189. package/dist-server/service/twin-model/twin-equipment.js +10 -0
  190. package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
  191. package/dist-server/service/twin-model/twin-lineage-query.js +75 -1
  192. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  193. package/dist-server/service/twin-model/twin-model-item-query.js +94 -5
  194. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  195. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  196. package/dist-server/service/twin-space/twin-space.d.ts +29 -0
  197. package/dist-server/service/twin-space/twin-space.js +13 -0
  198. package/dist-server/service/twin-space/twin-space.js.map +1 -1
  199. package/dist-server/service/twin-subject/event-subjects.d.ts +3 -1
  200. package/dist-server/service/twin-subject/event-subjects.js +8 -1
  201. package/dist-server/service/twin-subject/event-subjects.js.map +1 -1
  202. package/dist-shared/entity-delta.js +1 -1
  203. package/dist-shared/entity-delta.js.map +1 -1
  204. package/package.json +7 -7
  205. package/server/engine/canonical-ingest.ts +119 -33
  206. package/server/engine/event-time.ts +96 -0
  207. package/server/engine/index.ts +20 -0
  208. package/server/engine/ingest-dedupe.ts +203 -27
  209. package/server/engine/ingest-health.ts +91 -6
  210. package/server/engine/kpi-fold.ts +24 -1
  211. package/server/engine/kpi-query.ts +23 -4
  212. package/server/engine/oee-accumulator.ts +80 -8
  213. package/server/engine/twin-engine.ts +119 -5
  214. package/server/index.ts +23 -0
  215. package/server/migrations/1786600000000-RenameOperatoMesAdapterType.ts +66 -0
  216. package/server/migrations/1786700000000-AddDispatchRetryColumns.ts +82 -0
  217. package/server/migrations/index.ts +5 -1
  218. package/server/routes.ts +12 -24
  219. package/server/service/actuation/actuation-advisor.ts +124 -0
  220. package/server/service/actuation/actuation-outcome.ts +97 -0
  221. package/server/service/actuation/actuation-rule-resolver.ts +147 -0
  222. package/server/service/actuation/actuation-rule.ts +139 -0
  223. package/server/service/actuation/approval-gateway.ts +44 -0
  224. package/server/service/actuation/catch-up-effects.ts +152 -0
  225. package/server/service/actuation/command-dispatcher.ts +401 -20
  226. package/server/service/actuation/command-store.ts +182 -8
  227. package/server/service/actuation/dispatch-after-approval.ts +71 -0
  228. package/server/service/actuation/effect-match.ts +124 -0
  229. package/server/service/actuation/index.ts +21 -2
  230. package/server/service/actuation/retry-plan.ts +145 -0
  231. package/server/service/actuation/rule-evaluate.ts +352 -0
  232. package/server/service/actuation/rule-loop.ts +160 -0
  233. package/server/service/actuation/rule-runner.ts +216 -0
  234. package/server/service/actuation/rule-wiring.ts +84 -0
  235. package/server/service/actuation/settle-effects.ts +115 -0
  236. package/server/service/actuation/sweep-dispatch.ts +179 -0
  237. package/server/service/actuation/twin-command-resolver.ts +387 -0
  238. package/server/service/actuation/twin-command.ts +128 -1
  239. package/server/service/actuation/twin-now.ts +97 -0
  240. package/server/service/index.ts +9 -1
  241. package/server/service/reference/actuation-routing.ts +165 -0
  242. package/server/service/reference/actuation-target.ts +163 -0
  243. package/server/service/reference/connection-portability-resolver.ts +171 -0
  244. package/server/service/reference/connection-portability.ts +285 -0
  245. package/server/service/reference/fill-cadence.ts +29 -0
  246. package/server/service/reference/fill-dedupe.ts +66 -0
  247. package/server/service/reference/fill-loop.ts +153 -0
  248. package/server/service/reference/fill-span.ts +31 -0
  249. package/server/service/reference/hook-contract.ts +185 -5
  250. package/server/service/reference/hook-rejected.ts +53 -0
  251. package/server/service/reference/index.ts +5 -1
  252. package/server/service/reference/live-cadence.ts +129 -0
  253. package/server/service/reference/live-feed-lease.ts +56 -0
  254. package/server/service/reference/live-ingest.ts +94 -0
  255. package/server/service/reference/reference-adapter.ts +327 -5
  256. package/server/service/reference/reference-fill-run.ts +165 -0
  257. package/server/service/reference/reference-fill.ts +223 -0
  258. package/server/service/reference/reference-hook.ts +162 -56
  259. package/server/service/reference/reference-link-groups.ts +21 -0
  260. package/server/service/reference/reference-links.ts +77 -0
  261. package/server/service/reference/reference-live.ts +30 -5
  262. package/server/service/reference/reference-master.ts +90 -5
  263. package/server/service/reference/reference-resolver.ts +77 -2
  264. package/server/service/twin-event/twin-event-keys.ts +37 -4
  265. package/server/service/twin-event/twin-event.ts +18 -1
  266. package/server/service/twin-model/choose-recipe.ts +31 -0
  267. package/server/service/twin-model/external-resolver.ts +161 -0
  268. package/server/service/twin-model/project-structure.ts +16 -3
  269. package/server/service/twin-model/twin-equipment.ts +22 -0
  270. package/server/service/twin-model/twin-lineage-query.ts +82 -1
  271. package/server/service/twin-model/twin-model-item-query.ts +95 -4
  272. package/server/service/twin-model/twin-model-tree-query.ts +1 -1
  273. package/server/service/twin-space/twin-space.ts +39 -0
  274. package/server/service/twin-subject/event-subjects.ts +8 -1
  275. package/shared/entity-delta.ts +1 -1
  276. package/test/ack-shape-alignment.test.ts +97 -0
  277. package/test/actuation-approval-door.test.ts +421 -0
  278. package/test/actuation-dispatch.test.ts +16 -3
  279. package/test/actuation-effect.test.ts +162 -0
  280. package/test/actuation-outcome.test.ts +152 -0
  281. package/test/actuation-rule-runner.test.ts +281 -0
  282. package/test/actuation-rule.test.ts +266 -0
  283. package/test/actuation-seam.test.ts +249 -0
  284. package/test/actuation-settle.test.ts +164 -0
  285. package/test/actuation-target.test.ts +145 -0
  286. package/test/canonical-ingest-vocabularies.test.ts +49 -2
  287. package/test/capability-mapping.test.ts +1 -1
  288. package/test/choose-recipe.test.ts +117 -0
  289. package/test/command-store-writes-columns.test.ts +126 -0
  290. package/test/connection-portability-doors.test.ts +87 -0
  291. package/test/connector-capability-declaration.test.ts +54 -4
  292. package/test/declaration-reaches-model.test.ts +43 -0
  293. package/test/dedupe-transformation.test.ts +112 -0
  294. package/test/entity-delta.test.ts +1 -1
  295. package/test/event-subjects.test.ts +2 -1
  296. package/test/external-resolver.test.ts +171 -0
  297. package/test/fill-dedupe.test.ts +59 -0
  298. package/test/fill-loop.test.ts +35 -0
  299. package/test/first-envelope.test.ts +153 -0
  300. package/test/fold-shift-dst.test.ts +110 -0
  301. package/test/home-in-area.test.ts +90 -0
  302. package/test/hook-rejected-shape.test.ts +86 -0
  303. package/test/hook-response-sum.test.ts +193 -0
  304. package/test/hook-sequence.test.ts +1 -1
  305. package/test/ingest-dedupe.test.ts +56 -0
  306. package/test/ingest-expected-quiet.test.ts +111 -0
  307. package/test/ingest-idempotent.test.ts +79 -0
  308. package/test/ingest-reconcile-four-ways.test.ts +97 -0
  309. package/test/ingest-rules-header.test.ts +100 -0
  310. package/test/ingest-window-rolls-unobserved.test.ts +72 -0
  311. package/test/intake-mapping-parity.test.ts +175 -0
  312. package/test/live-cadence.test.ts +170 -0
  313. package/test/live-feed-lease.test.ts +63 -0
  314. package/test/live-mirror-parity.test.ts +1 -1
  315. package/test/oee-accumulator.test.ts +3 -3
  316. package/test/oee-site-calendar.test.ts +146 -0
  317. package/test/order-and-transaction-split.test.ts +107 -0
  318. package/test/reference-fill.test.ts +235 -0
  319. package/test/reference-hook.test.ts +1 -1
  320. package/test/reference-links.test.ts +56 -0
  321. package/test/retry-plan.test.ts +139 -0
  322. package/test/sweep-dispatch.test.ts +191 -0
  323. package/test/twin-event-keys.test.ts +20 -6
  324. package/test/twin-model-item-db.test.ts +6 -6
  325. package/test/twin-model-tree-db.test.ts +6 -6
  326. package/tsconfig.shared.tsbuildinfo +1 -1
  327. package/tsconfig.tsbuildinfo +1 -1
@@ -8,6 +8,7 @@ exports.databaseCommandStore = databaseCommandStore;
8
8
  * 서지 않는다. 그러면 **게이트의 배선을 아무 시험도 걷지 않게 된다** — 훅에서 이미 겪은 부류다
9
9
  * (§`hook-store.ts`).
10
10
  */
11
+ const typeorm_1 = require("typeorm");
11
12
  const shell_1 = require("@things-factory/shell");
12
13
  const read_time_js_1 = require("../../engine/read-time.js");
13
14
  const twin_command_js_1 = require("./twin-command.js");
@@ -19,6 +20,10 @@ const twin_command_js_1 = require("./twin-command.js");
19
20
  */
20
21
  function toCommand(repo, row) {
21
22
  return {
23
+ ...(row.approvalRef ? { approvalRef: row.approvalRef } : {}),
24
+ ...(row.proposedBy ? { proposedBy: row.proposedBy } : {}),
25
+ ...(row.causeRef ? { causeRef: row.causeRef } : {}),
26
+ ...(row.ruleRef ? { ruleRef: row.ruleRef } : {}),
22
27
  id: row.id,
23
28
  instanceId: row.instanceId,
24
29
  type: row.type,
@@ -30,26 +35,184 @@ function toCommand(repo, row) {
30
35
  state: row.state,
31
36
  ...(row.decidedBy ? { approval: { by: row.decidedBy, at: row.decidedAt ?? '', ...(row.decisionNote ? { note: row.decisionNote } : {}) } } : {}),
32
37
  ...(row.dispatchRef ? { dispatchRef: row.dispatchRef } : {}),
38
+ ...(row.dispatchNote ? { dispatchNote: row.dispatchNote } : {}),
39
+ /* 셋이 다 있어야 효과다 — 하나라도 비면 적지 않는다(빈 값을 사실로 만들지 않는다). */
40
+ ...(row.effectStatus && row.effectAt
41
+ ? { effect: { status: row.effectStatus, at: row.effectAt, seen: row.effectSeen ?? 1 } }
42
+ : {}),
33
43
  ...(row.error ? { error: row.error } : {})
34
44
  };
35
45
  }
36
- /** 실제 저장소를 쓰는 구현 — 리졸버가 이것을 넣는다. */
37
- function databaseCommandStore() {
46
+ /**
47
+ * 실제 저장소를 쓰는 구현 — 리졸버가 이것을 넣는다.
48
+ *
49
+ * **트랜잭션을 받는다.** `getRepository(target)` 를 인자 없이 부르면 요청의 트랜잭션에 붙지 않고
50
+ * 별도로 커밋된다(`shell/initializers/database.ts:47`). 그러면 「올리고 적기」가 한 덩어리가 아니게 되고,
51
+ * 뒤가 실패했을 때 앞이 남는다 — 결재선에는 없는데 커맨드는 적혀 직접 결정이 열린 상태다.
52
+ *
53
+ * 콜백에서 부를 때도 넘긴다 — 업무 목록의 판정과 우리 커맨드가 같이 커밋되거나 같이 물러나야 한다.
54
+ */
55
+ function databaseCommandStore(tx) {
38
56
  return {
39
57
  load: async (domainId, id) => {
40
- const repo = await (0, shell_1.getRepository)(twin_command_js_1.TwinCommandRecord);
58
+ const repo = await (0, shell_1.getRepository)(twin_command_js_1.TwinCommandRecord, tx);
41
59
  const row = await repo.findOne({ where: { domain: { id: domainId }, id } }).catch(() => null);
42
60
  return row ? toCommand(repo, row) : null;
43
61
  },
62
+ /**
63
+ * 결정을 기다리는 것들의 **근거** — 규칙이 같은 조건을 두 번 올리지 않게.
64
+ *
65
+ * 저장소에서 읽는다. 프로세스 기억에 두면 재기동이 곧 중복이 된다 — 다시 선 트윈이 같은 조건을 처음
66
+ * 보는 것으로 읽고 대기 줄에 같은 지시를 또 올린다.
67
+ *
68
+ * `proposed` 만 본다 — 결정을 기다리는 것은 시간과 무관하게 막는다. 결정이 끝난 것을 언제 다시 낼지는
69
+ * 이것이 아니라 **기다리는 시간**이 정한다(§`lastProposedAt`).
70
+ */
71
+ openCauses: async (domainId, instanceId) => {
72
+ const repo = await (0, shell_1.getRepository)(twin_command_js_1.TwinCommandRecord, tx);
73
+ const rows = await repo
74
+ .find({ where: { domain: { id: domainId }, instanceId, state: 'proposed' }, select: ['causeRef'] })
75
+ .catch(() => []);
76
+ return new Set(rows.map(r => r.causeRef).filter((v) => !!v));
77
+ },
78
+ /**
79
+ * 근거마다 **마지막으로 조치를 낸 시각** — 결정이 끝난 것까지 본다.
80
+ *
81
+ * `openCauses` 만으로는 부족하다. 승인하는 순간 그 커맨드가 대기에서 빠지고 조건은 그대로 서 있어서
82
+ * 다음 바퀴가 같은 것을 또 낸다. 실제로 그렇게 됐다 — 승인 한 번에 대기 줄이 다시 찼다.
83
+ */
84
+ lastProposedAt: async (domainId, instanceId) => {
85
+ const repo = await (0, shell_1.getRepository)(twin_command_js_1.TwinCommandRecord, tx);
86
+ const rows = await repo
87
+ .find({ where: { domain: { id: domainId }, instanceId }, select: ['causeRef', 'createdAt'] })
88
+ .catch(() => []);
89
+ const out = new Map();
90
+ for (const row of rows) {
91
+ if (!row.causeRef)
92
+ continue;
93
+ /* 드라이버가 준 값을 직접 날짜로 만들지 않는다 — 컬럼 정의를 보고 푼다. */
94
+ const at = (0, read_time_js_1.hydratedDate)(repo, 'createdAt', row.createdAt)?.getTime();
95
+ if (at === undefined || !Number.isFinite(at))
96
+ continue;
97
+ const prev = out.get(row.causeRef);
98
+ if (prev === undefined || at > prev)
99
+ out.set(row.causeRef, at);
100
+ }
101
+ return out;
102
+ },
103
+ /**
104
+ * 넘긴 지시의 식별자로 찾는다 — 한 번에 묶어서 묻는다.
105
+ *
106
+ * 유입은 초당 수천 건이 지나는 자리다. 관측 하나마다 질의를 붙이면 그 경로가 느려지고, 느려지면
107
+ * 훅이 밀린다. 그래서 묶음 하나에 질의 하나다.
108
+ */
109
+ byDispatchRef: async (domainId, refs) => {
110
+ const out = new Map();
111
+ if (!refs.length)
112
+ return out;
113
+ const repo = await (0, shell_1.getRepository)(twin_command_js_1.TwinCommandRecord, tx);
114
+ const rows = await repo
115
+ .find({ where: { domain: { id: domainId }, dispatchRef: (0, typeorm_1.In)([...new Set(refs)]) } })
116
+ .catch(() => []);
117
+ for (const row of rows) {
118
+ if (row.dispatchRef)
119
+ out.set(row.dispatchRef, toCommand(repo, row));
120
+ }
121
+ return out;
122
+ },
123
+ /**
124
+ * 효과를 기다리는 조치들 — 넘긴 식별자가 있는 것만.
125
+ *
126
+ * 효과가 이미 적힌 것도 낸다. 상태는 여러 번 바뀌고(초안 → 나감 → 진행 중), 마지막 것이 남아야
127
+ * 화면이 「지금 저쪽에서 무엇인가」에 답할 수 있다.
128
+ *
129
+ * 결정이 안 끝난 것과 실패한 것은 빼지 않는다 — 실패로 적혔는데 저쪽은 받았을 수 있고, 그때
130
+ * 식별자가 있으면 그 사실이 이 길로 드러난다.
131
+ */
132
+ awaitingEffect: async (domainId) => {
133
+ const repo = await (0, shell_1.getRepository)(twin_command_js_1.TwinCommandRecord, tx);
134
+ const rows = await repo
135
+ .find({ where: { domain: { id: domainId }, dispatchRef: (0, typeorm_1.Not)((0, typeorm_1.IsNull)()) } })
136
+ .catch(() => []);
137
+ return rows.map(row => toCommand(repo, row));
138
+ },
139
+ /*
140
+ * 넘길 차례를 기다리는 것들 — **두 상태만** 본다.
141
+ *
142
+ * `approved` 는 승인 직후 발신이 놓친 것(프로세스가 그 사이에 죽었다), `failed` 는 다시 해
143
+ * 볼 것이다. 그 밖의 상태는 일꾼의 일이 아니다 — `proposed` 는 승인을 기다리고 `acked` 는
144
+ * 종착이다.
145
+ *
146
+ * 물러섬은 여기서 걸러내지 않는다. 걸러내려면 시각 비교를 SQL 방언으로 써야 하고(이 저장소는
147
+ * 다섯 드라이버를 지난다), 판정은 이미 순수 함수가 한다(§`retryDecisionOf`). 대기 줄이 큰
148
+ * 현장이 오면 그때 인덱스와 함께 다시 본다.
149
+ */
150
+ pendingDispatch: async (domainId) => {
151
+ const repo = await (0, shell_1.getRepository)(twin_command_js_1.TwinCommandRecord, tx);
152
+ const rows = await repo
153
+ .find({ where: { domain: { id: domainId }, state: (0, typeorm_1.In)(['approved', 'failed']) } })
154
+ .catch(() => []);
155
+ return rows.map(row => toCommand(repo, row));
156
+ },
157
+ /*
158
+ * 효과만 적는다 — 상태·승인·오류는 건드리지 않는다. 관측이 조치의 수명에 손을 대는 길은 두지
159
+ * 않는다. 그리고 도메인으로 좁힌다 — 식별자는 시스템마다 겹칠 수 있다.
160
+ */
161
+ recordEffects: async (domainId, updates) => {
162
+ const repo = await (0, shell_1.getRepository)(twin_command_js_1.TwinCommandRecord, tx);
163
+ for (const u of updates) {
164
+ await repo.update({ id: u.commandId, domain: { id: domainId } }, { effectStatus: u.status, effectAt: u.at, effectSeen: u.seen });
165
+ }
166
+ },
167
+ /*
168
+ * **근거는 여기서 안 적는다.** `causeRef`·`ruleRef` 는 낼 때 정해지고 바뀌지 않는다. 이 목록에 넣으면
169
+ * 상태가 바뀔 때마다 다시 쓰이고, 부르는 쪽이 그 칸을 빼먹은 날 **근거가 지워진다** — 그러면 중복
170
+ * 판정이 그 커맨드를 못 보고 같은 지시를 또 올린다.
171
+ */
44
172
  save: async (domainId, command) => {
45
- await (0, shell_1.getRepository)(twin_command_js_1.TwinCommandRecord).update({ id: command.id, domain: { id: domainId } }, {
173
+ await (0, shell_1.getRepository)(twin_command_js_1.TwinCommandRecord, tx).update({ id: command.id, domain: { id: domainId } }, {
46
174
  state: command.state,
175
+ approvalRef: command.approvalRef ?? null,
47
176
  decidedBy: command.approval?.by ?? null,
48
177
  decidedAt: command.approval?.at ?? null,
49
178
  decisionNote: command.approval?.note ?? null,
50
179
  dispatchRef: command.dispatchRef ?? null,
51
- error: command.error ?? null
180
+ dispatchNote: command.dispatchNote ?? null,
181
+ dispatchNoteNext: command.dispatchNoteNext ?? null,
182
+ error: command.error ?? null,
183
+ /* 일꾼이 쓰는 셋 — 없으면 물러섬이 표에 안 남고 재기동마다 즉시 재시도가 된다. */
184
+ retry: command.retry ?? null,
185
+ attempts: command.attempts ?? null,
186
+ nextAttemptAt: command.nextAttemptAt ?? null
187
+ });
188
+ },
189
+ /*
190
+ * 낼 때는 커맨드가 든 것을 그대로 적는다.
191
+ *
192
+ * `proposedAt` 은 옮기지 않는다 — 그 값은 `createdAt` 에서 읽으므로(§`toCommand`), 여기서 또 적으면
193
+ * 같은 사실이 두 칸에 살고 하나만 고쳐지는 날이 온다. 그리고 되돌려 줄 때는 **적힌 행을 다시 읽는다**
194
+ * — 낸 값을 그대로 답하면 드라이버가 실제로 무엇을 적었는지 모른 채로 사람에게 보여 준다.
195
+ */
196
+ create: async (domainId, command) => {
197
+ const repo = await (0, shell_1.getRepository)(twin_command_js_1.TwinCommandRecord, tx);
198
+ const saved = await repo.save({
199
+ id: command.id,
200
+ domain: { id: domainId },
201
+ instanceId: command.instanceId,
202
+ type: command.type,
203
+ payload: command.payload ?? null,
204
+ origin: command.origin,
205
+ proposedBy: command.proposedBy ?? null,
206
+ causeRef: command.causeRef ?? null,
207
+ ruleRef: command.ruleRef ?? null,
208
+ reversible: command.reversible ?? null,
209
+ state: command.state,
210
+ approvalRef: command.approvalRef ?? null
52
211
  });
212
+ const row = await repo.findOne({ where: { domain: { id: domainId }, id: saved.id } });
213
+ if (!row)
214
+ throw new Error(`커맨드를 적었는데 다시 읽을 수 없다 (${saved.id})`);
215
+ return toCommand(repo, row);
53
216
  }
54
217
  };
55
218
  }
@@ -1 +1 @@
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 { 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 } from './command-dispatcher.js'\n\n/**\n * 행 → 커맨드. 상태 어휘는 계약이 정한 문자열 그대로다.\n *\n * `createdAt` 은 드라이버가 준 값이라 **직접 날짜로 만들지 않는다** — 드라이버마다 모양이 다르고,\n * 문자열로 오는 드라이버에서는 시간대가 없는 값이 된다. `hydratedDate` 가 그 컬럼의 정의를 보고 푼다.\n */\nfunction toCommand(repo: any, row: TwinCommandRecord): TwinCommand {\n return {\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.error ? { error: row.error } : {})\n }\n}\n\n/** 실제 저장소를 쓰는 구현 — 리졸버가 이것을 넣는다. */\nexport function databaseCommandStore(): CommandStore {\n return {\n load: async (domainId: string, id: string) => {\n const repo = await getRepository(TwinCommandRecord)\n const row = await repo.findOne({ where: { domain: { id: domainId }, id } }).catch(() => null)\n return row ? toCommand(repo, row) : null\n },\n save: async (domainId: string, command: TwinCommand) => {\n await getRepository(TwinCommandRecord).update(\n { id: command.id, domain: { id: domainId } },\n {\n state: command.state,\n decidedBy: command.approval?.by ?? null,\n decidedAt: command.approval?.at ?? null,\n decisionNote: command.approval?.note ?? null,\n dispatchRef: command.dispatchRef ?? null,\n error: command.error ?? null\n } as any\n )\n }\n }\n}\n"]}
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"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 승인이 확정된 조치를 **커밋 뒤에** 현장으로 넘긴다.
3
+ *
4
+ * 부르는 쪽은 기다리지 않는다(`void`). 결재의 성공은 넘김의 성공에 달려 있지 않다 — 어댑터가
5
+ * 죽어 있어도 승인은 남아 있어야 하고, 그것이 승인과 넘김을 가른 이유다.
6
+ *
7
+ * **던지지 않는다.** 이 함수는 요청 밖에서 돌기 때문에, 던지면 처리되지 않은 거절이 되어
8
+ * 프로세스를 내린다. 넘기다 생긴 실패는 `dispatch` 가 `failed` 로 앉히고, 그 위의 예외(상태가
9
+ * 맞지 않음·커맨드가 사라짐)만 여기서 받아 적는다.
10
+ */
11
+ export declare function dispatchAfterApproval(domainId: string, commandId: string): void;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dispatchAfterApproval = dispatchAfterApproval;
4
+ /*
5
+ * **승인 다음에 넘긴다** — 승인 도장과 실제 발신 사이의 한 걸음.
6
+ *
7
+ * ── 무엇이 없었나 (2026-09-04) ──────────────────────────────────────────────
8
+ * 사람이 결재함에서 승인하면 커맨드가 `approved` 로 앉고 **거기서 멈췄다.** 그것을 집어 현장에
9
+ * 보내는 것이 아무것도 없었다 — 규칙 타이머는 조치를 *내는* 일만 하고, 화면에는 넘기기 단추가
10
+ * 없다. 폐루프를 라이브로 확인한 것도 사람이 뮤테이션을 손으로 불러서였다.
11
+ *
12
+ * 그래서 승인은 아무 데도 닿지 않았고, 현장은 오지 않는 지시를 기다렸다. 오류는 나지 않는다.
13
+ *
14
+ * ── 왜 승인 트랜잭션 안에서 보내지 않나 ─────────────────────────────────────
15
+ * 승인은 업무 목록의 트랜잭션 안에서 일어난다. 그 안에서 현장에 요청을 쏘면 두 가지가 깨진다.
16
+ *
17
+ * ① 롤백이 되돌리지 못한다. 공장에는 작업지시가 생겼는데 우리 장부는 승인을 취소한 상태가 된다.
18
+ * **네트워크로 나간 것은 트랜잭션이 못 되돌린다.**
19
+ * ② 트랜잭션이 원격 응답을 기다리는 동안 열려 있다. 저쪽이 느리면 결재 화면이 함께 멈춘다.
20
+ *
21
+ * 그래서 **커밋 다음으로 미룬다.** 미루면 「미룬 사이에 롤백됐으면?」이 남는데, 그것은
22
+ * `dispatch` 가 스스로 막는다 — 새 연결로 커맨드를 **다시 읽고** 상태를 확인한다. 롤백됐으면
23
+ * 승인 기록이 없어 그 자리에서 던진다(§`asApproved`). 우리가 따로 검사할 것이 없다.
24
+ *
25
+ * ── 정직한 한계 ─────────────────────────────────────────────────────────────
26
+ * 커밋과 발신 사이에 프로세스가 죽으면 그 커맨드는 `approved` 로 남는다. **잃지는 않지만 스스로
27
+ * 나가지도 않는다.** 그것을 사람이 볼 자리(「넘기지 못한 조치」 목록)와 다시 넘기는 길이 아직
28
+ * 없다 — 이 파일이 그 빈 자리를 메우지 않는다는 것을 여기 적어 둔다.
29
+ *
30
+ * 그리고 이 미룸은 **한 프로세스 안의 약속**이다. 여러 대로 늘리면 승인을 받은 프로세스가
31
+ * 보내는 것이 되고, 그 프로세스가 죽으면 위와 같다. 그때는 표를 보고 집어 가는 일꾼이 필요하다.
32
+ */
33
+ const log_js_1 = require("../../engine/log.js");
34
+ const command_store_js_1 = require("./command-store.js");
35
+ const command_dispatcher_js_1 = require("./command-dispatcher.js");
36
+ const actuation_routing_js_1 = require("../reference/actuation-routing.js");
37
+ /**
38
+ * 승인이 확정된 조치를 **커밋 뒤에** 현장으로 넘긴다.
39
+ *
40
+ * 부르는 쪽은 기다리지 않는다(`void`). 결재의 성공은 넘김의 성공에 달려 있지 않다 — 어댑터가
41
+ * 죽어 있어도 승인은 남아 있어야 하고, 그것이 승인과 넘김을 가른 이유다.
42
+ *
43
+ * **던지지 않는다.** 이 함수는 요청 밖에서 돌기 때문에, 던지면 처리되지 않은 거절이 되어
44
+ * 프로세스를 내린다. 넘기다 생긴 실패는 `dispatch` 가 `failed` 로 앉히고, 그 위의 예외(상태가
45
+ * 맞지 않음·커맨드가 사라짐)만 여기서 받아 적는다.
46
+ */
47
+ function dispatchAfterApproval(domainId, commandId) {
48
+ /*
49
+ * `setImmediate` 로 미룬다 — 지금 열린 트랜잭션이 커밋될 기회를 준다.
50
+ *
51
+ * 이것이 커밋을 **보장하지는 않는다.** 보장은 `dispatch` 의 다시 읽기가 한다(머리말). 여기서
52
+ * 미루는 것은 「대개 커밋된 뒤에 읽게 하려는」 것이고, 아니면 다시 읽기가 거절한다.
53
+ */
54
+ setImmediate(() => {
55
+ void (async () => {
56
+ try {
57
+ const settled = await (0, command_dispatcher_js_1.dispatch)((0, command_store_js_1.databaseCommandStore)(), domainId, commandId, command => (0, actuation_routing_js_1.routeActuation)(domainId, command.instanceId, command));
58
+ if (settled.state === 'failed') {
59
+ /* 실패는 결함이 아니다 — 저쪽이 안 받았거나 설정이 비었다는 사실이고, 행에 남는다. */
60
+ (0, log_js_1.twinWarn)(`[twin-actuation] 조치 ${commandId} 를 넘기지 못했다: ${settled.error ?? '(사유 없음)'}`);
61
+ }
62
+ }
63
+ catch (e) {
64
+ /*
65
+ * 여기 오는 것은 「넘길 수 없는 상태」다. 가장 흔한 원인은 **승인 트랜잭션이 롤백된 것**이고,
66
+ * 그때는 넘기지 않는 것이 맞다. 조용히 지나가지 않고 적는다 — 다른 원인이면 그것이 결함이다.
67
+ */
68
+ (0, log_js_1.twinWarn)(`[twin-actuation] 조치 ${commandId} 를 넘기지 않았다: ${String(e?.message ?? e)}`);
69
+ }
70
+ })();
71
+ });
72
+ }
73
+ //# sourceMappingURL=dispatch-after-approval.js.map
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1,53 @@
1
+ import type { ISOTime } from '@operato/ops-contract';
2
+ /** 관측 하나 — 오더 상태 보고에서 필요한 것만. */
3
+ export interface OrderObservation {
4
+ orderId: string;
5
+ status: string;
6
+ /** 이 보고를 관측한 시각. 없으면 이을 수 없다 — 언제인지 모르는 효과는 적지 않는다. */
7
+ at?: ISOTime;
8
+ }
9
+ /** 이을 대상 — 저장소가 `dispatchRef` 로 찾아 준 조치. */
10
+ export interface DispatchedCommand {
11
+ id: string;
12
+ dispatchRef: string;
13
+ /** 이미 적힌 효과가 있나 — 몇 번 보고됐는지를 이어서 센다. */
14
+ effect?: {
15
+ status: string;
16
+ at: ISOTime;
17
+ seen: number;
18
+ };
19
+ }
20
+ /** 적을 것 하나. */
21
+ export interface EffectUpdate {
22
+ commandId: string;
23
+ status: string;
24
+ at: ISOTime;
25
+ seen: number;
26
+ }
27
+ export interface EffectMatch {
28
+ updates: EffectUpdate[];
29
+ /** 이을 수 없던 관측과 그 이유 — 조용히 버리지 않는다. */
30
+ skips: {
31
+ orderId: string;
32
+ reason: string;
33
+ }[];
34
+ }
35
+ /**
36
+ * 한 번에 여러 관측을 잇는다.
37
+ *
38
+ * **같은 오더가 한 묶음에 여러 번 올 수 있다.** 그때는 마지막 것이 남고 `seen` 이 그만큼 는다 —
39
+ * 중간 상태를 덮어쓰는 것이 맞다(화면이 묻는 것은 「지금 저쪽에서 무엇인가」다). 다만 몇 번 바뀌었는지는
40
+ * 남긴다. 한 번도 안 바뀐 것과 다섯 번 바뀐 것은 다른 일이다.
41
+ */
42
+ export declare function matchEffects(args: {
43
+ observations: readonly OrderObservation[];
44
+ /** `dispatchRef` → 그 지시를 낸 조치. 저장소가 채운다. */
45
+ byRef: ReadonlyMap<string, DispatchedCommand>;
46
+ }): EffectMatch;
47
+ /**
48
+ * 이 묶음에서 **찾아봐야 할 오더 식별자**만 골라낸다 — 저장소에 물을 것을 줄인다.
49
+ *
50
+ * 관측 한 묶음이 수백 건일 수 있고 그중 우리가 낸 것은 몇 건이다. 전부를 하나씩 물으면 유입 경로에
51
+ * 질의가 그만큼 붙는다 — 유입은 초당 수천 건이 지나는 자리다.
52
+ */
53
+ export declare function refsToLookUp(observations: readonly OrderObservation[]): string[];
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.matchEffects = matchEffects;
4
+ exports.refsToLookUp = refsToLookUp;
5
+ /**
6
+ * 한 번에 여러 관측을 잇는다.
7
+ *
8
+ * **같은 오더가 한 묶음에 여러 번 올 수 있다.** 그때는 마지막 것이 남고 `seen` 이 그만큼 는다 —
9
+ * 중간 상태를 덮어쓰는 것이 맞다(화면이 묻는 것은 「지금 저쪽에서 무엇인가」다). 다만 몇 번 바뀌었는지는
10
+ * 남긴다. 한 번도 안 바뀐 것과 다섯 번 바뀐 것은 다른 일이다.
11
+ */
12
+ function matchEffects(args) {
13
+ const { observations, byRef } = args;
14
+ const updates = new Map();
15
+ const skips = [];
16
+ for (const ob of observations) {
17
+ const orderId = String(ob?.orderId ?? '').trim();
18
+ if (!orderId) {
19
+ skips.push({ orderId: '', reason: '오더 식별자가 없는 보고 — 이을 수 없다' });
20
+ continue;
21
+ }
22
+ const command = byRef.get(orderId);
23
+ if (!command) {
24
+ /*
25
+ * 대개 이것이다 — 우리가 내지 않은 오더의 상태 보고. 저쪽 시스템은 자기 오더를 다 보고하고,
26
+ * 그중 우리가 낸 것은 일부다. **이유로 남기되 흔한 것임을 적어 둔다** — 이 줄이 쌓이는 것을
27
+ * 결함으로 읽으면 안 된다.
28
+ */
29
+ skips.push({ orderId, reason: '우리가 낸 지시가 아니다' });
30
+ continue;
31
+ }
32
+ const status = String(ob?.status ?? '').trim();
33
+ if (!status) {
34
+ /* 빈 상태를 적으면 「보고는 왔는데 아무 말도 안 했다」가 「효과 없음」과 같아 보인다. */
35
+ skips.push({ orderId, reason: '상태를 말하지 않은 보고 — 빈 값을 효과로 적지 않는다' });
36
+ continue;
37
+ }
38
+ const at = String(ob?.at ?? '').trim();
39
+ if (!at) {
40
+ /* 언제인지 모르는 효과는 적지 않는다 — 「지금」으로 채우면 관측 시각이 아니라 처리 시각이 된다. */
41
+ skips.push({ orderId, reason: '시각이 없는 보고 — 지금으로 채우지 않는다' });
42
+ continue;
43
+ }
44
+ /* 같은 묶음 안에서 앞서 센 것이 있으면 그것을 잇는다 — 없으면 적힌 효과에서 잇는다. */
45
+ const prior = updates.get(command.id)?.seen ?? command.effect?.seen ?? 0;
46
+ updates.set(command.id, { commandId: command.id, status, at, seen: prior + 1 });
47
+ }
48
+ return { updates: [...updates.values()], skips };
49
+ }
50
+ /**
51
+ * 이 묶음에서 **찾아봐야 할 오더 식별자**만 골라낸다 — 저장소에 물을 것을 줄인다.
52
+ *
53
+ * 관측 한 묶음이 수백 건일 수 있고 그중 우리가 낸 것은 몇 건이다. 전부를 하나씩 물으면 유입 경로에
54
+ * 질의가 그만큼 붙는다 — 유입은 초당 수천 건이 지나는 자리다.
55
+ */
56
+ function refsToLookUp(observations) {
57
+ const out = new Set();
58
+ for (const ob of observations) {
59
+ const orderId = String(ob?.orderId ?? '').trim();
60
+ if (orderId)
61
+ out.add(orderId);
62
+ }
63
+ return [...out];
64
+ }
65
+ //# sourceMappingURL=effect-match.js.map
@@ -0,0 +1 @@
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,6 +1,25 @@
1
+ import { TwinActuationRule } from './actuation-rule.js';
2
+ import { TwinActuationRuleResolver } from './actuation-rule-resolver.js';
1
3
  import { TwinCommandRecord } from './twin-command.js';
2
- export declare const entities: (typeof TwinCommandRecord)[];
3
- export declare const resolvers: any[];
4
+ import { TwinCommandResolver } from './twin-command-resolver.js';
5
+ export declare const entities: (typeof TwinCommandRecord | typeof TwinActuationRule)[];
6
+ export declare const resolvers: (typeof TwinActuationRuleResolver | typeof TwinCommandResolver)[];
4
7
  export * from './twin-command.js';
8
+ export * from './actuation-rule.js';
9
+ export * from './approval-gateway.js';
10
+ export * from './dispatch-after-approval.js';
11
+ export * from './actuation-outcome.js';
12
+ export * from './retry-plan.js';
13
+ export * from './sweep-dispatch.js';
14
+ export * from './actuation-advisor.js';
5
15
  export * from './command-dispatcher.js';
6
16
  export * from './command-store.js';
17
+ export * from './twin-now.js';
18
+ export * from './effect-match.js';
19
+ export * from './settle-effects.js';
20
+ export * from './catch-up-effects.js';
21
+ export * from './rule-evaluate.js';
22
+ export * from './rule-runner.js';
23
+ export * from './rule-loop.js';
24
+ export * from './twin-command-resolver.js';
25
+ export * from './actuation-rule-resolver.js';
@@ -2,10 +2,29 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolvers = exports.entities = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const actuation_rule_js_1 = require("./actuation-rule.js");
6
+ const actuation_rule_resolver_js_1 = require("./actuation-rule-resolver.js");
5
7
  const twin_command_js_1 = require("./twin-command.js");
6
- exports.entities = [twin_command_js_1.TwinCommandRecord];
7
- exports.resolvers = [];
8
+ const twin_command_resolver_js_1 = require("./twin-command-resolver.js");
9
+ exports.entities = [twin_command_js_1.TwinCommandRecord, actuation_rule_js_1.TwinActuationRule];
10
+ exports.resolvers = [twin_command_resolver_js_1.TwinCommandResolver, actuation_rule_resolver_js_1.TwinActuationRuleResolver];
8
11
  tslib_1.__exportStar(require("./twin-command.js"), exports);
12
+ tslib_1.__exportStar(require("./actuation-rule.js"), exports);
13
+ tslib_1.__exportStar(require("./approval-gateway.js"), exports);
14
+ tslib_1.__exportStar(require("./dispatch-after-approval.js"), exports);
15
+ tslib_1.__exportStar(require("./actuation-outcome.js"), exports);
16
+ tslib_1.__exportStar(require("./retry-plan.js"), exports);
17
+ tslib_1.__exportStar(require("./sweep-dispatch.js"), exports);
18
+ tslib_1.__exportStar(require("./actuation-advisor.js"), exports);
9
19
  tslib_1.__exportStar(require("./command-dispatcher.js"), exports);
10
20
  tslib_1.__exportStar(require("./command-store.js"), exports);
21
+ tslib_1.__exportStar(require("./twin-now.js"), exports);
22
+ tslib_1.__exportStar(require("./effect-match.js"), exports);
23
+ tslib_1.__exportStar(require("./settle-effects.js"), exports);
24
+ tslib_1.__exportStar(require("./catch-up-effects.js"), exports);
25
+ tslib_1.__exportStar(require("./rule-evaluate.js"), exports);
26
+ tslib_1.__exportStar(require("./rule-runner.js"), exports);
27
+ tslib_1.__exportStar(require("./rule-loop.js"), exports);
28
+ tslib_1.__exportStar(require("./twin-command-resolver.js"), exports);
29
+ tslib_1.__exportStar(require("./actuation-rule-resolver.js"), exports);
11
30
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/actuation/index.ts"],"names":[],"mappings":";;;;AAAA,uDAAqD;AAExC,QAAA,QAAQ,GAAG,CAAC,mCAAiB,CAAC,CAAA;AAC9B,QAAA,SAAS,GAAG,EAAE,CAAA;AAE3B,4DAAiC;AACjC,kEAAuC;AACvC,6DAAkC","sourcesContent":["import { TwinCommandRecord } from './twin-command.js'\n\nexport const entities = [TwinCommandRecord]\nexport const resolvers = []\n\nexport * from './twin-command.js'\nexport * from './command-dispatcher.js'\nexport * from './command-store.js'\n"]}
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"]}