@things-factory/headless-twin 10.0.18 → 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 (331) 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 +19 -16
  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 +230 -0
  58. package/dist-server/service/actuation/command-dispatcher.js +269 -0
  59. package/dist-server/service/actuation/command-dispatcher.js.map +1 -0
  60. package/dist-server/service/actuation/command-store.d.ts +11 -0
  61. package/dist-server/service/actuation/command-store.js +219 -0
  62. package/dist-server/service/actuation/command-store.js.map +1 -0
  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 +25 -0
  70. package/dist-server/service/actuation/index.js +30 -0
  71. package/dist-server/service/actuation/index.js.map +1 -0
  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 +111 -0
  97. package/dist-server/service/actuation/twin-command.js +170 -0
  98. package/dist-server/service/actuation/twin-command.js.map +1 -0
  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 +28 -17
  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 +149 -22
  130. package/dist-server/service/reference/hook-contract.js +236 -23
  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/hook-store.d.ts +3 -0
  136. package/dist-server/service/reference/hook-store.js +28 -0
  137. package/dist-server/service/reference/hook-store.js.map +1 -0
  138. package/dist-server/service/reference/index.d.ts +4 -1
  139. package/dist-server/service/reference/index.js +5 -1
  140. package/dist-server/service/reference/index.js.map +1 -1
  141. package/dist-server/service/reference/live-cadence.d.ts +68 -0
  142. package/dist-server/service/reference/live-cadence.js +44 -0
  143. package/dist-server/service/reference/live-cadence.js.map +1 -0
  144. package/dist-server/service/reference/live-feed-lease.d.ts +15 -0
  145. package/dist-server/service/reference/live-feed-lease.js +56 -0
  146. package/dist-server/service/reference/live-feed-lease.js.map +1 -0
  147. package/dist-server/service/reference/live-ingest.d.ts +21 -0
  148. package/dist-server/service/reference/live-ingest.js +66 -0
  149. package/dist-server/service/reference/live-ingest.js.map +1 -0
  150. package/dist-server/service/reference/reference-adapter.d.ts +355 -2
  151. package/dist-server/service/reference/reference-adapter.js +19 -1
  152. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  153. package/dist-server/service/reference/reference-fill-run.d.ts +50 -0
  154. package/dist-server/service/reference/reference-fill-run.js +142 -0
  155. package/dist-server/service/reference/reference-fill-run.js.map +1 -0
  156. package/dist-server/service/reference/reference-fill.d.ts +90 -0
  157. package/dist-server/service/reference/reference-fill.js +141 -0
  158. package/dist-server/service/reference/reference-fill.js.map +1 -0
  159. package/dist-server/service/reference/reference-hook.d.ts +64 -6
  160. package/dist-server/service/reference/reference-hook.js +265 -27
  161. package/dist-server/service/reference/reference-hook.js.map +1 -1
  162. package/dist-server/service/reference/reference-link-groups.d.ts +10 -0
  163. package/dist-server/service/reference/reference-link-groups.js +15 -0
  164. package/dist-server/service/reference/reference-link-groups.js.map +1 -0
  165. package/dist-server/service/reference/reference-links.d.ts +14 -0
  166. package/dist-server/service/reference/reference-links.js +73 -0
  167. package/dist-server/service/reference/reference-links.js.map +1 -0
  168. package/dist-server/service/reference/reference-live.d.ts +1 -0
  169. package/dist-server/service/reference/reference-live.js +32 -6
  170. package/dist-server/service/reference/reference-live.js.map +1 -1
  171. package/dist-server/service/reference/reference-master.d.ts +56 -2
  172. package/dist-server/service/reference/reference-master.js +33 -3
  173. package/dist-server/service/reference/reference-master.js.map +1 -1
  174. package/dist-server/service/reference/reference-resolver.d.ts +27 -0
  175. package/dist-server/service/reference/reference-resolver.js +79 -1
  176. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  177. package/dist-server/service/twin-event/twin-event-keys.d.ts +31 -3
  178. package/dist-server/service/twin-event/twin-event-keys.js +36 -4
  179. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  180. package/dist-server/service/twin-event/twin-event.d.ts +1 -0
  181. package/dist-server/service/twin-event/twin-event.js +11 -2
  182. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  183. package/dist-server/service/twin-model/choose-recipe.d.ts +15 -0
  184. package/dist-server/service/twin-model/choose-recipe.js +33 -0
  185. package/dist-server/service/twin-model/choose-recipe.js.map +1 -0
  186. package/dist-server/service/twin-model/external-resolver.d.ts +76 -0
  187. package/dist-server/service/twin-model/external-resolver.js +109 -0
  188. package/dist-server/service/twin-model/external-resolver.js.map +1 -0
  189. package/dist-server/service/twin-model/project-structure.js +16 -3
  190. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  191. package/dist-server/service/twin-model/twin-equipment.d.ts +17 -0
  192. package/dist-server/service/twin-model/twin-equipment.js +10 -0
  193. package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
  194. package/dist-server/service/twin-model/twin-lineage-query.js +75 -1
  195. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  196. package/dist-server/service/twin-model/twin-model-item-query.js +94 -5
  197. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  198. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  199. package/dist-server/service/twin-space/twin-space.d.ts +29 -0
  200. package/dist-server/service/twin-space/twin-space.js +13 -0
  201. package/dist-server/service/twin-space/twin-space.js.map +1 -1
  202. package/dist-server/service/twin-subject/event-subjects.d.ts +3 -1
  203. package/dist-server/service/twin-subject/event-subjects.js +8 -1
  204. package/dist-server/service/twin-subject/event-subjects.js.map +1 -1
  205. package/dist-shared/entity-delta.js +1 -1
  206. package/dist-shared/entity-delta.js.map +1 -1
  207. package/package.json +7 -7
  208. package/server/engine/canonical-ingest.ts +119 -33
  209. package/server/engine/event-time.ts +96 -0
  210. package/server/engine/index.ts +20 -0
  211. package/server/engine/ingest-dedupe.ts +203 -27
  212. package/server/engine/ingest-health.ts +91 -6
  213. package/server/engine/kpi-fold.ts +24 -1
  214. package/server/engine/kpi-query.ts +23 -4
  215. package/server/engine/oee-accumulator.ts +80 -8
  216. package/server/engine/twin-engine.ts +119 -5
  217. package/server/index.ts +23 -0
  218. package/server/migrations/1786600000000-RenameOperatoMesAdapterType.ts +66 -0
  219. package/server/migrations/1786700000000-AddDispatchRetryColumns.ts +82 -0
  220. package/server/migrations/index.ts +5 -1
  221. package/server/routes.ts +19 -20
  222. package/server/service/actuation/actuation-advisor.ts +124 -0
  223. package/server/service/actuation/actuation-outcome.ts +97 -0
  224. package/server/service/actuation/actuation-rule-resolver.ts +147 -0
  225. package/server/service/actuation/actuation-rule.ts +139 -0
  226. package/server/service/actuation/approval-gateway.ts +44 -0
  227. package/server/service/actuation/catch-up-effects.ts +152 -0
  228. package/server/service/actuation/command-dispatcher.ts +511 -0
  229. package/server/service/actuation/command-store.ts +236 -0
  230. package/server/service/actuation/dispatch-after-approval.ts +71 -0
  231. package/server/service/actuation/effect-match.ts +124 -0
  232. package/server/service/actuation/index.ts +27 -0
  233. package/server/service/actuation/retry-plan.ts +145 -0
  234. package/server/service/actuation/rule-evaluate.ts +352 -0
  235. package/server/service/actuation/rule-loop.ts +160 -0
  236. package/server/service/actuation/rule-runner.ts +216 -0
  237. package/server/service/actuation/rule-wiring.ts +84 -0
  238. package/server/service/actuation/settle-effects.ts +115 -0
  239. package/server/service/actuation/sweep-dispatch.ts +179 -0
  240. package/server/service/actuation/twin-command-resolver.ts +387 -0
  241. package/server/service/actuation/twin-command.ts +232 -0
  242. package/server/service/actuation/twin-now.ts +97 -0
  243. package/server/service/index.ts +11 -0
  244. package/server/service/reference/actuation-routing.ts +165 -0
  245. package/server/service/reference/actuation-target.ts +163 -0
  246. package/server/service/reference/connection-portability-resolver.ts +171 -0
  247. package/server/service/reference/connection-portability.ts +285 -0
  248. package/server/service/reference/fill-cadence.ts +29 -0
  249. package/server/service/reference/fill-dedupe.ts +66 -0
  250. package/server/service/reference/fill-loop.ts +153 -0
  251. package/server/service/reference/fill-span.ts +31 -0
  252. package/server/service/reference/hook-contract.ts +280 -22
  253. package/server/service/reference/hook-rejected.ts +53 -0
  254. package/server/service/reference/hook-store.ts +28 -0
  255. package/server/service/reference/index.ts +5 -1
  256. package/server/service/reference/live-cadence.ts +129 -0
  257. package/server/service/reference/live-feed-lease.ts +56 -0
  258. package/server/service/reference/live-ingest.ts +94 -0
  259. package/server/service/reference/reference-adapter.ts +392 -5
  260. package/server/service/reference/reference-fill-run.ts +165 -0
  261. package/server/service/reference/reference-fill.ts +223 -0
  262. package/server/service/reference/reference-hook.ts +355 -32
  263. package/server/service/reference/reference-link-groups.ts +21 -0
  264. package/server/service/reference/reference-links.ts +77 -0
  265. package/server/service/reference/reference-live.ts +30 -5
  266. package/server/service/reference/reference-master.ts +90 -5
  267. package/server/service/reference/reference-resolver.ts +77 -2
  268. package/server/service/twin-event/twin-event-keys.ts +37 -4
  269. package/server/service/twin-event/twin-event.ts +18 -1
  270. package/server/service/twin-model/choose-recipe.ts +31 -0
  271. package/server/service/twin-model/external-resolver.ts +161 -0
  272. package/server/service/twin-model/project-structure.ts +16 -3
  273. package/server/service/twin-model/twin-equipment.ts +22 -0
  274. package/server/service/twin-model/twin-lineage-query.ts +82 -1
  275. package/server/service/twin-model/twin-model-item-query.ts +95 -4
  276. package/server/service/twin-model/twin-model-tree-query.ts +1 -1
  277. package/server/service/twin-space/twin-space.ts +39 -0
  278. package/server/service/twin-subject/event-subjects.ts +8 -1
  279. package/shared/entity-delta.ts +1 -1
  280. package/test/ack-shape-alignment.test.ts +97 -0
  281. package/test/actuation-approval-door.test.ts +421 -0
  282. package/test/actuation-dispatch.test.ts +209 -0
  283. package/test/actuation-effect.test.ts +162 -0
  284. package/test/actuation-outcome.test.ts +152 -0
  285. package/test/actuation-rule-runner.test.ts +281 -0
  286. package/test/actuation-rule.test.ts +266 -0
  287. package/test/actuation-seam.test.ts +249 -0
  288. package/test/actuation-settle.test.ts +164 -0
  289. package/test/actuation-target.test.ts +145 -0
  290. package/test/canonical-ingest-vocabularies.test.ts +49 -2
  291. package/test/capability-mapping.test.ts +1 -1
  292. package/test/choose-recipe.test.ts +117 -0
  293. package/test/command-store-writes-columns.test.ts +126 -0
  294. package/test/connection-portability-doors.test.ts +87 -0
  295. package/test/connector-capability-declaration.test.ts +54 -4
  296. package/test/declaration-reaches-model.test.ts +43 -0
  297. package/test/dedupe-transformation.test.ts +112 -0
  298. package/test/entity-delta.test.ts +1 -1
  299. package/test/event-subjects.test.ts +2 -1
  300. package/test/external-resolver.test.ts +171 -0
  301. package/test/fill-dedupe.test.ts +59 -0
  302. package/test/fill-loop.test.ts +35 -0
  303. package/test/first-envelope.test.ts +153 -0
  304. package/test/fold-shift-dst.test.ts +110 -0
  305. package/test/home-in-area.test.ts +90 -0
  306. package/test/hook-rejected-shape.test.ts +86 -0
  307. package/test/hook-response-sum.test.ts +193 -0
  308. package/test/hook-sequence.test.ts +333 -0
  309. package/test/ingest-dedupe.test.ts +56 -0
  310. package/test/ingest-expected-quiet.test.ts +111 -0
  311. package/test/ingest-idempotent.test.ts +79 -0
  312. package/test/ingest-reconcile-four-ways.test.ts +97 -0
  313. package/test/ingest-rules-header.test.ts +100 -0
  314. package/test/ingest-window-rolls-unobserved.test.ts +72 -0
  315. package/test/intake-mapping-parity.test.ts +175 -0
  316. package/test/live-cadence.test.ts +170 -0
  317. package/test/live-feed-lease.test.ts +63 -0
  318. package/test/live-mirror-parity.test.ts +1 -1
  319. package/test/oee-accumulator.test.ts +3 -3
  320. package/test/oee-site-calendar.test.ts +146 -0
  321. package/test/order-and-transaction-split.test.ts +107 -0
  322. package/test/reference-fill.test.ts +235 -0
  323. package/test/reference-hook.test.ts +114 -23
  324. package/test/reference-links.test.ts +56 -0
  325. package/test/retry-plan.test.ts +139 -0
  326. package/test/sweep-dispatch.test.ts +191 -0
  327. package/test/twin-event-keys.test.ts +20 -6
  328. package/test/twin-model-item-db.test.ts +6 -6
  329. package/test/twin-model-tree-db.test.ts +6 -6
  330. package/tsconfig.shared.tsbuildinfo +1 -1
  331. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_PERIOD_MS = void 0;
