@things-factory/headless-twin 10.1.278 → 10.1.280

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (614) hide show
  1. package/package.json +21 -7
  2. package/dist-server/engine/attention-digest.js.map +0 -1
  3. package/dist-server/engine/canonical-ingest.js.map +0 -1
  4. package/dist-server/engine/checkpoint-fold.js.map +0 -1
  5. package/dist-server/engine/command-routing.js.map +0 -1
  6. package/dist-server/engine/declared-stimulus.js.map +0 -1
  7. package/dist-server/engine/energy-topology.js.map +0 -1
  8. package/dist-server/engine/event-time.js.map +0 -1
  9. package/dist-server/engine/index.js.map +0 -1
  10. package/dist-server/engine/ingest-dedupe.js.map +0 -1
  11. package/dist-server/engine/ingest-health.js.map +0 -1
  12. package/dist-server/engine/integration-coverage.js.map +0 -1
  13. package/dist-server/engine/integration-probes.js.map +0 -1
  14. package/dist-server/engine/integration-runner.js.map +0 -1
  15. package/dist-server/engine/integration-target-profile.js.map +0 -1
  16. package/dist-server/engine/kpi-baseline.js.map +0 -1
  17. package/dist-server/engine/kpi-fold.js.map +0 -1
  18. package/dist-server/engine/kpi-query.js.map +0 -1
  19. package/dist-server/engine/kpi-target.js.map +0 -1
  20. package/dist-server/engine/live-attentions.js.map +0 -1
  21. package/dist-server/engine/live-feed-registry.js.map +0 -1
  22. package/dist-server/engine/load-meter.js.map +0 -1
  23. package/dist-server/engine/local-declarations.js.map +0 -1
  24. package/dist-server/engine/log.js.map +0 -1
  25. package/dist-server/engine/loop-lag.js.map +0 -1
  26. package/dist-server/engine/measured-estimator.js.map +0 -1
  27. package/dist-server/engine/measured-yield.js.map +0 -1
  28. package/dist-server/engine/model-basis.js.map +0 -1
  29. package/dist-server/engine/model-gap.js.map +0 -1
  30. package/dist-server/engine/model-refusal.js.map +0 -1
  31. package/dist-server/engine/model-vocabulary.js.map +0 -1
  32. package/dist-server/engine/oee-accumulator.js.map +0 -1
  33. package/dist-server/engine/operation-basis.js.map +0 -1
  34. package/dist-server/engine/producer-guard.js.map +0 -1
  35. package/dist-server/engine/property-effects.js.map +0 -1
  36. package/dist-server/engine/read-time.js.map +0 -1
  37. package/dist-server/engine/restart-policy.js.map +0 -1
  38. package/dist-server/engine/runtime-key.js.map +0 -1
  39. package/dist-server/engine/spec-coverage.js.map +0 -1
  40. package/dist-server/engine/stage-path.js.map +0 -1
  41. package/dist-server/engine/state-axes.js.map +0 -1
  42. package/dist-server/engine/structure-diff.js.map +0 -1
  43. package/dist-server/engine/travel-estimator.js.map +0 -1
  44. package/dist-server/engine/twin-engine.js.map +0 -1
  45. package/dist-server/engine/twin-state-channel.js.map +0 -1
  46. package/dist-server/engine/warm-start.js.map +0 -1
  47. package/dist-server/index.js.map +0 -1
  48. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +0 -1
  49. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +0 -1
  50. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +0 -1
  51. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +0 -1
  52. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +0 -1
  53. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +0 -1
  54. package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.js.map +0 -1
  55. package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.js.map +0 -1
  56. package/dist-server/migrations/1786800000000-CreateTwinReferenceReconciliations.js.map +0 -1
  57. package/dist-server/migrations/index.js.map +0 -1
  58. package/dist-server/routes.js.map +0 -1
  59. package/dist-server/service/actuation/actuation-advisor.js.map +0 -1
  60. package/dist-server/service/actuation/actuation-outcome.js.map +0 -1
  61. package/dist-server/service/actuation/actuation-rule-resolver.js.map +0 -1
  62. package/dist-server/service/actuation/actuation-rule.js.map +0 -1
  63. package/dist-server/service/actuation/approval-gateway.js.map +0 -1
  64. package/dist-server/service/actuation/catch-up-effects.js.map +0 -1
  65. package/dist-server/service/actuation/command-dispatcher.js.map +0 -1
  66. package/dist-server/service/actuation/command-store.js.map +0 -1
  67. package/dist-server/service/actuation/dispatch-after-approval.js.map +0 -1
  68. package/dist-server/service/actuation/effect-match.js.map +0 -1
  69. package/dist-server/service/actuation/index.js.map +0 -1
  70. package/dist-server/service/actuation/retry-plan.js.map +0 -1
  71. package/dist-server/service/actuation/rule-evaluate.js.map +0 -1
  72. package/dist-server/service/actuation/rule-loop.js.map +0 -1
  73. package/dist-server/service/actuation/rule-runner.js.map +0 -1
  74. package/dist-server/service/actuation/rule-wiring.js.map +0 -1
  75. package/dist-server/service/actuation/settle-effects.js.map +0 -1
  76. package/dist-server/service/actuation/sweep-dispatch.js.map +0 -1
  77. package/dist-server/service/actuation/twin-command-resolver.js.map +0 -1
  78. package/dist-server/service/actuation/twin-command.js.map +0 -1
  79. package/dist-server/service/actuation/twin-now.js.map +0 -1
  80. package/dist-server/service/index.js.map +0 -1
  81. package/dist-server/service/reference/actuation-routing.js.map +0 -1
  82. package/dist-server/service/reference/actuation-target.js.map +0 -1
  83. package/dist-server/service/reference/actuation-verification.js.map +0 -1
  84. package/dist-server/service/reference/attention-lane.js.map +0 -1
  85. package/dist-server/service/reference/connection-portability-resolver.js.map +0 -1
  86. package/dist-server/service/reference/connection-portability.js.map +0 -1
  87. package/dist-server/service/reference/control-routing.js.map +0 -1
  88. package/dist-server/service/reference/discovery-result.js.map +0 -1
  89. package/dist-server/service/reference/fill-cadence.js.map +0 -1
  90. package/dist-server/service/reference/fill-dedupe.js.map +0 -1
  91. package/dist-server/service/reference/fill-loop.js.map +0 -1
  92. package/dist-server/service/reference/fill-span.js.map +0 -1
  93. package/dist-server/service/reference/hook-contract.js.map +0 -1
  94. package/dist-server/service/reference/hook-rejected.js.map +0 -1
  95. package/dist-server/service/reference/hook-store.js.map +0 -1
  96. package/dist-server/service/reference/index.js.map +0 -1
  97. package/dist-server/service/reference/ingest-space.js.map +0 -1
  98. package/dist-server/service/reference/knob-defaults.js.map +0 -1
  99. package/dist-server/service/reference/live-cadence.js.map +0 -1
  100. package/dist-server/service/reference/live-feed-lease.js.map +0 -1
  101. package/dist-server/service/reference/live-ingest.js.map +0 -1
  102. package/dist-server/service/reference/reference-adapter.js.map +0 -1
  103. package/dist-server/service/reference/reference-assessment.js.map +0 -1
  104. package/dist-server/service/reference/reference-fill-run.js.map +0 -1
  105. package/dist-server/service/reference/reference-fill.js.map +0 -1
  106. package/dist-server/service/reference/reference-hook.js.map +0 -1
  107. package/dist-server/service/reference/reference-link-groups.js.map +0 -1
  108. package/dist-server/service/reference/reference-links.js.map +0 -1
  109. package/dist-server/service/reference/reference-live.js.map +0 -1
  110. package/dist-server/service/reference/reference-master.js.map +0 -1
  111. package/dist-server/service/reference/reference-probe.js.map +0 -1
  112. package/dist-server/service/reference/reference-progress-channel.js.map +0 -1
  113. package/dist-server/service/reference/reference-progress-subscription.js.map +0 -1
  114. package/dist-server/service/reference/reference-progress.js.map +0 -1
  115. package/dist-server/service/reference/reference-reconciliation-query.js.map +0 -1
  116. package/dist-server/service/reference/reference-reconciliation-writer.js.map +0 -1
  117. package/dist-server/service/reference/reference-reconciliation.js.map +0 -1
  118. package/dist-server/service/reference/reference-registry.js.map +0 -1
  119. package/dist-server/service/reference/reference-resolver.js.map +0 -1
  120. package/dist-server/service/reference/template-registry.js.map +0 -1
  121. package/dist-server/service/reference/twin-reference-reconciliation.js.map +0 -1
  122. package/dist-server/service/reference/twin-reference.js.map +0 -1
  123. package/dist-server/service/twin-attention/index.js.map +0 -1
  124. package/dist-server/service/twin-attention/twin-attention-query.js.map +0 -1
  125. package/dist-server/service/twin-audit/command-audit.js.map +0 -1
  126. package/dist-server/service/twin-audit/index.js.map +0 -1
  127. package/dist-server/service/twin-audit/twin-audit-event.js.map +0 -1
  128. package/dist-server/service/twin-audit/twin-audit-query.js.map +0 -1
  129. package/dist-server/service/twin-backfill/backfill-period-facts.js.map +0 -1
  130. package/dist-server/service/twin-backfill/backfill-shape.js.map +0 -1
  131. package/dist-server/service/twin-backfill/index.js.map +0 -1
  132. package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +0 -1
  133. package/dist-server/service/twin-control/index.js.map +0 -1
  134. package/dist-server/service/twin-control/twin-control-mutation.js.map +0 -1
  135. package/dist-server/service/twin-event/index.js.map +0 -1
  136. package/dist-server/service/twin-event/journal-count.js.map +0 -1
  137. package/dist-server/service/twin-event/twin-event-keys.js.map +0 -1
  138. package/dist-server/service/twin-event/twin-event-type.js.map +0 -1
  139. package/dist-server/service/twin-event/twin-event.js.map +0 -1
  140. package/dist-server/service/twin-forecast/forecast-metrics.js.map +0 -1
  141. package/dist-server/service/twin-forecast/forecast-tuning.js.map +0 -1
  142. package/dist-server/service/twin-forecast/gap-analytics.js.map +0 -1
  143. package/dist-server/service/twin-forecast/index.js.map +0 -1
  144. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +0 -1
  145. package/dist-server/service/twin-ingest-window/index.js.map +0 -1
  146. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +0 -1
  147. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +0 -1
  148. package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +0 -1
  149. package/dist-server/service/twin-instance/index.js.map +0 -1
  150. package/dist-server/service/twin-instance/twin-instance.js.map +0 -1
  151. package/dist-server/service/twin-journal/event-type-split-shape.js.map +0 -1
  152. package/dist-server/service/twin-journal/event-type-split.js.map +0 -1
  153. package/dist-server/service/twin-journal/index.js.map +0 -1
  154. package/dist-server/service/twin-journal/journal-order.js.map +0 -1
  155. package/dist-server/service/twin-journal/twin-journal-query.js.map +0 -1
  156. package/dist-server/service/twin-lifecycle/domain-catalog.js.map +0 -1
  157. package/dist-server/service/twin-lifecycle/index.js.map +0 -1
  158. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +0 -1
  159. package/dist-server/service/twin-metrics/index.js.map +0 -1
  160. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +0 -1
  161. package/dist-server/service/twin-model/axis-journal-evidence.js.map +0 -1
  162. package/dist-server/service/twin-model/choose-recipe.js.map +0 -1
  163. package/dist-server/service/twin-model/epcis-coverage.js.map +0 -1
  164. package/dist-server/service/twin-model/external-resolver.js.map +0 -1
  165. package/dist-server/service/twin-model/iec61850-coverage.js.map +0 -1
  166. package/dist-server/service/twin-model/index.js.map +0 -1
  167. package/dist-server/service/twin-model/isa95-coverage.js.map +0 -1
  168. package/dist-server/service/twin-model/item-ref.js.map +0 -1
  169. package/dist-server/service/twin-model/name-index.js.map +0 -1
  170. package/dist-server/service/twin-model/project-structure.js.map +0 -1
  171. package/dist-server/service/twin-model/standard-coverage.js.map +0 -1
  172. package/dist-server/service/twin-model/status-tally.js.map +0 -1
  173. package/dist-server/service/twin-model/twin-equipment.js.map +0 -1
  174. package/dist-server/service/twin-model/twin-lineage-query.js.map +0 -1
  175. package/dist-server/service/twin-model/twin-location.js.map +0 -1
  176. package/dist-server/service/twin-model/twin-model-item-query.js.map +0 -1
  177. package/dist-server/service/twin-model/twin-model-mutation.js.map +0 -1
  178. package/dist-server/service/twin-model/twin-model-query.js.map +0 -1
  179. package/dist-server/service/twin-model/twin-model-tree-query.js.map +0 -1
  180. package/dist-server/service/twin-model/twin-operation.js.map +0 -1
  181. package/dist-server/service/twin-readiness/index.js.map +0 -1
  182. package/dist-server/service/twin-readiness/twin-readiness-query.js.map +0 -1
  183. package/dist-server/service/twin-space/area-resolver.js.map +0 -1
  184. package/dist-server/service/twin-space/index.js.map +0 -1
  185. package/dist-server/service/twin-space/move-space.js.map +0 -1
  186. package/dist-server/service/twin-space/representation-role.js.map +0 -1
  187. package/dist-server/service/twin-space/space-integrity.js.map +0 -1
  188. package/dist-server/service/twin-space/twin-area.js.map +0 -1
  189. package/dist-server/service/twin-space/twin-space-area.js.map +0 -1
  190. package/dist-server/service/twin-space/twin-space-representation.js.map +0 -1
  191. package/dist-server/service/twin-space/twin-space-resolver.js.map +0 -1
  192. package/dist-server/service/twin-space/twin-space.js.map +0 -1
  193. package/dist-server/service/twin-state/index.js.map +0 -1
  194. package/dist-server/service/twin-state/twin-state-subscription.js.map +0 -1
  195. package/dist-server/service/twin-structure/index.js.map +0 -1
  196. package/dist-server/service/twin-structure/twin-structure.js.map +0 -1
  197. package/dist-server/service/twin-subject/event-subjects.js.map +0 -1
  198. package/dist-server/service/twin-subject/index.js.map +0 -1
  199. package/dist-server/service/twin-subject/item-resolver.js.map +0 -1
  200. package/dist-server/service/twin-subject/subject-query-shape.js.map +0 -1
  201. package/dist-server/service/twin-subject/twin-subject-backfill.js.map +0 -1
  202. package/dist-server/service/twin-subject/twin-subject-event.js.map +0 -1
  203. package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +0 -1
  204. package/dist-server/service/twin-subject/twin-subject-query.js.map +0 -1
  205. package/dist-server/service/twin-subject/twin-subject-writer.js.map +0 -1
  206. package/dist-server/service/twin-target/index.js.map +0 -1
  207. package/dist-server/service/twin-target/twin-target-resolver.js.map +0 -1
  208. package/dist-server/service/twin-target/twin-target.js.map +0 -1
  209. package/dist-shared/axis-read.js.map +0 -1
  210. package/dist-shared/entity-delta.js.map +0 -1
  211. package/dist-shared/kpi-broadcast.js.map +0 -1
  212. package/dist-shared/touched-items.js.map +0 -1
  213. package/dist-shared/twin-level.js.map +0 -1
  214. package/server/engine/attention-digest.ts +0 -102
  215. package/server/engine/canonical-ingest.ts +0 -511
  216. package/server/engine/checkpoint-fold.ts +0 -12
  217. package/server/engine/command-routing.ts +0 -67
  218. package/server/engine/declared-stimulus.ts +0 -66
  219. package/server/engine/energy-topology.ts +0 -189
  220. package/server/engine/event-time.ts +0 -96
  221. package/server/engine/index.ts +0 -51
  222. package/server/engine/ingest-dedupe.ts +0 -24
  223. package/server/engine/ingest-health.ts +0 -24
  224. package/server/engine/integration-coverage.ts +0 -147
  225. package/server/engine/integration-probes.ts +0 -144
  226. package/server/engine/integration-runner.ts +0 -95
  227. package/server/engine/integration-target-profile.ts +0 -103
  228. package/server/engine/kpi-baseline.ts +0 -202
  229. package/server/engine/kpi-fold.ts +0 -1516
  230. package/server/engine/kpi-query.ts +0 -1210
  231. package/server/engine/kpi-target.ts +0 -248
  232. package/server/engine/live-attentions.ts +0 -30
  233. package/server/engine/live-feed-registry.ts +0 -59
  234. package/server/engine/load-meter.ts +0 -393
  235. package/server/engine/local-declarations.ts +0 -779
  236. package/server/engine/log.ts +0 -72
  237. package/server/engine/loop-lag.ts +0 -120
  238. package/server/engine/measured-estimator.ts +0 -91
  239. package/server/engine/measured-yield.ts +0 -89
  240. package/server/engine/model-basis.ts +0 -162
  241. package/server/engine/model-gap.ts +0 -168
  242. package/server/engine/model-refusal.ts +0 -31
  243. package/server/engine/model-vocabulary.ts +0 -82
  244. package/server/engine/oee-accumulator.ts +0 -195
  245. package/server/engine/operation-basis.ts +0 -131
  246. package/server/engine/producer-guard.ts +0 -64
  247. package/server/engine/property-effects.ts +0 -216
  248. package/server/engine/read-time.ts +0 -77
  249. package/server/engine/restart-policy.ts +0 -55
  250. package/server/engine/runtime-key.ts +0 -58
  251. package/server/engine/spec-coverage.ts +0 -105
  252. package/server/engine/stage-path.ts +0 -172
  253. package/server/engine/state-axes.ts +0 -60
  254. package/server/engine/structure-diff.ts +0 -88
  255. package/server/engine/travel-estimator.ts +0 -138
  256. package/server/engine/twin-engine.ts +0 -4979
  257. package/server/engine/twin-state-channel.ts +0 -39
  258. package/server/engine/warm-start.ts +0 -265
  259. package/server/index.ts +0 -90
  260. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +0 -43
  261. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +0 -54
  262. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +0 -53
  263. package/server/migrations/1786300000000-IndexEventTypeByTime.ts +0 -48
  264. package/server/migrations/1786400000000-IndexEventCreatedAt.ts +0 -38
  265. package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +0 -93
  266. package/server/migrations/1786600000000-RenameOperatoMesAdapterType.ts +0 -66
  267. package/server/migrations/1786700000000-AddDispatchRetryColumns.ts +0 -82
  268. package/server/migrations/1786800000000-CreateTwinReferenceReconciliations.ts +0 -39
  269. package/server/migrations/index.ts +0 -25
  270. package/server/routes.ts +0 -75
  271. package/server/service/actuation/actuation-advisor.ts +0 -131
  272. package/server/service/actuation/actuation-outcome.ts +0 -97
  273. package/server/service/actuation/actuation-rule-resolver.ts +0 -147
  274. package/server/service/actuation/actuation-rule.ts +0 -139
  275. package/server/service/actuation/approval-gateway.ts +0 -44
  276. package/server/service/actuation/catch-up-effects.ts +0 -152
  277. package/server/service/actuation/command-dispatcher.ts +0 -474
  278. package/server/service/actuation/command-store.ts +0 -236
  279. package/server/service/actuation/dispatch-after-approval.ts +0 -71
  280. package/server/service/actuation/effect-match.ts +0 -124
  281. package/server/service/actuation/index.ts +0 -27
  282. package/server/service/actuation/retry-plan.ts +0 -145
  283. package/server/service/actuation/rule-evaluate.ts +0 -352
  284. package/server/service/actuation/rule-loop.ts +0 -160
  285. package/server/service/actuation/rule-runner.ts +0 -216
  286. package/server/service/actuation/rule-wiring.ts +0 -84
  287. package/server/service/actuation/settle-effects.ts +0 -115
  288. package/server/service/actuation/sweep-dispatch.ts +0 -179
  289. package/server/service/actuation/twin-command-resolver.ts +0 -387
  290. package/server/service/actuation/twin-command.ts +0 -232
  291. package/server/service/actuation/twin-now.ts +0 -97
  292. package/server/service/index.ts +0 -100
  293. package/server/service/reference/actuation-routing.ts +0 -192
  294. package/server/service/reference/actuation-target.ts +0 -163
  295. package/server/service/reference/actuation-verification.ts +0 -85
  296. package/server/service/reference/attention-lane.ts +0 -21
  297. package/server/service/reference/connection-portability-resolver.ts +0 -171
  298. package/server/service/reference/connection-portability.ts +0 -329
  299. package/server/service/reference/control-routing.ts +0 -62
  300. package/server/service/reference/discovery-result.ts +0 -95
  301. package/server/service/reference/fill-cadence.ts +0 -29
  302. package/server/service/reference/fill-dedupe.ts +0 -66
  303. package/server/service/reference/fill-loop.ts +0 -153
  304. package/server/service/reference/fill-span.ts +0 -31
  305. package/server/service/reference/hook-contract.ts +0 -364
  306. package/server/service/reference/hook-rejected.ts +0 -53
  307. package/server/service/reference/hook-store.ts +0 -28
  308. package/server/service/reference/index.ts +0 -18
  309. package/server/service/reference/ingest-space.ts +0 -104
  310. package/server/service/reference/knob-defaults.ts +0 -59
  311. package/server/service/reference/live-cadence.ts +0 -129
  312. package/server/service/reference/live-feed-lease.ts +0 -56
  313. package/server/service/reference/live-ingest.ts +0 -94
  314. package/server/service/reference/reference-adapter.ts +0 -985
  315. package/server/service/reference/reference-assessment.ts +0 -250
  316. package/server/service/reference/reference-fill-run.ts +0 -179
  317. package/server/service/reference/reference-fill.ts +0 -223
  318. package/server/service/reference/reference-hook.ts +0 -479
  319. package/server/service/reference/reference-link-groups.ts +0 -21
  320. package/server/service/reference/reference-links.ts +0 -77
  321. package/server/service/reference/reference-live.ts +0 -420
  322. package/server/service/reference/reference-master.ts +0 -999
  323. package/server/service/reference/reference-probe.ts +0 -264
  324. package/server/service/reference/reference-progress-channel.ts +0 -26
  325. package/server/service/reference/reference-progress-subscription.ts +0 -78
  326. package/server/service/reference/reference-progress.ts +0 -95
  327. package/server/service/reference/reference-reconciliation-query.ts +0 -28
  328. package/server/service/reference/reference-reconciliation-writer.ts +0 -38
  329. package/server/service/reference/reference-reconciliation.ts +0 -55
  330. package/server/service/reference/reference-registry.ts +0 -17
  331. package/server/service/reference/reference-resolver.ts +0 -969
  332. package/server/service/reference/template-registry.ts +0 -85
  333. package/server/service/reference/twin-reference-reconciliation.ts +0 -27
  334. package/server/service/reference/twin-reference.ts +0 -190
  335. package/server/service/twin-attention/index.ts +0 -3
  336. package/server/service/twin-attention/twin-attention-query.ts +0 -70
  337. package/server/service/twin-audit/command-audit.ts +0 -114
  338. package/server/service/twin-audit/index.ts +0 -5
  339. package/server/service/twin-audit/twin-audit-event.ts +0 -158
  340. package/server/service/twin-audit/twin-audit-query.ts +0 -72
  341. package/server/service/twin-backfill/backfill-period-facts.ts +0 -168
  342. package/server/service/twin-backfill/backfill-shape.ts +0 -86
  343. package/server/service/twin-backfill/index.ts +0 -3
  344. package/server/service/twin-backfill/twin-backfill-resolver.ts +0 -35
  345. package/server/service/twin-control/index.ts +0 -3
  346. package/server/service/twin-control/twin-control-mutation.ts +0 -150
  347. package/server/service/twin-event/index.ts +0 -3
  348. package/server/service/twin-event/journal-count.ts +0 -53
  349. package/server/service/twin-event/twin-event-keys.ts +0 -215
  350. package/server/service/twin-event/twin-event-type.ts +0 -68
  351. package/server/service/twin-event/twin-event.ts +0 -250
  352. package/server/service/twin-forecast/forecast-metrics.ts +0 -106
  353. package/server/service/twin-forecast/forecast-tuning.ts +0 -40
  354. package/server/service/twin-forecast/gap-analytics.ts +0 -139
  355. package/server/service/twin-forecast/index.ts +0 -3
  356. package/server/service/twin-forecast/twin-forecast-query.ts +0 -486
  357. package/server/service/twin-ingest-window/index.ts +0 -7
  358. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +0 -125
  359. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +0 -57
  360. package/server/service/twin-ingest-window/twin-ingest-window.ts +0 -113
  361. package/server/service/twin-instance/index.ts +0 -3
  362. package/server/service/twin-instance/twin-instance.ts +0 -199
  363. package/server/service/twin-journal/event-type-split-shape.ts +0 -112
  364. package/server/service/twin-journal/event-type-split.ts +0 -144
  365. package/server/service/twin-journal/index.ts +0 -3
  366. package/server/service/twin-journal/journal-order.ts +0 -20
  367. package/server/service/twin-journal/twin-journal-query.ts +0 -615
  368. package/server/service/twin-lifecycle/domain-catalog.ts +0 -51
  369. package/server/service/twin-lifecycle/index.ts +0 -3
  370. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +0 -285
  371. package/server/service/twin-metrics/index.ts +0 -3
  372. package/server/service/twin-metrics/twin-metrics-query.ts +0 -172
  373. package/server/service/twin-model/axis-journal-evidence.ts +0 -57
  374. package/server/service/twin-model/choose-recipe.ts +0 -31
  375. package/server/service/twin-model/epcis-coverage.ts +0 -251
  376. package/server/service/twin-model/external-resolver.ts +0 -173
  377. package/server/service/twin-model/iec61850-coverage.ts +0 -111
  378. package/server/service/twin-model/index.ts +0 -25
  379. package/server/service/twin-model/isa95-coverage.ts +0 -320
  380. package/server/service/twin-model/item-ref.ts +0 -80
  381. package/server/service/twin-model/name-index.ts +0 -94
  382. package/server/service/twin-model/project-structure.ts +0 -213
  383. package/server/service/twin-model/standard-coverage.ts +0 -35
  384. package/server/service/twin-model/status-tally.ts +0 -37
  385. package/server/service/twin-model/twin-equipment.ts +0 -191
  386. package/server/service/twin-model/twin-lineage-query.ts +0 -327
  387. package/server/service/twin-model/twin-location.ts +0 -147
  388. package/server/service/twin-model/twin-model-item-query.ts +0 -1029
  389. package/server/service/twin-model/twin-model-mutation.ts +0 -503
  390. package/server/service/twin-model/twin-model-query.ts +0 -792
  391. package/server/service/twin-model/twin-model-tree-query.ts +0 -211
  392. package/server/service/twin-model/twin-operation.ts +0 -157
  393. package/server/service/twin-readiness/index.ts +0 -3
  394. package/server/service/twin-readiness/twin-readiness-query.ts +0 -96
  395. package/server/service/twin-space/area-resolver.ts +0 -75
  396. package/server/service/twin-space/index.ts +0 -8
  397. package/server/service/twin-space/move-space.ts +0 -262
  398. package/server/service/twin-space/representation-role.ts +0 -15
  399. package/server/service/twin-space/space-integrity.ts +0 -293
  400. package/server/service/twin-space/twin-area.ts +0 -63
  401. package/server/service/twin-space/twin-space-area.ts +0 -66
  402. package/server/service/twin-space/twin-space-representation.ts +0 -99
  403. package/server/service/twin-space/twin-space-resolver.ts +0 -582
  404. package/server/service/twin-space/twin-space.ts +0 -148
  405. package/server/service/twin-state/index.ts +0 -3
  406. package/server/service/twin-state/twin-state-subscription.ts +0 -62
  407. package/server/service/twin-structure/index.ts +0 -3
  408. package/server/service/twin-structure/twin-structure.ts +0 -96
  409. package/server/service/twin-subject/event-subjects.ts +0 -162
  410. package/server/service/twin-subject/index.ts +0 -10
  411. package/server/service/twin-subject/item-resolver.ts +0 -92
  412. package/server/service/twin-subject/subject-query-shape.ts +0 -84
  413. package/server/service/twin-subject/twin-subject-backfill.ts +0 -196
  414. package/server/service/twin-subject/twin-subject-event.ts +0 -101
  415. package/server/service/twin-subject/twin-subject-fast-path.ts +0 -346
  416. package/server/service/twin-subject/twin-subject-query.ts +0 -210
  417. package/server/service/twin-subject/twin-subject-writer.ts +0 -189
  418. package/server/service/twin-target/index.ts +0 -5
  419. package/server/service/twin-target/twin-target-resolver.ts +0 -142
  420. package/server/service/twin-target/twin-target.ts +0 -97
  421. package/server/types/global.d.ts +0 -22
  422. package/shared/axis-read.ts +0 -28
  423. package/shared/entity-delta.ts +0 -335
  424. package/shared/kpi-broadcast.ts +0 -13
  425. package/shared/touched-items.ts +0 -73
  426. package/shared/twin-level.ts +0 -48
  427. package/test/ack-shape-alignment.test.ts +0 -97
  428. package/test/actuation-advisor.test.ts +0 -55
  429. package/test/actuation-approval-door.test.ts +0 -411
  430. package/test/actuation-dispatch.test.ts +0 -209
  431. package/test/actuation-effect.test.ts +0 -162
  432. package/test/actuation-outcome.test.ts +0 -152
  433. package/test/actuation-rule-runner.test.ts +0 -281
  434. package/test/actuation-rule.test.ts +0 -266
  435. package/test/actuation-seam.test.ts +0 -255
  436. package/test/actuation-settle.test.ts +0 -164
  437. package/test/actuation-target.test.ts +0 -145
  438. package/test/actuation-verification-state.test.ts +0 -139
  439. package/test/adopt-structure-live.test.ts +0 -133
  440. package/test/aggregate-not-findone.test.ts +0 -112
  441. package/test/area-resolver-db.test.ts +0 -110
  442. package/test/attention-digest.test.ts +0 -161
  443. package/test/axis-declaration-passthrough-db.test.ts +0 -152
  444. package/test/axis-journal-evidence.test.ts +0 -71
  445. package/test/axis-read.test.ts +0 -188
  446. package/test/backfill-shape.test.ts +0 -89
  447. package/test/boot-resume.test.ts +0 -287
  448. package/test/broadcast-cost-baseline.test.ts +0 -201
  449. package/test/broadcast-period.test.ts +0 -58
  450. package/test/broadcast-queue-overflow.test.ts +0 -244
  451. package/test/canonical-ingest-vocabularies.test.ts +0 -451
  452. package/test/canonical-quantity-door.test.ts +0 -61
  453. package/test/capability-mapping.test.ts +0 -89
  454. package/test/checkpoint-refuses-empty.test.ts +0 -103
  455. package/test/choose-recipe.test.ts +0 -117
  456. package/test/column-type-portability.test.ts +0 -122
  457. package/test/command-routing.test.ts +0 -61
  458. package/test/command-store-writes-columns.test.ts +0 -126
  459. package/test/connection-portability-doors.test.ts +0 -87
  460. package/test/connector-capability-declaration.test.ts +0 -195
  461. package/test/contract-layer-guard.test.ts +0 -48
  462. package/test/control-capability.test.ts +0 -98
  463. package/test/cursor-stall-not-read-failure.test.ts +0 -126
  464. package/test/declaration-reaches-model.test.ts +0 -226
  465. package/test/declared-location-types.test.ts +0 -89
  466. package/test/declared-stimulus.test.ts +0 -88
  467. package/test/dedupe-transformation.test.ts +0 -112
  468. package/test/discovery-result.test.ts +0 -75
  469. package/test/dispatch-after-approval-source.test.ts +0 -81
  470. package/test/duration-estimators.test.ts +0 -144
  471. package/test/energy-topology.test.ts +0 -113
  472. package/test/entity-delta.test.ts +0 -352
  473. package/test/equipment-identity-reaches.test.ts +0 -57
  474. package/test/event-subjects.test.ts +0 -148
  475. package/test/event-time-column.test.ts +0 -67
  476. package/test/event-type-split.test.ts +0 -178
  477. package/test/external-resolver.test.ts +0 -177
  478. package/test/fact-scope-wiring.test.ts +0 -96
  479. package/test/fill-dedupe.test.ts +0 -59
  480. package/test/fill-loop.test.ts +0 -35
  481. package/test/first-envelope.test.ts +0 -153
  482. package/test/fold-gap-speaks.test.ts +0 -53
  483. package/test/fold-shift-dst.test.ts +0 -110
  484. package/test/forecast-metrics.test.ts +0 -157
  485. package/test/forecast-tuning.test.ts +0 -42
  486. package/test/gap-analytics.test.ts +0 -113
  487. package/test/generated-not-counted.test.ts +0 -99
  488. package/test/guard-roots.ts +0 -76
  489. package/test/home-in-area.test.ts +0 -90
  490. package/test/hook-address.test.ts +0 -28
  491. package/test/hook-rejected-shape.test.ts +0 -86
  492. package/test/hook-response-sum.test.ts +0 -193
  493. package/test/hook-sequence.test.ts +0 -333
  494. package/test/ingest-bench.test.ts +0 -70
  495. package/test/ingest-dedupe.test.ts +0 -56
  496. package/test/ingest-expected-quiet.test.ts +0 -111
  497. package/test/ingest-health-engine.test.ts +0 -248
  498. package/test/ingest-health-wiring.test.ts +0 -120
  499. package/test/ingest-health.test.ts +0 -306
  500. package/test/ingest-history.test.ts +0 -247
  501. package/test/ingest-idempotent.test.ts +0 -211
  502. package/test/ingest-reconcile-four-ways.test.ts +0 -97
  503. package/test/ingest-rules-header.test.ts +0 -100
  504. package/test/ingest-running-guard.test.ts +0 -136
  505. package/test/ingest-space.test.ts +0 -78
  506. package/test/ingest-window-rolls-unobserved.test.ts +0 -72
  507. package/test/ingest-wiring-guard.test.ts +0 -197
  508. package/test/instance-cache-lifecycle.test.ts +0 -110
  509. package/test/intake-mapping-parity.test.ts +0 -175
  510. package/test/integration-probes.test.ts +0 -103
  511. package/test/integration-runner.test.ts +0 -95
  512. package/test/item-ref.test.ts +0 -78
  513. package/test/journal-order-axis.test.ts +0 -80
  514. package/test/journal-read-discipline.test.ts +0 -186
  515. package/test/journal-retention.test.ts +0 -275
  516. package/test/journal-sort-axis.test.ts +0 -142
  517. package/test/journal-write-door.test.ts +0 -110
  518. package/test/journal-write-trend.test.ts +0 -115
  519. package/test/kernel-kind-guard.test.ts +0 -98
  520. package/test/knob-defaults.test.ts +0 -72
  521. package/test/kpi-baseline-db.test.ts +0 -216
  522. package/test/kpi-baseline.test.ts +0 -196
  523. package/test/kpi-fold.test.ts +0 -1456
  524. package/test/kpi-query-bench.test.ts +0 -130
  525. package/test/lens-own-places.test.ts +0 -50
  526. package/test/lineage-survives-restart.test.ts +0 -234
  527. package/test/live-cadence.test.ts +0 -170
  528. package/test/live-cursor-wiring.test.ts +0 -87
  529. package/test/live-feed-lease.test.ts +0 -63
  530. package/test/live-feed-registry.test.ts +0 -75
  531. package/test/live-kernel-facts.test.ts +0 -161
  532. package/test/live-mirror-parity.test.ts +0 -286
  533. package/test/load-meter.test.ts +0 -328
  534. package/test/local-declarations.test.ts +0 -662
  535. package/test/log-stamp.test.ts +0 -59
  536. package/test/loop-lag.test.ts +0 -82
  537. package/test/master-to-twin.test.ts +0 -279
  538. package/test/measured-yield.test.ts +0 -90
  539. package/test/merge-streams.test.ts +0 -70
  540. package/test/mirror-resumes-from-checkpoint.test.ts +0 -192
  541. package/test/model-basis.test.ts +0 -141
  542. package/test/model-gap.test.ts +0 -153
  543. package/test/model-refusal.test.ts +0 -75
  544. package/test/model-vocabulary.test.ts +0 -114
  545. package/test/move-space.test.ts +0 -149
  546. package/test/mutation-gate.test.ts +0 -122
  547. package/test/oee-accumulator.test.ts +0 -204
  548. package/test/oee-site-calendar.test.ts +0 -146
  549. package/test/open-doors.test.ts +0 -197
  550. package/test/operation-basis.test.ts +0 -127
  551. package/test/operational-vocabulary.test.ts +0 -108
  552. package/test/operations-capability-db.test.ts +0 -165
  553. package/test/order-and-transaction-split.test.ts +0 -107
  554. package/test/order-fold-host.test.ts +0 -112
  555. package/test/producer-guard.test.ts +0 -66
  556. package/test/project-structure-db.test.ts +0 -226
  557. package/test/projection-reaches-screen.test.ts +0 -277
  558. package/test/projection-reads-declared.test.ts +0 -109
  559. package/test/property-effects.test.ts +0 -128
  560. package/test/provenance-promotion.test.ts +0 -99
  561. package/test/read-failure-ledger.test.ts +0 -106
  562. package/test/read-failure-visible.test.ts +0 -145
  563. package/test/read-time.test.ts +0 -118
  564. package/test/reference-fill.test.ts +0 -235
  565. package/test/reference-grounding.test.ts +0 -70
  566. package/test/reference-hook.test.ts +0 -177
  567. package/test/reference-links.test.ts +0 -56
  568. package/test/reference-reconciliation.test.ts +0 -39
  569. package/test/registry-key-guard.test.ts +0 -80
  570. package/test/rejection-carries-event-id.test.ts +0 -101
  571. package/test/rename-twin.test.ts +0 -89
  572. package/test/resolve-ts-siblings.mjs +0 -52
  573. package/test/restart-policy.test.ts +0 -111
  574. package/test/resync-origin-site.test.ts +0 -83
  575. package/test/retry-plan.test.ts +0 -139
  576. package/test/revision-axis.test.ts +0 -110
  577. package/test/runtime-key.test.ts +0 -66
  578. package/test/save-intake-collision.test.ts +0 -91
  579. package/test/scale-twin-bench.test.ts +0 -103
  580. package/test/secrets-at-rest.test.ts +0 -135
  581. package/test/snapshot-freshness.test.ts +0 -60
  582. package/test/source-outcome-audit.test.ts +0 -104
  583. package/test/space-integrity.test.ts +0 -223
  584. package/test/space-representation.test.ts +0 -66
  585. package/test/space-scope-includes-stopped.test.ts +0 -55
  586. package/test/spec-coverage.test.ts +0 -113
  587. package/test/stage-path.test.ts +0 -95
  588. package/test/standard-coverage.test.ts +0 -148
  589. package/test/state-axes.test.ts +0 -74
  590. package/test/status-tally.test.ts +0 -55
  591. package/test/streamline-e2e.test.ts +0 -76
  592. package/test/structure-revision-db.test.ts +0 -315
  593. package/test/subject-fast-path.test.ts +0 -148
  594. package/test/subject-read-model.test.ts +0 -175
  595. package/test/sweep-dispatch.test.ts +0 -191
  596. package/test/tenant-registry-db.test.ts +0 -149
  597. package/test/time-range.test.ts +0 -152
  598. package/test/touched-items.test.ts +0 -61
  599. package/test/twin-audit.test.ts +0 -83
  600. package/test/twin-event-keys.test.ts +0 -193
  601. package/test/twin-model-item-db.test.ts +0 -319
  602. package/test/twin-model-parent-relation-db.test.ts +0 -125
  603. package/test/twin-model-tree-db.test.ts +0 -209
  604. package/test/twin-origin-resync.test.ts +0 -133
  605. package/test/twin-state-channel.test.ts +0 -111
  606. package/test/vocabulary-guard.test.ts +0 -43
  607. package/test/warm-start-seam.test.ts +0 -162
  608. package/test/warm-start.test.ts +0 -297
  609. package/test/withheld-door.test.ts +0 -95
  610. package/test/yield-loop.test.ts +0 -199
  611. package/tsconfig.json +0 -15
  612. package/tsconfig.shared.json +0 -23
  613. package/tsconfig.shared.tsbuildinfo +0 -1
  614. package/tsconfig.tsbuildinfo +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"rule-evaluate.js","sourceRoot":"","sources":["../../../server/service/actuation/rule-evaluate.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **관측에서 조치를 낸다** — 그 판정 하나.\n *\n * ── 무엇이 없었나 (2026-09-01) ───────────────────────────────────────────\n * 승인 게이트를 세우고 나니 **낼 수 있는 것이 사람뿐**이었다. 계약은 낸 쪽을 네 가지로 두는데\n * (`operator` · `rule` · `ai` · `schedule`) 사람 말고는 길이 없었다. 게이트의 값어치는 트윈이 스스로\n * 알아챈 것을 사람이 판단하는 데 있는데, 알아챈 것을 올릴 자리가 없었다.\n *\n * ── 조건을 새로 만들지 않는다 ────────────────────────────────────────────\n * 커널이 이미 상태에서 「봐야 할 것」을 판단해 `Attention` 으로 낸다 — 안정된 종류 코드(`kind`),\n * 무엇에 대한 것인지(`anchor`), 언제부터인지(`timeStamp`), 표현용 값(`params`). 조건 언어를 새로 만들면\n * 같은 판단이 두 벌이 되고, 한쪽만 고쳐지는 날이 온다.\n *\n * 그래서 규칙은 **어느 주의에 어느 지시를 붙이는가**만 정한다.\n *\n * ── 자극과 지시를 잇지 않는다 ────────────────────────────────────────────\n * 주의에는 `suggestedAction.command` 가 실려 있지만 그것은 **커널 안에서 도는 자극**이다\n * (`resource.repair` = 모델에서 수리된 것으로 둔다). 여기서 내는 것은 현장에 나가는 지시다. 그 값을\n * 그대로 쓰면 「모델을 그렇게 두라」가 「사람을 보내라」가 된다(§`propose` 의 거절).\n *\n * ── 무너지는 것 넷 ───────────────────────────────────────────────────────\n * 행복한 길은 짧다. 실제로 무너지는 것은 이것들이다.\n *\n * 같은 조건이 매 주기 다시 올라온다 대기 줄에 같은 지시가 수십 개 선다\n * 방금 생긴 조건에 바로 올린다 깜빡인 것에 현장 지시가 나간다\n * 조건이 사라진 뒤에도 대기에 남는다 승인하는 사람이 없는 문제를 판단한다\n * 한 번에 수백 개가 올라온다 사람이 읽을 수 없고, 읽지 않으면 게이트가 시늉이 된다\n *\n * 넷 다 오류가 나지 않는다. 그래서 이 파일이 넷을 다룬다.\n */\nimport type { CommandTypeSpec } from '@operato/ops-contract'\n\n/** 어느 주의에 어느 지시를 붙이는가 — 선언 한 줄. */\nexport interface ActuationRuleSpec {\n id: string\n /** 이 트윈에만 적용된다. 트윈마다 라인이 다르므로 전역 규칙은 두지 않는다. */\n instanceId: string\n /** 어느 주의인가 — 커널의 안정 코드(`Attention.kind`). */\n attentionKind: string\n /** 이 심각도 이상일 때만 — 없으면 종류만 본다. */\n minSeverity?: 'low' | 'medium' | 'high' | 'critical'\n /**\n * **조건이 이만큼 이어졌을 때** 올린다.\n *\n * 없으면 처음 본 주기에 바로 올린다. 깜빡이는 조건에 현장 지시가 나가는 것을 막으려면 이 값이 필요하다\n * — 커널이 조건이 처음 성립한 시각을 기억하므로(`Attention.timeStamp`) 여기서 잴 수 있다.\n */\n minHeldMs?: number\n /** 무엇을 내는가 — 선언된 조치 종류(`CommandTypeSpec.type`). 커널 자극이 아니다. */\n commandType: string\n /**\n * 지시의 내용 — `{이름}` 을 주의의 값으로 바꿔 넣는다.\n *\n * 바꿀 값은 `Attention.params` 와 `anchor` 에서 온다(`{locationId}` · `{ratioPct}`). 한 겹 중괄호를\n * 쓰는 것은 이 저장소의 i18n 관례와 같다.\n *\n * 바꿀 것이 없으면 **그 자리를 비운다** — 지어낸 값을 넣지 않는다. 요구하는 칸이 비면 계약이 거절하고\n * (§`missingCommandPayload`) 그 거절이 사람에게 전해진다.\n */\n payloadTemplate?: Record<string, unknown>\n /**\n * **한 번 낸 근거에 다시 내기까지 기다리는 시간.**\n *\n * 이것이 없으면 승인하는 순간 규칙이 같은 것을 또 낸다 — 실제로 그랬다. 「결정을 기다리는 것」만 보고\n * 중복을 막았는데, 승인하면 그 커맨드가 대기에서 빠지고 조건은 그대로 서 있기 때문이다. 사람이\n * 승인할수록 대기 줄이 다시 차는 모양이 된다.\n *\n * 조건은 이어지는 것이고 조치는 시간이 걸린다. 낸 조치가 효과를 낼 때까지 기다리는 시간이 이 값이다.\n */\n cooldownMs?: number\n /** 꺼 둔 규칙은 판정하지 않는다. 지우지 않고 끌 수 있어야 한다. */\n enabled?: boolean\n}\n\n/** 판정에 들어가는 주의 — 커널이 낸 모양 그대로 중 필요한 것만. */\nexport interface AttentionLike {\n id: string\n kind: string\n severity: string\n state?: string\n anchor?: { locationId?: string; moverId?: string; orderId?: string; operation?: string }\n timeStamp?: string\n params?: Record<string, string | number>\n}\n\n/** 올릴 것 하나. */\nexport interface RuleProposal {\n ruleId: string\n attentionId: string\n attentionKind: string\n commandType: string\n payload: Record<string, unknown>\n}\n\n/** 올리지 않은 것과 그 이유 — **말하지 않으면 사람은 규칙이 도는 줄 안다.** */\nexport interface RuleSkip {\n ruleId: string\n attentionId?: string\n reason: string\n}\n\nexport interface RuleVerdict {\n proposals: RuleProposal[]\n /** 왜 안 올렸나. 조용히 넘어가지 않는다. */\n skips: RuleSkip[]\n /** 상한에 닿아 잘랐나 — 잘랐으면 몇 개를 두고 왔나. */\n withheld?: number\n}\n\nconst SEVERITY_ORDER = ['low', 'medium', 'high', 'critical'] as const\n\n/** 심각도가 문턱에 닿나 — 모르는 값은 닿지 않는 것으로 본다(모름을 통과로 읽지 않는다). */\nfunction severityMeets(actual: string, min: string | undefined): boolean {\n if (!min) return true\n const a = SEVERITY_ORDER.indexOf(actual as any)\n const m = SEVERITY_ORDER.indexOf(min as any)\n if (a < 0 || m < 0) return false\n return a >= m\n}\n\n/**\n * `{이름}` 을 바꿔 넣는다 — 바꿀 값이 없으면 **그 칸을 뺀다.**\n *\n * 원래 문자열을 그대로 두면 `{quantity}` 같은 글자가 지시의 수량으로 들어가고, 그 지시가 현장에 나간다.\n * 빼면 계약이 「요구하는 칸이 없다」로 거절하고 그 거절이 사람에게 전해진다.\n */\nfunction fill(template: Record<string, unknown> | undefined, values: Record<string, string | number>): {\n payload: Record<string, unknown>\n missing: string[]\n} {\n const payload: Record<string, unknown> = {}\n const missing: string[] = []\n if (!template) return { payload, missing }\n\n for (const [key, raw] of Object.entries(template)) {\n if (typeof raw !== 'string') {\n payload[key] = raw\n continue\n }\n\n /* 값 하나를 그대로 나르는 자리 — 수를 문자열로 만들지 않는다(수량이 문자열이면 계산이 무너진다). */\n const whole = /^\\{([A-Za-z0-9_.]+)\\}$/.exec(raw)\n if (whole) {\n const name = whole[1]!\n if (!(name in values)) {\n missing.push(name)\n continue\n }\n payload[key] = values[name]\n continue\n }\n\n let gap = false\n const text = raw.replace(/\\{([A-Za-z0-9_.]+)\\}/g, (_m, name: string) => {\n if (name in values) return String(values[name])\n missing.push(name)\n gap = true\n return ''\n })\n if (!gap) payload[key] = text\n }\n\n return { payload, missing }\n}\n\n/** 바꿔 넣을 값 — 주의의 `params` 와 `anchor` 에서. */\nfunction valuesOf(attention: AttentionLike): Record<string, string | number> {\n const out: Record<string, string | number> = { ...(attention.params ?? {}) }\n for (const [key, value] of Object.entries(attention.anchor ?? {})) {\n if (value !== undefined && value !== null && value !== '') out[key] = value as string\n }\n out.attentionId = attention.id\n out.attentionKind = attention.kind\n return out\n}\n\n/**\n * 이번에 무엇을 올릴지 정한다 — **순수 함수**. 저장소도 시계도 보지 않는다.\n *\n * 시험이 재기동과 상한과 중복을 다 걸을 수 있어야 하고, 그러려면 이 판정이 밖에서 값을 받아야 한다.\n * 이 저장소에서 가장 자주 나는 결함이 「자리는 있고 길이 없다」이고, 게이트에 물리는 것이므로 여기서\n * 나면 결과가 현장의 지시다.\n */\nexport function evaluateRules(args: {\n rules: readonly ActuationRuleSpec[]\n attentions: readonly AttentionLike[]\n /** 선언된 조치 종류 — 없는 종류를 가리키는 규칙은 여기서 걸린다. */\n specs: readonly CommandTypeSpec[]\n /**\n * **이미 다루고 있는 근거들** — 근거마다 마지막으로 조치를 낸 시각.\n *\n * 결정을 기다리는 것만 보면 부족하다. 승인하는 순간 그 커맨드가 대기에서 빠지고 조건은 그대로\n * 서 있어서, 다음 바퀴가 같은 것을 또 낸다 — 걸어 보고 그 상태를 실제로 만들었다.\n *\n * 그래서 **낸 시각**을 받는다. 결정을 기다리는 것은 시각과 무관하게 막는다(§`openCauses`).\n * 저장소에서 읽어 넘긴다 — 프로세스 기억에 두면 재기동이 곧 중복이 된다.\n */\n openCauses: ReadonlySet<string>\n /** 근거 → 마지막으로 조치를 낸 시각(ms). 없으면 낸 적이 없다. */\n lastProposedAt?: ReadonlyMap<string, number>\n /** 지금 — 조건이 얼마나 이어졌는지 재는 기준. */\n nowMs: number\n /** 한 번에 몇 개까지 올리나. 넘는 것은 두고 오고, 두고 온 수를 말한다. */\n maxPerRun?: number\n}): RuleVerdict {\n const { rules, attentions, specs, openCauses, nowMs } = args\n const maxPerRun = args.maxPerRun ?? DEFAULT_MAX_PER_RUN\n\n const proposals: RuleProposal[] = []\n const skips: RuleSkip[] = []\n let withheld = 0\n\n /* 한 주의에 규칙이 둘 붙어 있어도 조치는 하나만 올린다 — 같은 조건에 두 지시가 나가면 현장이 두 번 움직인다. */\n const claimed = new Set<string>(openCauses)\n\n for (const rule of rules) {\n if (rule.enabled === false) continue\n\n const gap = commandTypeGap(rule.commandType, specs)\n if (gap) {\n skips.push({ ruleId: rule.id, reason: gap })\n continue\n }\n const spec = specs.find(s => s.type === rule.commandType)!\n\n for (const attention of attentions) {\n if (attention.kind !== rule.attentionKind) continue\n\n /* 확인된 것과 사라진 것에는 올리지 않는다 — 사람이 이미 본 것이다. */\n if (attention.state && attention.state !== 'active') continue\n\n if (!severityMeets(attention.severity, rule.minSeverity)) continue\n\n if (claimed.has(attention.id)) continue\n\n /*\n * 낸 지 얼마 안 됐으면 기다린다. 조건은 이어지는 것이고 조치는 시간이 걸린다 — 효과를 보기 전에\n * 또 내면 대기 줄이 같은 지시로 찬다.\n */\n const lastAt = args.lastProposedAt?.get(attention.id)\n const cooldown = rule.cooldownMs ?? DEFAULT_COOLDOWN_MS\n if (lastAt !== undefined && cooldown > 0 && nowMs - lastAt < cooldown) continue\n\n const held = heldMs(attention, nowMs)\n if (rule.minHeldMs && rule.minHeldMs > 0) {\n if (held === undefined) {\n /*\n * 언제부터인지 모르는 주의다. **모름을 「충분히 오래됐다」로 읽지 않는다** — 그렇게 읽으면\n * 방금 생긴 깜빡임에 현장 지시가 나간다.\n */\n skips.push({\n ruleId: rule.id,\n attentionId: attention.id,\n reason: `이 주의는 언제부터인지 말하지 않는다 — ${rule.minHeldMs}ms 이어졌는지 알 수 없어 올리지 않았다`\n })\n continue\n }\n if (held < rule.minHeldMs) continue\n }\n\n if (proposals.length >= maxPerRun) {\n withheld++\n continue\n }\n\n const { payload, missing } = fill(rule.payloadTemplate, valuesOf(attention))\n if (missing.length) {\n skips.push({\n ruleId: rule.id,\n attentionId: attention.id,\n reason: `지시에 넣을 값이 주의에 없다 (${[...new Set(missing)].join(', ')}) — 지어낸 값으로 현장 지시를 만들지 않았다`\n })\n continue\n }\n\n const gaps = missingRequired(spec, payload)\n if (gaps.length) {\n skips.push({\n ruleId: rule.id,\n attentionId: attention.id,\n reason: `지시가 요구하는 칸이 비었다 (${gaps.join(', ')}) — 무엇을 지시하는지 모르면 승인할 근거가 없다`\n })\n continue\n }\n\n claimed.add(attention.id)\n proposals.push({\n ruleId: rule.id,\n attentionId: attention.id,\n attentionKind: attention.kind,\n commandType: rule.commandType,\n payload\n })\n }\n }\n\n return { proposals, skips, ...(withheld ? { withheld } : {}) }\n}\n\n/** 한 번에 올리는 상한 — 사람이 읽을 수 있는 수. 읽지 않는 대기 줄은 게이트가 아니다. */\nexport const DEFAULT_MAX_PER_RUN = 20\n\n/**\n * 같은 근거에 다시 내기까지 — **선언하지 않아도 기다린다.**\n *\n * 기본을 0 으로 두면 선언하지 않은 규칙이 승인할 때마다 같은 것을 또 낸다. 그 결함을 기본값으로 두지\n * 않는다. 더 빨리 내야 하는 규칙은 스스로 짧게 적는다.\n */\nexport const DEFAULT_COOLDOWN_MS = 60 * 60_000\n\n/**\n * 이 규칙이 낼 수 있는 종류인가 — **판정이 한 자리에만 있게.**\n *\n * 저장할 때와 돌 때가 같은 것을 봐야 한다. 두 벌이면 저장은 되는데 안 도는 규칙이 생기고, 사람은 규칙이\n * 걸린 줄로 읽는다.\n *\n * `stimuli` 를 받는 이유: 커널 자극(`CMD`)을 적었을 때 **왜 안 되는지**를 말하려면 그 어휘를 알아야 한다.\n * 「선언되지 않았다」로만 답하면 부르는 쪽은 선언이 빠진 줄로 읽고 선언을 추가한다 — 그 순간 모델 안의\n * 자극이 현장 지시가 된다.\n */\nexport function commandTypeGap(\n commandType: string,\n specs: readonly CommandTypeSpec[],\n stimuli?: readonly string[]\n): string | undefined {\n if (specs.some(s => s.type === commandType)) return undefined\n\n if (stimuli?.includes(commandType)) {\n return (\n `그것은 커널 안에서 도는 자극이다 (${commandType}) — 현장에 나가는 지시가 아니다. ` +\n '모델 상태를 바꾸려면 커널에 자극을 넣고, 현장에 무언가를 시키려면 그 지시를 조치 종류로 선언한다'\n )\n }\n\n return `선언되지 않은 조치 종류를 가리킨다 (${commandType}) — 선언된 것: ${specs.map(s => s.type).join(', ') || '(없음)'}`\n}\n\n/** 조건이 얼마나 이어졌나 — 모르면 `undefined`(0 이 아니다). */\nfunction heldMs(attention: AttentionLike, nowMs: number): number | undefined {\n if (!attention.timeStamp) return undefined\n const at = Date.parse(attention.timeStamp)\n if (!Number.isFinite(at)) return undefined\n return Math.max(0, nowMs - at)\n}\n\n/** 계약의 요구를 그대로 본다 — 판정을 여기서 다시 쓰지 않는다. */\nfunction missingRequired(spec: CommandTypeSpec, payload: Record<string, unknown>): string[] {\n return (spec.required ?? []).filter(key => {\n const v = payload[key]\n return v === undefined || v === null || v === ''\n })\n}\n"]}
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"rule-runner.js","sourceRoot":"","sources":["../../../server/service/actuation/rule-runner.ts"],"names":[],"mappings":"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","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"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"rule-wiring.js","sourceRoot":"","sources":["../../../server/service/actuation/rule-wiring.ts"],"names":[],"mappings":"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","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"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"settle-effects.js","sourceRoot":"","sources":["../../../server/service/actuation/settle-effects.ts"],"names":[],"mappings":";;AA6CA,kDAaC;AAeD,sCAyCC;AAlHD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wDAAgD;AAEhD,gDAA8C;AAG9C,uDAAqF;AAErF,wCAAwC;AACxC,SAAgB,mBAAmB,CAAC,SAAyB;IAC3D,MAAM,GAAG,GAAuB,EAAE,CAAA;IAClC,KAAK,MAAM,CAAC,IAAI,SAAS,IAAI,EAAE,EAAE,CAAC;QAChC,IAAI,CAAC,EAAE,SAAS,KAAK,uBAAQ,CAAC,KAAK;YAAE,SAAQ;QAC7C,MAAM,IAAI,GAAG,CAAC,EAAE,IAAI,CAAA;QACpB,IAAI,CAAC,IAAI,EAAE,OAAO;YAAE,SAAQ;QAC5B;;;WAGG;QACH,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,CAAA;IAClG,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AASD;;;;;GAKG;AACI,KAAK,UAAU,aAAa,CAAC,IAInC;IACC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,IAAI,CAAA;IAC3C,MAAM,IAAI,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAA;IAE1C,MAAM,YAAY,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAA;IACnD,IAAI,CAAC,YAAY,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IAErC;;;OAGG;IACH,IAAI,CAAC,KAAK,CAAC,aAAa,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;QACjD,IAAA,iBAAQ,EACN,4EAA4E;YAC1E,kCAAkC,CACrC,CAAA;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAA,8BAAY,EAAC,YAAY,CAAC,CAAC,CAAA;QAC7E,IAAI,CAAC,KAAK,CAAC,IAAI;YAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,MAAM,EAAE,CAAA;QAEvE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,IAAA,8BAAY,EAAC;YACtC,YAAY;YACZ,KAAK,EAAE,IAAI,GAAG,CACZ,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAC/G;SACF,CAAC,CAAA;QAEF,IAAI,OAAO,CAAC,MAAM;YAAE,MAAM,KAAK,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAChE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,CAAA;IAC9D,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,4CAA4C;QAC5C,IAAA,iBAAQ,EAAC,6BAA6B,MAAM,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;QAC9E,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC","sourcesContent":["/*\n * **관측이 들어올 때 나간 지시와 잇는다** — 폐루프의 배선. 판단은 `effect-match.ts` 가 한다.\n *\n * ── 무엇이 실측으로 확인됐나 (2026-09-02) ─────────────────────────────────────\n * 인티그레이션 레인이 조치 둘을 MES 까지 걸었다. 트윈 쪽 두 행이 **구별되지 않았다.**\n *\n * 325e9bb1 acked WO-20260902-004 지시서 붙음 MES 에서 DRAFT\n * 57428362 acked WO-20260902-005 지시서 못 붙음 MES 에서 DRAFT\n *\n * `state`·`dispatch_ref`·`error` 가 셋 다 같다. 그래서 화면은 둘 다 「나갔다」로 읽는데, 둘 다 아직\n * 현장에 안 갔다. `acked` 의 뜻은 **「저쪽이 받았다」**까지다.\n *\n * 빠져나오는 것은 관측이다 — 저쪽이 그 오더를 현장에 내면 `order.status` 가 우리가 받은 식별자로\n * 돌아온다. 그것을 이 파일이 잇는다.\n *\n * ── 왜 유입 자리인가 ─────────────────────────────────────────────────────────\n * 관측의 **시각**이 봉투에만 있다. 나중에 커널 상태에서 읽으면 「지금」밖에 알 수 없고, 그것은 보고된\n * 시각이 아니라 우리가 처리한 시각이다. 없는 사실을 만들지 않는다.\n *\n * ── 어디서 부르나 ───────────────────────────────────────────────────────────\n *\n * routes.ts 훅으로 밀려 들어온 것\n * reference-live.ts 커넥터가 이어 내는 것 · 폴링으로 물어 온 것\n *\n * **백필(`backfill-period-facts.ts`)에서는 부르지 않는다.** 그것은 마감된 구간의 저널을 채우는\n * 일이고, 이 자리가 답하는 것은 「방금 보낸 지시가 어떻게 됐나」다. 저널을 고치는 작업이 조치 기록을\n * 바꾸게 두면, 백필을 다시 돌릴 때마다 같은 보고가 다시 세어진다(`seen` 이 는다).\n *\n * 그래서 지난 기록에서 효과가 확인되는 경우는 지금 다루지 않는다. 필요해지면 그때는 **백필 전용\n * 경로**로 만들 일이다 — 몇 번 세었나가 아니라 「그때 이랬다」만 적는 자리다.\n *\n * ── 값은 얼마나 드나 ─────────────────────────────────────────────────────────\n * 오더 보고가 든 묶음마다 질의 하나다. 조치가 하나도 안 나간 도메인에서도 그 질의가 돈다 — 지금\n * 규모에서 그 값은 작지만 공짜라고 말하지 않는다. 더 줄이려면 「효과를 기다리는 식별자」를 프로세스가\n * 들고 있어야 하고, 그러면 재기동 때 저장소에서 되세우는 자리가 하나 더 생긴다. 그것이 필요해지는\n * 시점은 **오더 보고가 초당 수백 건 오는 도메인**이다.\n */\nimport { OP_EVENT } from '@operato/ops-contract'\n\nimport { twinWarn } from '../../engine/log.js'\n\nimport type { CommandStore } from './command-dispatcher.js'\nimport { matchEffects, refsToLookUp, type OrderObservation } from './effect-match.js'\n\n/** 봉투에서 오더 보고만 골라낸다 — 다른 어휘는 보지 않는다. */\nexport function orderObservationsIn(envelopes: readonly any[]): OrderObservation[] {\n const out: OrderObservation[] = []\n for (const e of envelopes ?? []) {\n if (e?.eventType !== OP_EVENT.order) continue\n const data = e?.data\n if (!data?.orderId) continue\n /*\n * 시각은 **봉투의 것**을 쓴다. 보고한 쪽이 말한 시각이고, 우리가 처리한 시각이 아니다.\n * 없으면 `matchEffects` 가 거른다 — 여기서 「지금」으로 채우지 않는다.\n */\n out.push({ orderId: String(data.orderId), status: String(data.status ?? ''), at: e?.eventTime })\n }\n return out\n}\n\nexport interface SettleEffectsResult {\n /** 효과를 적은 조치 수. */\n recorded: number\n /** 이을 수 없던 보고 수 — 대개 우리가 내지 않은 오더다. */\n unmatched: number\n}\n\n/**\n * 이 묶음의 오더 보고를 나간 지시와 잇는다 — **아무것도 못 이어도 조용히 지난다.**\n *\n * 유입 경로에서 부른다. 그래서 **던지지 않는다** — 효과를 적다 실패해서 관측 유입이 멈추면, 고치는\n * 동안 트윈이 눈을 감는다. 효과는 다음 보고에 다시 이을 수 있고 관측은 다시 오지 않는다.\n */\nexport async function settleEffects(args: {\n domainId: string\n envelopes: readonly any[]\n store: CommandStore\n}): Promise<SettleEffectsResult> {\n const { domainId, envelopes, store } = args\n const none = { recorded: 0, unmatched: 0 }\n\n const observations = orderObservationsIn(envelopes)\n if (!observations.length) return none\n\n /*\n * 저장소가 두 자리를 안 갖추면 이을 길이 없다. **조용히 지나지 않고 말한다** — 이 상태에서는\n * 넘긴 지시가 저쪽에서 어떻게 됐는지 영원히 모르고, 화면은 그 사실을 알 수 없다.\n */\n if (!store.byDispatchRef || !store.recordEffects) {\n twinWarn(\n '[actuation] 나간 지시와 관측을 이을 수 없다 — 저장소에 byDispatchRef 나 recordEffects 가 없다. ' +\n '넘긴 조치가 저쪽에서 무엇이 되었는지 알 수 없는 상태다.'\n )\n return none\n }\n\n try {\n const byRef = await store.byDispatchRef(domainId, refsToLookUp(observations))\n if (!byRef.size) return { recorded: 0, unmatched: observations.length }\n\n const { updates, skips } = matchEffects({\n observations,\n byRef: new Map(\n [...byRef].map(([ref, c]) => [ref, { id: c.id, dispatchRef: ref, ...(c.effect ? { effect: c.effect } : {}) }])\n )\n })\n\n if (updates.length) await store.recordEffects(domainId, updates)\n return { recorded: updates.length, unmatched: skips.length }\n } catch (e: any) {\n /* 관측 유입을 멈추지 않는다 — 위 §. 다만 조용히 넘기지도 않는다. */\n twinWarn(`[actuation] 효과를 잇다 실패했다 — ${String(e?.message ?? e).slice(0, 200)}`)\n return none\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"sweep-dispatch.js","sourceRoot":"","sources":["../../../server/service/actuation/sweep-dispatch.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **남은 것을 줍는 그물** — 승인은 됐는데 안 나간 것, 실패해서 다시 해 볼 것.\n *\n * ── 왜 필요한가 (2026-09-04) ────────────────────────────────────────────────\n * 승인 직후에 한 번 보내는 것만 있었다(§`dispatchAfterApproval`). 정상 경로는 그것으로 282ms 에\n * 끝난다 — 실측이다. 그런데 두 자리가 비어 있었다.\n *\n * 커밋과 발신 사이에 프로세스가 죽으면 그 조치는 `approved` 로 남고 스스로 안 나간다\n * 실패한 것을 다시 보내려면 사람이 시켜야 했다\n *\n * 2026-09-02 에 뒤엣것을 겪었다: 저쪽 인증 정보가 채워진 뒤 사람이 손으로 다시 넘겨서 나갔다.\n *\n * ── 왜 새 타이머를 세우지 않나 ──────────────────────────────────────────────\n * 규칙 주기(`rule-loop`)에 얹는다. 그 파일이 이미 그 이유를 적어 두었다 — 「타이머를 둘 두면\n * 내리고 세울 자리가 둘이 된다」. 놓친 보고를 backfill 하는 것(`catchUpEffects`)도 같은 자리에 있다.\n *\n * 30초면 충분하다. **아무도 안 기다린다** — 정상 경로가 이미 끝냈고, 이 그물은 남은 것만 본다.\n *\n * ── 판단은 여기 없다 ────────────────────────────────────────────────────────\n * 「지금 넘길 것인가, 다음은 언제인가」는 순수 함수가 답한다(§`retryDecisionOf`). 이 파일은\n * 표를 읽고 그 답대로 하고 결과를 적는 배선이다. 그렇게 갈라 둔 이유는 물러섬 규칙을 30분\n * 기다리지 않고 값으로 잴 수 있어야 하기 때문이다.\n *\n * ── 던지지 않는다 ───────────────────────────────────────────────────────────\n * 주기 안에서 돈다. 한 조치가 터져도 다음 조치를 봐야 하고, 이 그물이 터져도 규칙과 backfill 은\n * 돌아야 한다. 조치를 못 넘기는 것과 트윈이 멈추는 것은 다른 사고다.\n */\nimport { nextCommandState, type ApprovedCommand } from '@operato/ops-contract'\n\nimport { twinWarn } from '../../engine/log.js'\nimport { dispatch, type CommandAck, type CommandStore, type StoredCommand } from './command-dispatcher.js'\nimport { retryDecisionOf } from './retry-plan.js'\n\n/*\n * **의존을 인자로 받는다** — 이 파일이 커넥터 등록소·연결 표를 직접 부르지 않는다.\n *\n * 규칙 실행이 이미 그 모양이다(`runRulesOnce({ store, rulesOf, attentionsOf … })`). 그렇게 두는\n * 값이 둘이다: 배선을 시험이 굴릴 수 있고(직접 부르면 시험이 DB·모듈 부팅을 끌어온다 — 실제로\n * 그렇게 났다), 그리고 이 파일이 무엇에 의존하는지가 서명에 적힌다.\n */\n\n/** 한 바퀴에 볼 최대 수 — 대기 줄이 커도 한 주기를 독차지하지 않게. */\nexport const SWEEP_LIMIT = 50\n\nexport interface SweepReport {\n /** 표에서 읽은 수. */\n seen: number\n /** 실제로 넘긴 수. */\n dispatched: number\n /** 넘기지 않은 이유별 수 — 목록이 그대로 보인다. */\n skipped: Record<string, number>\n}\n\n/**\n * 이 테넌트의 대기 줄을 한 바퀴 훑는다.\n *\n * 저장소가 대기 줄을 못 내면 **아무것도 하지 않는다** — 그 능력이 없는 구현도 그대로 돌아야\n * 한다(옛 거동).\n */\nexport interface SweepDeps {\n store: CommandStore\n /** 이 조치를 저쪽에 낸다 — 부르는 쪽이 커넥터 배선을 안다. */\n /*\n * `dispatch` 가 승인 기록을 확인한 뒤의 커맨드를 넘겨 준다(`ApprovedCommand`) — 그 타입을 그대로\n * 받는다. 우리가 `StoredCommand` 로 넓히면 「승인 없이 나갈 수 있는 서명」이 된다.\n */\n actuate: (command: ApprovedCommand) => Promise<CommandAck>\n /** 이 연결이 두 번 보내도 지시가 하나인가 — **선언만 참으로 읽는다.** */\n idempotent: (instanceId: string) => Promise<boolean>\n /**\n * 우리 쪽 설정만으로 답할 수 있는 준비 상태. 저쪽을 두드리지 않는 답만 준다.\n *\n * 없으면 앞점검을 하지 않는다(그냥 넘긴다) — 모르는 것을 거절로 접지 않는 규율이다.\n */\n localReadiness?: (instanceId: string) => Promise<{ ready?: boolean; reason?: string; code?: string } | undefined>\n nowMs?: () => number\n}\n\nexport async function sweepPendingDispatch(domainId: string, deps: SweepDeps): Promise<SweepReport> {\n const { store } = deps\n const now = deps.nowMs ?? (() => Date.now())\n const report: SweepReport = { seen: 0, dispatched: 0, skipped: {} }\n if (!store.pendingDispatch) return report\n\n const pending = await store.pendingDispatch(domainId).catch(() => [] as readonly StoredCommand[])\n report.seen = pending.length\n\n const note = (why: string) => {\n report.skipped[why] = (report.skipped[why] ?? 0) + 1\n }\n\n for (const command of pending.slice(0, SWEEP_LIMIT)) {\n try {\n const decision = retryDecisionOf({\n state: command.state,\n dispatchable: nextCommandState(command.state, 'dispatch') !== undefined,\n retry: command.retry,\n attempts: command.attempts,\n nextAttemptAt: command.nextAttemptAt,\n idempotent: await deps.idempotent(command.instanceId).catch(() => false),\n nowMs: now()\n })\n\n if (!decision.take) {\n note(decision.skip ?? 'unknown')\n continue\n }\n\n /*\n * **넘기기 전에 우리 설정을 먼저 본다** — 아끼려는 것이 아니라 **이유를 지금 값으로\n * 적으려는** 것이다(인티그레이션 레인이 제 첫 근거를 깼다: 그쪽 어댑터는 이미 `fetch` 전에\n * 되돌아오므로 아낄 요청이 없다).\n *\n * 건너뛰면서 아무것도 안 적으면 **옛 `error` 가 남는다.** 저쪽 인증 정보 때문에 실패해 앉은\n * 뒤 우리 쪽 설정이 지워지면, 목록에 계속 저쪽 얘기가 보이고 사람이 남의 서버를 보러 간다.\n *\n * 모르면(`undefined`) 그냥 넘긴다 — 모르는 것을 거절로 접지 않는다.\n */\n const readiness = await deps.localReadiness?.(command.instanceId).catch(() => undefined)\n if (readiness && readiness.ready === false) {\n await noteBlocked(store, domainId, command, decision.nextAttemptAt, readiness.reason, readiness.code)\n note('not-ready')\n continue\n }\n\n const settled = await dispatch(store, domainId, command.id, c => deps.actuate(c))\n\n await recordAttempt(store, domainId, settled, decision.attempt ?? 1, decision.nextAttemptAt)\n\n if (settled.state === 'failed') {\n note('failed')\n twinWarn(`[twin-sweep] 조치 ${command.id} 를 넘기지 못했다: ${settled.error ?? '(사유 없음)'}`)\n } else {\n report.dispatched++\n }\n } catch (e: any) {\n /* 한 조치가 터져도 다음을 본다 — 가장 흔한 원인은 그 사이에 상태가 바뀐 것이다. */\n note('threw')\n twinWarn(`[twin-sweep] 조치 ${command.id} 에서 예외 — ${String(e?.message ?? e)}`)\n }\n }\n\n return report\n}\n\n/**\n * 못 나가는 이유를 **지금 값으로** 적는다 — 넘기지 않았으므로 상태는 그대로다.\n *\n * **바뀌었을 때만 쓴다.** 10분마다 같은 이유를 다시 쓰면 `updatedAt` 이 계속 튀고, 목록의\n * 「언제부터 이랬나」가 사라진다(§`catch-up-effects` 에서 같은 규율을 지켰다).\n */\nasync function noteBlocked(\n store: CommandStore,\n domainId: string,\n command: StoredCommand,\n nextAttemptAt: string | undefined,\n reason?: string,\n code?: string\n): Promise<void> {\n const sentence = reason ?? code ?? '연결 설정이 조치를 낼 준비가 되지 않았습니다'\n if (command.error === sentence && command.nextAttemptAt === nextAttemptAt) return\n\n await store\n .save(domainId, { ...command, error: sentence, ...(nextAttemptAt ? { nextAttemptAt } : {}) })\n .catch(err => twinWarn(`[twin-sweep] 조치 ${command.id} 의 사유를 적지 못했다 — ${String(err?.message ?? err)}`))\n}\n\n/** 시도 횟수와 다음 시각을 적는다 — `dispatch` 가 쓴 상태를 덮지 않고 이 둘만 더한다. */\nasync function recordAttempt(\n store: CommandStore,\n domainId: string,\n settled: StoredCommand,\n attempt: number,\n nextAttemptAt: string | undefined\n): Promise<void> {\n await store\n .save(domainId, { ...settled, attempts: attempt, ...(nextAttemptAt ? { nextAttemptAt } : {}) })\n .catch(err => twinWarn(`[twin-sweep] 조치 ${settled.id} 의 시도 기록을 적지 못했다 — ${String(err?.message ?? err)}`))\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"twin-command-resolver.js","sourceRoot":"","sources":["../../../server/service/actuation/twin-command-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, Directive, Mutation, Query, Resolver } from 'type-graphql'\n\nimport { MES_COMMANDS, nextCommandState } from '@operato/ops-contract'\n\nimport { randomUUID } from 'node:crypto'\n\nimport { ListParam, ScalarObject, getQueryBuilderFromListParams, getRepository } from '@things-factory/shell'\n\nimport { askAdvisor } from './actuation-advisor.js'\nimport { approvalGateway } from './approval-gateway.js'\nimport { approve, dispatch, linkApproval, propose, reject } from './command-dispatcher.js'\nimport { actuationReadinessOf, actuationTargetOf, routeActuation } from '../reference/actuation-routing.js'\n\nimport { actuationOutcomeOf } from './actuation-outcome.js'\nimport { databaseCommandStore } from './command-store.js'\nimport { TwinCommandRecord } from './twin-command.js'\nimport { twinNow } from './twin-now.js'\n\n/*\n * 승인 대기 줄과 그 결정 — **사람이 쓰는 자리**.\n *\n * ── 왜 필요한가 (2026-08-31) ─────────────────────────────────────────────\n * 게이트를 세우고 승인할 화면을 만들지 않았다. 그래서 되돌릴 수 없는 조치는 전부 대기 상태로 쌓이고\n * **사람이 승인할 방법이 없었다.** 서버만 있고 사람이 쓸 자리가 없는 상태다.\n *\n * ── 왜 조회와 결정을 한 파일에 두나 ──────────────────────────────────────\n * 대기 줄을 여는 것과 그 줄에서 결정하는 것은 한 화면의 일이다. 나누면 화면이 두 곳을 부르게 되고,\n * 그중 하나만 권한이 바뀌는 날 목록은 보이는데 결정이 안 되거나 그 반대가 된다.\n */\n\n@Resolver()\nexport class TwinCommandResolver {\n /**\n * 커맨드 목록 — 기본은 **결정을 기다리는 것부터**.\n *\n * 정렬을 최근순으로 두지 않는다. 승인 대기 줄에서 사람이 찾는 것은 「새로 온 것」이 아니라\n * **「오래 기다린 것」**이다 — 오래 기다린 조치가 뒤로 밀리면 그것이 가장 늦게 처리된다.\n */\n @Query(returns => ScalarObject, {\n nullable: true,\n description: 'List actuation commands with the standard list contract. Defaults to the oldest waiting first.'\n })\n async twinCommands(\n /* 표준 목록 계약은 `@Args` 로 펼쳐 받는다 — `@Arg` 로 받으면 스키마 조립이 실패한다. */\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain?.id\n if (!domainId) return { items: [], total: 0 }\n\n const effective: ListParam = {\n ...(params ?? ({} as ListParam)),\n sortings: params?.sortings?.length ? params.sortings : [{ name: 'createdAt', desc: false }]\n }\n\n const qb = getQueryBuilderFromListParams({\n repository: getRepository(TwinCommandRecord),\n params: effective,\n domain: context.state.domain,\n alias: 'c',\n /* 전부 실컬럼이다 — 질의 빌더는 없는 컬럼의 LIKE 를 경고 후 무시한다(인덱스 없는 전체 스캔 방지). */\n searchables: ['instanceId', 'type', 'origin', 'state', 'approvalRef', 'decidedBy', 'error'],\n sortables: ['createdAt', 'updatedAt', 'state', 'type', 'instanceId'],\n /* 사람이 읽는 속도로 본다. */\n defaultLimit: 50,\n maxLimit: 200\n })\n\n const [items, total] = await qb.getManyAndCount()\n return { items, total }\n }\n\n /**\n * **이 조치에 대해 트윈이 지금 보는 것과 AI 의견** — 승인 화면이 부른다.\n *\n * ── 왜 조회인가 (2026-09-01) ───────────────────────────────────────────\n * 결재선에 올릴 때 묻지 않는다. 그 자리는 트랜잭션 안이고 거기서 LLM 을 기다리면 DB 연결 하나를 몇 초\n * 붙잡는다. 그리고 **승인하는 사람은 지금 판단한다** — 올린 시각의 의견은 낡았다.\n *\n * AI 는 판정하지 않는다. 이 값을 읽어 승인·거절을 막는 코드가 없다. 반대해도 승인되고 동의해도\n * 거절된다 — 판정에 쓰면 AI 가 사실상 승인자가 되고, 그러면 사고 뒤에 책임을 물을 자리가 사라진다.\n */\n @Query(returns => ScalarObject, {\n nullable: true,\n description: 'What the twin sees now for one command, plus an AI opinion when asked. Advisory only — never gates the decision.'\n })\n async twinActuationOpinion(\n @Arg('id') id: string,\n /**\n * AI 에게도 물을 것인가 — **누르면 묻는다.**\n *\n * 기본은 안 묻는다. 화면을 열 때마다 부르면 승인자 다섯 명이 두 번씩 열 때 한 건에 열 번이고,\n * 대부분의 지시는 평범해서 그중 아홉 번은 같은 말을 다시 받는 것이다.\n */\n @Arg('askAi', { nullable: true }) askAi: boolean,\n @Arg('locale', { nullable: true }) locale: string,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const domainId = context.state.domain?.id\n if (!domainId) throw new Error('테넌트를 알 수 없습니다')\n\n const command = await databaseCommandStore().load(domainId, id)\n if (!command) throw new Error(`모르는 조치입니다 (${id})`)\n\n /* 트윈의 지금 상태는 늘 새로 읽는다 — 계산값이라 싸고, 승인자는 지금을 봐야 한다. */\n const now = twinNow(domainId, command.instanceId, command.causeRef)\n\n if (!askAi) return { command, twinNow: now }\n\n /*\n * **의견을 저장하지 않는다.**\n *\n * 의견은 「지금」에 대한 것이고 승인자마다 여는 시각이 다르다. 저장해서 같은 문장을 돌려주면 그것이\n * 오히려 낡은 값이다. 그리고 이 값을 읽어 승인·거절을 막는 코드가 없다 — 판정에 쓰면 AI 가 사실상\n * 승인자가 되고, 사고 뒤에 책임을 물을 자리가 사라진다.\n */\n const advice = await askAdvisor({ domainId, command, twinNow: now, ...(locale ? { locale } : {}) })\n\n return { command, twinNow: now, advice }\n }\n\n /**\n * 조치를 낸다 — **대기 줄에 무언가가 들어오는 유일한 자리.**\n *\n * 결재선이 등록된 배포에서는 여기서 바로 결재선에 올라간다(§`ApprovalGateway`). 등록되지 않았으면\n * 제안 상태로 남고, 이 리졸버의 직접 승인이 유일한 문이 된다. 어느 쪽인지는 답에 적어 준다 — 화면이\n * 「어디서 승인하는가」를 사람에게 말해야 한다.\n *\n * 되돌릴 수 있는 조치는 결재선에 올리지 않는다. 계약이 그 판단을 든다(§`requiresApproval`).\n */\n @Directive('@transaction')\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Propose one actuation command against a twin. Raises it for approval when an approval gateway is installed.' })\n async proposeTwinCommand(\n @Arg('instanceId') instanceId: string,\n @Arg('type') type: string,\n @Arg('payload', type => ScalarObject, { nullable: true }) payload: any,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const { domain, user } = context.state\n const by = String(user?.email ?? user?.name ?? '').trim()\n if (!by) throw new Error('누가 내는지 알 수 없습니다 — 로그인한 사용자가 없습니다')\n\n const store = databaseCommandStore(context.state.tx)\n const command = await propose(store, domain.id, MES_COMMANDS, {\n id: randomUUID(),\n instanceId,\n type,\n /*\n * 사람이 낸 것이다. 규칙·AI 가 내는 길은 아직 없다 — 그 길은 그것을 내는 쪽이 붙인다.\n * 여기서 인자로 받으면 사람이 「규칙이 냈다」로 적어 승인자를 오판하게 만들 수 있다.\n */\n origin: 'operator',\n /* 낸 사람을 적는다 — 이 값이 있어야 자기 것을 자기가 승인하는 것을 막을 수 있다. */\n proposedBy: by,\n proposedAt: new Date().toISOString(),\n ...(payload ? { payload } : {})\n })\n\n if (command.reversible === true) {\n return { command, gateway: null, note: '되돌릴 수 있는 조치입니다 — 승인 없이 넘길 수 있습니다' }\n }\n\n const gateway = approvalGateway()\n if (!gateway) {\n return {\n command,\n gateway: null,\n note: '결재선이 설치되지 않은 배포입니다 — 이 목록에서 직접 승인합니다'\n }\n }\n\n /*\n * 올리기와 적기가 한 트랜잭션에 있다(`@transaction`). 올렸는데 적지 못하면 결재선에는 올라가 있고\n * 커맨드는 직접 결정도 되는 상태가 남는다 — 그때 승인이 두 곳에 갈린다.\n */\n const approvalRef = await gateway.request(command, context)\n const linked = await linkApproval(store, domain.id, command.id, approvalRef)\n\n return { command: linked, gateway: gateway.label, note: `${gateway.label} 에 올렸습니다 — 승인은 그곳에서 합니다` }\n }\n\n /**\n * **사람이 결재선에 올린다** — 규칙이 낸 조치가 승인 앞에 서는 유일한 길.\n *\n * ── 왜 규칙이 직접 올리지 않나 ─────────────────────────────────────────\n * 업무 목록의 결재선은 **올린 사람 기준**으로 풀린다 — `Myself` · `MyDepartment` · `MySupervisor` 가\n * 전부 로그인한 사람을 본다. 배경 루프에는 「나」가 없으므로, 시스템 사용자를 지어내 올리면 그 셋이\n * 시스템 사용자를 가리키고 결재선이 뜻을 잃는다.\n *\n * 그래서 규칙은 내는 것까지 하고, 여기서 사람이 올린다. **올린 사람이 「이것을 승인자들 앞에 놓았다」의\n * 주체**가 되고, 그 사람 기준으로 결재선이 풀린다.\n *\n * 올린 사람은 자기가 올린 것을 승인할 수 있다 — 규칙이 낸 조치에는 낸 사람이 없으므로 승인자가 곧\n * 두 번째 사람이다. 올린 것과 낸 것은 다르다.\n */\n @Directive('@transaction')\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Raise one proposed command to the approval line. The signed-in user is the submitter.' })\n async raiseTwinCommand(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<any> {\n const { domain, user } = context.state\n if (!user?.id) throw new Error('누가 올리는지 알 수 없습니다 — 로그인한 사용자가 없습니다')\n\n const store = databaseCommandStore(context.state.tx)\n const command = await store.load(domain.id, id)\n if (!command) throw new Error(`모르는 조치입니다 (${id})`)\n\n if (command.approvalRef) {\n throw new Error(`이미 결재선에 올라가 있습니다 (${command.approvalRef}) — 승인은 업무 목록에서 합니다`)\n }\n if (command.state !== 'proposed') {\n throw new Error(`${command.state} 상태의 조치는 올릴 수 없습니다 — 이미 결정되었거나 나갔습니다`)\n }\n\n const gateway = approvalGateway()\n if (!gateway) {\n throw new Error('결재선이 설치되지 않은 배포입니다 — 이 목록에서 직접 승인합니다')\n }\n\n const approvalRef = await gateway.request(command, context)\n const linked = await linkApproval(store, domain.id, command.id, approvalRef)\n\n return { command: linked, gateway: gateway.label, note: `${gateway.label} 에 올렸습니다 — 승인은 그곳에서 합니다` }\n }\n\n /**\n * 승인한다 — **누가 승인했는지는 로그인한 사람에서 온다.**\n *\n * 인자로 받지 않는다. 받으면 다른 사람 이름으로 승인할 수 있고, 그러면 그 기록이 사고 뒤에 쓸 수\n * 없어진다. 승인 기록의 값어치가 거기서 나온다.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Approve one actuation command. The approver is the signed-in user.' })\n async approveTwinCommand(\n @Arg('id') id: string,\n @Arg('note', { nullable: true }) note: string,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const { domain, user } = context.state\n const by = String(user?.email ?? user?.name ?? '').trim()\n if (!by) throw new Error('누가 승인하는지 알 수 없습니다 — 로그인한 사용자가 없습니다')\n\n return approve(databaseCommandStore(context.state.tx), domain.id, id, {\n by,\n at: new Date().toISOString(),\n ...(note?.trim() ? { note: note.trim() } : {})\n })\n }\n\n /**\n * 거절한다 — 여기서 끝난다.\n *\n * 사유를 요구한다. 거절은 현장이 기다리던 조치를 취소하는 것이고, 이유 없이 거절되면 낸 쪽이 다시\n * 낸다. 그러면 같은 것이 대기 줄에 계속 돌아온다.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Reject one actuation command. A reason is required.' })\n async rejectTwinCommand(\n @Arg('id') id: string,\n @Arg('reason') reason: string,\n @Ctx() context: ResolverContext\n ): Promise<any> {\n const { domain, user } = context.state\n const by = String(user?.email ?? user?.name ?? '').trim()\n if (!by) throw new Error('누가 거절하는지 알 수 없습니다 — 로그인한 사용자가 없습니다')\n if (!reason?.trim()) throw new Error('거절 사유가 필요합니다 — 이유 없이 거절되면 같은 조치가 다시 올라옵니다')\n\n return reject(databaseCommandStore(context.state.tx), domain.id, id, { by, at: new Date().toISOString(), note: reason.trim() })\n }\n\n /**\n * 어댑터에 넘긴다 — **승인을 지나지 않은 것은 여기서 막힌다.**\n *\n * ── 이름이 왜 `dispatchTwinCommand` 가 아닌가 (2026-09-01) ─────────────\n * 그 이름은 이미 **커널 자극**이 쓴다(`TwinControlMutation.dispatchTwinCommand` —\n * `order.hold`·`resource.repair` 를 커널에 넣는다). 같은 이름을 두 번 선언하면 type-graphql 이\n * 오류 없이 하나만 남기고, 실제로 이 뮤테이션이 통째로 가려져 있었다 — 걸어 보고 알았다.\n *\n * 자극과 조치가 다른 어휘라는 것을 계약에서 갈라 놓고(§`propose` 의 거절), 정작 API 이름에서 겹쳐\n * 두었다. 여기서는 조치임을 이름에 적는다.\n *\n * 화면이 이것을 따로 부르는 이유는, 승인과 넘김이 다른 순간이기 때문이다. 승인한 사람과 넘긴 시각이\n * 같아야 할 이유가 없고, 어댑터가 죽어 있으면 승인은 남아 있어야 한다.\n *\n * ── 여기가 끊겨 있었다 (2026-09-02) ──────────────────────────────────\n * 이 자리가 **어댑터를 찾지도 않고** 「구현이 없습니다」를 답했다. MES 커넥터는 그때 이미 `actuate`\n * 를 다 만들어 두었다. 그 답을 사실로 읽어서 설계 문서에도, 다른 레인에도 그렇게 알렸다 — 둘 다\n * 틀렸다.\n *\n * 이제 원본을 찾아 그 원본이 선언한 능력으로 판단한다(§`routeActuation`). 보내지 못하면 **무엇이\n * 없어서인지** 말한다. 조용히 성공을 답하면 화면은 나간 줄로 읽고, 현장은 오지 않는 지시를 기다린다.\n *\n * ── 여기에는 트랜잭션을 걸지 않는다 ──────────────────────────────────\n * 넘기기 전에 「넘기는 중」을 먼저 적는 것이 이 함수의 요점이다. 한 트랜잭션에 묶으면 그 기록이\n * 커밋 전까지 아무에게도 보이지 않고, 넘기는 사이에 프로세스가 죽으면 그대로 사라진다. 그러면 저쪽은\n * 받았는데 우리는 안 보낸 줄 알고 다시 보낸다 — 현장이 두 번 움직인다.\n */\n /**\n * **이 조치를 넘길 수 있나** — 누르기 전에 답한다.\n *\n * ── 왜 이 자리가 필요한가 (2026-09-03) ────────────────────────────────────\n * 판정 함수 둘을 만들어 두고 **부르는 자리를 안 만들었다**(`actuationTargetOf`·\n * `actuationReadinessOf`). 그래서 화면이 물을 방법이 없었고, 못 넘기는 조치도 눌러 보고 알게 됐다.\n * 인티그레이션 레인이 그 사실을 두 번 짚어 줬다 — 자리는 있고 길이 없는 그 부류다.\n *\n * 그동안 값이 실제로 없어졌다. 저쪽에 비밀값이 없던 날, 사람이 승인 화면까지 가서 누르고 실패를\n * 보고 다시 로그인했다. **승인은 살아 있지만 사람의 시간은 돌아오지 않는다.**\n *\n * ── 둘을 함께 답한다 ──────────────────────────────────────────────────────\n * 화면이 묻는 것은 하나다 — 「지금 넘겨도 되나」. 그 답이 두 겹이라 둘을 함께 싣는다.\n *\n * target 보낼 대상이 정해지나 — 망을 타지 않는다(원본·능력·사이트)\n * readiness 저쪽이 받을 준비가 됐나 — 커넥터가 망을 탈 수 있다\n *\n * **문이 아니다.** 이 답으로 넘김을 막지 않는다(§`actuationReadinessOf`) — 화면이 미리 말하는\n * 데까지다. 그래서 조회이고 뮤테이션이 아니다.\n *\n * 화면이 **목록을 그릴 때 부르지 말 것.** 커넥터가 저쪽에 물을 수 있어서 목록 한 줄마다 망을 탄다.\n * 사람이 「넘기기」를 열 때 한 번이다.\n */\n /*\n * 권한 지시자를 달지 않는다 — 이 파일의 다른 조회 둘과 같다. **쓰기만 게이트를 지난다**(ADR-0027),\n * 그리고 조회는 테넌트로 좁혀진다. 처음에 `privilege: \"query\"` 를 달았더니 권한 문장 검사가\n * 「정본과 다르다」로 잡았다.\n */\n @Query(returns => ScalarObject, {\n nullable: true,\n description: 'Whether one approved actuation can be dispatched now — target and far-side readiness. Advisory, not a gate.'\n })\n async twinActuationReadiness(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<any> {\n const domainId = context.state.domain?.id\n if (!domainId) throw new Error('테넌트를 알 수 없습니다')\n\n const command = await databaseCommandStore().load(domainId, id)\n if (!command) throw new Error(`모르는 조치입니다 (${id})`)\n\n /*\n * 상태를 함께 낸다 — **이 조치가 넘길 수 있는 상태인가**는 저쪽 사정과 다른 물음이다. 이미\n * `acked` 인 것은 저쪽이 아무리 준비돼 있어도 다시 넘기지 않는다(종착).\n */\n const dispatchable = nextCommandState(command.state, 'dispatch') !== undefined\n\n const [target, readiness] = await Promise.all([\n actuationTargetOf(domainId, command.instanceId),\n actuationReadinessOf(domainId, command.instanceId)\n ])\n\n return { id, state: command.state, dispatchable, target, readiness }\n }\n\n /*\n * 권한 지시자를 달지 않는다 — 이 파일의 다른 조회들과 같다(ADR-0027: 쓰기만 게이트를 지난다).\n */\n @Query(returns => ScalarObject, {\n nullable: true,\n description: 'What happened to one actuation — for the person who just approved it. Includes whether work remains at the source.'\n })\n async twinActuationOutcome(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<any> {\n const domainId = context.state.domain?.id\n if (!domainId) throw new Error('테넌트를 알 수 없습니다')\n\n const command = await databaseCommandStore().load(domainId, id)\n if (!command) throw new Error(`모르는 조치입니다 (${id})`)\n\n /*\n * **승인 직후에 부르는 자리다.** 발신은 승인 응답이 나간 다음 틱에 일어나므로(§\n * `dispatchAfterApproval`), 그 순간에는 아직 `approved` 일 수 있다. 그것을 감추지 않는다 —\n * 화면이 「보내는 중」을 그리고 짧게 다시 묻는다(UX 레인 규칙: 대략 0.5초·2초 두 번, 그래도\n * 안 끝나면 조치 목록으로 안내).\n *\n * 여기서 기다리지 않는 이유: 기다리면 이 조회가 저쪽 왕복만큼 멈추고, 사람이 화면을 떠난\n * 뒤에도 붙잡는다.\n */\n return actuationOutcomeOf(command, nextCommandState(command.state, 'dispatch') !== undefined)\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, { description: 'Send one approved actuation command to its connector. Not a kernel stimulus.' })\n async dispatchTwinActuation(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<any> {\n const { domain } = context.state\n\n /* 트랜잭션을 넘기지 않는다 — 위의 이유다. 지시자가 뒤에 붙어도 이 자리는 바뀌지 않아야 한다. */\n return dispatch(databaseCommandStore(), domain.id, id, command =>\n routeActuation(domain.id, command.instanceId, command)\n )\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"twin-command.js","sourceRoot":"","sources":["../../../server/service/actuation/twin-command.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;;;;;GAYG;AACH,qCAAkI;AAClI,+CAAyD;AAEzD,iDAA4D;AAMrD,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;CAiN7B,CAAA;AAjNY,8CAAiB;AAGnB;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,EAAE,EAAE,WAAW,EAAE,mCAAmC,EAAE,CAAC;;6CACrD;AAInB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;IACzB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,iCAAiC,EAAE,CAAC;sCACjF,cAAM;iDAAA;AAGf;IADC,IAAA,oBAAU,EAAC,CAAC,CAAoB,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;;mDAC9B;AAIjB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,qCAAqC,EAAE,CAAC;;qDAC5C;AAIlB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,wFAAwF,EAAE,CAAC;;+CACrG;AAIZ;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,qCAAqC,EAAE,CAAC;;kDACvF;AAIb;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,uFAAuF,EAAE,CAAC;;iDAClG;AAad;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,sEAAsE,EAAE,CAAC;;qDAC5F;AAUnB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,sFAAsF,EAAE,CAAC;;qDAC3G;AAIpB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,+DAA+D,EAAE,CAAC;;gDAC3E;AAiBb;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,sFAAsF,EAAE,CAAC;;mDAC9G;AAKjB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,+CAA+C,EAAE,CAAC;;kDACxE;AAYhB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,mFAAmF,EAAE,CAAC;;sDACxG;AAUpB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,8BAA8B,EAAE,CAAC;;oDACrD;AASlB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,8CAA8C,EAAE,CAAC;;oDACrE;AAIlB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,qCAAqC,EAAE,CAAC;;uDACzD;AASrB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,iFAAiF,EAAE,CAAC;;sDACtG;AAiBpB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,yFAAyF,EAAE,CAAC;;uDAC7G;AAKrB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,mGAAmG,EAAE,CAAC;;uDACvH;AAKrB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,+EAA+E,EAAE,CAAC;;mDACvG;AAKjB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,4DAA4D,EAAE,CAAC;;qDAC/F;AAWnB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,6EAA6E,EAAE,CAAC;;2DAC7F;AAiBzB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,4FAA4F,EAAE,CAAC;;gDACvH;AAKd;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,oEAAoE,EAAE,CAAC;;mDACzG;AAYjB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uEAAuE,EAAE,CAAC;;wDAC1F;AAItB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,gEAAgE,EAAE,CAAC;;gDAC3F;AAId;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,gCAAgC,EAAE,CAAC;sCAC7D,IAAI;oDAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,4BAA4B,EAAE,CAAC;sCACzD,IAAI;oDAAA;4BAhNL,iBAAiB;IAJ7B,IAAA,gBAAM,GAAE;IACT,4DAA4D;;IAC3D,IAAA,eAAK,EAAC,mBAAmB,EAAE,CAAC,CAAoB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;IACvF,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,yEAAyE,EAAE,CAAC;GAC1F,iBAAiB,CAiN7B","sourcesContent":["/*\n * **현장에 내리는 조치 하나** — 제안 · 승인 · 넘김 · 응답이 한 행에 남는다.\n *\n * ── 왜 표인가 (2026-08-31) ───────────────────────────────────────────────\n * 승인이 프로세스 안에만 있으면 트윈이 재기동한 순간 승인 대기 줄이 사라진다. 그때 사람은 **자기가\n * 승인한 것이 실행됐는지 알 수 없다.** 조치는 되돌리기 어려운 것이므로 그 모름이 그대로 사고가 된다.\n *\n * 그리고 이 행은 **누가 언제 승인했나**의 기록이기도 하다. 사고 뒤에 그것을 물을 자리가 여기다.\n *\n * ── 판정은 여기 없다 ─────────────────────────────────────────────────────\n * 어느 상태에서 어느 사건이 허용되는지는 계약이 든다(`@operato/ops-contract` §`nextCommandState`).\n * 이 파일은 그 값을 담기만 한다 — 판정이 두 벌이 되면 화면과 서버가 다른 답을 낸다.\n */\nimport { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, UpdateDateColumn } from 'typeorm'\nimport { Field, ID, Int, ObjectType } from 'type-graphql'\n\nimport { Domain, ScalarObject } from '@things-factory/shell'\n\n@Entity()\n/* 대기 줄을 여는 질의가 이 세 칸으로 묻는다 — 어느 테넌트의 · 어느 트윈의 · 어느 상태인가. */\n@Index('ix_twin_command_0', (e: TwinCommandRecord) => [e.domain, e.instanceId, e.state])\n@ObjectType({ description: 'One actuation command proposed against a twin, with its approval trail.' })\nexport class TwinCommandRecord {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the command.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Tenant this command belongs to.' })\n domain?: Domain\n\n @RelationId((e: TwinCommandRecord) => e.domain)\n domainId?: string\n\n @Column()\n @Field({ description: 'Twin instance this command acts on.' })\n instanceId: string\n\n @Column()\n @Field({ description: 'Command type in twin vocabulary. Translating it into a legacy call is the adapter job.' })\n type: string\n\n @Column({ type: 'simple-json', nullable: true })\n @Field(type => ScalarObject, { nullable: true, description: 'Command payload in twin vocabulary.' })\n payload?: any\n\n @Column()\n @Field({ description: 'Who proposed it: operator, rule, ai or schedule. A person weighs this when approving.' })\n origin: string\n\n /**\n * **낸 사람** — 종류가 아니라 사람이다.\n *\n * `origin` 은 「사람이 냈나 · 규칙이 냈나」만 말한다. 그것만으로는 **낸 사람이 자기 것을 승인하는 것**을\n * 막을 수 없고, 그러면 게이트가 시늉이 된다. 되돌릴 수 없는 조치에 두 사람이 필요하다는 것이 이 표의\n * 존재 이유다.\n *\n * 규칙·AI 가 낸 것은 비어 있다 — 사람이 아니다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The person who proposed it. Absent when a rule or model proposed it.' })\n proposedBy?: string\n\n /**\n * **되돌릴 수 있는가** — 게이트가 걸리는 기준이다.\n *\n * 비어 있으면 「되돌릴 수 없다」로 읽는다(§`requiresApproval`). 모름을 되돌릴 수 있다로 읽으면 그\n * 커맨드가 승인 없이 나간다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Whether the effect can be undone. Absent means it cannot, and a person must approve.' })\n reversible?: boolean\n\n @Column()\n @Field({ description: 'proposed · approved · rejected · dispatched · acked · failed.' })\n state: string\n\n /**\n * **무엇 때문에 냈나** — 규칙이 낸 조치가 가리키는 관측의 식별자(`Attention.id`).\n *\n * 두 가지를 한다.\n *\n * 같은 조건을 두 번 올리지 않는다 결정을 기다리는 커맨드의 이 값을 모아 판정에 넘긴다\n * 승인하는 사람이 근거를 본다 「무슨 일이 있어서 이것이 올라왔나」\n *\n * 앞엣것이 이 칸의 존재 이유다. 프로세스 기억으로 중복을 막으면 **재기동이 곧 중복**이 된다 — 다시 선\n * 트윈이 같은 조건을 처음 보는 것으로 읽고 대기 줄에 같은 지시를 또 올린다.\n *\n * 사람이 낸 것은 비어 있다. 사람은 자기가 왜 내는지 알고 있고, 그 이유를 값으로 요구하면 아무 글자나 적힌다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The observation that caused a rule to propose this. Empty when a person proposed it.' })\n causeRef?: string\n\n /** 어느 규칙이 냈나 — 규칙을 끄거나 고칠 때 그 규칙이 무엇을 냈는지 되짚을 자리. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The rule that proposed this, when a rule did.' })\n ruleRef?: string\n\n /**\n * **결재선에 올라간 자리** — 업무 목록(`@things-factory/worklist`)의 액티비티 인스턴스 식별자.\n *\n * 이 값이 있으면 승인·거절은 **그 목록에서만** 한다. 여기 있는 직접 결정 뮤테이션은 막힌다 — 문이 두\n * 벌이면 결재선에는 대기로 남아 있는데 조치는 이미 나간 상태가 만들어진다.\n *\n * 비어 있으면 결재선을 쓰지 않는 배포다. 그때는 직접 결정이 유일한 문이다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Approval record this command waits on. While set, decisions come only from there.' })\n approvalRef?: string\n\n /* ── 승인의 기록 ─────────────────────────────────────────────────────────\n * 한 칸에 JSON 으로 담지 않고 세 칸으로 나눈다 — 「누가 승인했나」로 묻는 질의가 실제로 온다.\n *\n * 결재선을 지난 경우에도 여기에 적는다. 판정의 온전한 기록(위임·상신·회차)은 결재선이 들고, 이 세 칸은\n * **이 커맨드에 대한 마지막 판정**이다 — 커맨드 목록이 결재선을 조회하지 않고도 「누가 언제」를 보여야 한다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Who approved or rejected it.' })\n decidedBy?: string\n\n /*\n * ISO 문자열로 든다 — `datetime` 은 드라이버 방언이라 다섯 드라이버를 그대로 지나지 못하고,\n * sqlite 는 그것을 검증하지 않아 개발에서 잡히지 않는다. 이 저장소의 다른 시각 칸도 그 모양이다\n * (§`TwinReference.lastSyncedAt`). 계약의 `CommandApproval.at` 도 ISO 문자열이라 옮길 것이 없다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When it was approved or rejected (ISO 8601).' })\n decidedAt?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Why — free text the approver wrote.' })\n decisionNote?: string\n\n /**\n * 어댑터가 돌려준 것 — 저쪽에서 무엇이 되었는지 되짚을 수 있어야 한다.\n *\n * 이것이 없으면 되돌릴 때 **무엇을 되돌릴지 말할 수 없다.**\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Reference the adapter returned. Without it a reversal cannot name what to undo.' })\n dispatchRef?: string\n\n /*\n * ── 저쪽에서 실제로 무엇이 되었나 ─────────────────────────────────────────\n * 상태(`state`)와 다른 축이다. `acked` 는 「저쪽이 답했다」로 끝나고, 이 셋은 그 뒤에 관측으로\n * 돌아온 사실이다. 되돌아오는 관측이 없는 커넥터의 조치는 이 셋이 영원히 빈다 — 그것이 정상이고,\n * 빈 것은 「효과가 없었다」가 아니라 **「모른다」**다.\n */\n\n /**\n * 받아들였으나 저쪽이 덧붙인 말 — **실패가 아니고 「끝났다」도 아니다.**\n *\n * MES 가 지시를 접수해 놓고 지시서를 못 붙이면 그 사실이 여기 온다. `error` 와 다른 칸이어야 한다 —\n * `error` 에 적으면 실패로 읽혀 커맨드가 `failed` 로 앉는다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'What the source added while accepting — not a failure, not a completion. Kept verbatim.' })\n dispatchNote?: string\n\n /** 저쪽이 보고한 상태 — **해석하지 않고 그대로.** 어느 값이 「나갔다」인지는 현장이 안다. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Status the source reported for this instruction, kept verbatim. The kernel does not interpret it.' })\n effectStatus?: string\n\n /** 그 보고를 관측한 시각. 컬럼 타입을 적지 않는다 — 드라이버 방언이라 개발에서 안 잡힌다. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When that report was observed. Empty means no report has been correlated yet.' })\n effectAt?: string\n\n /** 몇 번 보고됐나 — 한 번도 안 바뀐 것과 다섯 번 바뀐 것은 다른 일이다. */\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'How many reports have been correlated to this instruction.' })\n effectSeen?: number\n\n /**\n * 저쪽이 받으면서 덧붙인 말 중 **다음에 할 일** 한 줄 — 원인은 `dispatchNote` 다.\n *\n * 왜 둘로 나누나: 읽는 사람은 「그래서 내가 뭘 해야 하나」를 먼저 찾는다(UX 레인). 한 문장으로\n * 주면 화면이 자르게 되고, 자르는 규칙이 화면마다 생긴다. 어댑터가 이미 둘로 알고 있으니\n * 붙이지 않고 그대로 받는다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The next action the source expects, as one line. The cause is dispatchNote.' })\n dispatchNoteNext?: string\n\n /**\n * **다시 해서 될 일인가** — 어댑터가 실패마다 답한다(§`CommandAck.retry`).\n *\n * again 그대로 다시 해 볼 만하다 — 못 닿았거나 저쪽이 잠깐 흔들렸다\n * after-fix 사람이 고친 뒤 그대로 나간다 — 설정 문제\n * never 이 조치로는 영원히 안 된다 — 지시 내용이 틀렸다\n *\n * 가르는 자리는 「조치를 다시 낼 필요가 있나」다. 설정이 틀린 것은 조치가 멀쩡하므로 `after-fix`,\n * 지시 내용이 틀린 것은 그 조치가 영원히 틀렸으므로 `never` 다(인티그레이션 레인 판단).\n *\n * **비어 있으면 `never` 로 읽지 않는다.** 모르는 것과 영원히 안 되는 것은 다르다 — 선언하지 않은\n * 커넥터의 실패를 `never` 로 접으면 고칠 수 있는 것을 사람이 포기한다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Whether retrying can work: again · after-fix · never. Empty means the adapter did not say.' })\n retry?: string\n\n /** 몇 번 넘겨 봤나 — 목록의 「몇 번 해 봤나」가 이 값이다. 첫 시도가 1이다. */\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'How many dispatch attempts have been made. The first attempt is 1.' })\n attempts?: number\n\n /**\n * **다음에 넘겨 볼 시각**(ISO) — 물러섬을 표로 든다.\n *\n * 메모리에 두지 않는 이유: 재기동하면 사라지고, 그러면 물러서던 것이 전부 즉시 재시도가 된다.\n * 표에 두면 일꾼이 여러 대여도 같은 답을 본다.\n *\n * 비어 있으면 **다음 주기에 바로** 본다(한 번도 실패하지 않은 것).\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When to try dispatching again (ISO 8601). Empty means the next cycle.' })\n nextAttemptAt?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Why the dispatch failed. The server sentence, kept as it came.' })\n error?: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'When the command was proposed.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'When the row last changed.' })\n updatedAt?: Date\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"twin-now.js","sourceRoot":"","sources":["../../../server/service/actuation/twin-now.ts"],"names":[],"mappings":";;;AAkEA,0BA8BC;AAhGD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,0EAAmE;AACnE,gEAAwD;AAgCxD,qDAAqD;AACxC,QAAA,cAAc,GAAG,CAAC,CAAA;AAE/B;;;;;GAKG;AACH,SAAgB,OAAO,CAAC,QAAgB,EAAE,UAAkB,EAAE,QAAiB,EAAE,KAAK,GAAG,sBAAc;IACrG,IAAI,CAAC,2BAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,CAAC;QAC3C,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,4CAA4C,EAAE,CAAA;IAChG,CAAC;IAED,MAAM,MAAM,GAAG,2BAAU,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;IACtD,MAAM,IAAI,GAAG,MAAM,EAAE,WAAW,EAAE,EAAE,IAAI,2BAAU,CAAC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;IACjF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,oCAAoC,EAAE,CAAA;IACxF,CAAC;IAED,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAuB,CAAA;IAEzD,wDAAwD;IACxD,MAAM,MAAM,GAAG,IAAA,sCAAgB,EAAC,GAAU,EAAE,KAAK,CAAC,CAAA;IAElD,MAAM,UAAU,GAAI,MAAM,CAAC,UAAiC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACrE,GAAG,CAAC;QACJ,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5D,CAAC,CAAC,CAAA;IAEH,OAAO;QACL,UAAU;QACV,KAAK,EAAE,MAAM,CAAC,cAAc;QAC5B;;;WAGG;QACH,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzE,CAAA;AACH,CAAC","sourcesContent":["/*\n * **승인하는 사람에게 트윈이 지금 보고 있는 것을 함께 준다.**\n *\n * ── 왜 필요한가 (2026-09-01, 사용자 지적) ────────────────────────────────\n * 사용자가 물었다 — 「보통 AI 는 분명하지 않으면 되묻고, 잘못된 명령이면 확인이나 역제안을 하는데\n * 왜 이 기능에는 그것이 없나.」\n *\n * 확인해 보니 대화하는 AI 는 되묻는다(`twin-ai` §assistant 프롬프트). 그런데 **현장에 지시를 내는\n * 경로는 아무것도 묻지 않는다.** 사람이 「볼트 30개 추가」를 올리면 그대로 결재선에 올라간다. 트윈이\n * 도금 대기가 여섯 배인 것을 알고 있어도 그 말을 할 자리가 없다.\n *\n * 말하는 쪽은 안전한 경로이고, 말 못 하는 쪽이 공장을 움직인다.\n *\n * ── 판정하지 않는다 ──────────────────────────────────────────────────────\n * 트윈이 「이 지시는 틀렸다」고 말하지 않는다. 무엇이 무엇과 어긋나는지 판정하려면 그 규칙을 선언해야\n * 하고, 선언 없이 판정하면 지어낸 규칙이 사람의 지시를 막는다.\n *\n * **트윈은 지금 보이는 것을 내놓고, 승인하는 사람이 판단한다.**\n *\n * ── 이름을 정확히 쓴다 ───────────────────────────────────────────────────\n * 이 값의 이름은 `twinNow` 다. `related` 나 `relevant` 가 아니다 — **이 지시와 관련 있다고 판정한 것이\n * 아니고**, 이 트윈에서 지금 서 있는 것 전부 중 심각한 순서로 몇 개다. 이름이 관련성을 주장하면 승인자가\n * 「트윈이 이 지시 때문에 이것을 골랐다」로 읽는다.\n */\nimport { digestAttentions } from '../../engine/attention-digest.js'\nimport { TwinEngine } from '../../engine/twin-engine.js'\n\n/** 승인 화면이 읽는 한 줄 — 커널 낱말 그대로. 사람의 말은 표현계층이 만든다. */\nexport interface TwinNowAttention {\n id: string\n kind: string\n severity: string\n anchor?: Record<string, string | undefined>\n params?: Record<string, string | number>\n timeStamp?: string\n /** 커널이 권하는 방향 — 실행 가능한 것과 권고가 섞여 있다(`command` 가 있으면 자극이다). */\n recommendedActions?: { code: string; command?: string; args?: unknown }[]\n /** 이 조치가 이것 때문에 올라왔나 — 규칙이 낸 것만 표시된다. */\n isCause?: boolean\n}\n\nexport interface TwinNow {\n /** 심각한 순서로 몇 개. 전부가 아니다. */\n attentions: TwinNowAttention[]\n /** 지금 서 있는 것의 전체 수 — **잘랐으면 몇을 잘랐는지 알 수 있게.** */\n total: number\n /** 이 조치를 낸 근거가 아직 서 있나 — 규칙이 낸 것에만 값이 있다. */\n causeStanding?: boolean\n /**\n * 트윈의 상태를 못 읽었으면 그 이유.\n *\n * 빈 목록으로 답하지 않는다 — 「지금 아무 문제 없다」와 「트윈이 멈춰 있다」는 다른 사실이고,\n * 승인하는 사람에게 그 차이가 크다.\n */\n unavailable?: string\n}\n\n/** 승인 화면에 몇 개까지 싣나 — 사람이 읽는 수. 스물여섯 줄은 아무도 안 읽는다. */\nexport const TWIN_NOW_LIMIT = 5\n\n/**\n * 이 트윈이 지금 무엇을 보고 있나 — 결재선 입력에 실어 보낼 모양으로.\n *\n * `causeRef` 를 주면 그것이 아직 서 있는지도 함께 답한다. 승인하는 사람이 물어야 하는 것 중 가장 중요한\n * 것이 「지금도 그런가」이고, 조건이 사라진 뒤 승인하면 필요 없는 것을 만든다.\n */\nexport function twinNow(domainId: string, instanceId: string, causeRef?: string, limit = TWIN_NOW_LIMIT): TwinNow {\n if (!TwinEngine.owns(domainId, instanceId)) {\n return { attentions: [], total: 0, unavailable: '이 서버가 이 트윈을 들고 있지 않다 — 멈춰 있거나 다른 서버가 돌고 있다' }\n }\n\n const kernel = TwinEngine.kernel(domainId, instanceId)\n const snap = kernel?.getSnapshot?.() ?? TwinEngine.snapshot(domainId, instanceId)\n if (!snap) {\n return { attentions: [], total: 0, unavailable: '이 트윈의 상태를 읽을 수 없다 — 아직 서지 않았거나 멈췄다' }\n }\n\n const all = (snap.attentions ?? []) as TwinNowAttention[]\n\n /* 자리 색은 쓰지 않는다 — 승인 화면은 지도가 아니다. 자른 뒤의 수는 반드시 함께 낸다. */\n const digest = digestAttentions(all as any, limit)\n\n const attentions = (digest.attentions as TwinNowAttention[]).map(a => ({\n ...a,\n ...(causeRef && a.id === causeRef ? { isCause: true } : {})\n }))\n\n return {\n attentions,\n total: digest.attentionTotal,\n /*\n * 근거가 서 있나 — **자른 목록이 아니라 전량에서 찾는다.** 상위 다섯에 안 들었다고 사라진 것이\n * 아니다. 잘린 목록에서 찾으면 심각한 것이 여럿일 때 「사라졌다」로 답한다.\n */\n ...(causeRef ? { causeStanding: all.some(a => a.id === causeRef) } : {})\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,qEAA0C;AAC1C,2EAAgD;AAChD,qFAA0D;AAC1D,2EAAgD;AAChD,qEAA0C;AAC1C,oFAAyD;AACzD,0EAA+C;AAC/C,uEAA4C;AAC5C,6EAAkD;AAClD,kFAAuD;AACvD,0EAA+C;AAC/C,4EAAiD;AACjD,2EAAgD;AAChD,sEAA2C;AAC3C,wEAA6C;AAC7C,wEAA6C;AAC7C,uFAA4D;AAC5D,kFAAuD;AACvD,gFAAqD;AACrD,2EAAgD;AAChD,4DAA4D;AAC5D,yEAA8C;AAC9C,iEAAiE;AACjE,8EAAmD;AACnD,sEAAsE;AACtE,6EAAkD;AAClD;;;GAGG;AACH,+DAAoC;AAEpC,mCAAmC;AACnC,oDAAqE;AACrE,sDAA4G;AAC5G,oDAAsG;AACtG,uDAA2E;AAC3E,wDAA6E;AAC7E,oDAAsG;AACtG,qDAAyG;AACzG,mDAA6G;AAC7G,2EAA2E;AAC3E,mDAA6G;AAC7G,qDAAuE;AACvE,uDAA2E;AAC3E,qDAAsG;AACtG,mEAAmE;AACnE,6DAA4H;AAC5H,yDAA+E;AAC/E,uDAA2E;AAC3E,wDAA6E;AAC7E,yDAA+E;AAC/E,uDAA2E;AAC3E,yDAA+E;AAC/E,qDAAqD;AACrD,wDAA6E;AAEhE,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,mBAAiB;IACpB,GAAG,mBAAmB;IACtB,GAAG,oBAAwB;IAC3B,GAAG,mBAAiB;IACpB,GAAG,mBAAoB;IACvB,GAAG,mBAAqB;IACxB,GAAG,mBAAiB;IACpB,GAAG,mBAAkB;IACrB,GAAG,mBAAqB;IACxB,GAAG,mBAAqB;IACxB,4DAA4D;IAC5D,GAAG,oBAAiB;CACrB,CAAA;AAEY,QAAA,WAAW,GAAG,EAAE,CAAA;AAEhB,QAAA,MAAM,GAAG;IACpB,QAAQ,EAAE,EAAE;IACZ,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,qBAAkB;QACrB,GAAG,qBAAoB;QACvB,GAAG,qBAAsB;QACzB,GAAG,qBAAoB;QACvB,GAAG,oBAAkB;QACrB,GAAG,qBAAqB;QACxB,GAAG,qBAAsB;QACzB,GAAG,qBAAoB;QACvB,GAAG,qBAAsB;QACzB,GAAG,qBAAqB;QACxB,GAAG,qBAAyB;QAC5B,GAAG,qBAAkB;QACrB,GAAG,oBAAkB;QACrB,GAAG,oBAAmB;QACtB,GAAG,oBAAsB;QACzB,GAAG,oBAAsB;QACzB,GAAG,oBAAoB;KACxB;IACD,UAAU,EAAE,EAAE;CACf,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './twin-event/twin-event.js'\nexport * from './twin-audit/twin-audit-event.js'\nexport * from './twin-ingest-window/twin-ingest-window.js'\nexport * from './twin-instance/twin-instance.js'\nexport * from './twin-space/twin-space.js'\nexport * from './twin-space/twin-space-representation.js'\nexport * from './twin-space/twin-space-area.js'\nexport * from './twin-target/twin-target.js'\nexport * from './twin-structure/twin-structure.js'\nexport * from './twin-state/twin-state-subscription.js'\nexport * from './reference/reference-master.js'\nexport * from './reference/reference-registry.js'\nexport * from './reference/reference-adapter.js'\nexport * from './reference/live-cadence.js'\nexport * from './reference/reference-live.js'\nexport * from './reference/twin-reference.js'\nexport * from './reference/twin-reference-reconciliation.js'\nexport * from './reference/reference-reconciliation.js'\nexport * from './reference/connection-portability.js'\nexport * from './reference/template-registry.js'\n/* 커넥터 점검 하네스 — 실 원본에 붙여 무엇이 채워지고 무엇이 비는지 센다(앱을 띄우지 않는다). */\nexport * from './reference/reference-probe.js'\n/* 읽은 뒤의 총평(사실 층) — 화면이 표로 그리고, 더 알고 싶을 때 AI 가 이 사실만 근거로 서술한다. */\nexport * from './reference/reference-assessment.js'\n/* 도메인 어휘 카탈로그 조합(커널 SSOT 노출) — 서버측 소비자(board-ai 도구 등)가 같은 형태를 쓰도록. */\nexport * from './twin-lifecycle/domain-catalog.js'\n/*\n * 조치 — 앱이 결재선을 채우고(§`setApprovalGateway`) 판정을 옮긴다(§`settleByApproval`). 이 줄이 없으면\n * 자리는 있는데 앱이 닿을 길이 없다.\n */\nexport * from './actuation/index.js'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as TwinEventEntities } from './twin-event/index.js'\nimport { entities as TwinSubjectEntities, resolvers as TwinSubjectResolvers } from './twin-subject/index.js'\nimport { entities as TwinAuditEntities, resolvers as TwinAuditResolvers } from './twin-audit/index.js'\nimport { entities as TwinInstanceEntities } from './twin-instance/index.js'\nimport { entities as TwinStructureEntities } from './twin-structure/index.js'\nimport { entities as TwinSpaceEntities, resolvers as TwinSpaceResolvers } from './twin-space/index.js'\nimport { entities as TwinTargetEntities, resolvers as TwinTargetResolvers } from './twin-target/index.js'\nimport { entities as TwinReferenceEntities, resolvers as TwinReferenceResolvers } from './reference/index.js'\n/* 현장에 내리는 조치 — 승인이 재기동을 넘어 살아야 하므로 표로 든다(§`actuation/twin-command.ts`). */\nimport { entities as TwinActuationEntities, resolvers as TwinActuationResolvers } from './actuation/index.js'\nimport { resolvers as TwinStateResolvers } from './twin-state/index.js'\nimport { resolvers as TwinControlResolvers } from './twin-control/index.js'\nimport { entities as TwinModelEntities, resolvers as TwinModelResolvers } from './twin-model/index.js'\n/* 인제스트 창 — 동기화 추이를 재기동 뒤에도 이어 보기 위한 행(리졸버 없음: 메모리 장부가 조회를 답한다). */\nimport { entities as TwinIngestWindowEntities, resolvers as TwinIngestWindowResolvers } from './twin-ingest-window/index.js'\nimport { resolvers as TwinLifecycleResolvers } from './twin-lifecycle/index.js'\nimport { resolvers as TwinJournalResolvers } from './twin-journal/index.js'\nimport { resolvers as TwinForecastResolvers } from './twin-forecast/index.js'\nimport { resolvers as TwinAttentionResolvers } from './twin-attention/index.js'\nimport { resolvers as TwinMetricsResolvers } from './twin-metrics/index.js'\nimport { resolvers as TwinReadinessResolvers } from './twin-readiness/index.js'\n/* 지난 기록 채우기 — 저널에만 쓰는 문. 라이브 유입과 판단이 달라 문을 따로 둔다. */\nimport { resolvers as TwinBackfillResolvers } from './twin-backfill/index.js'\n\nexport const entities = [\n /* ENTITIES */\n ...TwinEventEntities,\n ...TwinSubjectEntities,\n ...TwinIngestWindowEntities,\n ...TwinAuditEntities,\n ...TwinInstanceEntities,\n ...TwinStructureEntities,\n ...TwinSpaceEntities,\n ...TwinTargetEntities,\n ...TwinReferenceEntities,\n ...TwinActuationEntities,\n /* 구조 투영 — model JSON 에서 풀어낸 표준 엔티티(ADR-0032). 원본에서 온 캐시. */\n ...TwinModelEntities\n]\n\nexport const subscribers = []\n\nexport const schema = {\n typeDefs: {},\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...TwinStateResolvers,\n ...TwinControlResolvers,\n ...TwinLifecycleResolvers,\n ...TwinJournalResolvers,\n ...TwinAuditResolvers,\n ...TwinForecastResolvers,\n ...TwinAttentionResolvers,\n ...TwinMetricsResolvers,\n ...TwinReadinessResolvers,\n ...TwinBackfillResolvers,\n ...TwinIngestWindowResolvers,\n ...TwinModelResolvers,\n ...TwinSpaceResolvers,\n ...TwinTargetResolvers,\n ...TwinReferenceResolvers,\n ...TwinActuationResolvers,\n ...TwinSubjectResolvers\n ],\n directives: {}\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"actuation-routing.js","sourceRoot":"","sources":["../../../server/service/reference/actuation-routing.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **승인된 조치가 원본으로 나가는 길** — 판단은 `actuation-target.ts`, 여기는 배선.\n *\n * ── 무엇이 끊겨 있었나 (2026-09-02) ────────────────────────────────────────────\n * MES 커넥터는 `actuate` 를 다 만들어 두었다. 서명·접수 판정·초안 상태까지 있었다. 그런데 넘기는\n * 리졸버가 **어댑터를 찾지도 않고** 「구현이 없습니다」라고 답했다. 그 문장을 사실로 읽어서 설계\n * 문서에도, 다른 레인에도 그렇게 알렸다 — 둘 다 틀렸다.\n *\n * 자리는 있고 길이 없는 상태다. 이 저장소에서 가장 자주 나는 결함 부류이고, 조치 경로에서 나면\n * 결과가 하나다 — **승인은 쌓이는데 현장에는 아무것도 가지 않는다.**\n *\n * ── 왜 구동과 따로 두나 ───────────────────────────────────────────────────────\n * 모양이 같아서 한 함수로 묶고 싶어진다. 묶지 않는다.\n *\n * 구동은 시뮬레이터를 움직인다 틀리면 시늉이 어긋난다\n * 조치는 현장에 작업지시를 낸다 틀리면 만들지 않아도 될 것을 만든다\n */\nimport { getRepository } from '@things-factory/shell'\n\nimport type { ApprovedCommand } from '@operato/ops-contract'\n\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\n\nimport { TwinReference } from './twin-reference.js'\nimport { twinError } from '../../engine/log.js'\nimport { actuationOutcomePatch } from './actuation-verification.js'\nimport { capabilitiesOf, getAdapter, type ActuationReadiness, type ActuationResult, type SiteDescriptor } from './reference-adapter.js'\nimport { APPROVAL_STANDS, actuationTarget, isActuationAllowed, type ActuationTarget } from './actuation-target.js'\n\n/** 어댑터가 답하는 모양 그대로 — 디스패처가 이 값을 커맨드 행에 적는다. */\n/**\n * 조치를 낸 결과 — **어댑터가 답한 것을 좁히지 않고 그대로 나른다.**\n *\n * ── 왜 넓혔나 (2026-09-04, 인티그레이션 레인이 찾음) ────────────────────────\n * 이 타입이 `ok`·`ref`·`error` 셋만 들고 있었다. 그래서 `routeActuation` 이 어댑터의 답을\n * 그대로 되돌리는데 **타입은 `note`·`noteNext`·`retry` 를 버린다고 말했다.**\n *\n * 값은 흘렀다 — JS 객체가 키를 안 버리므로. 어제 `dispatch_note` 에 실제로 앉았다. 그런데\n * **커넥터를 만드는 사람이 그 칸이 있다는 것을 알 방법이 없었다.** 우리가 어제 하루 종일\n * 「자리는 있고 길이 없다」로 부른 것의 타입 버전이다.\n *\n * 좁히지 않는다: 세 칸을 다 실어 `CommandAck` 와 같은 모양으로 둔다. 커널이 어느 칸을 어떻게\n * 쓰는지는 `dispatch` 가 정하고, 이 자리는 나르기만 한다.\n */\nexport interface ActuationAck {\n ok: boolean\n /** 저쪽 문서·작업 식별자. 없으면 무엇을 되돌릴지 지목할 수 없다. */\n ref?: string\n /** 받아들였으나 저쪽이 덧붙인 말 — **실패가 아니다.** 원인 쪽 문장. */\n note?: string\n /** 그 말 중 다음에 할 일 한 줄 — 붙여 보내지 않는다. */\n noteNext?: string\n error?: string\n /** 다시 해서 될 일인가 — 실패에만. 말하지 않으면 일꾼이 집지 않는다. */\n retry?: 'again' | 'after-fix' | 'never'\n}\n\n/**\n * 저장소를 **한 번만** 읽고, 판정과 함께 부를 것까지 돌려준다.\n *\n * 판정한 뒤 보낼 때 다시 읽으면 그 사이에 연결 설정이 바뀔 수 있다 — 판정한 커넥터와 실제로 부르는\n * 커넥터가 달라진다. 그리고 같은 행을 두 번 읽을 이유가 없다.\n */\nasync function resolve(domainId: string, instanceId: string) {\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n const ref = inst\n ? await getRepository(TwinReference).findOne({ where: { domain: { id: domainId }, source: instanceId } })\n : null\n const adapter = ref ? getAdapter(ref.adapterType) : undefined\n\n const target = actuationTarget({\n instanceId,\n twinExists: !!inst,\n siteId: (inst?.origin as any)?.siteId,\n ...(ref ? { ref: { source: ref.source, adapterType: ref.adapterType } } : {}),\n adapterRegistered: !!adapter,\n capabilities: capabilitiesOf(adapter),\n declaresActuate: !!adapter?.capabilities?.includes('actuate'),\n hasActuateImpl: typeof adapter?.actuate === 'function'\n })\n\n return { target, ref, adapter }\n}\n\n/**\n * 이 트윈의 원본에 승인된 조치를 보낸다.\n *\n * **거절할 때는 무엇이 없어서인지 말한다.** 「보내지 못했습니다」만 답하면 사람이 연결 설정을 열어야\n * 하는지, 커넥터를 고쳐야 하는지, 트윈을 다시 만들어야 하는지 알 수 없다.\n */\nexport async function routeActuation(\n domainId: string,\n instanceId: string,\n command: ApprovedCommand\n): Promise<ActuationAck> {\n const { target, ref, adapter } = await resolve(domainId, instanceId)\n if (!isActuationAllowed(target)) return { ok: false, error: `${target.detail}. ${APPROVAL_STANDS}` }\n\n /*\n * 여기까지 왔으면 판정이 그 둘의 존재를 이미 확인했다(§`actuationTarget`). 그래도 조용히 지나가지\n * 않는다 — 판정과 배선이 어긋나면 그것이 결함이고, `!` 로 덮으면 그 결함이 「아무 일도 일어나지\n * 않았다」로 나타난다.\n */\n if (!adapter?.actuate) {\n return { ok: false, error: `커넥터를 찾았는데 조치를 보낼 자리가 없습니다 (${target.adapterType}). ${APPROVAL_STANDS}` }\n }\n\n const site: SiteDescriptor = { siteId: target.siteId }\n const result = await adapter.actuate(ref?.connectionConfig ?? {}, site, command)\n\n /*\n * **왕복이 일어났다는 사실을 남긴다** (ADR-0046 ②).\n *\n * 설정이 맞는 것과 저쪽이 받는 것은 다르고, 그 차이는 한 번 넘겨 봐야만 안다. 승인 화면이 그것을\n * 미리 말하려면 지난 왕복의 결과가 어딘가에 남아 있어야 한다 — 없으면 화면은 매번 「해 봐야 안다」만\n * 말한다.\n *\n * 두 경로(`dispatchAfterApproval` · `sweepPendingDispatch`)가 다 이 함수를 지나므로 여기 한 곳이면\n * 된다. 실패해도 조치 결과는 그대로 돌려준다 — 기록이 본업을 막으면 안 되고, 삼키지도 않는다.\n */\n if (ref) await recordActuationOutcome(ref, result).catch(err => twinError('[reference] actuation state not recorded', err))\n\n return result\n}\n\n/**\n * 조치 결과를 레퍼런스에 적는다 — **코드만, 문장은 아니다.**\n *\n * 거절 사유에 코드가 없으면 `actuationFailure` 를 비워 둔다. 자리를 채우려고 `error` 문장을 옮기면\n * `lastError` 가 이미 겪은 것을 반복한다 — 저장된 문장은 번역도 집계도 안 되고, 커넥터가 그 문장을\n * 고치는 날 옛 행과 새 행이 다른 말을 한다. **모르는 것은 안 적는다.**\n */\nasync function recordActuationOutcome(ref: TwinReference, result: ActuationResult): Promise<void> {\n await getRepository(TwinReference).update({ id: ref.id }, actuationOutcomePatch(result, new Date().toISOString()))\n}\n\n/**\n * 이 트윈이 조치를 받을 수 있나 — **넘기기 전에 화면이 먼저 알 수 있게.**\n *\n * 승인 화면이 「넘기기」를 보이기 전에 이것을 묻는다. 누를 수 없는 것을 보여 주고 눌렀을 때 거절하는\n * 것보다, 왜 못 넘기는지 먼저 말하는 것이 낫다.\n */\nexport async function actuationTargetOf(domainId: string, instanceId: string): Promise<ActuationTarget> {\n return (await resolve(domainId, instanceId)).target\n}\n\n/**\n * **저쪽이 받을 준비가 됐나** — 넘기기 전에 묻는다. 화면이 사람을 헛걸음시키지 않게.\n *\n * ── 왜 `actuationTargetOf` 와 따로인가 ──────────────────────────────────────\n * 그쪽은 **망을 타지 않는다.** 화면이 목록을 그릴 때마다 부를 수 있어야 하므로 그래야 한다.\n * 이 함수는 커넥터가 저쪽에 물을 수 있고, 그래서 부르는 시점이 다르다 — 사람이 「넘기기」를\n * 열었을 때다.\n *\n * ── 답이 문이 아니다 ────────────────────────────────────────────────────────\n * **이 답으로 넘김을 막지 않는다.** 막으면 점검이 틀렸거나 잠깐 못 닿은 사이에 성공할 수 있었던\n * 조치가 못 나간다. 화면이 미리 말하는 데까지가 이 함수의 일이다.\n *\n * 커넥터가 이 얼굴을 안 만들었으면 **「모른다」로 답한다** — 「준비됐다」가 아니다. 없는 확인을\n * 통과로 읽으면 이 함수가 있으나 없으나 같아진다.\n */\nexport async function actuationReadinessOf(domainId: string, instanceId: string): Promise<ActuationReadiness> {\n const { target, ref, adapter } = await resolve(domainId, instanceId)\n\n /* 보낼 대상 자체가 없으면 저쪽에 물을 것도 없다 — 그 사실을 그대로 옮긴다. */\n if (!isActuationAllowed(target)) {\n return { checked: 'local', ready: false, reason: target.detail, code: target.code }\n }\n\n if (!adapter?.actuationReadiness) {\n /* 확인할 길이 없다. 「준비됐다」로 답하지 않는다 — 그러면 화면이 없는 확인을 통과로 읽는다. */\n return {\n checked: 'local',\n reason: `커넥터 \"${target.adapterType}\" 는 저쪽이 받을 준비가 됐는지 알려 주지 않습니다 — 넘겨 보고 알게 됩니다`,\n code: 'not-checkable'\n }\n }\n\n try {\n return await adapter.actuationReadiness(ref?.connectionConfig ?? {}, { siteId: target.siteId })\n } catch (e: any) {\n /*\n * 점검이 터진 것을 「저쪽이 죽었다」로 옮기지 않는다 — 우리 점검의 실패다. 그리고 던지지 않는다:\n * 이 함수 때문에 승인 화면이 안 열리면, 알려 주려던 것보다 큰 것을 잃는다.\n */\n return {\n checked: 'local',\n reason: `준비 상태를 확인하다 실패했습니다 — ${String(e?.message ?? e).slice(0, 160)}`,\n code: 'check-failed'\n }\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"actuation-target.js","sourceRoot":"","sources":["../../../server/service/reference/actuation-target.ts"],"names":[],"mappings":";;;AA0DA,gDAEC;AAsBD,0CA6EC;AA5GD;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAAC,MAAuB;IACxD,OAAO,MAAM,CAAC,EAAE,KAAK,IAAI,CAAA;AAC3B,CAAC;AAiBD;;;;GAIG;AACH,SAAgB,eAAe,CAAC,KAA2B;IACzD,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,KAAK,CAAC,UAAU,GAAG,EAAE,CAAA;IACvF,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACf;;;;WAIG;QACH,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,OAAO,KAAK,CAAC,UAAU,gCAAgC;SAChE,CAAA;IACH,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,mBAAmB,KAAK,CAAC,GAAG,CAAC,WAAW,GAAG,EAAE,CAAA;IAC/F,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5C;;;;;;;;;;WAUG;QACH,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAA;QACzC,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;YACnD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,cAAc;gBACpB,MAAM,EACJ,QAAQ,WAAW,wCAAwC;oBAC3D,gDAAgD;aACnD,CAAA;QACH,CAAC;QACD,IAAI,KAAK,CAAC,eAAe,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;YACnD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,iBAAiB;gBACvB,MAAM,EACJ,QAAQ,WAAW,kCAAkC;oBACrD,4BAA4B;aAC/B,CAAA;QACH,CAAC;QACD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,eAAe;YACrB,MAAM,EAAE,OAAO,MAAM,MAAM,WAAW,kCAAkC;SACzE,CAAA;IACH,CAAC;IAED;;;;;;OAMG;IACH,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAChD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,OAAO,KAAK,CAAC,UAAU,kDAAkD;SAClF,CAAA;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,CAAA;AAC3F,CAAC;AAED,gDAAgD;AACnC,QAAA,eAAe,GAAG,qCAAqC,CAAA","sourcesContent":["/*\n * **조치를 어디로 보낼 수 있나** — 판단만. 저장소도 어댑터 호출도 여기 없다.\n *\n * 배선(§`actuation-routing.ts`)에서 갈라 두는 이유는 규칙 쪽과 같다(§`rule-evaluate.ts`). 이 파일을\n * 부르는 시험이 TypeORM 데코레이터를 지나면 시험이 서지 않고, 그러면 **거절 판정을 아무 시험도 걷지\n * 않는다.** 조치 경로에서 그것은 승인이 쌓이는데 현장에 아무것도 안 가는 상태로 나타난다.\n */\nimport type { ReferenceCapability } from './reference-adapter.js'\n\n/** 판단에 필요한 것만 — 엔티티 전체를 받지 않는다. */\nexport interface ActuationTargetInput {\n /** 어느 트윈인가 — **거절 문장이 이것을 말한다.** 「모르는 트윈입니다」만으로는 찾을 수 없다. */\n instanceId: string\n /** 트윈이 있나. 없으면 `undefined`. */\n twinExists: boolean\n /** 이 트윈이 어느 사이트에서 왔나 — `TwinInstance.origin.siteId`. */\n siteId?: string\n /** 연결된 원본. 없으면 `undefined`(시뮬레이션으로만 세운 트윈). */\n ref?: { source: string; adapterType: string }\n /** 커넥터가 등록되어 있나. */\n adapterRegistered: boolean\n /**\n * 그 커넥터가 실제로 낼 수 있는 능력 — `capabilitiesOf` 가 판정한 값.\n *\n * **낼 수 있나는 이 값만 본다.** 아래 둘은 못 낼 때 무엇을 고쳐야 하는지 말하기 위한 것이고,\n * 판정을 다시 만들지 않는다.\n */\n capabilities: readonly ReferenceCapability[]\n /** 커넥터가 `capabilities` 에 조치를 적었나. */\n declaresActuate: boolean\n /** 커넥터에 `actuate` 함수가 있나. */\n hasActuateImpl: boolean\n}\n\n/** 보낼 수 있다 — 어디로까지 정해진 상태. */\nexport interface ActuationAllowed {\n ok: true\n siteId: string\n adapterType: string\n source: string\n}\n\n/** 보낼 수 없다 — 왜인지 코드와 문장으로. */\nexport interface ActuationRefused {\n ok: false\n code: ActuationRefusal\n detail: string\n}\n\nexport type ActuationTarget = ActuationAllowed | ActuationRefused\n\n/**\n * 보낼 수 있나 — **타입 가드로 둔다.**\n *\n * 이 저장소는 기본 설정이 `strict: false` 이고(`packages/tsconfig-base.json`), 그 상태에서는 `ok` 가\n * `true`·`false` 로 갈린 유니온이 `if (!t.ok)` 로 좁혀지지 않는다. 부르는 쪽마다 캐스팅을 넣으면 그\n * 캐스팅이 언젠가 틀린 쪽을 가리킨다. 판정을 여기 한 자리에 둔다.\n */\nexport function isActuationAllowed(target: ActuationTarget): target is ActuationAllowed {\n return target.ok === true\n}\n\n/**\n * 왜 못 보내나 — **코드로 답한다.**\n *\n * 화면이 이 코드로 다음 할 일을 가른다. 문장만 주면 화면은 그것을 그대로 띄우는 것밖에 못 하고,\n * 사람은 연결 설정을 열어야 하는지 커넥터를 고쳐야 하는지 모른다.\n */\nexport type ActuationRefusal =\n | 'unknown-twin'\n | 'no-source' /* 원본이 없는 트윈 — 보낼 곳이 없다 */\n | 'no-adapter' /* 커넥터가 등록되지 않았다 */\n | 'not-declared' /* 구현은 있는데 선언이 없다 — 한 줄 넣으면 된다 */\n | 'not-implemented' /* 선언은 있는데 구현이 없다 — 만들다 만 커넥터다 */\n | 'not-supported' /* 선언도 구현도 없다 — 이 커넥터는 관측만 낸다 */\n | 'no-site' /* 어느 사이트로 낼지 모른다 */\n\n/**\n * 보낼 수 있나 — 보낼 수 있으면 **어디로**까지 답한다.\n *\n * 순서가 뜻을 갖는다. 앞의 것이 없으면 뒤를 물을 수 없다 — 원본을 모르면 능력을 물을 데가 없다.\n */\nexport function actuationTarget(input: ActuationTargetInput): ActuationTarget {\n if (!input.twinExists) {\n return { ok: false, code: 'unknown-twin', detail: `모르는 트윈입니다 (${input.instanceId})` }\n }\n\n if (!input.ref) {\n /*\n * 시뮬레이션으로만 세운 트윈이 이 경우다. 규칙 경로가 시뮬 트윈을 이미 막지만\n * (§`rule-runner.ts`), 사람이 손으로 낸 조치는 그 문을 지나지 않는다. 막는 지점이 두 경로에\n * 각각 있는 것이고, 같은 판정을 두 벌 만든 것이 아니다.\n */\n return {\n ok: false,\n code: 'no-source',\n detail: `트윈 \"${input.instanceId}\" 에는 연결된 원본이 없습니다 — 보낼 곳이 없습니다`\n }\n }\n\n if (!input.adapterRegistered) {\n return { ok: false, code: 'no-adapter', detail: `등록되지 않은 커넥터입니다 (${input.ref.adapterType})` }\n }\n\n if (!input.capabilities.includes('actuate')) {\n /*\n * **왜 못 내는지를 셋으로 가른다** — 고칠 것이 셋 다 다르다.\n *\n * 선언 없음 · 구현 있음 한 줄 넣으면 된다\n * 선언 있음 · 구현 없음 만들다 만 커넥터다. 등록 검사는 이쪽을 말하지 않는다\n * 선언도 구현도 없음 이 커넥터는 조치를 받지 않는다\n *\n * 뭉개면 한 줄로 끝날 일에 커넥터를 다시 만들려 들거나, 반대로 「관측만 냅니다」를 믿고 조치를\n * 포기한다. 가운데 것을 「관측만 냅니다」로 답하는 것은 **사실이 아니다** — 그 사람은 조치를\n * 하겠다고 적어 두었다.\n */\n const { adapterType, source } = input.ref\n if (input.hasActuateImpl && !input.declaresActuate) {\n return {\n ok: false,\n code: 'not-declared',\n detail:\n `커넥터 \"${adapterType}\" 가 조치를 보내는 구현은 있는데 능력으로 선언하지 않았습니다 — ` +\n \"connector 의 capabilities 에 'actuate' 를 넣어야 합니다\"\n }\n }\n if (input.declaresActuate && !input.hasActuateImpl) {\n return {\n ok: false,\n code: 'not-implemented',\n detail:\n `커넥터 \"${adapterType}\" 가 조치를 보낸다고 선언했는데 그 구현이 없습니다 — ` +\n 'actuate 를 만들거나 선언을 지워야 합니다'\n }\n }\n return {\n ok: false,\n code: 'not-supported',\n detail: `원본 \"${source}\" (${adapterType}) 는 조치를 받지 않습니다 — 이 커넥터는 관측만 냅니다`\n }\n }\n\n /*\n * 사이트를 **지어내지 않는다.** 원본 하나가 여러 사이트를 낼 수 있다(`discoverSites`).\n *\n * 구동(§`control-routing.ts`)은 사이트를 몰라도 넘긴다 — 시늉을 멈추는 일에는 대상이 하나다.\n * 조치는 안 된다. 어느 공장에 낼지 모르는 작업지시를 첫 번째 사이트로 보내면 **잘못된 공장이\n * 실제로 만든다.**\n */\n const siteId = String(input.siteId ?? '').trim()\n if (!siteId) {\n return {\n ok: false,\n code: 'no-site',\n detail: `트윈 \"${input.instanceId}\" 이 어느 사이트에서 왔는지 알 수 없습니다 — 조치를 보낼 대상을 정할 수 없습니다`\n }\n }\n\n return { ok: true, siteId, adapterType: input.ref.adapterType, source: input.ref.source }\n}\n\n/** 승인은 남는다는 것을 함께 말한다 — 원인을 고친 뒤 다시 넘길 수 있다. */\nexport const APPROVAL_STANDS = '승인은 그대로 남아 있습니다 — 고친 뒤 다시 넘길 수 있습니다'\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"actuation-verification.js","sourceRoot":"","sources":["../../../server/service/reference/actuation-verification.ts"],"names":[],"mappings":";;AA+BA,sDAWC;AAwBD,kDAMC;AAQD,gDAIC;AAlED;;;;;;;;;;;;GAYG;AACH,SAAgB,qBAAqB,CAAC,MAAuB,EAAE,EAAU;IACvE,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;QACd,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,mBAAmB,EAAE,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAA;IACxF,CAAC;IAED,OAAO;QACL,cAAc,EAAE,QAAQ;QACxB,gBAAgB,EAAE,MAAM,CAAC,SAAS;YAChC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,SAAS,EAAE,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;YAC/F,CAAC,CAAC,IAAI;KACT,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,iBAAiB,GAAG,CAAC,gBAAgB,EAAE,cAAc,EAAE,QAAQ,EAAE,cAAc,CAAU,CAAA;AAE/F,MAAM,SAAS,GAAG,CAAC,GAA+C,EAAE,EAAE,CACpE,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAE,GAAW,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;AAEtG;;;;;;;;;GASG;AACH,SAAgB,mBAAmB,CACjC,MAAkD,EAClD,KAAiD;IAEjD,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACvD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAA;AAC5F,CAAC;AAED;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,GAA+C;IAChF,MAAM,QAAQ,GAAG,MAAM,CAAE,GAAW,EAAE,cAAc,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IAClE,MAAM,IAAI,GAAG,MAAM,CAAE,GAAW,EAAE,YAAY,IAAK,GAAW,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACpF,OAAO,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAA;AAC7B,CAAC","sourcesContent":["/*\n * 조치 왕복의 결과를 레퍼런스에 어떻게 적을 것인가 — **순수하게.**\n *\n * `actuation-routing.ts` 에 두면 시험이 그 파일을 들여야 하고, 그 파일은 엔티티를 값으로 들인다\n * (데코레이터가 붙은 클래스). 가벼운 모듈로 떼어 두면 판단만 따로 확인할 수 있다.\n *\n * 여기서 들이는 것은 **타입뿐**이다 — 런타임에 남지 않는다.\n */\nimport type { ActuationFailure, ActuationState } from './twin-reference.js'\nimport type { ActuationResult } from './reference-adapter.js'\n\n/** 레퍼런스에 적을 칸들. `actuationVerifiedAt` 이 없는 것은 「건드리지 않는다」는 뜻이다. */\nexport interface ActuationOutcomePatch {\n actuationState: ActuationState\n actuationVerifiedAt?: string\n actuationFailure: ActuationFailure | null\n}\n\n/**\n * 결과 → 적을 것.\n *\n * ── 세 가지를 지킨다 ───────────────────────────────────────────────────────\n * **받아들여지면 지난 거절을 지운다.** 남겨 두면 화면이 「됐는데 실패 사유가 있다」를 그린다.\n *\n * **거절은 성공 시각을 건드리지 않는다.** 「전에 한 번 닿았다」와 「이번에 안 됐다」는 둘 다 사실이고,\n * 지우면 앞엣것이 사라진다 — 사람이 「한 번이라도 닿은 적 있나」를 물을 자리가 없어진다.\n *\n * **코드가 없으면 사유를 비운다.** 자리를 채우려고 `error` 문장을 옮기지 않는다. `lastError` 가 이미\n * 겪었다 — 저장된 사람 문장은 번역도 집계도 안 되고, 커넥터가 그 문장을 고치는 날 옛 행과 새 행이\n * 서로 다른 말을 한다. 모르는 것은 안 적는다.\n */\nexport function actuationOutcomePatch(result: ActuationResult, at: string): ActuationOutcomePatch {\n if (result.ok) {\n return { actuationState: 'verified', actuationVerifiedAt: at, actuationFailure: null }\n }\n\n return {\n actuationState: 'failed',\n actuationFailure: result.errorCode\n ? { code: result.errorCode, at, ...(result.errorParams ? { params: result.errorParams } : {}) }\n : null\n }\n}\n\n/**\n * 조치가 가는 주소를 이루는 칸들 — 이 중 하나라도 바뀌면 지난 왕복은 **다른 주소의 이야기**다.\n *\n * `intakeSiteId` 가 없으면 `siteId` 가 그 자리를 대신한다(부딪힘 판정과 같은 규칙이다 —\n * `connection-portability.ts`). 비밀값을 함께 보는 이유는, 주소가 같아도 저쪽이 우리를 못 알아보면\n * 그 왕복이 다시 성립하지 않기 때문이다.\n */\nconst ACTUATION_ADDRESS = ['intakeEndpoint', 'intakeSiteId', 'siteId', 'intakeSecret'] as const\n\nconst addressOf = (cfg: Record<string, unknown> | null | undefined) =>\n ACTUATION_ADDRESS.map(k => String((cfg as any)?.[k] ?? '').trim().replace(/\\/$/, '')).join('\\u0000')\n\n/**\n * 저장이 조치의 주소를 바꾸나 — **바꿨으면 검증은 저장 시각부터 다시 없다**(ADR-0046 ②).\n *\n * 2026-09-18 실측: 칸 셋은 서 있고 왕복의 결과를 적는 자리도 있는데, 주소를 바꿔도 `verified` 가\n * 그대로 남았다. 화면은 다른 공장을 가리키는 연결을 「확인됨」으로 그린다 — 그 초록이 가장 위험한\n * 거짓이다.\n *\n * `unverified` 는 실패가 아니다. 아직 그 주소로 넘겨 본 적이 없다는 사실이고, 사람이 할 일은 한 번\n * 넘겨 보는 것이다.\n */\nexport function actuationResetPatch(\n before: Record<string, unknown> | null | undefined,\n after: Record<string, unknown> | null | undefined\n): { actuationState: ActuationState; actuationVerifiedAt: null; actuationFailure: null } | null {\n if (addressOf(before) === addressOf(after)) return null\n return { actuationState: 'unverified', actuationVerifiedAt: null, actuationFailure: null }\n}\n\n/**\n * 이 연결이 조치를 **보낼 수 있게 적혀 있나** — 주소가 없으면 어디로도 안 간다.\n *\n * 검증 상태를 화면에 보일지 가르는 값이다. 읽기만 하는 연결(마스터 동기만 하는 커넥터)에 「아직\n * 확인 안 됨」을 그리면, 할 일이 없는 사람에게 할 일이 있는 것처럼 보인다.\n */\nexport function actuationAddressed(cfg: Record<string, unknown> | null | undefined): boolean {\n const endpoint = String((cfg as any)?.intakeEndpoint ?? '').trim()\n const site = String((cfg as any)?.intakeSiteId ?? (cfg as any)?.siteId ?? '').trim()\n return !!endpoint && !!site\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"attention-lane.js","sourceRoot":"","sources":["../../../server/service/reference/attention-lane.ts"],"names":[],"mappings":";;AAaA,4DAEC;AAED,4CAGC;AAVD,IAAI,OAAqC,CAAA;AAEzC,sFAAsF;AACtF,SAAgB,wBAAwB,CAAC,IAAsB;IAC7D,OAAO,GAAG,IAAI,CAAA;AAChB,CAAC;AAEM,KAAK,UAAU,gBAAgB,CAAC,QAA2B;IAChE,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;IAClF,OAAO,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAA;AAChC,CAAC","sourcesContent":["import type { IngestResult } from './reference-hook.js'\n\nexport interface AttentionDelivery {\n domainId: string\n source: string\n instanceId: string\n records: unknown[]\n}\n\ntype AttentionHandler = (delivery: AttentionDelivery) => IngestResult | Promise<IngestResult>\nlet handler: AttentionHandler | undefined\n\n/** Product modules opt into the ATTENTIONS lane; core owns only transport routing. */\nexport function registerAttentionHandler(next: AttentionHandler): void {\n handler = next\n}\n\nexport async function deliverAttention(delivery: AttentionDelivery): Promise<IngestResult> {\n if (!handler) throw new Error('ATTENTIONS lane has no registered product handler')\n return await handler(delivery)\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"connection-portability-resolver.js","sourceRoot":"","sources":["../../../server/service/reference/connection-portability-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **연결을 배포 사이로 옮기는 문 셋** — 몸통은 `connection-portability.ts` 의 순수 함수들이다.\n *\n * ── 무엇을 옮기나 (사용자와 맞춘 개념, 2026-09-05) ─────────────────────────\n * **옮기는 것은 「붙는 법」이지 「붙은 결과」가 아니다.**\n *\n * 옮긴다 연결 선언 — 주소 · 도메인 · 라인 크기 · 자재 이름공간\n * 안 옮긴다 마스터 원본에서 다시 읽으면 된다\n * 안 옮긴다 사실(저널) 원본이 다시 보내면 된다\n * 안 옮긴다 잰 값 그 서버에서 다시 세면 된다\n * 안 옮긴다 비밀값 그 환경의 것이지 셋업이 아니다\n *\n * **패키지는 정본이 아니라 운반 수단이다.** 정본은 언제나 그 배포의 DB 이고, 패키지는 쓰고 버린다.\n *\n * ── 주체는 받는 쪽이다 ─────────────────────────────────────────────────────\n * 운영이 개발에서 **당겨온다.** 개발이 운영으로 밀면 개발 환경에 운영 자격이 살고, 그 통로는 실수로도\n * 열린다. 그래서 `pullTwinConnectionsFrom` 이 본래 원하던 문이고, 나머지 둘은 저쪽에 못 닿을 때의\n * 길이다.\n *\n * ── 쓰기 전에 계획을 보인다 ────────────────────────────────────────────────\n * `planImport` 가 새로/갱신/건너뜀과 **사람이 채울 비밀값**을 먼저 낸다. 부딪힘(두 연결이 같은 공장에\n * 조치를 내는 것)이 하나라도 있으면 **아무것도 쓰지 않는다** — 반만 적용된 연결 설정은 어느 쪽이\n * 참인지 아무도 모른다.\n */\nimport { Arg, Ctx, Directive, Mutation, Query, Resolver } from 'type-graphql'\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { TwinReference } from './twin-reference.js'\nimport { exportConnections, intakeCollisions, planImport, sayPlan, secretEnvName } from './connection-portability.js'\n\n/** 그 환경이 이 비밀값을 줄 수 있나 — **값은 읽지 않는다.** 이름의 존재만 본다. */\nfunction hasSecret(source: string, key: string): boolean {\n const name = secretEnvName(source, key)\n const v = process.env[name]\n return typeof v === 'string' && v.trim().length > 0\n}\n\n@Resolver()\nexport class ConnectionPortabilityResolver {\n /**\n * 지금 연결을 패키지로 낸다 — **비밀값은 안 나간다.**\n *\n * 이름 규칙으로 걸러서 **새 비밀 칸도 기본으로 안 나간다**(`SECRET_KEYS`). 나가지 않은 것은\n * 패키지가 이름으로 말하고, 받는 쪽이 그 이름을 사람에게 보인다.\n */\n /*\n * **읽기인데 쓰기와 같은 문을 쓴다.** 이 패키지는 연결 주소·도메인과 **비밀 칸의 이름**까지 낸다 —\n * 그 이름만으로도 그 배포가 무엇에 붙어 있는지 드러난다. 권한 가드가 「쓰기는 GATE 하나」로\n * 통일해 두었고, 이 문은 읽기지만 그 무게가 쓰기 쪽이다(ADR-0027).\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Query(returns => ScalarObject, {\n description:\n 'Export this deployment’s twin connection declarations as a portable bundle. Secrets are never included — the bundle names them so the receiving side can ask a human.'\n })\n async twinConnectionBundle(\n @Ctx() context: ResolverContext,\n @Arg('note', { nullable: true, description: 'Free note carried in the bundle (why this export was made).' }) note?: string\n ): Promise<any> {\n const domain = context.state.domain\n const rows = await getRepository(TwinReference).find({ where: { domain: { id: domain.id } } })\n return exportConnections(rows as any, { at: new Date().toISOString(), domain: domain.subdomain ?? domain.name, ...(note ? { note } : {}) })\n }\n\n /**\n * 패키지를 받아 적용한다 — **`dryRun` 이 기본이다.**\n *\n * 쓰기가 기본이면 「무엇이 바뀌나」를 보기 위해 쓰게 된다. 계획을 먼저 보고 사람이 정하는 것이\n * 이 문의 요점이고, 그래서 적용은 명시해야 일어난다.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, {\n description:\n 'Apply a connection bundle. Returns the plan (new / updated / skipped, secrets a human must supply, collisions). Defaults to dryRun — pass apply:true to write.'\n })\n async importTwinConnections(\n @Arg('bundle', type => ScalarObject) bundle: any,\n @Ctx() context: ResolverContext,\n @Arg('apply', { nullable: true, description: 'Write the plan. Omit or false to only see what would happen.' }) apply?: boolean\n ): Promise<any> {\n const domainId = context.state.domain.id\n const repo = getRepository(TwinReference)\n const existing = await repo.find({ where: { domain: { id: domainId } } })\n\n const plan = planImport(bundle, existing as any, hasSecret)\n const said = sayPlan(plan)\n\n /*\n * 부딪힘이 있으면 **아무것도 쓰지 않는다.** 두 연결이 같은 공장에 조치를 내면 어느 쪽이 그 라인의\n * 주인인지 정할 수 없고, 반만 적용하면 그 물음이 조용히 남는다.\n */\n if (plan.collisions.length || plan.errors.length) {\n return { ok: false, applied: 0, plan, said, refused: plan.collisions.length ? 'intake-collision' : 'bundle-error' }\n }\n if (!apply) return { ok: true, applied: 0, plan, said, dryRun: true }\n\n let applied = 0\n for (const a of plan.actions) {\n /* 건너뛴 것은 이유가 계획에 남아 있다 — 여기서 다시 판정하지 않는다. */\n if (a.action === 'skip') continue\n\n const c = a.connection\n /*\n * **비밀값을 빈 값으로 안 채운다.** 패키지에 비밀값이 없고, `planImport` 가 그 이름을\n * `missingSecrets`·`needsHuman` 으로 이미 냈다. 여기서 그 칸을 `undefined` 로 쓰면 사람이\n * 화면에서 넣어 둔 값을 파일 하나가 지운다.\n *\n * 그래서 갱신은 **선언 칸만** 건드린다. 이미 있는 연결의 `connectionConfig` 는 패키지가 든\n * 것만 덮어쓰고 나머지 키는 그대로 남긴다.\n */\n const found = await repo.findOne({ where: { domain: { id: domainId }, source: c.source } })\n const declared = {\n ...(c.system ? { system: c.system } : {}),\n ...(c.siteName ? { siteName: c.siteName } : {}),\n adapterType: c.adapterType,\n ...(c.scope ? { scopeSpec: c.scope } : {})\n } as any\n\n if (found) {\n const merged = { ...((found as any).connectionConfig ?? {}), ...(c.config ?? {}) }\n await repo.update({ id: (found as any).id }, { ...declared, connectionConfig: merged } as any)\n } else {\n await repo.save(repo.create({ source: c.source, ...declared, connectionConfig: c.config ?? {}, domain: { id: domainId } as any }))\n }\n applied++\n }\n return { ok: true, applied, plan, said }\n }\n\n /**\n * 저쪽 배포에서 당겨와 적용한다 — **본래 원한 문.**\n *\n * 받는 쪽이 주체다. 저쪽의 `twinConnectionBundle` 을 불러 이 배포에 적용한다 — 위 두 문을 잇는\n * 얇은 층이고, 판정과 쓰기는 `importTwinConnections` 와 같은 규칙을 쓴다.\n *\n * **자격은 부르는 사람이 준다.** 이 서버가 저쪽 자격을 들고 있으면 그 통로가 상시로 열려 있게 되고,\n * 그것은 옮기는 일에 필요한 것보다 넓다.\n */\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\", domainOwnerGranted: true, superUserGranted: true)')\n @Mutation(returns => ScalarObject, {\n description:\n 'Pull connection declarations from another deployment and apply them here. The caller supplies that deployment’s URL and token — this server does not keep them.'\n })\n async pullTwinConnectionsFrom(\n @Arg('endpoint', { description: 'That deployment’s GraphQL endpoint.' }) endpoint: string,\n @Ctx() context: ResolverContext,\n @Arg('token', { nullable: true, description: 'Bearer token for that deployment. Not stored.' }) token?: string,\n @Arg('apply', { nullable: true }) apply?: boolean\n ): Promise<any> {\n let bundle: any\n try {\n const res = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },\n body: JSON.stringify({ query: '{ twinConnectionBundle }' })\n })\n const json: any = await res.json()\n /* 저쪽이 오류를 냈으면 그 말을 그대로 올린다 — 여기서 다시 지으면 원인이 흐려진다. */\n if (json?.errors?.length) return { ok: false, applied: 0, refused: 'remote-error', remote: json.errors.map((e: any) => e?.message) }\n bundle = json?.data?.twinConnectionBundle\n } catch (e: any) {\n return { ok: false, applied: 0, refused: 'unreachable', remote: [String(e?.message ?? e)] }\n }\n if (!bundle) return { ok: false, applied: 0, refused: 'empty-bundle' }\n\n return this.importTwinConnections(bundle, context, apply)\n }\n}\n\n/** 이 리졸버가 다루는 부딪힘 판정 — 화면이 같은 함수를 쓸 수 있게 다시 내보낸다. */\nexport { intakeCollisions }\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"connection-portability.js","sourceRoot":"","sources":["../../../server/service/reference/connection-portability.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **연결을 내보내고 다시 들여옵니다** — 실제 셋업을 파일로 보관하고 되세웁니다.\n *\n * ── 이것은 데모가 아니라 제품 기능입니다 (2026-09-05) ───────────────────────\n * 데모 시드(`seed-demo-twins.ts`)와 **다른 것**입니다. 그쪽은 내장 가상 마스터로 보여 줄 트윈을\n * 만들고 `TWIN_PROVISION_DEMO` 뒤에 숨습니다. 이것은 **실제로 붙인 연결을 옮기는 길**이라\n * 데모 스위치 뒤에 두지 않습니다 — 백업·이관·복구가 데모일 수 없습니다.\n *\n * 그래서 특정 공장을 제품 코드에 적지 않습니다. 적으면 배포마다 남의 공장이 하나씩 따라가고,\n * 데모 시드 머리말이 적어 둔 「프로덕션 배포는 **트윈 0** 에서 시작한다」가 깨집니다. 무엇을 세울지는\n * **파일이 말하고 코드는 그 파일을 읽을 줄만 압니다.**\n *\n * ── 왜 필요했나 ────────────────────────────────────────────────────────────\n * 실 MES 두 공장을 트윈에 세웠는데(`mes-line-a` · `mes-miratek`) **그 셋업이 한 데이터베이스\n * 안에만 있었습니다.** 잃으면 다시 손으로 만들어야 하고, 다른 배포에 옮길 길도 없었습니다.\n *\n * ── 담는 것과 안 담는 것 ────────────────────────────────────────────────────\n *\n * 담는다 연결 선언 — 주소 · 도메인 · 사이트 · 치수 · 이름공간 · 받는 자리\n * 안 담는다 비밀값 — 토큰 · 웹훅 서명값 · 받는 쪽 비밀값\n * 안 담는다 사실(저널) — 원본이 다시 보내면 된다. 담으면 **가짜 과거가 배포마다 복제된다**\n * 안 담는다 마스터에서 온 트윈 모델 — 연결이 있으면 재동기화가 다시 만든다\n *\n * **비밀값은 이름만 적습니다.** 어느 칸이 채워져 있었는지는 알려 주되 값은 안 나갑니다 — 파일은\n * 주고받는 물건이고, 한 번 나간 비밀값은 되돌릴 수 없습니다. 들여올 때 「이 셋이 없다」고 말하고\n * 사람이 연결 화면에서 채웁니다.\n *\n * ── 왜 사실을 안 담나 ───────────────────────────────────────────────────────\n * 저널은 되풀이 읽히고 미러로 복제되고 구독으로 방송됩니다. 내보낸 사실을 다른 배포에 들여오면\n * **일어나지 않은 일이 그 공장의 지난 기록에 섭니다.** 커서만 옮겨 두면 원본이 그 뒤부터 다시\n * 보내 주므로, 사실은 원본에서 오는 것이 맞습니다.\n */\n\n/** 값이 아니라 **채워져 있었다는 사실**만 나가는 칸들. */\nexport const SECRET_KEYS = ['token', 'hookSecret', 'intakeSecret', 'password', 'apiKey'] as const\n\nexport interface PortableConnection {\n source: string\n system?: string\n siteName?: string\n adapterType: string\n /** 비밀값을 뺀 연결 설정. */\n config: Record<string, unknown>\n /** 내보낼 때 채워져 있던 비밀 칸 이름 — 값은 없습니다. */\n secretsRequired: string[]\n /** 이 연결이 무엇을 세우나. */\n scope?: unknown\n}\n\nexport interface ConnectionBundle {\n kind: 'operato-twin/connections'\n version: 1\n exportedAt: string\n /** 어디서 내보냈나 — 사람이 나중에 「이게 어느 배포 것인가」를 물을 때 답합니다. */\n exportedFrom?: { domain?: string; note?: string }\n connections: PortableConnection[]\n}\n\n/**\n * 무엇을 세우나 **선언만** 남깁니다 — 지난 동기화의 **측정값은 뺍니다.**\n *\n * `scopeSpec` 에는 `produced`(선언) 옆에 `assessments`(그때 몇 개가 섰나) · `lastFailures` 가 함께\n * 삽니다. 그것까지 내보내면 새 배포가 **다른 기계에서 잰 수**를 자기 것으로 보입니다 — 「설비 42」가\n * 아무것도 안 세운 배포의 화면에 서고, 그 수가 어디서 왔는지 아무도 모릅니다.\n *\n * 재는 것은 그 배포에서 다시 재야 합니다. 오늘 「설정을 고쳤다」와 「모델이 다시 섰다」를 가른 것과\n * 같은 구별입니다.\n */\nfunction producedOf(scopeSpec: any): { produced: unknown } | undefined {\n const produced = scopeSpec?.produced\n return Array.isArray(produced) && produced.length ? { produced } : undefined\n}\n\nconst isSecret = (key: string): boolean =>\n (SECRET_KEYS as readonly string[]).includes(key) || /secret|token|password|apikey/i.test(key)\n\n/**\n * 연결 행들을 옮길 수 있는 모양으로 — **비밀값을 빼고**.\n *\n * 빼는 판정을 이름으로 합니다. 목록으로만 하면 커넥터가 새 비밀 칸을 더할 때 그 값이 조용히\n * 파일로 나갑니다 — 이름 규칙을 함께 두어 **새 칸이 기본으로 안 나가게** 합니다.\n */\nexport function exportConnections(\n rows: readonly { source: string; system?: string; siteName?: string; adapterType: string; connectionConfig?: any; scopeSpec?: any }[],\n meta: { at: string; domain?: string; note?: string }\n): ConnectionBundle {\n return {\n kind: 'operato-twin/connections',\n version: 1,\n exportedAt: meta.at,\n ...(meta.domain || meta.note ? { exportedFrom: { ...(meta.domain ? { domain: meta.domain } : {}), ...(meta.note ? { note: meta.note } : {}) } } : {}),\n connections: rows.map(r => {\n const cfg = (r.connectionConfig ?? {}) as Record<string, unknown>\n const kept: Record<string, unknown> = {}\n const secrets: string[] = []\n for (const [k, v] of Object.entries(cfg)) {\n if (isSecret(k)) {\n /* 비어 있던 칸은 적지 않습니다 — 「채워야 한다」와 「이 커넥터에 그런 칸이 있다」는 다릅니다. */\n if (String(v ?? '').trim()) secrets.push(k)\n continue\n }\n kept[k] = v\n }\n return {\n source: r.source,\n ...(r.system ? { system: r.system } : {}),\n ...(r.siteName ? { siteName: r.siteName } : {}),\n adapterType: r.adapterType,\n config: kept,\n secretsRequired: secrets.sort(),\n ...(producedOf(r.scopeSpec) ? { scope: producedOf(r.scopeSpec) } : {})\n }\n })\n }\n}\n\nexport type ImportAction =\n | { action: 'create'; connection: PortableConnection; missingSecrets: string[] }\n | { action: 'update'; connection: PortableConnection; missingSecrets: string[]; keptSecrets: string[] }\n | { action: 'skip'; source: string; why: string }\n\nexport interface ImportPlan {\n actions: ImportAction[]\n /** 사람이 연결 화면에서 채워야 하는 것 — 이것이 비어야 「시드만으로 끝」입니다. */\n /**\n * 사람이 환경에 넣어야 하는 비밀값.\n *\n * `env` 를 함께 냅니다 — **그 이름을 사람이 손으로 적기 때문**입니다. 화면이 이름을 따로 만들면\n * 규칙이 두 곳에 살고, 갈리는 날 「넣었는데 안 읽힌다」가 됩니다. 실제로 났습니다(2026-09-06):\n * 화면이 `TWIN_SECRET__MES-MIRATEK__HOOKSECRET` 을 알려 줬는데 서버가 읽는 이름은\n * `TWIN_SECRET__MES_MIRATEK__HOOK_SECRET` 이었습니다. 하이픈과 낙타 경계 둘 다 달랐습니다.\n *\n * `keys` 와 순서가 같습니다 — `keys[i]` 의 이름이 `env[i]` 입니다.\n */\n needsHuman: { source: string; keys: string[]; env: string[] }[]\n /** 들여온 **뒤에** 두 연결이 같은 곳에 조치를 내게 되나 — 있으면 들여오면 안 됩니다. */\n collisions: { intake: string; sources: string[] }[]\n errors: string[]\n}\n\n/**\n * 들여오기 **계획**을 먼저 냅니다 — 쓰기 전에 무엇이 일어날지 사람이 봅니다.\n *\n * ── 이미 있는 연결을 덮지 않습니다 ──────────────────────────────────────────\n * 같은 이름이 이미 있으면 **선언만 갱신하고 비밀값은 그대로 둡니다.** 사람이 화면에서 넣은 값을\n * 파일이 지우면, 파일 하나 들여왔다가 도는 연결이 멈춥니다. 데모 시드도 같은 규율을 적어 두었습니다\n * (「옆 설정을 지우지 않는다 — 시드는 자기가 아는 것만 채운다」).\n *\n * ── 없는 비밀값을 지어내지 않습니다 ─────────────────────────────────────────\n * 빈 값으로 채워 두면 연결이 서고 **동기화할 때 처음 실패합니다.** 그때는 무엇이 없어서인지 화면에서\n * 멉니다. 여기서 이름을 대고 멈추는 것이 낫습니다.\n */\nexport function planImport(\n bundle: unknown,\n existing: readonly { source: string; connectionConfig?: any }[],\n /** 지금 환경이 줄 수 있는 비밀값 이름들 — 값은 안 받습니다(이 함수는 값을 만지지 않습니다). */\n availableSecrets: (source: string, key: string) => boolean = () => false\n): ImportPlan {\n const errors: string[] = []\n const b = bundle as ConnectionBundle\n if (!b || typeof b !== 'object') return { actions: [], needsHuman: [], collisions: [], errors: ['들여올 것이 객체가 아닙니다'] }\n if (b.kind !== 'operato-twin/connections') {\n return { actions: [], needsHuman: [], collisions: [], errors: [`이 파일은 연결 설정 파일가 아닙니다(kind: ${JSON.stringify((b as any).kind ?? null)})`] }\n }\n if (b.version !== 1) errors.push(`모르는 버전입니다(version: ${JSON.stringify((b as any).version)}) — 읽을 수 있는 것만 읽습니다`)\n if (!Array.isArray(b.connections)) return { actions: [], needsHuman: [], collisions: [], errors: [...errors, 'connections 가 목록이 아닙니다'] }\n\n const have = new Map(existing.map(e => [e.source, e]))\n const seen = new Set<string>()\n const actions: ImportAction[] = []\n const needsHuman: { source: string; keys: string[]; env: string[] }[] = []\n\n for (const c of b.connections) {\n const source = String(c?.source ?? '').trim()\n if (!source) {\n errors.push('이름 없는 연결이 있습니다 — 건너뜁니다')\n continue\n }\n if (seen.has(source)) {\n actions.push({ action: 'skip', source, why: '같은 이름이 파일에 두 번 있습니다' })\n continue\n }\n seen.add(source)\n if (!String(c?.adapterType ?? '').trim()) {\n actions.push({ action: 'skip', source, why: '커넥터 종류가 없습니다 — 무엇으로 붙일지 모릅니다' })\n continue\n }\n\n const cur = have.get(source)\n const curCfg = (cur?.connectionConfig ?? {}) as Record<string, unknown>\n const wanted = Array.isArray(c.secretsRequired) ? c.secretsRequired.map(String) : []\n const kept = wanted.filter(k => String(curCfg[k] ?? '').trim())\n const missing = wanted.filter(k => !kept.includes(k) && !availableSecrets(source, k))\n\n if (missing.length) needsHuman.push({ source, keys: missing, env: missing.map(k => secretEnvName(source, k)) })\n actions.push(\n cur\n ? { action: 'update', connection: c, missingSecrets: missing, keptSecrets: kept }\n : { action: 'create', connection: c, missingSecrets: missing }\n )\n }\n\n /*\n * **들여온 뒤의 모습으로 봅니다.** 파일 안에서만 보면 「이미 있는 연결과 부딪히는 것」을 못\n * 잡습니다 — 그게 실제로 더 쉽게 나는 쪽입니다(하나는 화면에서, 하나는 파일에서 생깁니다).\n */\n const after = [\n /* 행 모양 그대로 넘긴다 — 옮기는 자리는 `intakeCollisions` 안 한 곳이다(ADR-0053 ①). */\n ...existing.filter(e => !seen.has(e.source)),\n ...actions\n .filter((a): a is Extract<ImportAction, { connection: PortableConnection }> => a.action !== 'skip')\n .map(a => ({ source: a.connection.source, config: a.connection.config }))\n ]\n return { actions, needsHuman, collisions: intakeCollisions(after), errors }\n}\n\n/** 사람이 읽는 한 줄 — 화면과 로그가 같은 문장을 쓰게. */\nexport function sayPlan(plan: ImportPlan): string {\n const n = (a: ImportAction['action']) => plan.actions.filter(x => x.action === a).length\n const head = `연결 ${plan.actions.length}개 — 새로 ${n('create')} · 갱신 ${n('update')} · 건너뜀 ${n('skip')}`\n /* 부딪힘을 먼저 말합니다 — 비밀값이 없는 것은 안 도는 것이고, 부딪히는 것은 **잘못 도는 것**입니다. */\n if (plan.collisions.length) {\n const who = plan.collisions.map(c => `${c.sources.join(' · ')} → ${c.intake}`).join(', ')\n return `${head}. ⚠ **두 연결이 같은 곳에 조치를 냅니다** — ${who}. 다른 공장의 작업지시가 한 공장에 섭니다`\n }\n if (!plan.needsHuman.length) return `${head}. 비밀값은 다 있습니다`\n const who = plan.needsHuman.map(h => `${h.source}(${h.keys.join(' · ')})`).join(', ')\n return `${head}. **비밀값을 사람이 넣어야 합니다** — ${who}. 연결 화면에서 채우면 그때부터 돕니다`\n}\n\n/**\n * 비밀값이 사는 **환경 변수 이름** — `mes-miratek` · `hookSecret` → `TWIN_SECRET__MES_MIRATEK__HOOK_SECRET`.\n *\n * 패키지에는 비밀 칸의 이름만 있고 값은 환경에서 옵니다. 그 이름을 **사람이 손으로 적으므로** 규칙이\n * 흔들리면 「넣었는데 안 읽힌다」가 됩니다 — 오늘 종일 잡은 「양쪽 다 초록인데 안 간다」의 설정 쪽\n * 버전입니다. 그래서 이 규칙을 한 곳에 두고 시험으로 못 박습니다.\n *\n * 낙타 이름의 경계를 밑줄로 가릅니다 — 안 그러면 `HOOKSECRET` 과 `HOOK_SECRET` 이 갈려 한쪽만 읽힙니다.\n */\nexport function secretEnvName(source: string, key: string): string {\n const up = (x: string) =>\n x\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[^A-Za-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .toUpperCase()\n return `TWIN_SECRET__${up(source)}__${up(key)}`\n}\n\n/**\n * **두 연결이 같은 곳에 조치를 내지 않나** — 같으면 다른 공장의 작업지시가 한 공장에 섭니다.\n *\n * ── 왜 이 검사가 생겼나 (2026-09-05) ────────────────────────────────────────\n * `mes-miratek` 이 서면서 한 커넥터가 처음으로 **공장 둘**을 달았습니다. 조치가 나가는 주소는\n * `intakeSiteId` 가 정하는데(`operato-plant.ts:389`), 그 값이 트윈의 사이트 이름을 **이깁니다** —\n * 트윈에서는 `mes-line-a` 인데 MES 의 도메인은 `system` 이라 그 override 가 필요합니다.\n *\n * 그래서 **연결을 베껴 만들고 그 칸만 안 고치면** 두 트윈의 조치가 같은 공장으로 갑니다. 오류가\n * 안 납니다 — 저쪽은 정상적인 작업지시로 받고 **실제로 만듭니다.**\n *\n * 게이트에 이미 「사이트를 모르면 보내지 않는다」가 있는데(`no-site`), 그 이유가 적혀 있는 문장이\n * **「잘못된 공장이 실제로 만든다」** 입니다. 이것은 그 둘째 길입니다 — 사이트를 아는데 **틀린\n * 사이트를 압니다.**\n *\n * 파일로 연결을 만들 수 있게 되면서 더 쉬워졌습니다: 패키지를 베껴 이름만 바꾸면 받는 자리가\n * 그대로 따라옵니다.\n */\nexport function intakeCollisions(\n connections: readonly {\n source: string\n adapterType?: string\n /** 패키지·계획이 쓰는 이름. */\n config?: Record<string, unknown> | null\n /** **DB 행이 쓰는 이름** — `TwinReference` 의 칸이다. 둘 다 받는다(ADR-0053 ①). */\n connectionConfig?: Record<string, unknown> | null\n }[]\n): { intake: string; sources: string[] }[] {\n const byIntake = new Map<string, string[]>()\n for (const c of connections) {\n /*\n * **모양이 둘인 것을 여기서 안다** (ADR-0053 ①, 2026-09-16).\n *\n * 패키지는 `config`, `TwinReference` 행은 `connectionConfig` 다. 전에는 `config` 만 읽어서, 행을\n * 그대로 넘기면 같은 충돌에도 `[]` 를 냈다 — 오류도 경고도 없이 **저장이 통과하고 가드는 선 채로\n * 아무 일도 안 한다.** 부르는 쪽마다 옮기게 두면 다음 소비처가 또 밟는다(이 함수는 화면이 쓰라고\n * 리졸버에서 다시 내보내진다).\n *\n * 「없다」가 편한 답인 자리라 더 위험하다 — 빨강이 아니라 **조용한 초록**으로 흐른다.\n */\n const cfg = c.config ?? c.connectionConfig ?? {}\n /* 받는 자리를 정하는 규칙은 커넥터의 것과 같아야 합니다 — 여기서 다시 지으면 두 벌이 됩니다. */\n const intake = String(cfg.intakeSiteId ?? cfg.siteId ?? '').trim()\n /* 조치를 낼 수 없는 연결은 부딪힐 것이 없습니다 — 주소가 없으면 아무 데도 안 갑니다. */\n if (!intake || !String(cfg.intakeEndpoint ?? '').trim()) continue\n const key = `${String(cfg.intakeEndpoint).trim().replace(/\\/$/, '')}::${intake}`\n byIntake.set(key, [...(byIntake.get(key) ?? []), c.source])\n }\n return [...byIntake.entries()]\n .filter(([, sources]) => sources.length > 1)\n .map(([intake, sources]) => ({ intake, sources: sources.sort() }))\n}\n\n/**\n * **화면 저장의 문** — 이 연결을 이 설정으로 저장하면 다른 연결과 받는 사이트가 겹치나 (ADR-0046 ②-1).\n *\n * 들여오기(`planImport`)는 `intakeCollisions` 로 거부하는데 화면 저장(`saveTwinReference`)은 부르지 않아서,\n * 연결을 베껴 이름만 바꾸면 경고 0 으로 두 트윈의 조치가 한 공장에 섰다. 판정은 같은 함수 하나로 한다.\n * 경고가 아니라 거부이고, 사람이 그 자리에서 고치도록 **이미 그 사이트를 쓰는 연결의 이름**을 싣는다.\n */\nexport function refuseIntakeCollision(\n source: string,\n config: Record<string, unknown> | undefined,\n others: readonly { source: string; siteName?: string | null; connectionConfig?: Record<string, unknown> | null }[]\n): { code: 'intake-site-taken'; message: string; params: { site: string; connection: string } } | null {\n const rest = others.filter(o => o.source !== source)\n /* `others` 는 DB 행이다 — 옮기지 않고 그대로 넘긴다(ADR-0053 ①). */\n const hit = intakeCollisions([...rest, { source, config: config ?? {} }]).find(c => c.sources.includes(source))\n if (!hit) return null\n const connection = rest\n .filter(o => hit.sources.includes(o.source))\n .map(o => o.siteName || o.source)\n .join(', ')\n const site = String(config?.intakeSiteId ?? config?.siteId ?? '').trim()\n return {\n code: 'intake-site-taken',\n message: `intake site ${site} is already used by ${connection}`,\n params: { site, connection }\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"control-routing.js","sourceRoot":"","sources":["../../../server/service/reference/control-routing.ts"],"names":[],"mappings":";;AA2BA,oCA2BC;AAGD,kDAIC;AA7DD;;;;;;;;;;;;;;GAcG;AACH,iDAAqD;AAErD,wEAAgE;AAChE,2DAAmD;AACnD,iEAAiI;AAEjI;;;;;GAKG;AACI,KAAK,UAAU,YAAY,CAAC,QAAgB,EAAE,UAAkB,EAAE,OAAuB;IAC9F,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,+BAAY,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;IAC3G,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,CAAA;IAEzE,MAAM,GAAG,GAAG,MAAM,IAAA,qBAAa,EAAC,iCAAa,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;IACnH,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,gEAAgE;QAChE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,UAAU,uCAAuC,EAAE,CAAA;IAC7G,CAAC;IAED,MAAM,OAAO,GAAG,IAAA,iCAAU,EAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IAC3C,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,WAAW,EAAE,CAAA;IAC/E,IAAI,CAAC,IAAA,qCAAc,EAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACjD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,WAAW,GAAG,CAAC,MAAM,MAAM,GAAG,CAAC,WAAW,yHAAyH;SAC5K,CAAA;IACH,CAAC;IAED;;;OAGG;IACH,MAAM,MAAM,GAAI,IAAI,CAAC,MAAc,EAAE,MAAM,CAAA;IAC3C,MAAM,IAAI,GAA+B,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IACxE,OAAO,OAAO,CAAC,OAAQ,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAA;AACjG,CAAC;AAED,sDAAsD;AAC/C,KAAK,UAAU,mBAAmB,CAAC,QAAgB,EAAE,UAAkB;IAC5E,MAAM,GAAG,GAAG,MAAM,IAAA,qBAAa,EAAC,iCAAa,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;IACnH,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,CAAA;IACrC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,YAAY,EAAE,IAAA,qCAAc,EAAC,IAAA,iCAAU,EAAC,GAAG,CAAC,WAAW,CAAC,CAAC,EAAE,CAAA;AACxH,CAAC","sourcesContent":["/*\n * **구동은 원본을 지난다** — 트윈을 우회하지 않는다 (ADR-0029 §8, 2026-08-19).\n *\n * ── 무엇이 뚫려 있었나 ──────────────────────────────────────────────────────\n * `controlTwinScenario` 가 `TwinEngine.runtime(...).scenario` 를 **직접** 잡았다. 그래서 시뮬만 할 수 있는\n * 일(자극·배속·정지)이 계약 밖의 지름길로 들어왔고, 실 시스템에는 그 자리가 아예 없었다 — ADR-0029 의\n * 판정 문장에 걸리는 상태였다.\n *\n * 이제 길은 하나다: **트윈 → 그 트윈의 원본 → 어댑터가 선언한 `control` 능력.** 선언하지 않은 원본에는\n * 문이 열리지 않고, 그 사실을 코드로 말한다(`not-declared`). 실 WMS 가 정지되지 않는 것은 결손이 아니다.\n *\n * ── 전송은 여기서 묻지 않는다 (§7) ──────────────────────────────────────────\n * 인프로세스 시뮬레이터는 같은 계약을 인프로세스로 이행한다. 나중에 그것이 다른 프로세스로 나가도 이 경로는\n * 바뀌지 않는다 — 「계약을 지나는가」만 보고 「네트워크를 타는가」는 묻지 않는다.\n */\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinReference } from './twin-reference.js'\nimport { capabilitiesOf, getAdapter, type ControlCommand, type ControlResult, type SiteDescriptor } from './reference-adapter.js'\n\n/**\n * 이 트윈의 원본에 구동 명령을 보낸다 — **원본이 없거나 능력을 선언하지 않으면 거절한다.**\n *\n * 거절을 알리지 않고 하지 않는다: 사유 코드를 돌려주고 화면이 그것을 사람 말로 옮긴다. 「아무 일도 일어나지\n * 않았다」가 가장 비싼 답이다.\n */\nexport async function routeControl(domainId: string, instanceId: string, command: ControlCommand): Promise<ControlResult> {\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!inst) return { ok: false, code: 'unknown-twin', detail: instanceId }\n\n const ref = await getRepository(TwinReference).findOne({ where: { domain: { id: domainId }, source: instanceId } })\n if (!ref) {\n /* 원본을 모르는 트윈 — 구동할 대상이 없다. 트윈을 직접 흔드는 길은 두지 않는다(그것이 지름길이었다). */\n return { ok: false, code: 'no-source', detail: `twin \"${instanceId}\" does not remember a source to drive` }\n }\n\n const adapter = getAdapter(ref.adapterType)\n if (!adapter) return { ok: false, code: 'no-adapter', detail: ref.adapterType }\n if (!capabilitiesOf(adapter).includes('control')) {\n return {\n ok: false,\n code: 'not-declared',\n detail: `source \"${ref.source}\" (${ref.adapterType}) does not declare the control capability — a real system is not paused or seeded, and that is a fact rather than a gap`\n }\n }\n\n /*\n * 사이트를 함께 넘긴다 — 원본 하나가 여러 사이트를 낼 수 있다(`discoverSites`). 우리가 아는 것은\n * 이 트윈이 어느 사이트에서 왔는지이고(`origin.siteId`), 모르면 넘기지 않는다(지어내지 않는다).\n */\n const siteId = (inst.origin as any)?.siteId\n const site: SiteDescriptor | undefined = siteId ? { siteId } : undefined\n return adapter.control!(ref.connectionConfig ?? {}, site, { ...command, instanceId, domainId })\n}\n\n/** 이 트윈의 원본이 선언한 능력 — 화면이 「무엇을 할 수 있나」를 먼저 알 수 있게. */\nexport async function controlCapabilityOf(domainId: string, instanceId: string): Promise<{ source?: string; adapterType?: string; capabilities: string[] }> {\n const ref = await getRepository(TwinReference).findOne({ where: { domain: { id: domainId }, source: instanceId } })\n if (!ref) return { capabilities: [] }\n return { source: ref.source, adapterType: ref.adapterType, capabilities: capabilitiesOf(getAdapter(ref.adapterType)) }\n}\n"]}