4
+ exports.startActuationRuleLoop = startActuationRuleLoop;
5
+ exports.stopActuationRuleLoop = stopActuationRuleLoop;
6
+ /*
7
+ * 규칙을 주기로 돌리는 타이머 하나.
8
+ *
9
+ * ── 왜 커널 틱이 아닌가 ──────────────────────────────────────────────────
10
+ * `tickGuarded` 는 예외가 나면 **그 트윈을 멈춘다.** 규칙 하나가 잘못됐다고 공장이 멈추면 안 된다.
11
+ * 그리고 굶김 감시가 느린 틱 셋에 트윈을 내리는데, 규칙은 DB 를 두 번 건드리므로 그 감시에 걸린다.
12
+ *
13
+ * ── 왜 이 간격인가 ───────────────────────────────────────────────────────
14
+ * 규칙이 보는 것은 **오래 이어진 조건**이다. 「병목이 세 시간째」를 1초마다 다시 물을 이유가 없다.
15
+ * 짧게 잡으면 얻는 것 없이 DB 를 두 번씩 더 친다.
16
+ *
17
+ * ── 겹쳐 돌지 않는다 ─────────────────────────────────────────────────────
18
+ * 한 바퀴가 간격보다 오래 걸리면 다음 발화가 겹친다. 겹치면 두 바퀴가 같은 근거 집합을 읽고 **같은
19
+ * 조치를 두 벌 낸다** — 중복을 막는 장치가 그 순간 통과된다.
20
+ */
21
+ const ops_contract_1 = require("@operato/ops-contract");
22
+ const log_js_1 = require("../../engine/log.js");
23
+ const twin_engine_js_1 = require("../../engine/twin-engine.js");
24
+ const reference_adapter_js_1 = require("../reference/reference-adapter.js");
25
+ const actuation_routing_js_1 = require("../reference/actuation-routing.js");
26
+ const sweep_dispatch_js_1 = require("./sweep-dispatch.js");
27
+ const catch_up_effects_js_1 = require("./catch-up-effects.js");
28
+ const command_store_js_1 = require("./command-store.js");
29
+ const rule_runner_js_1 = require("./rule-runner.js");
30
+ const rule_wiring_js_1 = require("./rule-wiring.js");
31
+ /** 몇 초마다 도나. 오래 이어진 조건을 보는 일이라 촘촘할 이유가 없다. */
32
+ exports.RULE_PERIOD_MS = 30_000;
33
+ let timer;
34
+ let running = false;
35
+ /** 주기를 세운다 — 두 번 불러도 하나만 선다. */
36
+ function startActuationRuleLoop(periodMs = exports.RULE_PERIOD_MS) {
37
+ if (timer)
38
+ return;
39
+ timer = setInterval(() => {
40
+ /* 앞 바퀴가 아직 돈다 — 겹치면 같은 조치를 두 벌 낸다. 건너뛰는 것이 맞다. */
41
+ if (running)
42
+ return;
43
+ running = true;
44
+ tickRules()
45
+ .catch(err => (0, log_js_1.twinError)(`[twin-rule] 규칙 주기가 실패했다 — ${err?.message ?? err}`))
46
+ .finally(() => {
47
+ running = false;
48
+ });
49
+ }, periodMs);
50
+ /* 이 타이머 때문에 프로세스가 안 죽지 않게. */
51
+ if (typeof timer?.unref === 'function')
52
+ timer.unref();
53
+ }
54
+ function stopActuationRuleLoop() {
55
+ if (!timer)
56
+ return;
57
+ clearInterval(timer);
58
+ timer = undefined;
59
+ }
60
+ /**
61
+ * 일꾼이 한 바퀴에 무엇을 했나 — **바뀌었을 때만** 적는다.
62
+ *
63
+ * ── 왜 필요한가 (2026-09-04) ────────────────────────────────────────────────
64
+ * 처음에 보고를 받고 버렸다. 그러니 대기 줄에 조치가 앉아 있는데 아무 일도 안 일어날 때
65
+ * **이유를 알 길이 없었다** — 실제로 그 상태를 만나 30분을 뒤졌다. 집지 않는 것은 대개 옳은
66
+ * 판단이지만, 옳은 판단도 보이지 않으면 결함과 구별되지 않는다.
67
+ *
68
+ * ── 왜 바뀌었을 때만인가 ────────────────────────────────────────────────────
69
+ * 30초마다 같은 줄을 적으면 하루 2,880줄이고 그 사이의 진짜 변화가 묻힌다. 대기 줄의 모양이
70
+ * 그대로면 새로 말할 것이 없다 — `noteBlocked` 에서 지킨 것과 같은 규율이다.
71
+ */
72
+ const lastSweep = new Map();
73
+ function noteSweep(domainId, report) {
74
+ if (!report.seen) {
75
+ lastSweep.delete(domainId);
76
+ return;
77
+ }
78
+ const skips = Object.entries(report.skipped)
79
+ .sort(([a], [b]) => a.localeCompare(b))
80
+ .map(([why, n]) => `${why} ${n}`)
81
+ .join(' · ');
82
+ const line = `${report.seen}건 대기 · 넘김 ${report.dispatched}${skips ? ` · ${skips}` : ''}`;
83
+ if (lastSweep.get(domainId) === line)
84
+ return;
85
+ lastSweep.set(domainId, line);
86
+ (0, log_js_1.twinLog)(`[twin-sweep] ${line}`);
87
+ }
88
+ async function tickRules() {
89
+ /*
90
+ * ── 유입 창을 먼저 굴린다 (2026-09-05) ─────────────────────────────────────
91
+ * 창이 닫히는 이유는 **시간이 지난 것**이다. 그런데 예전에는 「다음 사건이 왔을 때」와 「누가
92
+ * 물어볼 때」만 굴렀다 — 둘 다 안 올 수 있다.
93
+ *
94
+ * 그래서 사실이 드문 트윈은 표에 아무것도 안 남았다. 실측: 46건이 62분에 흩어져 온 트윈의 창이
95
+ * 1개, 사실 11건뿐인 트윈의 창이 48개 — **사실 수와 창 수가 거꾸로**였다. 창을 만든 것이 유입이
96
+ * 아니라 누가 물어본 것이었기 때문이다.
97
+ *
98
+ * **아무도 안 보는 트윈일수록 기록이 없었다.** 사실이 조용히 사라지는 것을 잡으려는 장부가 정확히
99
+ * 그 반대로 돌고 있었다(137봉투가 장부 없이 지나갔다 — 인티그레이션 레인 실측).
100
+ *
101
+ * 소유한 트윈이 없어도 굴린다 — 장부는 프로세스가 사는 동안 이어지고, 규칙을 돌 트윈이 없다는
102
+ * 것과 기록할 창이 없다는 것은 다른 사실이다.
103
+ */
104
+ const rolled = twin_engine_js_1.TwinEngine.rollAllIngestWindows();
105
+ if (rolled)
106
+ (0, log_js_1.twinLog)(`[twin-ingest] 창 ${rolled}개를 닫아 적었다`);
107
+ const owned = twin_engine_js_1.TwinEngine.ownedTwins();
108
+ if (!owned.length)
109
+ return;
110
+ /*
111
+ * 트랜잭션을 넘기지 않는다 — 여러 트윈을 돌고, 한 트윈이 실패해도 앞의 트윈이 낸 것은 남아야 한다.
112
+ * 한 덩어리로 묶으면 마지막 트윈의 실패가 앞의 결과를 전부 물린다.
113
+ */
114
+ /*
115
+ * **놓친 보고를 함께 backfill 한다** — 관측 → 조치의 반대 방향이다.
116
+ *
117
+ * 같은 주기를 쓰는 이유는 값이다: 둘 다 몇 초마다 볼 일이고, 타이머를 둘 두면 내리고 세울 자리가
118
+ * 둘이 된다. 다만 **서로 막지 않는다** — 규칙이 터져도 backfill 은 돌아야 한다. 조치를 못 내는 것과
119
+ * 나간 조치의 결과를 모르는 것은 다른 사고다.
120
+ */
121
+ for (const domainId of new Set(owned.map(t => t.domainId))) {
122
+ await (0, catch_up_effects_js_1.catchUpEffects)(domainId, (0, command_store_js_1.databaseCommandStore)());
123
+ /*
124
+ * **넘길 차례를 기다리는 것들도 함께 줍는다** — 승인만 되고 안 나간 것, 실패해서 다시 해 볼 것.
125
+ *
126
+ * 정상 경로는 승인 직후에 이미 끝난다(§`dispatchAfterApproval`, 실측 282ms). 이 자리는 **그
127
+ * 사이에 프로세스가 죽은 경우**와 재시도만 본다. 그래서 30초로 충분하고 아무도 안 기다린다.
128
+ */
129
+ const swept = await (0, sweep_dispatch_js_1.sweepPendingDispatch)(domainId, {
130
+ store: (0, command_store_js_1.databaseCommandStore)(),
131
+ actuate: command => (0, actuation_routing_js_1.routeActuation)(domainId, command.instanceId, command),
132
+ idempotent: async (instanceId) => {
133
+ const target = await (0, actuation_routing_js_1.actuationTargetOf)(domainId, instanceId).catch(() => undefined);
134
+ const type = target?.adapterType;
135
+ return type ? (0, reference_adapter_js_1.capabilitiesOf)((0, reference_adapter_js_1.getAdapter)(type)).includes('idempotent-actuation') : false;
136
+ },
137
+ localReadiness: async (instanceId) => {
138
+ const r = await (0, actuation_routing_js_1.actuationReadinessOf)(domainId, instanceId).catch(() => undefined);
139
+ /* 원격으로 물어 본 답은 쓰지 않는다 — 이 자리는 저쪽을 건드리지 않는 것이 규약이다. */
140
+ return r && r.checked === 'local' ? r : undefined;
141
+ }
142
+ });
143
+ noteSweep(domainId, swept);
144
+ }
145
+ await (0, rule_runner_js_1.runRulesOnce)({
146
+ ownedTwins: () => owned,
147
+ rulesOf: rule_wiring_js_1.rulesOf,
148
+ attentionsOf: rule_wiring_js_1.attentionsOf,
149
+ modeOf: rule_wiring_js_1.modeOf,
150
+ store: (0, command_store_js_1.databaseCommandStore)(),
151
+ specs: ops_contract_1.MES_COMMANDS,
152
+ nowMs: () => Date.now(),
153
+ noteRun: rule_wiring_js_1.noteRun
154
+ });
155
+ }
156
+ //# sourceMappingURL=rule-loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-loop.js","sourceRoot":"","sources":["../../../server/service/actuation/rule-loop.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 규칙을 주기로 돌리는 타이머 하나.\n *\n * ── 왜 커널 틱이 아닌가 ──────────────────────────────────────────────────\n * `tickGuarded` 는 예외가 나면 **그 트윈을 멈춘다.** 규칙 하나가 잘못됐다고 공장이 멈추면 안 된다.\n * 그리고 굶김 감시가 느린 틱 셋에 트윈을 내리는데, 규칙은 DB 를 두 번 건드리므로 그 감시에 걸린다.\n *\n * ── 왜 이 간격인가 ───────────────────────────────────────────────────────\n * 규칙이 보는 것은 **오래 이어진 조건**이다. 「병목이 세 시간째」를 1초마다 다시 물을 이유가 없다.\n * 짧게 잡으면 얻는 것 없이 DB 를 두 번씩 더 친다.\n *\n * ── 겹쳐 돌지 않는다 ─────────────────────────────────────────────────────\n * 한 바퀴가 간격보다 오래 걸리면 다음 발화가 겹친다. 겹치면 두 바퀴가 같은 근거 집합을 읽고 **같은\n * 조치를 두 벌 낸다** — 중복을 막는 장치가 그 순간 통과된다.\n */\nimport { MES_COMMANDS } from '@operato/ops-contract'\n\nimport { twinError, twinLog } from '../../engine/log.js'\nimport { TwinEngine } from '../../engine/twin-engine.js'\n\nimport { capabilitiesOf, getAdapter } from '../reference/reference-adapter.js'\nimport { actuationReadinessOf, actuationTargetOf, routeActuation } from '../reference/actuation-routing.js'\nimport { sweepPendingDispatch, type SweepReport } from './sweep-dispatch.js'\nimport { catchUpEffects } from './catch-up-effects.js'\nimport { databaseCommandStore } from './command-store.js'\nimport { runRulesOnce } from './rule-runner.js'\nimport { attentionsOf, modeOf, noteRun, rulesOf } from './rule-wiring.js'\n\n/** 몇 초마다 도나. 오래 이어진 조건을 보는 일이라 촘촘할 이유가 없다. */\nexport const RULE_PERIOD_MS = 30_000\n\nlet timer: any\nlet running = false\n\n/** 주기를 세운다 — 두 번 불러도 하나만 선다. */\nexport function startActuationRuleLoop(periodMs = RULE_PERIOD_MS): void {\n if (timer) return\n\n timer = setInterval(() => {\n /* 앞 바퀴가 아직 돈다 — 겹치면 같은 조치를 두 벌 낸다. 건너뛰는 것이 맞다. */\n if (running) return\n running = true\n\n tickRules()\n .catch(err => twinError(`[twin-rule] 규칙 주기가 실패했다 — ${err?.message ?? err}`))\n .finally(() => {\n running = false\n })\n }, periodMs)\n\n /* 이 타이머 때문에 프로세스가 안 죽지 않게. */\n if (typeof timer?.unref === 'function') timer.unref()\n}\n\nexport function stopActuationRuleLoop(): void {\n if (!timer) return\n clearInterval(timer)\n timer = undefined\n}\n\n/**\n * 일꾼이 한 바퀴에 무엇을 했나 — **바뀌었을 때만** 적는다.\n *\n * ── 왜 필요한가 (2026-09-04) ────────────────────────────────────────────────\n * 처음에 보고를 받고 버렸다. 그러니 대기 줄에 조치가 앉아 있는데 아무 일도 안 일어날 때\n * **이유를 알 길이 없었다** — 실제로 그 상태를 만나 30분을 뒤졌다. 집지 않는 것은 대개 옳은\n * 판단이지만, 옳은 판단도 보이지 않으면 결함과 구별되지 않는다.\n *\n * ── 왜 바뀌었을 때만인가 ────────────────────────────────────────────────────\n * 30초마다 같은 줄을 적으면 하루 2,880줄이고 그 사이의 진짜 변화가 묻힌다. 대기 줄의 모양이\n * 그대로면 새로 말할 것이 없다 — `noteBlocked` 에서 지킨 것과 같은 규율이다.\n */\nconst lastSweep = new Map<string, string>()\n\nfunction noteSweep(domainId: string, report: SweepReport): void {\n if (!report.seen) {\n lastSweep.delete(domainId)\n return\n }\n\n const skips = Object.entries(report.skipped)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([why, n]) => `${why} ${n}`)\n .join(' · ')\n const line = `${report.seen}건 대기 · 넘김 ${report.dispatched}${skips ? ` · ${skips}` : ''}`\n\n if (lastSweep.get(domainId) === line) return\n lastSweep.set(domainId, line)\n twinLog(`[twin-sweep] ${line}`)\n}\n\nasync function tickRules(): Promise<void> {\n /*\n * ── 유입 창을 먼저 굴린다 (2026-09-05) ─────────────────────────────────────\n * 창이 닫히는 이유는 **시간이 지난 것**이다. 그런데 예전에는 「다음 사건이 왔을 때」와 「누가\n * 물어볼 때」만 굴렀다 — 둘 다 안 올 수 있다.\n *\n * 그래서 사실이 드문 트윈은 표에 아무것도 안 남았다. 실측: 46건이 62분에 흩어져 온 트윈의 창이\n * 1개, 사실 11건뿐인 트윈의 창이 48개 — **사실 수와 창 수가 거꾸로**였다. 창을 만든 것이 유입이\n * 아니라 누가 물어본 것이었기 때문이다.\n *\n * **아무도 안 보는 트윈일수록 기록이 없었다.** 사실이 조용히 사라지는 것을 잡으려는 장부가 정확히\n * 그 반대로 돌고 있었다(137봉투가 장부 없이 지나갔다 — 인티그레이션 레인 실측).\n *\n * 소유한 트윈이 없어도 굴린다 — 장부는 프로세스가 사는 동안 이어지고, 규칙을 돌 트윈이 없다는\n * 것과 기록할 창이 없다는 것은 다른 사실이다.\n */\n const rolled = TwinEngine.rollAllIngestWindows()\n if (rolled) twinLog(`[twin-ingest] 창 ${rolled}개를 닫아 적었다`)\n\n const owned = TwinEngine.ownedTwins()\n if (!owned.length) return\n\n /*\n * 트랜잭션을 넘기지 않는다 — 여러 트윈을 돌고, 한 트윈이 실패해도 앞의 트윈이 낸 것은 남아야 한다.\n * 한 덩어리로 묶으면 마지막 트윈의 실패가 앞의 결과를 전부 물린다.\n */\n /*\n * **놓친 보고를 함께 backfill 한다** — 관측 → 조치의 반대 방향이다.\n *\n * 같은 주기를 쓰는 이유는 값이다: 둘 다 몇 초마다 볼 일이고, 타이머를 둘 두면 내리고 세울 자리가\n * 둘이 된다. 다만 **서로 막지 않는다** — 규칙이 터져도 backfill 은 돌아야 한다. 조치를 못 내는 것과\n * 나간 조치의 결과를 모르는 것은 다른 사고다.\n */\n for (const domainId of new Set(owned.map(t => t.domainId))) {\n await catchUpEffects(domainId, databaseCommandStore())\n /*\n * **넘길 차례를 기다리는 것들도 함께 줍는다** — 승인만 되고 안 나간 것, 실패해서 다시 해 볼 것.\n *\n * 정상 경로는 승인 직후에 이미 끝난다(§`dispatchAfterApproval`, 실측 282ms). 이 자리는 **그\n * 사이에 프로세스가 죽은 경우**와 재시도만 본다. 그래서 30초로 충분하고 아무도 안 기다린다.\n */\n const swept = await sweepPendingDispatch(domainId, {\n store: databaseCommandStore(),\n actuate: command => routeActuation(domainId, command.instanceId, command),\n idempotent: async instanceId => {\n const target = await actuationTargetOf(domainId, instanceId).catch(() => undefined)\n const type = (target as any)?.adapterType\n return type ? capabilitiesOf(getAdapter(type)).includes('idempotent-actuation') : false\n },\n localReadiness: async instanceId => {\n const r = await actuationReadinessOf(domainId, instanceId).catch(() => undefined)\n /* 원격으로 물어 본 답은 쓰지 않는다 — 이 자리는 저쪽을 건드리지 않는 것이 규약이다. */\n return r && (r as any).checked === 'local' ? (r as any) : undefined\n }\n })\n noteSweep(domainId, swept)\n }\n\n await runRulesOnce({\n ownedTwins: () => owned,\n rulesOf,\n attentionsOf,\n modeOf,\n store: databaseCommandStore(),\n specs: MES_COMMANDS,\n nowMs: () => Date.now(),\n noteRun\n })\n}\n"]}
@@ -0,0 +1,52 @@
1
+ import type { CommandTypeSpec } from '@operato/ops-contract';
2
+ import { type CommandStore } from './command-dispatcher.js';
3
+ import { type ActuationRuleSpec, type AttentionLike, type RuleSkip } from './rule-evaluate.js';
4
+ /** 이 프로세스가 든 트윈 하나. */
5
+ export interface OwnedTwin {
6
+ domainId: string;
7
+ instanceId: string;
8
+ }
9
+ /** 한 트윈을 돌고 나서 남는 사실. */
10
+ export interface TwinRunReport {
11
+ domainId: string;
12
+ instanceId: string;
13
+ /** 실제로 적힌 커맨드 수. */
14
+ proposed: number;
15
+ /** 규칙마다 몇 개를 냈나 — 규칙 행에 누계를 적으려면 나누어 세어야 한다. */
16
+ proposedByRule: Record<string, number>;
17
+ /** 왜 안 올렸나 — 규칙이 조용히 안 도는 것과 조건이 없어서 안 내는 것을 구별한다. */
18
+ skips: RuleSkip[];
19
+ /** 상한에 닿아 두고 온 수. */
20
+ withheld?: number;
21
+ /** 이 트윈을 아예 못 돌았으면 그 이유. */
22
+ failed?: string;
23
+ }
24
+ /** 밖에서 받는 것들 — 저장소도 엔진도 시계도 인자다. 그래야 시험이 재기동과 상한을 걸을 수 있다. */
25
+ export interface RuleRunnerDeps {
26
+ /** 이 프로세스가 든 트윈들. 남의 프로세스가 든 트윈은 그쪽이 돈다. */
27
+ ownedTwins(): OwnedTwin[];
28
+ /** 이 트윈에 걸린 규칙 — 없으면 스냅샷을 읽지 않는다. */
29
+ rulesOf(twin: OwnedTwin): Promise<ActuationRuleSpec[]>;
30
+ /** 지금 서 있는 주의들. */
31
+ attentionsOf(twin: OwnedTwin): AttentionLike[] | undefined;
32
+ /**
33
+ * 이 트윈이 **무엇을 비추고 있나** — 관측(`live`)인가 가정(`sim`)인가.
34
+ *
35
+ * 이 값이 없거나 `live` 가 아니면 조치를 내지 않는다(§`runOneTwin`). 모름을 통과로 읽지 않는다.
36
+ */
37
+ modeOf(twin: OwnedTwin): 'live' | 'sim' | undefined;
38
+ /** 커맨드가 사는 자리. */
39
+ store: CommandStore;
40
+ /** 선언된 조치 종류 — 없는 종류를 가리키는 규칙은 판정이 걸러 낸다. */
41
+ specs: readonly CommandTypeSpec[];
42
+ nowMs(): number;
43
+ /** 돈 결과를 규칙 행에 남긴다 — 화면이 「이 규칙이 돌고 있나」를 말할 수 있게. */
44
+ noteRun?(twin: OwnedTwin, report: TwinRunReport): Promise<void>;
45
+ maxPerRun?: number;
46
+ }
47
+ /**
48
+ * 한 바퀴 돈다 — **한 트윈이 실패해도 나머지는 돈다.**
49
+ *
50
+ * 트윈 하나의 규칙이 잘못됐다고 다른 공장의 규칙이 멈추면, 고치는 동안 전체가 멈춘 셈이 된다.
51
+ */
52
+ export declare function runRulesOnce(deps: RuleRunnerDeps): Promise<TwinRunReport[]>;
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runRulesOnce = runRulesOnce;
4
+ const log_js_1 = require("../../engine/log.js");
5
+ const command_dispatcher_js_1 = require("./command-dispatcher.js");
6
+ const rule_evaluate_js_1 = require("./rule-evaluate.js");
7
+ /**
8
+ * 한 바퀴 돈다 — **한 트윈이 실패해도 나머지는 돈다.**
9
+ *
10
+ * 트윈 하나의 규칙이 잘못됐다고 다른 공장의 규칙이 멈추면, 고치는 동안 전체가 멈춘 셈이 된다.
11
+ */
12
+ async function runRulesOnce(deps) {
13
+ const out = [];
14
+ for (const twin of deps.ownedTwins()) {
15
+ const report = { ...twin, proposed: 0, proposedByRule: {}, skips: [] };
16
+ try {
17
+ Object.assign(report, await runOneTwin(twin, deps));
18
+ }
19
+ catch (e) {
20
+ report.failed = String(e?.message ?? e);
21
+ (0, log_js_1.twinError)(`[twin-rule] "${twin.instanceId}" 규칙을 돌지 못했다 — ${report.failed}`);
22
+ }
23
+ out.push(report);
24
+ if (deps.noteRun) {
25
+ await deps.noteRun(twin, report).catch(err => (0, log_js_1.twinWarn)(`[twin-rule] "${twin.instanceId}" 규칙 기록을 남기지 못했다 — ${err?.message ?? err}`));
26
+ }
27
+ }
28
+ return out;
29
+ }
30
+ async function runOneTwin(twin, deps) {
31
+ const rules = await deps.rulesOf(twin);
32
+ /* 규칙이 없으면 스냅샷도 읽지 않는다 — 아무것도 안 할 일에 값을 만들지 않는다. */
33
+ if (!rules.length)
34
+ return {};
35
+ /*
36
+ * **이미 올라가 있는 근거를 저장소에서 읽는다.**
37
+ *
38
+ * 저장소가 그 길을 내주지 않으면 **판정하지 않는다.** 빈 집합을 만들어 주면 매 주기 같은 조건이 다시
39
+ * 올라가 대기 줄에 같은 지시가 쌓인다 — 그것을 조용히 하지 않는다.
40
+ */
41
+ if (!deps.store.openCauses) {
42
+ return {
43
+ skips: [
44
+ {
45
+ ruleId: '(전체)',
46
+ reason: '이 저장소는 이미 올라간 근거를 말해 주지 않는다 — 판정하면 같은 조건이 주기마다 다시 올라간다. ' +
47
+ 'CommandStore.openCauses 가 필요하다'
48
+ }
49
+ ]
50
+ };
51
+ }
52
+ /*
53
+ * ── **시늉된 조건에서 현장 지시를 내지 않는다** (2026-09-01) ────────────
54
+ *
55
+ * 시뮬 트윈의 상태는 **가정**이다. 커널이 스스로 물품을 옮겨 만든 병목이고, 현장에서 관측된 것이
56
+ * 아니다. 그 위에서 생산 지시를 내면 **지어낸 조건이 실제 지시가 된다** — 오늘 자극과 조치를 갈라
57
+ * 놓은 것과 같은 부류의 잘못이고, 이쪽이 더 나쁘다(자극은 모델 안에서 끝나지만 이것은 현장으로 나간다).
58
+ *
59
+ * 실제로 그렇게 걸었다. 오늘 규칙을 확인한 트윈이 전부 `sim` 이었고, 시늉된 병목에서 생산 지시가
60
+ * 올라왔다. 배선은 옳게 돌았지만 근거는 가정이었다.
61
+ *
62
+ * 예측을 근거로 미리 지시를 내는 것 자체는 있을 수 있는 일이다. 다만 그것은 **선언해서** 할 일이고,
63
+ * 승인하는 사람이 「이것은 예측이다」를 알아야 한다. 그 선언이 아직 없으므로 지금은 막고, 왜 막았는지
64
+ * 말한다 — 그 문장이 필요해진 사람이 무엇을 요청해야 하는지 알려 준다.
65
+ */
66
+ const mode = deps.modeOf(twin);
67
+ if (mode !== 'live') {
68
+ return {
69
+ skips: [
70
+ {
71
+ ruleId: '(전체)',
72
+ reason: mode === 'sim'
73
+ ? '이 트윈은 시뮬레이션이다 — 시늉된 조건에서 현장 지시를 내지 않는다. ' +
74
+ '예측을 근거로 미리 내려면 그것을 선언하는 자리가 필요하고, 아직 없다'
75
+ : '이 트윈이 관측을 비추는지 알 수 없다 — 모름을 「관측이다」로 읽지 않는다'
76
+ }
77
+ ]
78
+ };
79
+ }
80
+ const attentions = deps.attentionsOf(twin);
81
+ if (attentions === undefined) {
82
+ /*
83
+ * 스냅샷이 없다 — 멈춘 트윈이거나 아직 안 선 트윈이다. **주의가 0건인 것과 다르다.** 0건으로 읽으면
84
+ * 「조건이 하나도 없다」가 되고, 규칙이 도는 줄로 보인다.
85
+ */
86
+ return {
87
+ skips: [{ ruleId: '(전체)', reason: '이 트윈의 상태를 읽을 수 없다 — 멈춰 있거나 아직 서지 않았다' }]
88
+ };
89
+ }
90
+ const openCauses = await deps.store.openCauses(twin.domainId, twin.instanceId);
91
+ /* 없으면 넘기지 않는다 — 판정이 「받지 못했다」와 「낸 적이 없다」를 구별해야 한다. */
92
+ const lastProposedAt = await deps.store.lastProposedAt?.(twin.domainId, twin.instanceId);
93
+ const verdict = (0, rule_evaluate_js_1.evaluateRules)({
94
+ rules,
95
+ attentions,
96
+ specs: deps.specs,
97
+ openCauses,
98
+ ...(lastProposedAt ? { lastProposedAt } : {}),
99
+ nowMs: deps.nowMs(),
100
+ ...(deps.maxPerRun ? { maxPerRun: deps.maxPerRun } : {})
101
+ });
102
+ const skips = [...verdict.skips];
103
+ const proposedByRule = {};
104
+ let proposed = 0;
105
+ for (const p of verdict.proposals) {
106
+ try {
107
+ await (0, command_dispatcher_js_1.propose)(deps.store, twin.domainId, deps.specs, {
108
+ id: newCommandId(),
109
+ instanceId: twin.instanceId,
110
+ type: p.commandType,
111
+ /* 사람이 아니다. 그래서 낸 사람이 없고, 아무 승인자든 두 번째 사람이 된다. */
112
+ origin: 'rule',
113
+ proposedAt: new Date(deps.nowMs()).toISOString(),
114
+ payload: p.payload,
115
+ causeRef: p.attentionId,
116
+ ruleRef: p.ruleId
117
+ });
118
+ proposed++;
119
+ proposedByRule[p.ruleId] = (proposedByRule[p.ruleId] ?? 0) + 1;
120
+ }
121
+ catch (e) {
122
+ /*
123
+ * 하나가 거절돼도 나머지는 낸다. 그리고 **거절을 삼키지 않는다** — 규칙이 무언가를 내려 했는데
124
+ * 못 냈다는 사실이 사라지면, 사람은 그 조건에 규칙이 안 걸린 줄로 읽는다.
125
+ */
126
+ skips.push({ ruleId: p.ruleId, attentionId: p.attentionId, reason: String(e?.message ?? e) });
127
+ }
128
+ }
129
+ return { proposed, proposedByRule, skips, ...(verdict.withheld ? { withheld: verdict.withheld } : {}) };
130
+ }
131
+ /** 식별자 — 이 파일이 만든다. 저장소가 만들면 커맨드를 적기 전에 근거를 붙일 수 없다. */
132
+ function newCommandId() {
133
+ return globalThis.crypto.randomUUID();
134
+ }
135
+ //# sourceMappingURL=rule-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-runner.js","sourceRoot":"","sources":["../../../server/service/actuation/rule-runner.ts"],"names":[],"mappings":";;AA+EA,oCAqBC;AA7ED,gDAAyD;AAEzD,mEAAoE;AACpE,yDAA6G;AAgD7G;;;;GAIG;AACI,KAAK,UAAU,YAAY,CAAC,IAAoB;IACrD,MAAM,GAAG,GAAoB,EAAE,CAAA;IAE/B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QACrC,MAAM,MAAM,GAAkB,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,cAAc,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;QACrF,IAAI,CAAC;YACH,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;QACrD,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAA;YACvC,IAAA,kBAAS,EAAC,gBAAgB,IAAI,CAAC,UAAU,kBAAkB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;QAC7E,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAEhB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAC3C,IAAA,iBAAQ,EAAC,gBAAgB,IAAI,CAAC,UAAU,sBAAsB,GAAG,EAAE,OAAO,IAAI,GAAG,EAAE,CAAC,CACrF,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAe,EAAE,IAAoB;IAC7D,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,mDAAmD;IACnD,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,CAAA;IAE5B;;;;;OAKG;IACH,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QAC3B,OAAO;YACL,KAAK,EAAE;gBACL;oBACE,MAAM,EAAE,MAAM;oBACd,MAAM,EACJ,0DAA0D;wBAC1D,gCAAgC;iBACnC;aACF;SACF,CAAA;IACH,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC9B,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO;YACL,KAAK,EAAE;gBACL;oBACE,MAAM,EAAE,MAAM;oBACd,MAAM,EACJ,IAAI,KAAK,KAAK;wBACZ,CAAC,CAAC,0CAA0C;4BAC1C,yCAAyC;wBAC3C,CAAC,CAAC,4CAA4C;iBACnD;aACF;SACF,CAAA;IACH,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAA;IAC1C,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B;;;WAGG;QACH,OAAO;YACL,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,sCAAsC,EAAE,CAAC;SAC5E,CAAA;IACH,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;IAC9E,qDAAqD;IACrD,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;IAExF,MAAM,OAAO,GAAG,IAAA,gCAAa,EAAC;QAC5B,KAAK;QACL,UAAU;QACV,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,UAAU;QACV,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE;QACnB,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzD,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IAChC,MAAM,cAAc,GAA2B,EAAE,CAAA;IACjD,IAAI,QAAQ,GAAG,CAAC,CAAA;IAEhB,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,IAAA,+BAAO,EAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE;gBACnD,EAAE,EAAE,YAAY,EAAE;gBAClB,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,IAAI,EAAE,CAAC,CAAC,WAAW;gBACnB,iDAAiD;gBACjD,MAAM,EAAE,MAAM;gBACd,UAAU,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,WAAW,EAAE;gBAChD,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,QAAQ,EAAE,CAAC,CAAC,WAAW;gBACvB,OAAO,EAAE,CAAC,CAAC,MAAM;aAClB,CAAC,CAAA;YACF,QAAQ,EAAE,CAAA;YACV,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;QAChE,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB;;;eAGG;YACH,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,CAAA;QAC/F,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AACzG,CAAC;AAED,wDAAwD;AACxD,SAAS,YAAY;IACnB,OAAO,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,CAAA;AACvC,CAAC","sourcesContent":["/*\n * **규칙을 주기로 돈다** — 판정을 저장소에 잇는 자리.\n *\n * ── 왜 틱이 아닌가 (2026-09-01) ──────────────────────────────────────────\n * 커널 틱 안에 넣으려다 `tickGuarded` 를 읽고 접었다. 그 자리는 **예외가 나면 트윈을 멈추고**, 굶김\n * 감시가 느린 틱 셋에 트윈을 내린다. 규칙 판정은 DB 를 두 번 건드리므로 그 감시에 정확히 걸린다.\n *\n * 그리고 필요하지도 않다. 「병목이 세 시간째」를 1초마다 다시 물을 이유가 없다.\n *\n * ── 규칙은 결재선에 올리지 않는다 ────────────────────────────────────────\n * 이것이 이 파일의 가장 중요한 규율이다.\n *\n * 업무 목록의 결재선은 **올린 사람 기준**으로 풀린다 — `Myself` · `MyDepartment` · `MySupervisor` 가\n * 전부 `context.state.user` 를 본다(§`getSystemUserFromOrgMemberItem`). 배경 루프에는 「나」가 없다.\n * 시스템 사용자를 지어내 넘기면 그 셋이 **시스템 사용자를 가리키게 되고**, 결재선이 뜻을 잃는다.\n *\n * 그래서 규칙은 **내는 것까지** 한다. 결재선에 올리는 것은 사람이 한다(§`raiseTwinCommand`) — 그 사람이\n * 「이것을 승인자들 앞에 놓았다」의 주체가 되고, 그 사람 기준으로 결재선이 풀린다.\n *\n * 올리기 전까지 그 조치는 나가지 못한다. 게이트는 그대로 서 있다.\n */\nimport type { CommandTypeSpec } from '@operato/ops-contract'\n\nimport { twinError, twinWarn } from '../../engine/log.js'\n\nimport { propose, type CommandStore } from './command-dispatcher.js'\nimport { evaluateRules, type ActuationRuleSpec, type AttentionLike, type RuleSkip } from './rule-evaluate.js'\n\n/** 이 프로세스가 든 트윈 하나. */\nexport interface OwnedTwin {\n domainId: string\n instanceId: string\n}\n\n/** 한 트윈을 돌고 나서 남는 사실. */\nexport interface TwinRunReport {\n domainId: string\n instanceId: string\n /** 실제로 적힌 커맨드 수. */\n proposed: number\n /** 규칙마다 몇 개를 냈나 — 규칙 행에 누계를 적으려면 나누어 세어야 한다. */\n proposedByRule: Record<string, number>\n /** 왜 안 올렸나 — 규칙이 조용히 안 도는 것과 조건이 없어서 안 내는 것을 구별한다. */\n skips: RuleSkip[]\n /** 상한에 닿아 두고 온 수. */\n withheld?: number\n /** 이 트윈을 아예 못 돌았으면 그 이유. */\n failed?: string\n}\n\n/** 밖에서 받는 것들 — 저장소도 엔진도 시계도 인자다. 그래야 시험이 재기동과 상한을 걸을 수 있다. */\nexport interface RuleRunnerDeps {\n /** 이 프로세스가 든 트윈들. 남의 프로세스가 든 트윈은 그쪽이 돈다. */\n ownedTwins(): OwnedTwin[]\n /** 이 트윈에 걸린 규칙 — 없으면 스냅샷을 읽지 않는다. */\n rulesOf(twin: OwnedTwin): Promise<ActuationRuleSpec[]>\n /** 지금 서 있는 주의들. */\n attentionsOf(twin: OwnedTwin): AttentionLike[] | undefined\n /**\n * 이 트윈이 **무엇을 비추고 있나** — 관측(`live`)인가 가정(`sim`)인가.\n *\n * 이 값이 없거나 `live` 가 아니면 조치를 내지 않는다(§`runOneTwin`). 모름을 통과로 읽지 않는다.\n */\n modeOf(twin: OwnedTwin): 'live' | 'sim' | undefined\n /** 커맨드가 사는 자리. */\n store: CommandStore\n /** 선언된 조치 종류 — 없는 종류를 가리키는 규칙은 판정이 걸러 낸다. */\n specs: readonly CommandTypeSpec[]\n nowMs(): number\n /** 돈 결과를 규칙 행에 남긴다 — 화면이 「이 규칙이 돌고 있나」를 말할 수 있게. */\n noteRun?(twin: OwnedTwin, report: TwinRunReport): Promise<void>\n maxPerRun?: number\n}\n\n/**\n * 한 바퀴 돈다 — **한 트윈이 실패해도 나머지는 돈다.**\n *\n * 트윈 하나의 규칙이 잘못됐다고 다른 공장의 규칙이 멈추면, 고치는 동안 전체가 멈춘 셈이 된다.\n */\nexport async function runRulesOnce(deps: RuleRunnerDeps): Promise<TwinRunReport[]> {\n const out: TwinRunReport[] = []\n\n for (const twin of deps.ownedTwins()) {\n const report: TwinRunReport = { ...twin, proposed: 0, proposedByRule: {}, skips: [] }\n try {\n Object.assign(report, await runOneTwin(twin, deps))\n } catch (e: any) {\n report.failed = String(e?.message ?? e)\n twinError(`[twin-rule] \"${twin.instanceId}\" 규칙을 돌지 못했다 — ${report.failed}`)\n }\n out.push(report)\n\n if (deps.noteRun) {\n await deps.noteRun(twin, report).catch(err =>\n twinWarn(`[twin-rule] \"${twin.instanceId}\" 규칙 기록을 남기지 못했다 — ${err?.message ?? err}`)\n )\n }\n }\n\n return out\n}\n\nasync function runOneTwin(twin: OwnedTwin, deps: RuleRunnerDeps): Promise<Partial<TwinRunReport>> {\n const rules = await deps.rulesOf(twin)\n /* 규칙이 없으면 스냅샷도 읽지 않는다 — 아무것도 안 할 일에 값을 만들지 않는다. */\n if (!rules.length) return {}\n\n /*\n * **이미 올라가 있는 근거를 저장소에서 읽는다.**\n *\n * 저장소가 그 길을 내주지 않으면 **판정하지 않는다.** 빈 집합을 만들어 주면 매 주기 같은 조건이 다시\n * 올라가 대기 줄에 같은 지시가 쌓인다 — 그것을 조용히 하지 않는다.\n */\n if (!deps.store.openCauses) {\n return {\n skips: [\n {\n ruleId: '(전체)',\n reason:\n '이 저장소는 이미 올라간 근거를 말해 주지 않는다 — 판정하면 같은 조건이 주기마다 다시 올라간다. ' +\n 'CommandStore.openCauses 가 필요하다'\n }\n ]\n }\n }\n\n /*\n * ── **시늉된 조건에서 현장 지시를 내지 않는다** (2026-09-01) ────────────\n *\n * 시뮬 트윈의 상태는 **가정**이다. 커널이 스스로 물품을 옮겨 만든 병목이고, 현장에서 관측된 것이\n * 아니다. 그 위에서 생산 지시를 내면 **지어낸 조건이 실제 지시가 된다** — 오늘 자극과 조치를 갈라\n * 놓은 것과 같은 부류의 잘못이고, 이쪽이 더 나쁘다(자극은 모델 안에서 끝나지만 이것은 현장으로 나간다).\n *\n * 실제로 그렇게 걸었다. 오늘 규칙을 확인한 트윈이 전부 `sim` 이었고, 시늉된 병목에서 생산 지시가\n * 올라왔다. 배선은 옳게 돌았지만 근거는 가정이었다.\n *\n * 예측을 근거로 미리 지시를 내는 것 자체는 있을 수 있는 일이다. 다만 그것은 **선언해서** 할 일이고,\n * 승인하는 사람이 「이것은 예측이다」를 알아야 한다. 그 선언이 아직 없으므로 지금은 막고, 왜 막았는지\n * 말한다 — 그 문장이 필요해진 사람이 무엇을 요청해야 하는지 알려 준다.\n */\n const mode = deps.modeOf(twin)\n if (mode !== 'live') {\n return {\n skips: [\n {\n ruleId: '(전체)',\n reason:\n mode === 'sim'\n ? '이 트윈은 시뮬레이션이다 — 시늉된 조건에서 현장 지시를 내지 않는다. ' +\n '예측을 근거로 미리 내려면 그것을 선언하는 자리가 필요하고, 아직 없다'\n : '이 트윈이 관측을 비추는지 알 수 없다 — 모름을 「관측이다」로 읽지 않는다'\n }\n ]\n }\n }\n\n const attentions = deps.attentionsOf(twin)\n if (attentions === undefined) {\n /*\n * 스냅샷이 없다 — 멈춘 트윈이거나 아직 안 선 트윈이다. **주의가 0건인 것과 다르다.** 0건으로 읽으면\n * 「조건이 하나도 없다」가 되고, 규칙이 도는 줄로 보인다.\n */\n return {\n skips: [{ ruleId: '(전체)', reason: '이 트윈의 상태를 읽을 수 없다 — 멈춰 있거나 아직 서지 않았다' }]\n }\n }\n\n const openCauses = await deps.store.openCauses(twin.domainId, twin.instanceId)\n /* 없으면 넘기지 않는다 — 판정이 「받지 못했다」와 「낸 적이 없다」를 구별해야 한다. */\n const lastProposedAt = await deps.store.lastProposedAt?.(twin.domainId, twin.instanceId)\n\n const verdict = evaluateRules({\n rules,\n attentions,\n specs: deps.specs,\n openCauses,\n ...(lastProposedAt ? { lastProposedAt } : {}),\n nowMs: deps.nowMs(),\n ...(deps.maxPerRun ? { maxPerRun: deps.maxPerRun } : {})\n })\n\n const skips = [...verdict.skips]\n const proposedByRule: Record<string, number> = {}\n let proposed = 0\n\n for (const p of verdict.proposals) {\n try {\n await propose(deps.store, twin.domainId, deps.specs, {\n id: newCommandId(),\n instanceId: twin.instanceId,\n type: p.commandType,\n /* 사람이 아니다. 그래서 낸 사람이 없고, 아무 승인자든 두 번째 사람이 된다. */\n origin: 'rule',\n proposedAt: new Date(deps.nowMs()).toISOString(),\n payload: p.payload,\n causeRef: p.attentionId,\n ruleRef: p.ruleId\n })\n proposed++\n proposedByRule[p.ruleId] = (proposedByRule[p.ruleId] ?? 0) + 1\n } catch (e: any) {\n /*\n * 하나가 거절돼도 나머지는 낸다. 그리고 **거절을 삼키지 않는다** — 규칙이 무언가를 내려 했는데\n * 못 냈다는 사실이 사라지면, 사람은 그 조건에 규칙이 안 걸린 줄로 읽는다.\n */\n skips.push({ ruleId: p.ruleId, attentionId: p.attentionId, reason: String(e?.message ?? e) })\n }\n }\n\n return { proposed, proposedByRule, skips, ...(verdict.withheld ? { withheld: verdict.withheld } : {}) }\n}\n\n/** 식별자 — 이 파일이 만든다. 저장소가 만들면 커맨드를 적기 전에 근거를 붙일 수 없다. */\nfunction newCommandId(): string {\n return globalThis.crypto.randomUUID()\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import type { ActuationRuleSpec, AttentionLike } from './rule-evaluate.js';
2
+ import type { OwnedTwin, TwinRunReport } from './rule-runner.js';
3
+ /** 이 트윈에 걸린 규칙 — 꺼 둔 것도 함께 준다(판정이 거른다. 「꺼져 있다」는 판정의 규율이다). */
4
+ export declare function rulesOf(twin: OwnedTwin): Promise<ActuationRuleSpec[]>;
5
+ /**
6
+ * 이 트윈이 관측을 비추나 가정을 돌리나 — 조치를 낼 수 있는지가 여기서 갈린다.
7
+ *
8
+ * 판정과 같은 술어를 쓴다(`TwinEngine.modeOf`). 두 벌이면 어긋난다.
9
+ */
10
+ export declare function modeOf(twin: OwnedTwin): 'live' | 'sim' | undefined;
11
+ /**
12
+ * 지금 서 있는 주의들 — **못 읽으면 `undefined`.**
13
+ *
14
+ * 빈 배열로 답하지 않는다. 멈춘 트윈을 「조건이 하나도 없다」로 읽으면 규칙이 도는 줄로 보인다.
15
+ * 주의 질의와 같은 원천을 쓴다(§`twin-attention-query.ts`) — 두 곳이 다른 것을 보면 화면에 보이는
16
+ * 조건과 규칙이 보는 조건이 어긋난다.
17
+ */
18
+ export declare function attentionsOf(twin: OwnedTwin): AttentionLike[] | undefined;
19
+ /**
20
+ * 돈 결과를 규칙 행에 남긴다 — **조용히 안 도는 규칙과 조건이 없어서 안 내는 규칙을 구별하려면 필요하다.**
21
+ *
22
+ * 이 트윈의 모든 규칙에 `lastRunAt` 을 찍는다. 이유는 그 규칙 것만 적는다.
23
+ */
24
+ export declare function noteRun(twin: OwnedTwin, report: TwinRunReport): Promise<void>;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rulesOf = rulesOf;
4
+ exports.modeOf = modeOf;
5
+ exports.attentionsOf = attentionsOf;
6
+ exports.noteRun = noteRun;
7
+ /*
8
+ * 규칙 판정이 **실제 엔진·저장소에 닿는 자리** — 한 벌.
9
+ *
10
+ * 리졸버(「지금 돌려 본다」)와 주기 루프가 같은 배선을 써야 한다. 두 벌이면 화면에서 돌려 봤을 때와
11
+ * 주기가 돌 때가 다르게 동작하고, 사람은 「돌려 봤을 때는 됐는데」를 겪는다.
12
+ */
13
+ const twin_engine_js_1 = require("../../engine/twin-engine.js");
14
+ const shell_1 = require("@things-factory/shell");
15
+ const actuation_rule_js_1 = require("./actuation-rule.js");
16
+ /** 이 트윈에 걸린 규칙 — 꺼 둔 것도 함께 준다(판정이 거른다. 「꺼져 있다」는 판정의 규율이다). */
17
+ async function rulesOf(twin) {
18
+ const rows = await (0, shell_1.getRepository)(actuation_rule_js_1.TwinActuationRule).find({
19
+ where: { domain: { id: twin.domainId }, instanceId: twin.instanceId }
20
+ });
21
+ return rows.map(r => ({
22
+ id: r.id,
23
+ instanceId: r.instanceId,
24
+ attentionKind: r.attentionKind,
25
+ ...(r.minSeverity ? { minSeverity: r.minSeverity } : {}),
26
+ ...(r.minHeldMs ? { minHeldMs: r.minHeldMs } : {}),
27
+ ...(r.cooldownMs === null || r.cooldownMs === undefined ? {} : { cooldownMs: r.cooldownMs }),
28
+ commandType: r.commandType,
29
+ ...(r.payloadTemplate ? { payloadTemplate: r.payloadTemplate } : {}),
30
+ enabled: r.enabled !== false
31
+ }));
32
+ }
33
+ /**
34
+ * 이 트윈이 관측을 비추나 가정을 돌리나 — 조치를 낼 수 있는지가 여기서 갈린다.
35
+ *
36
+ * 판정과 같은 술어를 쓴다(`TwinEngine.modeOf`). 두 벌이면 어긋난다.
37
+ */
38
+ function modeOf(twin) {
39
+ return twin_engine_js_1.TwinEngine.modeOf(twin.domainId, twin.instanceId);
40
+ }
41
+ /**
42
+ * 지금 서 있는 주의들 — **못 읽으면 `undefined`.**
43
+ *
44
+ * 빈 배열로 답하지 않는다. 멈춘 트윈을 「조건이 하나도 없다」로 읽으면 규칙이 도는 줄로 보인다.
45
+ * 주의 질의와 같은 원천을 쓴다(§`twin-attention-query.ts`) — 두 곳이 다른 것을 보면 화면에 보이는
46
+ * 조건과 규칙이 보는 조건이 어긋난다.
47
+ */
48
+ function attentionsOf(twin) {
49
+ if (!twin_engine_js_1.TwinEngine.owns(twin.domainId, twin.instanceId))
50
+ return undefined;
51
+ const kernel = twin_engine_js_1.TwinEngine.kernel(twin.domainId, twin.instanceId);
52
+ const snap = kernel?.getSnapshot?.() ?? twin_engine_js_1.TwinEngine.snapshot(twin.domainId, twin.instanceId);
53
+ if (!snap)
54
+ return undefined;
55
+ return (snap.attentions ?? []);
56
+ }
57
+ /**
58
+ * 돈 결과를 규칙 행에 남긴다 — **조용히 안 도는 규칙과 조건이 없어서 안 내는 규칙을 구별하려면 필요하다.**
59
+ *
60
+ * 이 트윈의 모든 규칙에 `lastRunAt` 을 찍는다. 이유는 그 규칙 것만 적는다.
61
+ */
62
+ async function noteRun(twin, report) {
63
+ const repo = (0, shell_1.getRepository)(actuation_rule_js_1.TwinActuationRule);
64
+ const rows = await repo.find({ where: { domain: { id: twin.domainId }, instanceId: twin.instanceId } });
65
+ if (!rows.length)
66
+ return;
67
+ const at = new Date().toISOString();
68
+ const reasonOf = (id) => report.skips.filter(s => s.ruleId === id).map(s => s.reason)[0] ?? null;
69
+ await repo.save(rows.map(row => ({
70
+ ...row,
71
+ lastRunAt: at,
72
+ proposedCount: (row.proposedCount ?? 0) + (report.proposedByRule[row.id] ?? 0),
73
+ /*
74
+ * 이유는 그 규칙 것만 적는다. 트윈 전체가 못 돈 경우(§`failed`)는 모든 규칙에 같은 이유가 붙는다
75
+ * — 그때는 규칙 하나의 문제가 아니기 때문이다.
76
+ */
77
+ lastSkipReason: report.failed ?? reasonOf(row.id)
78
+ })));
79
+ }
80
+ //# sourceMappingURL=rule-wiring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-wiring.js","sourceRoot":"","sources":["../../../server/service/actuation/rule-wiring.ts"],"names":[],"mappings":";;AAcA,0BAgBC;AAOD,wBAEC;AASD,oCAQC;AAOD,0BAoBC;AAnFD;;;;;GAKG;AACH,gEAAwD;AACxD,iDAAqD;AAErD,2DAAuD;AAIvD,8DAA8D;AACvD,KAAK,UAAU,OAAO,CAAC,IAAe;IAC3C,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,qCAAiB,CAAC,CAAC,IAAI,CAAC;QACvD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE;KACtE,CAAC,CAAA;IAEF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACpB,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,GAAG,CAAC,CAAC,CAAC,UAAU,KAAK,IAAI,IAAI,CAAC,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC;QAC5F,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,GAAG,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,OAAO,EAAE,CAAC,CAAC,OAAO,KAAK,KAAK;KAC7B,CAAC,CAAC,CAAA;AACL,CAAC;AAED;;;;GAIG;AACH,SAAgB,MAAM,CAAC,IAAe;IACpC,OAAO,2BAAU,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;AAC1D,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,YAAY,CAAC,IAAe;IAC1C,IAAI,CAAC,2BAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC;QAAE,OAAO,SAAS,CAAA;IAEtE,MAAM,MAAM,GAAG,2BAAU,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;IAChE,MAAM,IAAI,GAAG,MAAM,EAAE,WAAW,EAAE,EAAE,IAAI,2BAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;IAC3F,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IAE3B,OAAO,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAoB,CAAA;AACnD,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,OAAO,CAAC,IAAe,EAAE,MAAqB;IAClE,MAAM,IAAI,GAAG,IAAA,qBAAa,EAAC,qCAAiB,CAAC,CAAA;IAC7C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;IACvG,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAM;IAExB,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACnC,MAAM,QAAQ,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;IAExG,MAAM,IAAI,CAAC,IAAI,CACb,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACf,GAAG,GAAG;QACN,SAAS,EAAE,EAAE;QACb,aAAa,EAAE,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QAC9E;;;WAGG;QACH,cAAc,EAAE,MAAM,CAAC,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;KAClD,CAAC,CAAQ,CACX,CAAA;AACH,CAAC","sourcesContent":["/*\n * 규칙 판정이 **실제 엔진·저장소에 닿는 자리** — 한 벌.\n *\n * 리졸버(「지금 돌려 본다」)와 주기 루프가 같은 배선을 써야 한다. 두 벌이면 화면에서 돌려 봤을 때와\n * 주기가 돌 때가 다르게 동작하고, 사람은 「돌려 봤을 때는 됐는데」를 겪는다.\n */\nimport { TwinEngine } from '../../engine/twin-engine.js'\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinActuationRule } from './actuation-rule.js'\nimport type { ActuationRuleSpec, AttentionLike } from './rule-evaluate.js'\nimport type { OwnedTwin, TwinRunReport } from './rule-runner.js'\n\n/** 이 트윈에 걸린 규칙 — 꺼 둔 것도 함께 준다(판정이 거른다. 「꺼져 있다」는 판정의 규율이다). */\nexport async function rulesOf(twin: OwnedTwin): Promise<ActuationRuleSpec[]> {\n const rows = await getRepository(TwinActuationRule).find({\n where: { domain: { id: twin.domainId }, instanceId: twin.instanceId }\n })\n\n return rows.map(r => ({\n id: r.id,\n instanceId: r.instanceId,\n attentionKind: r.attentionKind,\n ...(r.minSeverity ? { minSeverity: r.minSeverity as any } : {}),\n ...(r.minHeldMs ? { minHeldMs: r.minHeldMs } : {}),\n ...(r.cooldownMs === null || r.cooldownMs === undefined ? {} : { cooldownMs: r.cooldownMs }),\n commandType: r.commandType,\n ...(r.payloadTemplate ? { payloadTemplate: r.payloadTemplate } : {}),\n enabled: r.enabled !== false\n }))\n}\n\n/**\n * 이 트윈이 관측을 비추나 가정을 돌리나 — 조치를 낼 수 있는지가 여기서 갈린다.\n *\n * 판정과 같은 술어를 쓴다(`TwinEngine.modeOf`). 두 벌이면 어긋난다.\n */\nexport function modeOf(twin: OwnedTwin): 'live' | 'sim' | undefined {\n return TwinEngine.modeOf(twin.domainId, twin.instanceId)\n}\n\n/**\n * 지금 서 있는 주의들 — **못 읽으면 `undefined`.**\n *\n * 빈 배열로 답하지 않는다. 멈춘 트윈을 「조건이 하나도 없다」로 읽으면 규칙이 도는 줄로 보인다.\n * 주의 질의와 같은 원천을 쓴다(§`twin-attention-query.ts`) — 두 곳이 다른 것을 보면 화면에 보이는\n * 조건과 규칙이 보는 조건이 어긋난다.\n */\nexport function attentionsOf(twin: OwnedTwin): AttentionLike[] | undefined {\n if (!TwinEngine.owns(twin.domainId, twin.instanceId)) return undefined\n\n const kernel = TwinEngine.kernel(twin.domainId, twin.instanceId)\n const snap = kernel?.getSnapshot?.() ?? TwinEngine.snapshot(twin.domainId, twin.instanceId)\n if (!snap) return undefined\n\n return (snap.attentions ?? []) as AttentionLike[]\n}\n\n/**\n * 돈 결과를 규칙 행에 남긴다 — **조용히 안 도는 규칙과 조건이 없어서 안 내는 규칙을 구별하려면 필요하다.**\n *\n * 이 트윈의 모든 규칙에 `lastRunAt` 을 찍는다. 이유는 그 규칙 것만 적는다.\n */\nexport async function noteRun(twin: OwnedTwin, report: TwinRunReport): Promise<void> {\n const repo = getRepository(TwinActuationRule)\n const rows = await repo.find({ where: { domain: { id: twin.domainId }, instanceId: twin.instanceId } })\n if (!rows.length) return\n\n const at = new Date().toISOString()\n const reasonOf = (id: string) => report.skips.filter(s => s.ruleId === id).map(s => s.reason)[0] ?? null\n\n await repo.save(\n rows.map(row => ({\n ...row,\n lastRunAt: at,\n proposedCount: (row.proposedCount ?? 0) + (report.proposedByRule[row.id] ?? 0),\n /*\n * 이유는 그 규칙 것만 적는다. 트윈 전체가 못 돈 경우(§`failed`)는 모든 규칙에 같은 이유가 붙는다\n * — 그때는 규칙 하나의 문제가 아니기 때문이다.\n */\n lastSkipReason: report.failed ?? reasonOf(row.id)\n })) as any\n )\n}\n"]}
@@ -0,0 +1,21 @@
1
+ import type { CommandStore } from './command-dispatcher.js';
2
+ import { type OrderObservation } from './effect-match.js';
3
+ /** 봉투에서 오더 보고만 골라낸다 — 다른 어휘는 보지 않는다. */
4
+ export declare function orderObservationsIn(envelopes: readonly any[]): OrderObservation[];
5
+ export interface SettleEffectsResult {
6
+ /** 효과를 적은 조치 수. */
7
+ recorded: number;
8
+ /** 이을 수 없던 보고 수 — 대개 우리가 내지 않은 오더다. */
9
+ unmatched: number;
10
+ }
11
+ /**
12
+ * 이 묶음의 오더 보고를 나간 지시와 잇는다 — **아무것도 못 이어도 조용히 지난다.**
13
+ *
14
+ * 유입 경로에서 부른다. 그래서 **던지지 않는다** — 효과를 적다 실패해서 관측 유입이 멈추면, 고치는
15
+ * 동안 트윈이 눈을 감는다. 효과는 다음 보고에 다시 이을 수 있고 관측은 다시 오지 않는다.
16
+ */
17
+ export declare function settleEffects(args: {
18
+ domainId: string;
19
+ envelopes: readonly any[];
20
+ store: CommandStore;
21
+ }): Promise<SettleEffectsResult>;