@things-factory/headless-twin 10.1.277 → 10.1.279

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (614) hide show
  1. package/package.json +21 -7
  2. package/dist-server/engine/attention-digest.js.map +0 -1
  3. package/dist-server/engine/canonical-ingest.js.map +0 -1
  4. package/dist-server/engine/checkpoint-fold.js.map +0 -1
  5. package/dist-server/engine/command-routing.js.map +0 -1
  6. package/dist-server/engine/declared-stimulus.js.map +0 -1
  7. package/dist-server/engine/energy-topology.js.map +0 -1
  8. package/dist-server/engine/event-time.js.map +0 -1
  9. package/dist-server/engine/index.js.map +0 -1
  10. package/dist-server/engine/ingest-dedupe.js.map +0 -1
  11. package/dist-server/engine/ingest-health.js.map +0 -1
  12. package/dist-server/engine/integration-coverage.js.map +0 -1
  13. package/dist-server/engine/integration-probes.js.map +0 -1
  14. package/dist-server/engine/integration-runner.js.map +0 -1
  15. package/dist-server/engine/integration-target-profile.js.map +0 -1
  16. package/dist-server/engine/kpi-baseline.js.map +0 -1
  17. package/dist-server/engine/kpi-fold.js.map +0 -1
  18. package/dist-server/engine/kpi-query.js.map +0 -1
  19. package/dist-server/engine/kpi-target.js.map +0 -1
  20. package/dist-server/engine/live-attentions.js.map +0 -1
  21. package/dist-server/engine/live-feed-registry.js.map +0 -1
  22. package/dist-server/engine/load-meter.js.map +0 -1
  23. package/dist-server/engine/local-declarations.js.map +0 -1
  24. package/dist-server/engine/log.js.map +0 -1
  25. package/dist-server/engine/loop-lag.js.map +0 -1
  26. package/dist-server/engine/measured-estimator.js.map +0 -1
  27. package/dist-server/engine/measured-yield.js.map +0 -1
  28. package/dist-server/engine/model-basis.js.map +0 -1
  29. package/dist-server/engine/model-gap.js.map +0 -1
  30. package/dist-server/engine/model-refusal.js.map +0 -1
  31. package/dist-server/engine/model-vocabulary.js.map +0 -1
  32. package/dist-server/engine/oee-accumulator.js.map +0 -1
  33. package/dist-server/engine/operation-basis.js.map +0 -1
  34. package/dist-server/engine/producer-guard.js.map +0 -1
  35. package/dist-server/engine/property-effects.js.map +0 -1
  36. package/dist-server/engine/read-time.js.map +0 -1
  37. package/dist-server/engine/restart-policy.js.map +0 -1
  38. package/dist-server/engine/runtime-key.js.map +0 -1
  39. package/dist-server/engine/spec-coverage.js.map +0 -1
  40. package/dist-server/engine/stage-path.js.map +0 -1
  41. package/dist-server/engine/state-axes.js.map +0 -1
  42. package/dist-server/engine/structure-diff.js.map +0 -1
  43. package/dist-server/engine/travel-estimator.js.map +0 -1
  44. package/dist-server/engine/twin-engine.js.map +0 -1
  45. package/dist-server/engine/twin-state-channel.js.map +0 -1
  46. package/dist-server/engine/warm-start.js.map +0 -1
  47. package/dist-server/index.js.map +0 -1
  48. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +0 -1
  49. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +0 -1
  50. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +0 -1
  51. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +0 -1
  52. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +0 -1
  53. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +0 -1
  54. package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.js.map +0 -1
  55. package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.js.map +0 -1
  56. package/dist-server/migrations/1786800000000-CreateTwinReferenceReconciliations.js.map +0 -1
  57. package/dist-server/migrations/index.js.map +0 -1
  58. package/dist-server/routes.js.map +0 -1
  59. package/dist-server/service/actuation/actuation-advisor.js.map +0 -1
  60. package/dist-server/service/actuation/actuation-outcome.js.map +0 -1
  61. package/dist-server/service/actuation/actuation-rule-resolver.js.map +0 -1
  62. package/dist-server/service/actuation/actuation-rule.js.map +0 -1
  63. package/dist-server/service/actuation/approval-gateway.js.map +0 -1
  64. package/dist-server/service/actuation/catch-up-effects.js.map +0 -1
  65. package/dist-server/service/actuation/command-dispatcher.js.map +0 -1
  66. package/dist-server/service/actuation/command-store.js.map +0 -1
  67. package/dist-server/service/actuation/dispatch-after-approval.js.map +0 -1
  68. package/dist-server/service/actuation/effect-match.js.map +0 -1
  69. package/dist-server/service/actuation/index.js.map +0 -1
  70. package/dist-server/service/actuation/retry-plan.js.map +0 -1
  71. package/dist-server/service/actuation/rule-evaluate.js.map +0 -1
  72. package/dist-server/service/actuation/rule-loop.js.map +0 -1
  73. package/dist-server/service/actuation/rule-runner.js.map +0 -1
  74. package/dist-server/service/actuation/rule-wiring.js.map +0 -1
  75. package/dist-server/service/actuation/settle-effects.js.map +0 -1
  76. package/dist-server/service/actuation/sweep-dispatch.js.map +0 -1
  77. package/dist-server/service/actuation/twin-command-resolver.js.map +0 -1
  78. package/dist-server/service/actuation/twin-command.js.map +0 -1
  79. package/dist-server/service/actuation/twin-now.js.map +0 -1
  80. package/dist-server/service/index.js.map +0 -1
  81. package/dist-server/service/reference/actuation-routing.js.map +0 -1
  82. package/dist-server/service/reference/actuation-target.js.map +0 -1
  83. package/dist-server/service/reference/actuation-verification.js.map +0 -1
  84. package/dist-server/service/reference/attention-lane.js.map +0 -1
  85. package/dist-server/service/reference/connection-portability-resolver.js.map +0 -1
  86. package/dist-server/service/reference/connection-portability.js.map +0 -1
  87. package/dist-server/service/reference/control-routing.js.map +0 -1
  88. package/dist-server/service/reference/discovery-result.js.map +0 -1
  89. package/dist-server/service/reference/fill-cadence.js.map +0 -1
  90. package/dist-server/service/reference/fill-dedupe.js.map +0 -1
  91. package/dist-server/service/reference/fill-loop.js.map +0 -1
  92. package/dist-server/service/reference/fill-span.js.map +0 -1
  93. package/dist-server/service/reference/hook-contract.js.map +0 -1
  94. package/dist-server/service/reference/hook-rejected.js.map +0 -1
  95. package/dist-server/service/reference/hook-store.js.map +0 -1
  96. package/dist-server/service/reference/index.js.map +0 -1
  97. package/dist-server/service/reference/ingest-space.js.map +0 -1
  98. package/dist-server/service/reference/knob-defaults.js.map +0 -1
  99. package/dist-server/service/reference/live-cadence.js.map +0 -1
  100. package/dist-server/service/reference/live-feed-lease.js.map +0 -1
  101. package/dist-server/service/reference/live-ingest.js.map +0 -1
  102. package/dist-server/service/reference/reference-adapter.js.map +0 -1
  103. package/dist-server/service/reference/reference-assessment.js.map +0 -1
  104. package/dist-server/service/reference/reference-fill-run.js.map +0 -1
  105. package/dist-server/service/reference/reference-fill.js.map +0 -1
  106. package/dist-server/service/reference/reference-hook.js.map +0 -1
  107. package/dist-server/service/reference/reference-link-groups.js.map +0 -1
  108. package/dist-server/service/reference/reference-links.js.map +0 -1
  109. package/dist-server/service/reference/reference-live.js.map +0 -1
  110. package/dist-server/service/reference/reference-master.js.map +0 -1
  111. package/dist-server/service/reference/reference-probe.js.map +0 -1
  112. package/dist-server/service/reference/reference-progress-channel.js.map +0 -1
  113. package/dist-server/service/reference/reference-progress-subscription.js.map +0 -1
  114. package/dist-server/service/reference/reference-progress.js.map +0 -1
  115. package/dist-server/service/reference/reference-reconciliation-query.js.map +0 -1
  116. package/dist-server/service/reference/reference-reconciliation-writer.js.map +0 -1
  117. package/dist-server/service/reference/reference-reconciliation.js.map +0 -1
  118. package/dist-server/service/reference/reference-registry.js.map +0 -1
  119. package/dist-server/service/reference/reference-resolver.js.map +0 -1
  120. package/dist-server/service/reference/template-registry.js.map +0 -1
  121. package/dist-server/service/reference/twin-reference-reconciliation.js.map +0 -1
  122. package/dist-server/service/reference/twin-reference.js.map +0 -1
  123. package/dist-server/service/twin-attention/index.js.map +0 -1
  124. package/dist-server/service/twin-attention/twin-attention-query.js.map +0 -1
  125. package/dist-server/service/twin-audit/command-audit.js.map +0 -1
  126. package/dist-server/service/twin-audit/index.js.map +0 -1
  127. package/dist-server/service/twin-audit/twin-audit-event.js.map +0 -1
  128. package/dist-server/service/twin-audit/twin-audit-query.js.map +0 -1
  129. package/dist-server/service/twin-backfill/backfill-period-facts.js.map +0 -1
  130. package/dist-server/service/twin-backfill/backfill-shape.js.map +0 -1
  131. package/dist-server/service/twin-backfill/index.js.map +0 -1
  132. package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +0 -1
  133. package/dist-server/service/twin-control/index.js.map +0 -1
  134. package/dist-server/service/twin-control/twin-control-mutation.js.map +0 -1
  135. package/dist-server/service/twin-event/index.js.map +0 -1
  136. package/dist-server/service/twin-event/journal-count.js.map +0 -1
  137. package/dist-server/service/twin-event/twin-event-keys.js.map +0 -1
  138. package/dist-server/service/twin-event/twin-event-type.js.map +0 -1
  139. package/dist-server/service/twin-event/twin-event.js.map +0 -1
  140. package/dist-server/service/twin-forecast/forecast-metrics.js.map +0 -1
  141. package/dist-server/service/twin-forecast/forecast-tuning.js.map +0 -1
  142. package/dist-server/service/twin-forecast/gap-analytics.js.map +0 -1
  143. package/dist-server/service/twin-forecast/index.js.map +0 -1
  144. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +0 -1
  145. package/dist-server/service/twin-ingest-window/index.js.map +0 -1
  146. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +0 -1
  147. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +0 -1
  148. package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +0 -1
  149. package/dist-server/service/twin-instance/index.js.map +0 -1
  150. package/dist-server/service/twin-instance/twin-instance.js.map +0 -1
  151. package/dist-server/service/twin-journal/event-type-split-shape.js.map +0 -1
  152. package/dist-server/service/twin-journal/event-type-split.js.map +0 -1
  153. package/dist-server/service/twin-journal/index.js.map +0 -1
  154. package/dist-server/service/twin-journal/journal-order.js.map +0 -1
  155. package/dist-server/service/twin-journal/twin-journal-query.js.map +0 -1
  156. package/dist-server/service/twin-lifecycle/domain-catalog.js.map +0 -1
  157. package/dist-server/service/twin-lifecycle/index.js.map +0 -1
  158. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +0 -1
  159. package/dist-server/service/twin-metrics/index.js.map +0 -1
  160. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +0 -1
  161. package/dist-server/service/twin-model/axis-journal-evidence.js.map +0 -1
  162. package/dist-server/service/twin-model/choose-recipe.js.map +0 -1
  163. package/dist-server/service/twin-model/epcis-coverage.js.map +0 -1
  164. package/dist-server/service/twin-model/external-resolver.js.map +0 -1
  165. package/dist-server/service/twin-model/iec61850-coverage.js.map +0 -1
  166. package/dist-server/service/twin-model/index.js.map +0 -1
  167. package/dist-server/service/twin-model/isa95-coverage.js.map +0 -1
  168. package/dist-server/service/twin-model/item-ref.js.map +0 -1
  169. package/dist-server/service/twin-model/name-index.js.map +0 -1
  170. package/dist-server/service/twin-model/project-structure.js.map +0 -1
  171. package/dist-server/service/twin-model/standard-coverage.js.map +0 -1
  172. package/dist-server/service/twin-model/status-tally.js.map +0 -1
  173. package/dist-server/service/twin-model/twin-equipment.js.map +0 -1
  174. package/dist-server/service/twin-model/twin-lineage-query.js.map +0 -1
  175. package/dist-server/service/twin-model/twin-location.js.map +0 -1
  176. package/dist-server/service/twin-model/twin-model-item-query.js.map +0 -1
  177. package/dist-server/service/twin-model/twin-model-mutation.js.map +0 -1
  178. package/dist-server/service/twin-model/twin-model-query.js.map +0 -1
  179. package/dist-server/service/twin-model/twin-model-tree-query.js.map +0 -1
  180. package/dist-server/service/twin-model/twin-operation.js.map +0 -1
  181. package/dist-server/service/twin-readiness/index.js.map +0 -1
  182. package/dist-server/service/twin-readiness/twin-readiness-query.js.map +0 -1
  183. package/dist-server/service/twin-space/area-resolver.js.map +0 -1
  184. package/dist-server/service/twin-space/index.js.map +0 -1
  185. package/dist-server/service/twin-space/move-space.js.map +0 -1
  186. package/dist-server/service/twin-space/representation-role.js.map +0 -1
  187. package/dist-server/service/twin-space/space-integrity.js.map +0 -1
  188. package/dist-server/service/twin-space/twin-area.js.map +0 -1
  189. package/dist-server/service/twin-space/twin-space-area.js.map +0 -1
  190. package/dist-server/service/twin-space/twin-space-representation.js.map +0 -1
  191. package/dist-server/service/twin-space/twin-space-resolver.js.map +0 -1
  192. package/dist-server/service/twin-space/twin-space.js.map +0 -1
  193. package/dist-server/service/twin-state/index.js.map +0 -1
  194. package/dist-server/service/twin-state/twin-state-subscription.js.map +0 -1
  195. package/dist-server/service/twin-structure/index.js.map +0 -1
  196. package/dist-server/service/twin-structure/twin-structure.js.map +0 -1
  197. package/dist-server/service/twin-subject/event-subjects.js.map +0 -1
  198. package/dist-server/service/twin-subject/index.js.map +0 -1
  199. package/dist-server/service/twin-subject/item-resolver.js.map +0 -1
  200. package/dist-server/service/twin-subject/subject-query-shape.js.map +0 -1
  201. package/dist-server/service/twin-subject/twin-subject-backfill.js.map +0 -1
  202. package/dist-server/service/twin-subject/twin-subject-event.js.map +0 -1
  203. package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +0 -1
  204. package/dist-server/service/twin-subject/twin-subject-query.js.map +0 -1
  205. package/dist-server/service/twin-subject/twin-subject-writer.js.map +0 -1
  206. package/dist-server/service/twin-target/index.js.map +0 -1
  207. package/dist-server/service/twin-target/twin-target-resolver.js.map +0 -1
  208. package/dist-server/service/twin-target/twin-target.js.map +0 -1
  209. package/dist-shared/axis-read.js.map +0 -1
  210. package/dist-shared/entity-delta.js.map +0 -1
  211. package/dist-shared/kpi-broadcast.js.map +0 -1
  212. package/dist-shared/touched-items.js.map +0 -1
  213. package/dist-shared/twin-level.js.map +0 -1
  214. package/server/engine/attention-digest.ts +0 -102
  215. package/server/engine/canonical-ingest.ts +0 -511
  216. package/server/engine/checkpoint-fold.ts +0 -12
  217. package/server/engine/command-routing.ts +0 -67
  218. package/server/engine/declared-stimulus.ts +0 -66
  219. package/server/engine/energy-topology.ts +0 -189
  220. package/server/engine/event-time.ts +0 -96
  221. package/server/engine/index.ts +0 -51
  222. package/server/engine/ingest-dedupe.ts +0 -24
  223. package/server/engine/ingest-health.ts +0 -24
  224. package/server/engine/integration-coverage.ts +0 -147
  225. package/server/engine/integration-probes.ts +0 -144
  226. package/server/engine/integration-runner.ts +0 -95
  227. package/server/engine/integration-target-profile.ts +0 -103
  228. package/server/engine/kpi-baseline.ts +0 -202
  229. package/server/engine/kpi-fold.ts +0 -1516
  230. package/server/engine/kpi-query.ts +0 -1210
  231. package/server/engine/kpi-target.ts +0 -248
  232. package/server/engine/live-attentions.ts +0 -30
  233. package/server/engine/live-feed-registry.ts +0 -59
  234. package/server/engine/load-meter.ts +0 -393
  235. package/server/engine/local-declarations.ts +0 -779
  236. package/server/engine/log.ts +0 -72
  237. package/server/engine/loop-lag.ts +0 -120
  238. package/server/engine/measured-estimator.ts +0 -91
  239. package/server/engine/measured-yield.ts +0 -89
  240. package/server/engine/model-basis.ts +0 -162
  241. package/server/engine/model-gap.ts +0 -168
  242. package/server/engine/model-refusal.ts +0 -31
  243. package/server/engine/model-vocabulary.ts +0 -82
  244. package/server/engine/oee-accumulator.ts +0 -195
  245. package/server/engine/operation-basis.ts +0 -131
  246. package/server/engine/producer-guard.ts +0 -64
  247. package/server/engine/property-effects.ts +0 -216
  248. package/server/engine/read-time.ts +0 -77
  249. package/server/engine/restart-policy.ts +0 -55
  250. package/server/engine/runtime-key.ts +0 -58
  251. package/server/engine/spec-coverage.ts +0 -105
  252. package/server/engine/stage-path.ts +0 -172
  253. package/server/engine/state-axes.ts +0 -60
  254. package/server/engine/structure-diff.ts +0 -88
  255. package/server/engine/travel-estimator.ts +0 -138
  256. package/server/engine/twin-engine.ts +0 -4979
  257. package/server/engine/twin-state-channel.ts +0 -39
  258. package/server/engine/warm-start.ts +0 -265
  259. package/server/index.ts +0 -90
  260. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +0 -43
  261. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +0 -54
  262. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +0 -53
  263. package/server/migrations/1786300000000-IndexEventTypeByTime.ts +0 -48
  264. package/server/migrations/1786400000000-IndexEventCreatedAt.ts +0 -38
  265. package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +0 -93
  266. package/server/migrations/1786600000000-RenameOperatoMesAdapterType.ts +0 -66
  267. package/server/migrations/1786700000000-AddDispatchRetryColumns.ts +0 -82
  268. package/server/migrations/1786800000000-CreateTwinReferenceReconciliations.ts +0 -39
  269. package/server/migrations/index.ts +0 -25
  270. package/server/routes.ts +0 -75
  271. package/server/service/actuation/actuation-advisor.ts +0 -131
  272. package/server/service/actuation/actuation-outcome.ts +0 -97
  273. package/server/service/actuation/actuation-rule-resolver.ts +0 -147
  274. package/server/service/actuation/actuation-rule.ts +0 -139
  275. package/server/service/actuation/approval-gateway.ts +0 -44
  276. package/server/service/actuation/catch-up-effects.ts +0 -152
  277. package/server/service/actuation/command-dispatcher.ts +0 -474
  278. package/server/service/actuation/command-store.ts +0 -236
  279. package/server/service/actuation/dispatch-after-approval.ts +0 -71
  280. package/server/service/actuation/effect-match.ts +0 -124
  281. package/server/service/actuation/index.ts +0 -27
  282. package/server/service/actuation/retry-plan.ts +0 -145
  283. package/server/service/actuation/rule-evaluate.ts +0 -352
  284. package/server/service/actuation/rule-loop.ts +0 -160
  285. package/server/service/actuation/rule-runner.ts +0 -216
  286. package/server/service/actuation/rule-wiring.ts +0 -84
  287. package/server/service/actuation/settle-effects.ts +0 -115
  288. package/server/service/actuation/sweep-dispatch.ts +0 -179
  289. package/server/service/actuation/twin-command-resolver.ts +0 -387
  290. package/server/service/actuation/twin-command.ts +0 -232
  291. package/server/service/actuation/twin-now.ts +0 -97
  292. package/server/service/index.ts +0 -100
  293. package/server/service/reference/actuation-routing.ts +0 -192
  294. package/server/service/reference/actuation-target.ts +0 -163
  295. package/server/service/reference/actuation-verification.ts +0 -85
  296. package/server/service/reference/attention-lane.ts +0 -21
  297. package/server/service/reference/connection-portability-resolver.ts +0 -171
  298. package/server/service/reference/connection-portability.ts +0 -329
  299. package/server/service/reference/control-routing.ts +0 -62
  300. package/server/service/reference/discovery-result.ts +0 -95
  301. package/server/service/reference/fill-cadence.ts +0 -29
  302. package/server/service/reference/fill-dedupe.ts +0 -66
  303. package/server/service/reference/fill-loop.ts +0 -153
  304. package/server/service/reference/fill-span.ts +0 -31
  305. package/server/service/reference/hook-contract.ts +0 -364
  306. package/server/service/reference/hook-rejected.ts +0 -53
  307. package/server/service/reference/hook-store.ts +0 -28
  308. package/server/service/reference/index.ts +0 -18
  309. package/server/service/reference/ingest-space.ts +0 -104
  310. package/server/service/reference/knob-defaults.ts +0 -59
  311. package/server/service/reference/live-cadence.ts +0 -129
  312. package/server/service/reference/live-feed-lease.ts +0 -56
  313. package/server/service/reference/live-ingest.ts +0 -94
  314. package/server/service/reference/reference-adapter.ts +0 -985
  315. package/server/service/reference/reference-assessment.ts +0 -250
  316. package/server/service/reference/reference-fill-run.ts +0 -179
  317. package/server/service/reference/reference-fill.ts +0 -223
  318. package/server/service/reference/reference-hook.ts +0 -479
  319. package/server/service/reference/reference-link-groups.ts +0 -21
  320. package/server/service/reference/reference-links.ts +0 -77
  321. package/server/service/reference/reference-live.ts +0 -420
  322. package/server/service/reference/reference-master.ts +0 -999
  323. package/server/service/reference/reference-probe.ts +0 -264
  324. package/server/service/reference/reference-progress-channel.ts +0 -26
  325. package/server/service/reference/reference-progress-subscription.ts +0 -78
  326. package/server/service/reference/reference-progress.ts +0 -95
  327. package/server/service/reference/reference-reconciliation-query.ts +0 -28
  328. package/server/service/reference/reference-reconciliation-writer.ts +0 -38
  329. package/server/service/reference/reference-reconciliation.ts +0 -55
  330. package/server/service/reference/reference-registry.ts +0 -17
  331. package/server/service/reference/reference-resolver.ts +0 -969
  332. package/server/service/reference/template-registry.ts +0 -85
  333. package/server/service/reference/twin-reference-reconciliation.ts +0 -27
  334. package/server/service/reference/twin-reference.ts +0 -190
  335. package/server/service/twin-attention/index.ts +0 -3
  336. package/server/service/twin-attention/twin-attention-query.ts +0 -70
  337. package/server/service/twin-audit/command-audit.ts +0 -114
  338. package/server/service/twin-audit/index.ts +0 -5
  339. package/server/service/twin-audit/twin-audit-event.ts +0 -158
  340. package/server/service/twin-audit/twin-audit-query.ts +0 -72
  341. package/server/service/twin-backfill/backfill-period-facts.ts +0 -168
  342. package/server/service/twin-backfill/backfill-shape.ts +0 -86
  343. package/server/service/twin-backfill/index.ts +0 -3
  344. package/server/service/twin-backfill/twin-backfill-resolver.ts +0 -35
  345. package/server/service/twin-control/index.ts +0 -3
  346. package/server/service/twin-control/twin-control-mutation.ts +0 -150
  347. package/server/service/twin-event/index.ts +0 -3
  348. package/server/service/twin-event/journal-count.ts +0 -53
  349. package/server/service/twin-event/twin-event-keys.ts +0 -215
  350. package/server/service/twin-event/twin-event-type.ts +0 -68
  351. package/server/service/twin-event/twin-event.ts +0 -250
  352. package/server/service/twin-forecast/forecast-metrics.ts +0 -106
  353. package/server/service/twin-forecast/forecast-tuning.ts +0 -40
  354. package/server/service/twin-forecast/gap-analytics.ts +0 -139
  355. package/server/service/twin-forecast/index.ts +0 -3
  356. package/server/service/twin-forecast/twin-forecast-query.ts +0 -486
  357. package/server/service/twin-ingest-window/index.ts +0 -7
  358. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +0 -125
  359. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +0 -57
  360. package/server/service/twin-ingest-window/twin-ingest-window.ts +0 -113
  361. package/server/service/twin-instance/index.ts +0 -3
  362. package/server/service/twin-instance/twin-instance.ts +0 -199
  363. package/server/service/twin-journal/event-type-split-shape.ts +0 -112
  364. package/server/service/twin-journal/event-type-split.ts +0 -144
  365. package/server/service/twin-journal/index.ts +0 -3
  366. package/server/service/twin-journal/journal-order.ts +0 -20
  367. package/server/service/twin-journal/twin-journal-query.ts +0 -615
  368. package/server/service/twin-lifecycle/domain-catalog.ts +0 -51
  369. package/server/service/twin-lifecycle/index.ts +0 -3
  370. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +0 -285
  371. package/server/service/twin-metrics/index.ts +0 -3
  372. package/server/service/twin-metrics/twin-metrics-query.ts +0 -172
  373. package/server/service/twin-model/axis-journal-evidence.ts +0 -57
  374. package/server/service/twin-model/choose-recipe.ts +0 -31
  375. package/server/service/twin-model/epcis-coverage.ts +0 -251
  376. package/server/service/twin-model/external-resolver.ts +0 -173
  377. package/server/service/twin-model/iec61850-coverage.ts +0 -111
  378. package/server/service/twin-model/index.ts +0 -25
  379. package/server/service/twin-model/isa95-coverage.ts +0 -320
  380. package/server/service/twin-model/item-ref.ts +0 -80
  381. package/server/service/twin-model/name-index.ts +0 -94
  382. package/server/service/twin-model/project-structure.ts +0 -213
  383. package/server/service/twin-model/standard-coverage.ts +0 -35
  384. package/server/service/twin-model/status-tally.ts +0 -37
  385. package/server/service/twin-model/twin-equipment.ts +0 -191
  386. package/server/service/twin-model/twin-lineage-query.ts +0 -327
  387. package/server/service/twin-model/twin-location.ts +0 -147
  388. package/server/service/twin-model/twin-model-item-query.ts +0 -1029
  389. package/server/service/twin-model/twin-model-mutation.ts +0 -503
  390. package/server/service/twin-model/twin-model-query.ts +0 -792
  391. package/server/service/twin-model/twin-model-tree-query.ts +0 -211
  392. package/server/service/twin-model/twin-operation.ts +0 -157
  393. package/server/service/twin-readiness/index.ts +0 -3
  394. package/server/service/twin-readiness/twin-readiness-query.ts +0 -96
  395. package/server/service/twin-space/area-resolver.ts +0 -75
  396. package/server/service/twin-space/index.ts +0 -8
  397. package/server/service/twin-space/move-space.ts +0 -262
  398. package/server/service/twin-space/representation-role.ts +0 -15
  399. package/server/service/twin-space/space-integrity.ts +0 -293
  400. package/server/service/twin-space/twin-area.ts +0 -63
  401. package/server/service/twin-space/twin-space-area.ts +0 -66
  402. package/server/service/twin-space/twin-space-representation.ts +0 -99
  403. package/server/service/twin-space/twin-space-resolver.ts +0 -582
  404. package/server/service/twin-space/twin-space.ts +0 -148
  405. package/server/service/twin-state/index.ts +0 -3
  406. package/server/service/twin-state/twin-state-subscription.ts +0 -62
  407. package/server/service/twin-structure/index.ts +0 -3
  408. package/server/service/twin-structure/twin-structure.ts +0 -96
  409. package/server/service/twin-subject/event-subjects.ts +0 -162
  410. package/server/service/twin-subject/index.ts +0 -10
  411. package/server/service/twin-subject/item-resolver.ts +0 -92
  412. package/server/service/twin-subject/subject-query-shape.ts +0 -84
  413. package/server/service/twin-subject/twin-subject-backfill.ts +0 -196
  414. package/server/service/twin-subject/twin-subject-event.ts +0 -101
  415. package/server/service/twin-subject/twin-subject-fast-path.ts +0 -346
  416. package/server/service/twin-subject/twin-subject-query.ts +0 -210
  417. package/server/service/twin-subject/twin-subject-writer.ts +0 -189
  418. package/server/service/twin-target/index.ts +0 -5
  419. package/server/service/twin-target/twin-target-resolver.ts +0 -142
  420. package/server/service/twin-target/twin-target.ts +0 -97
  421. package/server/types/global.d.ts +0 -22
  422. package/shared/axis-read.ts +0 -28
  423. package/shared/entity-delta.ts +0 -335
  424. package/shared/kpi-broadcast.ts +0 -13
  425. package/shared/touched-items.ts +0 -73
  426. package/shared/twin-level.ts +0 -48
  427. package/test/ack-shape-alignment.test.ts +0 -97
  428. package/test/actuation-advisor.test.ts +0 -55
  429. package/test/actuation-approval-door.test.ts +0 -411
  430. package/test/actuation-dispatch.test.ts +0 -209
  431. package/test/actuation-effect.test.ts +0 -162
  432. package/test/actuation-outcome.test.ts +0 -152
  433. package/test/actuation-rule-runner.test.ts +0 -281
  434. package/test/actuation-rule.test.ts +0 -266
  435. package/test/actuation-seam.test.ts +0 -255
  436. package/test/actuation-settle.test.ts +0 -164
  437. package/test/actuation-target.test.ts +0 -145
  438. package/test/actuation-verification-state.test.ts +0 -139
  439. package/test/adopt-structure-live.test.ts +0 -133
  440. package/test/aggregate-not-findone.test.ts +0 -112
  441. package/test/area-resolver-db.test.ts +0 -110
  442. package/test/attention-digest.test.ts +0 -161
  443. package/test/axis-declaration-passthrough-db.test.ts +0 -152
  444. package/test/axis-journal-evidence.test.ts +0 -71
  445. package/test/axis-read.test.ts +0 -188
  446. package/test/backfill-shape.test.ts +0 -89
  447. package/test/boot-resume.test.ts +0 -287
  448. package/test/broadcast-cost-baseline.test.ts +0 -201
  449. package/test/broadcast-period.test.ts +0 -58
  450. package/test/broadcast-queue-overflow.test.ts +0 -244
  451. package/test/canonical-ingest-vocabularies.test.ts +0 -451
  452. package/test/canonical-quantity-door.test.ts +0 -61
  453. package/test/capability-mapping.test.ts +0 -89
  454. package/test/checkpoint-refuses-empty.test.ts +0 -103
  455. package/test/choose-recipe.test.ts +0 -117
  456. package/test/column-type-portability.test.ts +0 -122
  457. package/test/command-routing.test.ts +0 -61
  458. package/test/command-store-writes-columns.test.ts +0 -126
  459. package/test/connection-portability-doors.test.ts +0 -87
  460. package/test/connector-capability-declaration.test.ts +0 -195
  461. package/test/contract-layer-guard.test.ts +0 -48
  462. package/test/control-capability.test.ts +0 -98
  463. package/test/cursor-stall-not-read-failure.test.ts +0 -126
  464. package/test/declaration-reaches-model.test.ts +0 -226
  465. package/test/declared-location-types.test.ts +0 -89
  466. package/test/declared-stimulus.test.ts +0 -88
  467. package/test/dedupe-transformation.test.ts +0 -112
  468. package/test/discovery-result.test.ts +0 -75
  469. package/test/dispatch-after-approval-source.test.ts +0 -81
  470. package/test/duration-estimators.test.ts +0 -144
  471. package/test/energy-topology.test.ts +0 -113
  472. package/test/entity-delta.test.ts +0 -352
  473. package/test/equipment-identity-reaches.test.ts +0 -57
  474. package/test/event-subjects.test.ts +0 -148
  475. package/test/event-time-column.test.ts +0 -67
  476. package/test/event-type-split.test.ts +0 -178
  477. package/test/external-resolver.test.ts +0 -177
  478. package/test/fact-scope-wiring.test.ts +0 -96
  479. package/test/fill-dedupe.test.ts +0 -59
  480. package/test/fill-loop.test.ts +0 -35
  481. package/test/first-envelope.test.ts +0 -153
  482. package/test/fold-gap-speaks.test.ts +0 -53
  483. package/test/fold-shift-dst.test.ts +0 -110
  484. package/test/forecast-metrics.test.ts +0 -157
  485. package/test/forecast-tuning.test.ts +0 -42
  486. package/test/gap-analytics.test.ts +0 -113
  487. package/test/generated-not-counted.test.ts +0 -99
  488. package/test/guard-roots.ts +0 -76
  489. package/test/home-in-area.test.ts +0 -90
  490. package/test/hook-address.test.ts +0 -28
  491. package/test/hook-rejected-shape.test.ts +0 -86
  492. package/test/hook-response-sum.test.ts +0 -193
  493. package/test/hook-sequence.test.ts +0 -333
  494. package/test/ingest-bench.test.ts +0 -70
  495. package/test/ingest-dedupe.test.ts +0 -56
  496. package/test/ingest-expected-quiet.test.ts +0 -111
  497. package/test/ingest-health-engine.test.ts +0 -248
  498. package/test/ingest-health-wiring.test.ts +0 -120
  499. package/test/ingest-health.test.ts +0 -306
  500. package/test/ingest-history.test.ts +0 -247
  501. package/test/ingest-idempotent.test.ts +0 -211
  502. package/test/ingest-reconcile-four-ways.test.ts +0 -97
  503. package/test/ingest-rules-header.test.ts +0 -100
  504. package/test/ingest-running-guard.test.ts +0 -136
  505. package/test/ingest-space.test.ts +0 -78
  506. package/test/ingest-window-rolls-unobserved.test.ts +0 -72
  507. package/test/ingest-wiring-guard.test.ts +0 -197
  508. package/test/instance-cache-lifecycle.test.ts +0 -110
  509. package/test/intake-mapping-parity.test.ts +0 -175
  510. package/test/integration-probes.test.ts +0 -103
  511. package/test/integration-runner.test.ts +0 -95
  512. package/test/item-ref.test.ts +0 -78
  513. package/test/journal-order-axis.test.ts +0 -80
  514. package/test/journal-read-discipline.test.ts +0 -186
  515. package/test/journal-retention.test.ts +0 -275
  516. package/test/journal-sort-axis.test.ts +0 -142
  517. package/test/journal-write-door.test.ts +0 -110
  518. package/test/journal-write-trend.test.ts +0 -115
  519. package/test/kernel-kind-guard.test.ts +0 -98
  520. package/test/knob-defaults.test.ts +0 -72
  521. package/test/kpi-baseline-db.test.ts +0 -216
  522. package/test/kpi-baseline.test.ts +0 -196
  523. package/test/kpi-fold.test.ts +0 -1456
  524. package/test/kpi-query-bench.test.ts +0 -130
  525. package/test/lens-own-places.test.ts +0 -50
  526. package/test/lineage-survives-restart.test.ts +0 -234
  527. package/test/live-cadence.test.ts +0 -170
  528. package/test/live-cursor-wiring.test.ts +0 -87
  529. package/test/live-feed-lease.test.ts +0 -63
  530. package/test/live-feed-registry.test.ts +0 -75
  531. package/test/live-kernel-facts.test.ts +0 -161
  532. package/test/live-mirror-parity.test.ts +0 -286
  533. package/test/load-meter.test.ts +0 -328
  534. package/test/local-declarations.test.ts +0 -662
  535. package/test/log-stamp.test.ts +0 -59
  536. package/test/loop-lag.test.ts +0 -82
  537. package/test/master-to-twin.test.ts +0 -279
  538. package/test/measured-yield.test.ts +0 -90
  539. package/test/merge-streams.test.ts +0 -70
  540. package/test/mirror-resumes-from-checkpoint.test.ts +0 -192
  541. package/test/model-basis.test.ts +0 -141
  542. package/test/model-gap.test.ts +0 -153
  543. package/test/model-refusal.test.ts +0 -75
  544. package/test/model-vocabulary.test.ts +0 -114
  545. package/test/move-space.test.ts +0 -149
  546. package/test/mutation-gate.test.ts +0 -122
  547. package/test/oee-accumulator.test.ts +0 -204
  548. package/test/oee-site-calendar.test.ts +0 -146
  549. package/test/open-doors.test.ts +0 -197
  550. package/test/operation-basis.test.ts +0 -127
  551. package/test/operational-vocabulary.test.ts +0 -108
  552. package/test/operations-capability-db.test.ts +0 -165
  553. package/test/order-and-transaction-split.test.ts +0 -107
  554. package/test/order-fold-host.test.ts +0 -112
  555. package/test/producer-guard.test.ts +0 -66
  556. package/test/project-structure-db.test.ts +0 -226
  557. package/test/projection-reaches-screen.test.ts +0 -277
  558. package/test/projection-reads-declared.test.ts +0 -109
  559. package/test/property-effects.test.ts +0 -128
  560. package/test/provenance-promotion.test.ts +0 -99
  561. package/test/read-failure-ledger.test.ts +0 -106
  562. package/test/read-failure-visible.test.ts +0 -145
  563. package/test/read-time.test.ts +0 -118
  564. package/test/reference-fill.test.ts +0 -235
  565. package/test/reference-grounding.test.ts +0 -70
  566. package/test/reference-hook.test.ts +0 -177
  567. package/test/reference-links.test.ts +0 -56
  568. package/test/reference-reconciliation.test.ts +0 -39
  569. package/test/registry-key-guard.test.ts +0 -80
  570. package/test/rejection-carries-event-id.test.ts +0 -101
  571. package/test/rename-twin.test.ts +0 -89
  572. package/test/resolve-ts-siblings.mjs +0 -52
  573. package/test/restart-policy.test.ts +0 -111
  574. package/test/resync-origin-site.test.ts +0 -83
  575. package/test/retry-plan.test.ts +0 -139
  576. package/test/revision-axis.test.ts +0 -110
  577. package/test/runtime-key.test.ts +0 -66
  578. package/test/save-intake-collision.test.ts +0 -91
  579. package/test/scale-twin-bench.test.ts +0 -103
  580. package/test/secrets-at-rest.test.ts +0 -135
  581. package/test/snapshot-freshness.test.ts +0 -60
  582. package/test/source-outcome-audit.test.ts +0 -104
  583. package/test/space-integrity.test.ts +0 -223
  584. package/test/space-representation.test.ts +0 -66
  585. package/test/space-scope-includes-stopped.test.ts +0 -55
  586. package/test/spec-coverage.test.ts +0 -113
  587. package/test/stage-path.test.ts +0 -95
  588. package/test/standard-coverage.test.ts +0 -148
  589. package/test/state-axes.test.ts +0 -74
  590. package/test/status-tally.test.ts +0 -55
  591. package/test/streamline-e2e.test.ts +0 -76
  592. package/test/structure-revision-db.test.ts +0 -315
  593. package/test/subject-fast-path.test.ts +0 -148
  594. package/test/subject-read-model.test.ts +0 -175
  595. package/test/sweep-dispatch.test.ts +0 -191
  596. package/test/tenant-registry-db.test.ts +0 -149
  597. package/test/time-range.test.ts +0 -152
  598. package/test/touched-items.test.ts +0 -61
  599. package/test/twin-audit.test.ts +0 -83
  600. package/test/twin-event-keys.test.ts +0 -193
  601. package/test/twin-model-item-db.test.ts +0 -319
  602. package/test/twin-model-parent-relation-db.test.ts +0 -125
  603. package/test/twin-model-tree-db.test.ts +0 -209
  604. package/test/twin-origin-resync.test.ts +0 -133
  605. package/test/twin-state-channel.test.ts +0 -111
  606. package/test/vocabulary-guard.test.ts +0 -43
  607. package/test/warm-start-seam.test.ts +0 -162
  608. package/test/warm-start.test.ts +0 -297
  609. package/test/withheld-door.test.ts +0 -95
  610. package/test/yield-loop.test.ts +0 -199
  611. package/tsconfig.json +0 -15
  612. package/tsconfig.shared.json +0 -23
  613. package/tsconfig.shared.tsbuildinfo +0 -1
  614. package/tsconfig.tsbuildinfo +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"discovery-result.js","sourceRoot":"","sources":["../../../server/service/reference/discovery-result.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;;;AAsDH,gCAEC;AAGD,wBAEC;AAGD,4BAKC;AAlDD,2CAA2C;AACpC,MAAM,iBAAiB,GAAG,CAAC,MAAc,EAAkB,EAAE,CAAC,CAAC;IACpE,IAAI,EAAE,qBAAqB;IAC3B,MAAM,EAAE,EAAE,MAAM,EAAE;IAClB,OAAO,EAAE,cAAc,MAAM,oCAAoC;CAClE,CAAC,CAAA;AAJW,QAAA,iBAAiB,qBAI5B;AAEF,+CAA+C;AACxC,MAAM,cAAc,GAAG,CAAC,WAAmB,EAAE,MAAc,EAAkB,EAAE,CAAC,CAAC;IACtF,IAAI,EAAE,iBAAiB;IACvB,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE;IAC/B,OAAO,EAAE,yBAAyB,WAAW,iCAAiC,MAAM,GAAG;CACxF,CAAC,CAAA;AAJW,QAAA,cAAc,kBAIzB;AAEF,0CAA0C;AACnC,MAAM,eAAe,GAAG,CAAC,MAAc,EAAE,MAAc,EAAkB,EAAE,CAAC,CAAC;IAClF,IAAI,EAAE,kBAAkB;IACxB,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;IAC1B,OAAO,EAAE,8BAA8B,MAAM,MAAM,MAAM,EAAE;CAC5D,CAAC,CAAA;AAJW,QAAA,eAAe,mBAI1B;AAEF,wCAAwC;AACjC,MAAM,iBAAiB,GAAG,CAAC,MAAc,EAAE,OAAe,EAAkB,EAAE,CAAC,CAAC;IACrF,IAAI,EAAE,mBAAmB;IACzB,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;IAC3B,OAAO,EAAE,IAAI,MAAM,2BAA2B,OAAO,GAAG;CACzD,CAAC,CAAA;AAJW,QAAA,iBAAiB,qBAI5B;AAEF;;;;;;GAMG;AACH,SAAgB,UAAU,CAAI,KAAsC;IAClE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;AAC1D,CAAC;AAED,yDAAyD;AACzD,SAAgB,MAAM,CAAI,KAAqB;IAC7C,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAA;AAC7B,CAAC;AAED,oDAAoD;AACpD,SAAgB,QAAQ,CAAC,CAAU;IACjC,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,OAAO;QAAE,OAAO,CAAC,CAAC,OAAO,CAAA;IACrD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC;QAAE,OAAO,CAAC,CAAA;IACxC,yDAAyD;IACzD,OAAO,wCAAwC,CAAA;AACjD,CAAC","sourcesContent":["/*\n * 사이트 발견 결과 — **\"못 찾았다\" 와 \"없다\" 를 구분한다.** 순수.\n *\n * ── 왜 있나 (2026-08-06) ────────────────────────────────────────────────────\n * 실 시스템을 연결해 \"거기 있는 것\" 으로 트윈을 만드는 경로에서, 발견이 실패하면 화면이\n * **\"사이트를 찾지 못했습니다\"** 라고만 말했다. 네 가지가 그 한 문장에 섞여 있었다.\n *\n * ① 그 레퍼런스가 등록돼 있지 않다 (`!ref → []`)\n * ② 어댑터 종류를 모른다 (`!adapter → []`)\n * ③ 연결·조회가 실패했다 (`catch { return [] }`)\n * ④ 정말로 사이트가 없다 (빈 배열)\n *\n * ①②③ 은 **고칠 수 있는 문제**이고 ④ 는 사실이다. 그런데 화면이 같은 말을 하므로 사용자는 자격이\n * 틀렸는지, 주소가 틀렸는지, 정말 공장이 없는지 알 수 없다 — 실 시스템 연결에서 가장 자주 막히는\n * 자리인데 거기서 아무 단서도 주지 않았다.\n *\n * ── 왜 문장이 아니라 코드인가 ───────────────────────────────────────────────\n * 이 프로젝트가 이미 쓰는 규약을 따른다(주의 신호·커맨드 거절·인제스트 경고와 같은 모양):\n * `code`(언어 중립) + `params`(문장에 끼울 값) + `message`(영어 canonical, 로그·폴백).\n * 화면은 `twin.connect.discover.<code>` 로 사람 말을 만든다.\n *\n * ── 원 시스템의 오류 문장을 감추지 않는다 ────────────────────────────────────\n * 어댑터가 준 사유(자격 거부·타임아웃·404)는 **그 시스템만 아는 사실**이라 우리가 다시 쓸 수 없다.\n * `detail` 로 그대로 실어 보내 화면이 보조 문장으로 보인다 — 원문이 있으면 사용자가 자기 관리자에게\n * 물어볼 수 있고, 없으면 우리 짐작만 남는다.\n */\n\n/** 발견이 왜 아무것도 주지 못했나 — 언어 중립 코드. */\nexport type DiscoveryErrorCode = 'reference-not-found' | 'adapter-unknown' | 'discovery-failed' | 'discovery-timeout'\n\nexport interface DiscoveryError {\n code: DiscoveryErrorCode\n params?: Record<string, string | number>\n /** 영어 canonical — 로그에 남고, 번역이 없으면 그대로 보인다. */\n message: string\n}\n\nexport interface DiscoveryResult<S = unknown> {\n /** 발견된 사이트들. 실패했으면 빈 배열이고 `error` 가 이유를 말한다. */\n sites: S[]\n /** 실패 사유. **없으면 성공이다** — 빈 배열 + 사유 없음 = \"연결은 됐지만 사이트가 없다\". */\n error?: DiscoveryError\n}\n\n/** 등록된 레퍼런스가 없다 — 목록이 낡았거나 다른 테넌트의 것이다. */\nexport const referenceNotFound = (source: string): DiscoveryError => ({\n code: 'reference-not-found',\n params: { source },\n message: `reference \"${source}\" is not registered in this tenant`\n})\n\n/** 어댑터 종류를 모른다 — 배포에 그 커넥터가 없다(패키지 미설치·오타). */\nexport const adapterUnknown = (adapterType: string, source: string): DiscoveryError => ({\n code: 'adapter-unknown',\n params: { adapterType, source },\n message: `no connector of type \"${adapterType}\" is available for reference \"${source}\"`\n})\n\n/** 연결·조회가 실패했다 — 원 시스템이 준 사유를 그대로 싣는다. */\nexport const discoveryFailed = (source: string, detail: string): DiscoveryError => ({\n code: 'discovery-failed',\n params: { source, detail },\n message: `could not read sites from \"${source}\": ${detail}`\n})\n\n/** 기다려도 답이 없었다 — 무한히 도는 화면보다 사유가 낫다. */\nexport const discoveryTimedOut = (source: string, seconds: number): DiscoveryError => ({\n code: 'discovery-timeout',\n params: { source, seconds },\n message: `\"${source}\" did not answer within ${seconds}s`\n})\n\n/**\n * 어댑터가 준 것을 결과로 처리한다.\n *\n * **사이트가 없는 것을 실패로 만들지 않는다** — 연결이 되고 조회가 성공했는데 공장이 하나도 없는\n * 시스템은 있을 수 있다(권한이 좁거나 아직 등록되지 않은 경우). 그것은 사실이고, 화면은 \"없다\" 고\n * 말해야 한다. 실패로 합치면 사용자가 있지도 않은 문제를 찾는다.\n */\nexport function discovered<S>(sites: readonly S[] | undefined | null): DiscoveryResult<S> {\n return { sites: Array.isArray(sites) ? [...sites] : [] }\n}\n\n/** 실패를 결과로 처리한다 — 사이트는 비어 있다(반쪽 목록을 성공처럼 보이게 하지 않는다). */\nexport function failed<S>(error: DiscoveryError): DiscoveryResult<S> {\n return { sites: [], error }\n}\n\n/** 어댑터가 오류를 낸 것에서 사람이 읽을 사유를 뽑는다 — 형태를 가정하지 않는다. */\nexport function detailOf(e: unknown): string {\n if (e instanceof Error && e.message) return e.message\n if (typeof e === 'string' && e) return e\n /* 무엇이 왔는지 모르면 그렇게 말한다 — 빈 문장으로 \"이유 없음\" 처럼 보이게 하지 않는다. */\n return 'the connector failed without a message'\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"fill-cadence.js","sourceRoot":"","sources":["../../../server/service/reference/fill-cadence.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAEH;;;;;;;;GAQG;AACU,QAAA,cAAc,GAAG,EAAE,GAAG,MAAM,CAAA;AAEzC;;;;;GAKG;AACU,QAAA,gBAAgB,GAAG,MAAM,CAAA","sourcesContent":["/*\n * 빠진 구간을 **언제** 다시 받나 — 간격만 여기 둔다.\n *\n * ── 왜 따로인가 ─────────────────────────────────────────────────────────────\n * `fill-loop.ts` 는 엔진과 TypeORM entity 를 물어서 테스트 프로세스가 적재할 수 없다. 값만 따로 두면\n * 테스트가 가벼운 import 로 확인한다.\n *\n * 오늘 같은 자리를 세 번 밟았다(`reference-links` · `fill-dedupe` · 여기). 이 저장소에서 **판정과\n * 값은 배선과 나누는 것이 규율**이다 — 나누면 테스트가 붙고, 안 나누면 안 붙는다.\n */\n\n/**\n * 그물의 간격 — **10분. 30초가 아니다.**\n *\n * 인티그레이션 레인이 측정으로 준 권고다. backfill 은 원본에 조회를 걸고 저널을 읽는다. 30초마다\n * 돌면 얻는 것 없이 두 쪽을 계속 친다 — 놓친 구간은 10분 뒤에 받아도 사실이 달라지지 않는다.\n *\n * 규칙 주기(`RULE_PERIOD_MS`, 30초)에 얹지 않는 이유이기도 하다. 얹으면 간격이 그쪽에 묶이고,\n * 나중에 규칙 주기를 바꾸면 이쪽이 소리 없이 따라간다.\n */\nexport const FILL_PERIOD_MS = 10 * 60_000\n\n/**\n * 같은 단위를 이 간격 안에 두 번 부르지 않는다.\n *\n * 구멍을 본 자리에서 부르는 길이 뜨거운 길이다. 밀어 받는 쪽이 구멍 뒤의 봉투를 계속 보내면 그때마다\n * 구멍 판정이 나고, 막지 않으면 **봉투마다 backfill 이 돈다.**\n */\nexport const FILL_DEBOUNCE_MS = 60_000\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"fill-dedupe.js","sourceRoot":"","sources":["../../../server/service/reference/fill-dedupe.ts"],"names":[],"mappings":";;;AAgDA,8CAiBC;AAjED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qCAAiC;AAEjC,iDAAqD;AAErD,+DAAuD;AACvD,oEAA4D;AAE5D,qDAAqD;AACrD,+CAAuC;AAA9B,sGAAA,MAAM,OAAA;AAEf,mDAAmD;AACnD,MAAM,QAAQ,GAAG,MAAM,CAAA;AAEvB;;;;;GAKG;AACI,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,UAAkB,EAClB,IAA8B;IAE9B,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,yBAAS,CAAC,CAAC,IAAI,CAAC;QAC/C,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAS,EAAE,UAAU,EAAE,SAAS,EAAE,IAAA,iBAAO,EAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAQ,EAAE;QACrG,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAS;QAC1C,IAAI,EAAE,QAAQ;KACf,CAAC,CAAA;IAEF,MAAM,UAAU,GAAa,EAAE,CAAA;IAC/B,KAAK,MAAM,CAAC,IAAI,IAAa,EAAE,CAAC;QAC9B,MAAM,EAAE,GAAG,IAAA,+BAAY,EAAC,CAAC,EAAE,OAAO,CAAC,CAAA;QACnC,IAAI,EAAE;YAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC7B,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAA;AACjF,CAAC","sourcesContent":["/*\n * **backfill 이 이미 있는 사실을 다시 앉히지 않게 한다** — 저널에서 정체를 읽어 기억을 채운다.\n *\n * ── 무엇이 있었나 (2026-09-06 측정) ─────────────────────────────────────────\n * 중복 판정 기억(`FactDeduper`)은 **프로세스 안에만 있다.** 재기동하면 비워지고, 그 뒤 backfill 을\n * 돌리면 이미 저널에 있는 사실도 「처음 본 것」이 된다.\n *\n * ```\n * mes-line-a 를 재기동 뒤 backfill\n * 저널 44 → 83건 · 서로 다른 것 39가지 · 중복 44가지\n * 같은 프로세스에서 다시 돌리면 applied 0 (기억이 살아 있어서)\n * ```\n *\n * push 경로에서는 안 보인다. 재전송이 몇 분 안에 오므로 창(20,000) 안에서 걸린다.\n * **backfill 이 그 전제를 깬다** — 몇 달 전 사실을 지금 받으니 재기동 한 번이면 그 구간이 통째로\n * 다시 앉는다.\n *\n * ── 왜 여기서만 하나 ────────────────────────────────────────────────────────\n * push 경로는 뜨거운 길이다. 봉투마다 저널을 조회하면 그 길이 느려지고, 거기서는 재전송이 창 안에\n * 들어와 이미 걸린다.\n *\n * backfill 은 자주 돌지 않고 한 번에 많이 받는다. 그래서 **한 번의 조회로 그 구간을 통째로** 읽어\n * 기억을 채운다 — 레코드마다 묻지 않는다.\n *\n * ── 근본 고침은 따로 있다 ───────────────────────────────────────────────────\n * 저널에 정체 칸과 unique index 를 두는 것이 근본이다. 그런데 저널이 2,484,472행이고, 개발 중에\n * 그 표에 칸을 더하면 sqlite 가 표를 통째로 다시 쓴다. postgres 첫 부팅 직전에 한 번에 하기로 적어\n * 두었다(`design/plans/postgres-column-types.md`).\n */\nimport { Between } from 'typeorm'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinEvent } from '../twin-event/twin-event.js'\nimport { factIdentity } from '../../engine/ingest-dedupe.js'\n\n/** 구간 판정은 가벼운 파일에 있다 — 부르는 쪽이 한 곳에서 가져가게 다시 내보낸다. */\nexport { spanOf } from './fill-span.js'\n\n/** 한 번에 읽을 저널 줄의 상한 — 넘으면 읽은 만큼만 채우고 그 사실을 말한다. */\nconst SCAN_MAX = 50_000\n\n/**\n * 그 구간의 저널을 읽어 **정체 목록**을 만든다.\n *\n * 저장된 payload 가 봉투 그대로라(`eventId` · `eventType` · `eventTime` · `data`) 유입 때와 같은\n * 함수로 정체를 만든다. 여기서 따로 만들면 두 곳이 다른 규칙을 갖고, 어긋나는 날 조용히 다시 앉는다.\n */\nexport async function journalIdentities(\n domainId: string,\n instanceId: string,\n span: { from: Date; to: Date }\n): Promise<{ identities: string[]; scanned: number; truncated: boolean }> {\n const rows = await getRepository(TwinEvent).find({\n where: { domain: { id: domainId } as any, instanceId, eventTime: Between(span.from, span.to) as any },\n select: { id: true, payload: true } as any,\n take: SCAN_MAX\n })\n\n const identities: string[] = []\n for (const r of rows as any[]) {\n const id = factIdentity(r?.payload)\n if (id) identities.push(id)\n }\n return { identities, scanned: rows.length, truncated: rows.length >= SCAN_MAX }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"fill-loop.js","sourceRoot":"","sources":["../../../server/service/reference/fill-loop.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **빠진 구간을 언제 다시 받나** — 세 자리에서 부른다.\n *\n * ── 왜 세 자리인가 (인티그레이션 레인의 측정 권고, 2026-09-05) ──────────────\n * ```\n * 1 재기동 직후 한 번 우리가 내려가 있는 동안 그쪽이 보낸 것을 못 받았다\n * 2 구멍을 본 그 자리 번호가 비었다는 것을 아는 순간이 가장 이르다\n * 3 10분 그물 위 둘이 다 놓치는 경우 — 그쪽이 아예 안 보낸 구간\n * ```\n *\n * **30초가 아니다.** 그 권고에 근거가 있었다 — backfill 은 원본에 조회를 걸고 저널을 읽는다.\n * 30초마다 돌면 얻는 것 없이 두 쪽을 계속 친다. 놓친 구간은 10분 뒤에 받아도 사실이 달라지지 않는다.\n *\n * 규칙 주기(`rule-loop.ts`, 30초)에 얹지 않는 이유가 그것이다. 얹으면 간격이 그쪽에 묶이고, 나중에\n * 규칙 주기를 바꾸면 이쪽이 소리 없이 따라간다.\n *\n * ── 겹쳐 돌지 않는다 ────────────────────────────────────────────────────────\n * 한 바퀴가 간격보다 오래 걸리면 다음 발화가 겹친다. 겹치면 두 바퀴가 같은 구간을 받고, 그 순간\n * 중복 판정이 두 벌의 기억을 갖는다.\n *\n * ── 도는 트윈만 본다 ────────────────────────────────────────────────────────\n * 멈춘 트윈에는 사실을 넣을 커널이 없다. `runFill` 이 그것을 다시 확인하지만 여기서 미리 걸러\n * 헛조회를 안 만든다.\n */\nimport { twinError, twinLog, twinWarn } from '../../engine/log.js'\nimport { TwinEngine } from '../../engine/twin-engine.js'\n\nimport { runFill, type FillRunResult } from './reference-fill-run.js'\n\n/* 간격은 가벼운 파일에 있다 — 테스트가 붙게(§`fill-cadence.ts`). 부르는 쪽이 한 곳에서 가져가게 다시 내보낸다. */\nexport { FILL_DEBOUNCE_MS, FILL_PERIOD_MS } from './fill-cadence.js'\nimport { FILL_DEBOUNCE_MS, FILL_PERIOD_MS } from './fill-cadence.js'\n\n/** 어느 단위를 언제 마지막으로 받았나 — `domainId::instanceId::scope`. */\nconst lastRun = new Map<string, number>()\n\nlet timer: any\nlet running = false\n\nfunction recently(key: string, now: number): boolean {\n const last = lastRun.get(key)\n return last !== undefined && now - last < FILL_DEBOUNCE_MS\n}\n\n/**\n * 그 단위를 한 번 받는다 — **커서부터 이어서.**\n *\n * `from` 을 주지 않는다. 밑에서부터 다시 받는 것은 사람이 정하는 일이고(`fillTwinScope(from:)`),\n * 주기가 스스로 할 일이 아니다 — 그것을 주기로 돌리면 매번 원본의 전 구간을 훑는다.\n */\nexport async function fillOnce(domainId: string, instanceId: string, scope: string): Promise<FillRunResult | undefined> {\n const key = `${domainId}::${instanceId}::${scope}`\n const now = Date.now()\n if (recently(key, now)) return undefined\n lastRun.set(key, now)\n\n const out = await runFill({ domainId, instanceId, scope })\n /*\n * 받은 것이 있을 때만 말한다. 「받을 것이 없었다」를 10분마다 적으면 로그가 그 줄로 차고, 그러면\n * 진짜 받은 날을 못 본다.\n */\n if (out.applied) twinLog(`[twin-fill] \"${instanceId}\" · ${scope} — 빠진 구간에서 ${out.applied}건을 받았습니다`)\n return out\n}\n\n/**\n * **구멍을 본 자리에서 부른다** — 기다리지 않는다.\n *\n * 훅의 응답이 이것 때문에 늦으면 보내는 쪽이 되풀이하고, 그러면 같은 봉투가 두 번 온다. 조치를\n * 잇는 자리(`settleEffects`)와 같은 규율이다.\n */\nexport function requestFill(domainId: string, instanceId: string, scope: string): void {\n void fillOnce(domainId, instanceId, scope).catch(err =>\n twinWarn(`[twin-fill] \"${instanceId}\" · ${scope} 를 받지 못했습니다 — ${err?.message ?? err}`)\n )\n}\n\n/**\n * 도는 트윈을 한 바퀴 돈다.\n *\n * **어느 단위를 받을지는 커서가 말한다.** 커서에 적힌 흐름이 그동안 받아 온 것들이고, 그것이\n * 없으면 아직 아무것도 안 받은 연결이라 받을 구간도 없다.\n */\nexport async function fillAllOnce(): Promise<{ tried: number; applied: number }> {\n let tried = 0\n let applied = 0\n for (const { domainId, instanceId } of TwinEngine.ownedTwins()) {\n for (const scope of await scopesOf(domainId, instanceId)) {\n tried++\n const out = await fillOnce(domainId, instanceId, scope)\n applied += out?.applied ?? 0\n }\n }\n return { tried, applied }\n}\n\n/**\n * 그 연결이 받아 온 단위들 — **커서에 적힌 것.**\n *\n * 커서 열쇠는 `instanceId::scope` 다(§`pushCursorKey`). 여기서 그 규칙을 다시 적지 않고 앞부분을\n * 떼어 낸다 — 두 곳이 같은 규칙을 가지면 어긋나는 날 이 주기가 엉뚱한 단위를 받는다.\n */\nasync function scopesOf(domainId: string, instanceId: string): Promise<string[]> {\n const { getRepository } = await import('@things-factory/shell')\n const { TwinReference } = await import('./twin-reference.js')\n const ref = await getRepository(TwinReference).findOne({ where: { domain: { id: domainId }, source: instanceId } })\n const streams = (ref as any)?.liveCursor?.streams\n if (!streams || typeof streams !== 'object') return []\n\n const prefix = `${instanceId}::`\n return Object.keys(streams)\n .filter(k => k.startsWith(prefix))\n .map(k => k.slice(prefix.length))\n .filter(s => s)\n}\n\n/**\n * 주기를 세운다 — 두 번 불러도 하나만 선다.\n *\n * **기동에서 한 번 먼저 돈다**(§1). 우리가 내려가 있는 동안 그쪽이 보낸 것을 그때 받는다. 첫 바퀴를\n * 10분 뒤로 미루면 그 10분 동안 화면이 빈 구간을 그대로 보인다.\n */\nexport function startFillLoop(periodMs = FILL_PERIOD_MS): void {\n if (timer) return\n\n const turn = () => {\n if (running) return\n running = true\n fillAllOnce()\n .then(({ tried, applied }) => {\n if (applied) twinLog(`[twin-fill] 한 바퀴 — ${tried}개 단위에서 ${applied}건을 받았습니다`)\n })\n .catch(err => twinError(`[twin-fill] 주기가 실패했습니다 — ${err?.message ?? err}`))\n .finally(() => {\n running = false\n })\n }\n\n timer = setInterval(turn, periodMs)\n /*\n * 기동 직후 한 번. 조금 늦춰서 돈다 — 커널이 되살아나기 전에 부르면 도는 트윈이 0개라 아무것도\n * 안 받고, 그러면 그 한 번이 없는 것과 같다.\n */\n setTimeout(turn, 15_000).unref?.()\n}\n\n/** 주기를 내린다 — 테스트가 재기동을 넣을 수 있게. */\nexport function stopFillLoop(): void {\n if (timer) clearInterval(timer)\n timer = undefined\n running = false\n lastRun.clear()\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"fill-span.js","sourceRoot":"","sources":["../../../server/service/reference/fill-span.ts"],"names":[],"mappings":";;AAcA,wBAeC;AA7BD;;;;;GAKG;AACH,8DAA0D;AAE1D;;;;;GAKG;AACH,SAAgB,MAAM,CAAC,OAA2B;IAChD,IAAI,EAAE,GAAG,MAAM,CAAC,iBAAiB,CAAA;IACjC,IAAI,EAAE,GAAG,MAAM,CAAC,iBAAiB,CAAA;IACjC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAA,6BAAa,EAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QACpD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,SAAQ;QACjC,IAAI,CAAC,GAAG,EAAE;YAAE,EAAE,GAAG,CAAC,CAAA;QAClB,IAAI,CAAC,GAAG,EAAE;YAAE,EAAE,GAAG,CAAC,CAAA;IACpB,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAAE,OAAO,SAAS,CAAA;IAC1C;;;OAGG;IACH,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAA;AAC/D,CAAC","sourcesContent":["/*\n * backfill 이 읽을 저널 구간을 정하는 판정.\n *\n * 조회 코드(`fill-dedupe.ts`)와 나눠 둔다 — 그쪽은 TypeORM entity 를 물어서 테스트 프로세스가\n * 적재할 수 없다. 판정만 따로 두면 테스트가 가벼운 import 로 확인할 수 있다.\n */\nimport { factEventTime } from '../../engine/event-time.js'\n\n/**\n * 이 레코드들이 덮는 시각 구간 — 하나도 시각을 말하지 않으면 `undefined`.\n *\n * **구간을 못 정하면 채우지 않는다.** 저널 전체를 읽는 쪽으로 넓히지 않는다 — 그것이 필요한\n * 상황이면 조회가 아니라 unique index 로 풀어야 한다.\n */\nexport function spanOf(records: readonly unknown[]): { from: Date; to: Date } | undefined {\n let lo = Number.POSITIVE_INFINITY\n let hi = Number.NEGATIVE_INFINITY\n for (const r of records) {\n const t = Date.parse(String(factEventTime(r) ?? ''))\n if (!Number.isFinite(t)) continue\n if (t < lo) lo = t\n if (t > hi) hi = t\n }\n if (!Number.isFinite(lo)) return undefined\n /*\n * 양쪽을 1초씩 넓힌다. 저장된 시각은 밀리초까지 있고 경계에서 같은 값이 부동소수로 갈릴 수 있다 —\n * 넓게 읽는 것은 기억이 조금 더 차는 것뿐이라 손해가 없고, 좁게 읽으면 그 사실이 다시 앉는다.\n */\n return { from: new Date(lo - 1000), to: new Date(hi + 1000) }\n}\n\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"hook-contract.js","sourceRoot":"","sources":["../../../server/service/reference/hook-contract.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 웹훅의 규약 — 상태 코드 · 비밀값 · 어느 트윈인가.\n *\n * 데이터베이스를 부르는 부분과 갈라 둔다(§`reference-hook`): 규약만 있는 자리라야 시험이 엔티티를\n * 끌고 오지 않는다. 실제로 그것 때문에 시험이 서지 않았다.\n *\n * ── 왜 필요한가 ─────────────────────────────────────────────────────────────\n * 지금 모든 커넥터가 주기적으로 물어봅니다(폴링). 그 방식은 두 가지를 치릅니다. 연결된 시스템이 값을\n * 갱신하는 주기보다 자주 물으면 같은 값을 되풀이 받고, 그쪽 서버에 그만큼 부담을 줍니다. 실제로\n * 태양광 발전소에 10초마다 물으면서 10분마다 바뀌는 값을 받아 왔습니다.\n *\n * 연결된 시스템이 「바뀌었다」를 말해 줄 수 있으면 그것을 받는 것이 낫습니다. 이 파일이 그 입구입니다.\n *\n * ── 규약 ────────────────────────────────────────────────────────────────────\n *\n * 주소 POST /twin/hook/<연결이름>/<트윈> — no /domain/<도메인> prefix: the route sits on the domain-public\n * router without one (server/routes.ts:31); a prefixed address falls through and answers 405\n * 도메인 X-Things-Factory-Domain: <도메인> — the webhook module sends it from the destination's\n * params.receivingDomain\n * 인증 연결 설정에 저장한 비밀값(`hookSecret`)을 헤더로 보낸다\n * X-Twin-Hook-Secret: <비밀값> 또는 Authorization: Bearer <비밀값>\n * 본문 연결된 시스템이 정한 모양 그대로. 우리 어휘로 옮기는 것은 커넥터의 일이다\n * 응답 아래 §`hookStatus`\n *\n * ── 왜 사람 인증(JWT)이 아닌가 ──────────────────────────────────────────────\n * 밀어 주는 쪽은 사람이 아닙니다. 우리 토큰을 발급받아 갱신하며 관리하라고 요구하면 대부분의 현장에서\n * 연동이 서지 않습니다. 그래서 연결마다 비밀값을 두고 그것으로 확인합니다.\n *\n * **비밀값이 없는 연결은 훅을 받지 않습니다.** 기본이 거부입니다 — 인증 없는 입구를 열어 두는 것보다\n * 훅을 못 쓰는 것이 낫습니다.\n *\n * ── 왜 커넥터가 옮기나 (시나리오가 아니라) ──────────────────────────────────\n * 연결된 시스템의 웹훅은 그쪽이 정한 고정된 모양으로 옵니다. 그 모양을 아는 것은 그 커넥터이고,\n * 옮기는 판단은 코드여야 테스트로 지킬 수 있습니다. 실제로 지금 커넥터들이 코드로 판단합니다 —\n * 통신이 끊긴 설비의 값은 보내지 않고, 잰 시각이 나아가지 않은 줄은 거릅니다. 그 판단을 변환식으로\n * 옮기면 약해지고 테스트가 없어집니다.\n *\n * ── 밀어 주기만으로는 안 된다 ───────────────────────────────────────────────\n * 훅은 반드시 놓칩니다 — 우리가 내려가 있을 때, 그쪽이 못 보냈을 때, 네트워크가 끊겼을 때. 그래서\n * 훅을 쓰는 연결도 **드문 폴링을 유지합니다.** 폴링의 역할이 「값을 가져오는 것」에서 「놓친 것이 있나\n * 확인하는 것」으로 바뀌는 것입니다. 그 주기는 연결 설정이 정합니다.\n *\n * ── 같은 것이 두 번 오는 것 ─────────────────────────────────────────────────\n * 밀어 주는 방식은 재전송이 정상입니다(그쪽이 우리 응답을 못 받으면 다시 보냅니다). 그것은 유입\n * 경계가 이미 거릅니다(§`FactDeduper`) — 이 파일이 따로 하지 않습니다. 응답에 몇 건이 중복이었는지\n * 함께 알립니다.\n */\n/*\n * `WebhookResponse` comes from the root, not from `@operato/ops-contract/webhook`.\n *\n * That subpath resolves to `webhook-signature`, which is a separate module kept out of the barrel\n * because it pulls in `node:crypto`. The module actually named `webhook.ts` — status codes, the\n * envelope, the response — reaches consumers through the root barrel. Importing the response type\n * from the subpath fails with TS2305, which is what happened here first.\n */\nimport { checkSequence, type WebhookResponse } from '@operato/ops-contract'\nimport { WEBHOOK_HEADER, verifyWebhookSignature } from '@operato/ops-contract/webhook'\n\n/*\n * The body is typed by the contract, not by this file (2026-09-07).\n *\n * It was `Record<string, unknown>`, which accepts anything — so nothing checked that what we reply\n * with is what a sender was told to expect. It was not: the contract declared `rejected` in prose\n * only, operato-plant guessed \"a list of envelope ids\", and a whole batch was stamped delivered\n * while one envelope never landed (mes_outbox_events seq 178 — SENT, attempts 0, no error).\n *\n * With `WebhookResponse` here, adding a field to a reply without declaring it fails the build on\n * this side, which is the side that would otherwise ship the surprise.\n */\n/** 훅 요청의 결과 — 상태 코드와 본문을 함께 정한다(부르는 쪽이 그대로 답한다). */\nexport interface HookOutcome {\n status: number\n body: WebhookResponse\n}\n\n/**\n * 응답 코드 규약 — **계약이 정하고 여기서는 그것을 쓴다**(2026-08-31).\n *\n * 예전에는 이 표가 여기 있었고 보내는 쪽(operato-plant)에 또 있었다. 그래서 같은 409 를 양쪽이 다른\n * 뜻으로 썼다 — 여기서는 「그 트윈이 실시간으로 돌지 않는다」, 저쪽에서는 「번호가 비었다」였다.\n * 붙였다면 트윈이 안 도는 동안 보내는 쪽이 같은 구간을 계속 다시 보냈다.\n *\n * 판정표는 만드는 쪽과 읽는 쪽이 합의해야 하는 것이라 계약 층에 있다(`@operato/ops-contract`).\n * `notLive` 가 409 에서 503 으로 옮겨진 이유도 그 파일에 적혀 있다.\n *\n * **여기서 이름을 바꿔 다시 내보내지 않는다.** 부르는 쪽은 `WEBHOOK_STATUS` 를 계약에서 바로 가져온다.\n * 별명을 두면 같은 표에 이름이 둘이 되고, 다음 사람이 어느 쪽이 정본인지 물어야 한다.\n */\n\n/** 헤더에서 비밀값을 꺼낸다 — 두 가지 방식을 받는다(밀어 주는 쪽의 관습이 갈린다). */\nexport function secretFromHeaders(headers: Record<string, unknown> | undefined): string {\n const pick = (k: string) => String((headers ?? {})[k] ?? '').trim()\n const direct = pick('x-twin-hook-secret')\n if (direct) return direct\n const auth = pick('authorization')\n return auth.toLowerCase().startsWith('bearer ') ? auth.slice(7).trim() : ''\n}\n\n/**\n * 비밀값 비교 — **길이가 다르면 바로 거절하고, 같으면 끝까지 비교한다.**\n *\n * 앞에서 다른 것을 발견하고 곧바로 답하면 응답 시간이 달라지고, 그 차이로 비밀값을 한 글자씩 알아낼 수\n * 있습니다. 그래서 같은 길이면 전부를 비교합니다.\n */\nexport function secretMatches(given: string, expected: string): boolean {\n if (!expected || !given || given.length !== expected.length) return false\n let diff = 0\n for (let i = 0; i < expected.length; i++) diff |= given.charCodeAt(i) ^ expected.charCodeAt(i)\n return diff === 0\n}\n\n/**\n * 인증 — **서명이 왔으면 서명을 보고, 아니면 비밀값을 본다.**\n *\n * 두 방식을 두는 이유는 밀어 주는 쪽의 사정이 다르기 때문이다. 이미 붙어 있는 커넥터는 비밀값을 헤더에\n * 싣는 방식으로 만들어졌고, 새로 만드는 것(operato-plant)은 서명을 쓴다. 서명 쪽이 낫다 — 비밀값을 그대로\n * 싣는 방식은 그 요청을 그대로 다시 보내는 것을 막지 못한다.\n *\n * **서명이 왔는데 확인할 수 없으면 거절한다.** 받은 바이트를 들고 있지 않으면(`rawBody` 가 없으면)\n * 파싱한 객체를 다시 문자열로 만들어 확인하고 싶어지는데, 키 순서와 공백이 달라져 어차피 맞지 않는다.\n * 맞지 않는 것을 통과시키면 서명이 있으나 마나 한 것이 된다.\n */\nexport function authorizeHook(args: {\n headers: Record<string, unknown> | undefined\n /** 받은 바이트 그대로. 서명을 쓰지 않는 연결에서는 없어도 된다. */\n rawBody: string | undefined\n /** 연결 설정에 저장된 비밀값. 서명과 비밀값이 같은 값을 쓴다. */\n secret: string\n nowMs: number\n}): { ok: true; method: 'signature' | 'secret' } | { ok: false; reason: string } {\n const { headers, rawBody, secret, nowMs } = args\n const pick = (k: string) => String((headers ?? {})[k] ?? '').trim()\n const signature = pick(WEBHOOK_HEADER.signature)\n\n if (signature) {\n if (rawBody === undefined) {\n return { ok: false, reason: 'signed request but the raw body was not kept — cannot verify' }\n }\n const verdict = verifyWebhookSignature({\n secret,\n timestamp: pick(WEBHOOK_HEADER.timestamp),\n signature,\n body: rawBody,\n nowMs\n })\n /* `=== false` 로 본다 — 이 저장소는 `strictNullChecks` 가 꺼져 있어 참·거짓만으로는 좁혀지지 않는다. */\n if (verdict.ok === false) return { ok: false, reason: `signature ${verdict.reason}` }\n return { ok: true, method: 'signature' }\n }\n\n return secretMatches(secretFromHeaders(headers), secret)\n ? { ok: true, method: 'secret' }\n : { ok: false, reason: 'hook secret does not match' }\n}\n\n/**\n * 커서에 적을 열쇠 — **트윈까지 넣는다.**\n *\n * ── 왜 (2026-08-31, MES 레인 질문에서 나옴) ───────────────────────────────\n * 커서는 **연결마다** 한 행(`TwinReference.liveCursor`)에 산다. 그런데 연결 하나가 트윈 여럿을\n * 만든다(`scopeSpec.produced` 가 목록이다). 보내는 쪽의 단위 이름만으로 열쇠를 잡으면, 같은 연결의\n * 두 공장이 같은 이름(`EVENTS`)을 쓸 때 **한 계수기를 함께 쓴다.**\n *\n * 그러면 A 공장이 5까지 올린 뒤 B 공장이 1을 보내면 이미 본 번호가 되어 버려지고, A 가 6을 보내면\n * B 의 번호와 어긋나 끊임없이 「빠졌다」가 된다. 같은 부류가 이 저장소에서 이미 났다 — 두 공장의\n * 같은 번호가 한 자리에 겹쳤다.\n *\n * 보내는 쪽에 트윈 이름을 넣으라고 하지 않는다. 어디로 갈지는 주소가 이미 말했고, 그것을 본문에도\n * 적으라고 하면 둘이 어긋나는 날 어느 쪽을 믿을지 정해야 한다.\n */\nexport function pushCursorKey(instanceId: string, scope: string): string {\n return `${instanceId}::${scope}`\n}\n\n/**\n * 커서에서 그 단위의 마지막 번호를 꺼낸다.\n *\n * 커서 모양은 `{ streams: { [흐름]: { since?, seen[], seq? } } }` 이고 **흐름 이름은 어댑터가 정한다**\n * (§`TwinReference.liveCursor`). 밀어 주는 연결에서는 그 이름이 곧 번호를 매기는 단위다.\n *\n * 자리를 따로 만들지 않는 이유는 「재기동을 넘어 사는 진행 위치」가 두 벌이 되기 때문이다. 폴링이 쓰는\n * `since` 와 밀어 주기가 쓰는 `seq` 는 둘 다 「이 흐름을 어디까지 읽었나」이고 원본이 되풀어 줄 수 있다.\n */\nexport function seqOf(cursor: unknown, scope: string): number | undefined {\n const streams = (cursor as any)?.streams\n const seq = streams?.[scope]?.seq\n return typeof seq === 'number' && Number.isInteger(seq) ? seq : undefined\n}\n\n/** 그 단위의 번호만 바꾼 커서를 만든다 — 다른 흐름과 `since`·`seen` 은 그대로 둔다. */\nexport function withSeq(cursor: unknown, scope: string, seq: number, firstSeq?: number): Record<string, unknown> {\n const base = (cursor && typeof cursor === 'object' ? (cursor as Record<string, unknown>) : {}) as Record<string, any>\n const streams = base.streams && typeof base.streams === 'object' ? { ...base.streams } : {}\n const prev = streams[scope] ?? {}\n /*\n * **`firstSeq` 는 한 번만 적힌다.** 이 연결이 이 단위를 몇 번부터 받기 시작했는지는 바뀌지 않는\n * 사실이고, 나중에 덮으면 「앞이 비었나」를 다시 물을 수 없다.\n *\n * 그래서 첫 봉투 판정이 있을 때만 넘어온다(`SequencedTake.firstSeq`). 커서가 이미 나아간 뒤에\n * 이 값을 만들면 그 번호는 첫 번호가 아니라 그때 마침 온 번호다.\n */\n streams[scope] = { ...prev, seq, ...(firstSeq !== undefined && prev.firstSeq === undefined ? { firstSeq } : {}) }\n return { ...base, streams }\n}\n\n/**\n * 이 연결이 이 단위를 **몇 번부터 받기 시작했나** — 적힌 적이 없으면 `undefined`.\n *\n * 「앞이 비었나」를 답하려면 이 값이 있어야 한다. 없으면 **모른다고 말해야 한다** — 0 으로 답하면\n * 1번부터 다 받은 것으로 읽힌다.\n */\nexport function firstSeqOf(cursor: unknown, scope: string): number | undefined {\n const seq = (cursor as any)?.streams?.[scope]?.firstSeq\n return typeof seq === 'number' && Number.isInteger(seq) ? seq : undefined\n}\n\n/**\n * **번호대로 받을 수 있는 데까지 자른다** — 밀어 받는 길과 backfill 하는 길이 이 함수 하나를 지난다.\n *\n * ── 왜 갈라 두나 (2026-08-31) ────────────────────────────────────────────\n * 이 판정이 훅 안에 박혀 있었다. backfill(`fetchSince`)이 같은 판정을 해야 하는데, 거기서 다시 만들면\n * **두 길이 다른 규칙으로 받는다** — 한쪽만 고쳐지는 날이 오고, 어긋난 그 길에서만 사실이 사라진다.\n *\n * 번호 확인을 커넥터가 아니라 프레임워크로 가져온 것과 같은 이유다. 자리가 하나여야 한다.\n *\n * ── 첫 구멍에서 멈춘다 ───────────────────────────────────────────────────\n * 구멍 뒤의 봉투를 받아들이면 그 구멍이 영영 메워지지 않는다. 보내는 쪽은 `expectedSeq` 부터 다시\n * 보내고, 뒤엣것은 그때 함께 온다.\n */\nexport interface SequencedTake {\n /** 받아들인 레코드 — 구멍 앞까지다. */\n records: unknown[]\n /** 번호를 확인했나. 번호를 매기지 않는 원본은 거짓이고, 그 사실을 응답에 남긴다. */\n numbered: boolean\n /** 커서에 적을 번호 — 받은 것이 없으면 없다. */\n advancedSeq?: number\n /** 구멍이 났다 — 그 앞부터 다시 받아야 한다. */\n gap?: { expectedSeq: number; lastSeq: number }\n /**\n * 이 단위의 **첫 봉투였다** — 그 앞으로 몇 개를 못 봤나.\n *\n * ── 왜 이 칸이 생겼나 (2026-09-06 실측) ──────────────────────────────────\n * plant 아웃박스의 1~20 이 400 으로 거절돼 그 연결에 커서가 없었다. 다음에 온 21번이 첫 봉투로\n * 앉으며 **커서가 21 로 뛰었다.** 커서가 0 이었다면 같은 21이 구멍이었다.\n *\n * 스무 건이 없는데 구멍으로도 안 세어지고, 경고도 없고, backfill 대상도 아니었다 — backfill 은 커서부터\n * 묻는다. 그 사실이 아무 데도 안 남았다.\n *\n * **손실이라고 말하지 않는다.** 원본이 오래 돌고 있는데 트윈을 나중에 붙이면 그 아래는 애초에\n * 안 받기로 한 것이다. 계약은 둘을 구별할 수 없으므로 수만 낸다.\n *\n * 0 이면 1번부터 시작한 것이라 이 칸을 내지 않는다 — 말할 것이 없다.\n */\n firstUnseenBefore?: number\n /**\n * 이 묶음의 첫 봉투가 **이 단위의 첫 봉투였다** — 그 번호.\n *\n * `firstUnseenBefore` 와 다르다: 이쪽은 1번부터 시작한 연결에도 있다(값 1). 커서에 적어 두면\n * 나중에 「앞이 비었나」를 답할 수 있다.\n */\n firstSeq?: number\n /**\n * 이 묶음에서 **가장 낮게 받아들인 번호** — 하나도 못 받았으면 없다.\n *\n * `firstSeq` 와 다르다. `firstSeq` 는 「이 단위의 첫 봉투였다」일 때만 있고, 이쪽은 언제나 있다.\n *\n * 밑에서부터 다시 받을 때 이것이 필요하다. `from: 0` 으로 부르면 커서가 0 이므로 seq 1 은\n * `next` 판정이고 `firstSeq` 가 안 생긴다 — 그런데 우리는 실제로 1번부터 갖게 된다. 그 사실을\n * 적어야 「앞이 비었나」에 답할 수 있다.\n */\n lowestSeq?: number\n /**\n * 받을 수 없는 묶음 — 그 이유.\n *\n * 번호가 있는 봉투와 없는 봉투가 섞였거나 번호가 정수가 아니다. 둘 다 보내는 쪽의 결함이고,\n * **반쯤 확인한 것을 확인했다고 말하지 않는다.**\n */\n refused?: string\n}\n\nexport function takeInSequence(args: {\n items: { record: unknown; seq?: number; at?: string }[]\n scope?: string\n /** 이 단위에서 마지막으로 받아들인 번호. 받은 적이 없으면 `undefined`. */\n lastSeq?: number\n}): SequencedTake {\n const { items, scope, lastSeq } = args\n\n const withSeq = items.filter(i => typeof i?.seq === 'number').length\n if (withSeq && withSeq !== items.length) {\n return { records: [], numbered: false, refused: `mixed batch — ${withSeq} of ${items.length} items carry a sequence number` }\n }\n const numbered = withSeq > 0 && !!scope\n\n if (!numbered) return { records: items.map(i => i.record), numbered: false }\n\n let cursor = lastSeq\n let take = 0\n let gap: { expectedSeq: number; lastSeq: number } | undefined\n /* 첫 봉투가 몇 번부터 시작했나 — 그 앞을 못 본 사실을 응답에 남긴다. */\n let firstUnseenBefore: number | undefined\n /* 이 단위를 몇 번부터 받기 시작했나 — 커서에 적어 두면 「앞이 비었나」를 나중에 답할 수 있다. */\n let firstSeq: number | undefined\n /* 이 묶음에서 가장 낮게 받아들인 번호 — 밑에서부터 다시 받을 때 이것이 firstSeq 가 된다. */\n let lowestSeq: number | undefined\n for (const item of items) {\n let verdict\n try {\n verdict = checkSequence(cursor, item.seq as number)\n } catch (e: any) {\n return { records: [], numbered: true, refused: e?.message ?? 'bad sequence number' }\n }\n if (verdict.kind === 'gap') {\n gap = { expectedSeq: verdict.expectedSeq, lastSeq: verdict.lastSeq }\n break\n }\n if (verdict.kind === 'first') {\n firstSeq = item.seq as number\n /*\n * 계약이 `unseenBefore` 를 내면 그것을 쓴다. 설치된 버전이 그보다 오래됐으면 번호에서 센다 —\n * 첫 봉투의 번호 아래로 그만큼이 있고 우리는 그것을 받은 적이 없다.\n *\n * ⚠ 이 두 번째 길은 계약 0.9.9 가 깔리면 지운다. 그때까지 두는 이유는, 계약을 기다리며\n * 비워 두면 **코드는 있는데 아무 일도 안 하는 상태**가 되고 그것이 이 결함의 원인과 같은\n * 모양이기 때문이다.\n */\n const unseen = (verdict as any).unseenBefore ?? (item.seq as number) - 1\n if (unseen > 0) firstUnseenBefore = unseen\n }\n if (lowestSeq === undefined) lowestSeq = item.seq as number\n cursor = verdict.lastSeq\n take++\n }\n\n return {\n /*\n * **봉투의 시각을 레코드에 얹는다** — 레코드가 자기 시각을 말하면 그것이 이긴다.\n *\n * 시각을 안 싣는 레코드는 ingest 시각으로 떨어지고, 그 값이 실행할 때마다 달라서 fact identity 가\n * 매번 바뀐다. 그래서 같은 사실을 다시 받으면 dedupe 가 안 되고 새 사실로 앉는다 —\n * `fillTwinScope` 를 두 번 돌렸더니 저널이 40 → 69건이 됐다(2026-09-06 측정).\n */\n records: items.slice(0, take).map(i => {\n const r = i.record as any\n if (!i.at || r == null || typeof r !== 'object') return i.record\n return r.at === undefined && r.eventTime === undefined ? { ...r, at: i.at } : i.record\n }),\n numbered: true,\n /* 앞의 것을 하나도 못 받았으면 커서를 올릴 것이 없다. */\n ...(take > 0 && typeof cursor === 'number' ? { advancedSeq: cursor } : {}),\n ...(gap ? { gap } : {}),\n ...(firstUnseenBefore ? { firstUnseenBefore } : {}),\n ...(firstSeq !== undefined ? { firstSeq } : {}),\n ...(lowestSeq !== undefined ? { lowestSeq } : {})\n }\n}\n\n/** 이 연결이 이 트윈을 만들었나 — 남의 트윈에 밀어 넣지 못하게. */\nexport function producedInstance(ref: { scopeSpec?: any } | null | undefined, instanceId: string): { siteId: string } | undefined {\n const produced: any[] = (ref?.scopeSpec as any)?.produced ?? []\n const hit = produced.find(p => String(p?.instanceId ?? '') === instanceId)\n return hit?.siteId ? { siteId: String(hit.siteId) } : undefined\n}\n\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"hook-rejected.js","sourceRoot":"","sources":["../../../server/service/reference/hook-rejected.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;AAeH,8CAgBC;AA1BD;;;;;;;;;GASG;AACH,SAAgB,iBAAiB,CAC/B,QAA0D,EAC1D,KAAyD;IAEzD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAmB,CAAA;IAEvC,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,OAAO,EAAE,EAAE,OAAO,KAAK,QAAQ,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;QACxF,IAAI,EAAE,IAAI,EAAE,EAAE,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC7D,CAAC;IAED,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;QAElC,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAA;IACzF,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["/*\n * 떨어진 것을 **보내는 쪽이 알아들을 모양으로** 바꾼다 — 순수 판정, 한 곳.\n *\n * ── 무엇이 있었나 (2026-09-07 측정) ─────────────────────────────────────────\n * 훅 응답의 떨어진 목록이 `{ record, errors }` 였고, plant 은 그것을 **봉투 id 문자열 목록**으로\n * 읽고 있었다.\n *\n * ```\n * headless-twin rejected: { record, errors }[] 객체 배열\n * plant \"rejected is a list of envelope ids\" 문자열 배열\n * ```\n *\n * `Array.isArray` 는 통과하므로 그쪽의 새 guard 도 안 걸렸다. id 를 맞추는데 객체라 하나도 안 맞고\n * **묶음 전체가 「보냈음」으로 찍혔다.** 422 와 `ok: false` 를 보냈는데 그쪽 행은 재시도 0 ·\n * 오류 없음 · 보냈음이었다. 인티그레이션 레인이 두 파일을 나란히 읽어서 찾았다.\n *\n * 우리가 말했는데 상대가 못 알아듣는 모양이면, 말한 것이 아니다.\n *\n * ── 왜 배선에서 갈라 놓나 ───────────────────────────────────────────────────\n * `reference-hook.ts` 는 엔티티를 물어서 시험 러너가 못 읽는다. 이 판정은 평범한 값으로 확인할 수\n * 있어야 하고, 오늘 이 저장소에서 「고쳐도 통과하고 안 고쳐도 통과하는 시험」이 여럿 나왔다.\n */\n\n/** 훅 응답에 실리는 떨어진 것 하나 — 봉투 id 를 알면 그것을, 모르면 레코드를. */\nexport type RejectedForCaller = { eventId: string; errors: string[] } | { record: unknown; errors: string[] }\n\n/**\n * 떨어진 것에 봉투 id 를 붙인다.\n *\n * **짝은 객체 동일성으로 짓는다.** 유입에 넘긴 레코드가 배치의 원소 그대로이므로(§`takeInSequence`)\n * 커넥터를 고치지 않고도 봉투로 되짚을 수 있다. 값으로 비교하면 같은 모양의 레코드 둘이 서로의 id 를\n * 가져간다 — 그러면 보내는 쪽이 **엉뚱한 행**을 떨어진 것으로 표시한다.\n *\n * **id 를 못 찾으면 레코드를 그대로 싣는다.** 빈 id 를 실으면 보내는 쪽이 아무 행도 못 찾으면서\n * 「알았다」고 여긴다 — 지금 났던 일이 정확히 그것이다.\n */\nexport function rejectedForCaller(\n rejected: readonly { record: unknown; errors: string[] }[],\n items: readonly { record?: unknown; eventId?: unknown }[]\n): RejectedForCaller[] {\n const idOf = new Map<unknown, string>()\n\n for (const it of items) {\n const id = typeof it?.eventId === 'string' && it.eventId.trim() ? it.eventId : undefined\n if (id && it?.record !== undefined) idOf.set(it.record, id)\n }\n\n return rejected.map(r => {\n const eventId = idOf.get(r.record)\n\n return eventId ? { eventId, errors: r.errors } : { record: r.record, errors: r.errors }\n })\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"hook-store.js","sourceRoot":"","sources":["../../../server/service/reference/hook-store.ts"],"names":[],"mappings":";;AAiBA,sCAUC;AA3BD;;;;;;;;;;GAUG;AACH,iDAAqD;AAErD,2DAAmD;AAGnD,oCAAoC;AACpC,SAAgB,aAAa;IAC3B,OAAO;QACL,IAAI,EAAE,CAAC,QAAgB,EAAE,MAAc,EAAE,EAAE,CACzC,IAAA,qBAAa,EAAC,iCAAa,CAAC;aACzB,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC;aACxD,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;QACtB,UAAU,EAAE,KAAK,EAAE,KAAa,EAAE,MAAe,EAAE,EAAE;YACnD,MAAM,IAAA,qBAAa,EAAC,iCAAa,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,EAAS,CAAC,CAAA;QACzF,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["/*\n * 훅이 저장소에 닿는 두 자리 — 연결을 찾는 것과 커서를 적는 것.\n *\n * ── 왜 갈라져 있나 (2026-08-31) ──────────────────────────────────────────\n * `reference-hook.ts` 가 엔티티를 직접 가져오면 그 파일을 부르는 시험이 TypeORM 데코레이터를 지나게\n * 되고, 시험이 서지 않는다. 실제로 훅 시험이 규약 파일(`hook-contract.ts`)만 부르고 있었던 이유가\n * 그것이다 — 그래서 **배선은 아무 시험도 걷지 않았다.**\n *\n * 저장소에 닿는 두 함수만 여기로 내면, 처리 순서 전체를 시험이 같은 함수로 걸을 수 있다. 시험은\n * 저장본을 손에 들고 있는 구현을 넣어 재기동까지 넣는다.\n */\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinReference } from './twin-reference.js'\nimport type { HookStore } from './reference-hook.js'\n\n/** 실제 저장소를 쓰는 구현 — 라우터가 이것을 넣는다. */\nexport function databaseStore(): HookStore {\n return {\n load: (domainId: string, source: string) =>\n getRepository(TwinReference)\n .findOne({ where: { domain: { id: domainId }, source } })\n .catch(() => null),\n saveCursor: async (refId: string, cursor: unknown) => {\n await getRepository(TwinReference).update({ id: refId }, { liveCursor: cursor } as any)\n }\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/reference/index.ts"],"names":[],"mappings":";;;;AAAA,6FAAoF;AACpF,mEAA+D;AAC/D,6FAAwF;AACxF,2DAAmD;AACnD,yFAAgF;AAChF,2FAAsF;AAEzE,QAAA,QAAQ,GAAG,CAAC,iCAAa,EAAE,8DAA2B,CAAC,CAAA;AACvD,QAAA,SAAS,GAAG,CAAC,6CAAqB,EAAE,sEAAiC,EAAE,kEAA6B,EAAE,oEAAgC,CAAC,CAAA;AACpJ,+DAAoC;AACpC,+DAA+D;AAC/D,kEAAuC;AACvC,4DAA4D;AAC5D,+DAAoC;AACpC,uDAAuD;AACvD,8DAAmC;AACnC,wEAA6C;AAC7C,sEAA2C","sourcesContent":["import { ConnectionPortabilityResolver } from './connection-portability-resolver.js'\nimport { TwinReferenceResolver } from './reference-resolver.js'\nimport { TwinReferenceProgressSubscription } from './reference-progress-subscription.js'\nimport { TwinReference } from './twin-reference.js'\nimport { TwinReferenceReconciliation } from './twin-reference-reconciliation.js'\nimport { TwinReferenceReconciliationQuery } from './reference-reconciliation-query.js'\n\nexport const entities = [TwinReference, TwinReferenceReconciliation]\nexport const resolvers = [TwinReferenceResolver, TwinReferenceProgressSubscription, ConnectionPortabilityResolver, TwinReferenceReconciliationQuery]\nexport * from './control-routing.js'\n/* 진행 단계 어휘 — **모든 커넥터가 같은 낱말로 말한다.** 화면은 단계마다 번역 하나만 두면 된다. */\nexport * from './reference-progress.js'\n/* 커넥터 점검 하네스 — 실 원본에 붙여 무엇이 채워지고 무엇이 비는지 센다(앱을 띄우지 않는다). */\nexport * from './reference-probe.js'\n/* 놓친 구간을 backfill 해 채우는 길 — 밀어 받는 길과 같은 번호 검사를 지난다. */\nexport * from './reference-fill.js'\nexport * from './reference-reconciliation.js'\nexport * from './connection-portability.js'\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ingest-space.js","sourceRoot":"","sources":["../../../server/service/reference/ingest-space.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAqBH,gDAUC;AA0BD,8CAQC;AAaD,oCAKC;AAzED,0CAA0C;AAC7B,QAAA,sBAAsB,GAAG,iBAAiB,CAAA;AAEvD;;;;;;;GAOG;AACH,SAAgB,kBAAkB,CAAC,aAAqB,EAAE,SAAoC,EAAE,eAAwB;IACtH,MAAM,IAAI,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACrC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAC3D,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,UAAU,IAAI,iEAAiE,CAAC,CACnG;QAAC,CAAS,CAAC,IAAI,GAAG,8BAAsB,CAAA;QACzC,MAAM,CAAC,CAAA;IACT,CAAC;IACD,kDAAkD;IAClD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,KAAK,aAAa,EAAE,CAAA;AAC1D,CAAC;AAqBD;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,SAAiB,EAAE,SAAyB;IAC5E,MAAM,IAAI,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;IACrC,IAAI,CAAC,IAAI,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO,SAAS,CAAA;IAC7C,OAAO;QACL,IAAI,EAAE,2BAA2B;QACjC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE;QAC3C,OAAO,EAAE,oBAAoB,SAAS,sBAAsB,IAAI,gEAAgE;KACjI,CAAA;AACH,CAAC;AAOD;;;;;GAKG;AACH,SAAgB,YAAY,CAAC,QAAkD,EAAE,QAAkD;IACjI,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAa,CAAC,CAAA;IAClF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,KAAK,MAAM,CAAC,IAAI,QAAQ,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAC3E,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;AACzB,CAAC","sourcesContent":["/*\n * 인제스트가 어느 공간으로 들어갈지 정한다 — **순수**.\n *\n * ── 왜 있나 (2026-08-05) ────────────────────────────────────────────────────\n * 한 현실(공장·물류센터)을 여러 렌즈가 비춘다: 창고(WMS)·공정(MES)·야드(YMS) 가 같은 부지에 있다.\n * 엔진은 이미 그것을 지원한다 — 같은 `spaceId` 로 들어오면 공간 content 를 병합하고 기존 값을 이긴다.\n *\n * 그런데 **만드는 흐름에 그 선택이 없었다.** 템플릿은 `space.spaceId` 를 정하지 않으므로 항상\n * `<source>-site` 로 파생돼, 새 트윈은 늘 새 현장처럼 생겼다. 데이터로는 가능한 일이 저작으로는\n * 불가능했다.\n *\n * ── 없는 공간에 알리지 않고 새 공간을 만들지 않는다 ────────────────────────────────\n * \"이 현장에 더한다\" 는 요청이 오타·낡은 목록으로 빗나갔을 때 새 공간을 만들어 주면, 사용자는\n * 합쳤다고 믿고 화면에서는 따로 놀게 된다 — 나중에 두 현장을 손으로 합쳐야 하고, 그때는 이미\n * 이벤트가 양쪽에 쌓여 있다. 그래서 **모르는 공간이면 거절한다.**\n *\n * ── 같은 id 를 쓰는 구역은 알리지 않고 합쳐지므로 알린다 ──────────────────────────\n * 공간 content 의 구역·랜드마크는 id 로 합집합을 만든다(`unionById`). 두 렌즈가 우연히 같은 구역\n * id(`a-yard` 따위)를 쓰면 서로 다른 구역이 하나로 합쳐진다. 막을 일은 아니다(같은 현장의 같은 구역을\n * 가리키는 정당한 경우가 많다) — 다만 **모르고 지나가지 않게** 경고로 돌려준다.\n */\n\n/** 공간 판정 결과. */\nexport interface IngestSpaceChoice {\n /** 실제로 쓸 공간 id. */\n spaceId: string\n /** 사용자가 기존 현장에 합치기를 골랐나(마스터가 파생한 공간과 다른 곳으로 들어가나). */\n joined: boolean\n}\n\n/** 거절 사유 — 호출측이 문장을 만들 때 쓴다(언어 중립 코드). */\nexport const INGEST_SPACE_NOT_FOUND = 'space-not-found'\n\n/**\n * 요청받은 공간과 마스터가 파생한 공간 중 무엇으로 들어갈지 정한다.\n *\n * @param masterSpaceId 마스터에서 파생된 공간 id(요청이 없을 때의 기본).\n * @param requested 사용자가 고른 기존 공간 id. 없으면 빈 값/undefined.\n * @param requestedExists 그 공간이 이 테넌트에 실제로 있나(호출측이 조회해 넘긴다).\n * @throws 요청한 공간이 없으면 — 알리지 않고 새로 만들지 않는다.\n */\nexport function resolveIngestSpace(masterSpaceId: string, requested: string | undefined | null, requestedExists: boolean): IngestSpaceChoice {\n const want = (requested ?? '').trim()\n if (!want) return { spaceId: masterSpaceId, joined: false }\n if (!requestedExists) {\n const e = new Error(`space \"${want}\" not found — cannot co-locate into a space that does not exist`)\n ;(e as any).code = INGEST_SPACE_NOT_FOUND\n throw e\n }\n /* 마스터가 이미 그 공간을 가리키면 합치기가 아니다 — 같은 공간의 재인제스트다. */\n return { spaceId: want, joined: want !== masterSpaceId }\n}\n\n/**\n * 실 시스템 import 가 현장을 고를 때의 거절 판정 — **순수**.\n *\n * 레퍼런스 하나는 사이트를 여럿 발견할 수 있고(`discoverSites`), 그 사이트마다 트윈이 하나씩 생긴다.\n * 그런데 \"이 현장에 더한다\" 는 **한 곳**을 가리키는 말이다. 사이트 여럿을 한 공간에 넣으면 서로 다른\n * 현장의 구역·랜드마크가 id 로 합쳐지고(`unionById`), 그 사실을 알아차릴 때는 이미 이벤트가 양쪽에\n * 쌓여 있다 — 손으로 되돌릴 수 없는 종류다. 그래서 **묻지 않고 합치지 않고, 거절한다.**\n *\n * 공간을 고르지 않으면 지금까지의 거동 그대로다: 사이트마다 마스터가 파생한 공간으로 들어간다.\n */\nexport type ImportSpaceRefusalCode = 'space-with-multiple-sites'\n\nexport interface ImportSpaceRefusal {\n code: ImportSpaceRefusalCode\n params?: Record<string, string | number>\n /** 영어 canonical — 로그에 남고, 번역이 없으면 그대로 보인다(발견 오류와 같은 규약). */\n message: string\n}\n\n/**\n * @param siteCount 실제로 만들 사이트 수(필터 적용 후).\n * @param requested 사용자가 고른 기존 공간 id. 없으면 판정할 것이 없다.\n * @returns 거절 사유. 없으면 진행해도 된다.\n */\nexport function refuseImportSpace(siteCount: number, requested?: string | null): ImportSpaceRefusal | undefined {\n const want = (requested ?? '').trim()\n if (!want || siteCount <= 1) return undefined\n return {\n code: 'space-with-multiple-sites',\n params: { spaceId: want, count: siteCount },\n message: `cannot co-locate ${siteCount} sites into space \"${want}\" — pick one site, or omit the space so each site gets its own`\n }\n}\n\n/** 구역·랜드마크처럼 id 로 합쳐지는 것들. */\ninterface Identified {\n id?: string\n}\n\n/**\n * 합치는 쪽과 기존 쪽이 같은 id 를 쓰는 것들을 찾아 경고 문장을 만든다.\n *\n * 정당한 경우(같은 현장의 같은 구역)와 사고(서로 다른 구역이 우연히 같은 이름)를 기계가 가릴 수는\n * 없다. 구분할 수 없는 것을 고르지 않고, **무엇이 합쳐졌는지 말한다.**\n */\nexport function collidingIds(existing: readonly Identified[] | undefined | null, incoming: readonly Identified[] | undefined | null): string[] {\n const have = new Set((existing ?? []).map(x => x?.id).filter(Boolean) as string[])\n const hits = new Set<string>()\n for (const x of incoming ?? []) if (x?.id && have.has(x.id)) hits.add(x.id)\n return [...hits].sort()\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"knob-defaults.js","sourceRoot":"","sources":["../../../server/service/reference/knob-defaults.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;AAeH,8CASC;AAQD,wCAKC;AA7BD;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAAC,KAA6C,EAAE,MAAkD;IACjI,MAAM,GAAG,GAA4B,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAA;IAC1D,KAAK,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;QAC5B,IAAI,CAAC,CAAC,EAAE,GAAG;YAAE,SAAQ;QACrB,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS;YAAE,SAAQ,CAAC,4BAA4B;QAClE,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QACxB,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAA;IACnE,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,KAA6C,EAAE,MAAkD;IAC9H,MAAM,KAAK,GAAG,MAAM,IAAI,EAAE,CAAA;IAC1B,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;SACjB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAI,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,GAAI,CAAC,KAAK,IAAI,CAAC,CAAC;SACzG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAI,CAAC,CAAA;AACrB,CAAC","sourcesContent":["/*\n * 노브 기본값 적용 — **순수**. \"안 준 값은 템플릿이 선언한 기본값으로.\"\n *\n * ── 왜 있나 (2026-08-05) ────────────────────────────────────────────────────\n * 템플릿은 노브마다 기본값을 선언한다(`knobs[].default`). 그런데 그 기본값을 채우는 일을 **위자드만**\n * 하고 있었다 — 서버는 받은 파라미터를 그대로 `build(p)` 에 넘긴다. 그래서 파라미터를 일부만 주고\n * 부르면(AI·스크립트·API 호출자) 템플릿 안에서 `Number(p.inboundRate) || 0` 이 **0** 이 되고,\n * 발생률 0 = 도착 없음 = **아무것도 하지 않는 트윈**이 알리지 않고 만들어진다.\n *\n * 실제로 그 함정을 밟았다: 유통가공 창고를 파라미터 일부만 주고 만들었더니 이벤트가 0건이었고,\n * 처음에는 템플릿이 깨진 것으로 오진했다. 기본값을 아는 곳이 계약(노브 스키마)인데 그것을 적용하는\n * 곳이 화면뿐이면, 화면을 거치지 않는 모든 경로가 함정이 된다.\n *\n * ── 0 을 기본값으로 덮지 않는다 ─────────────────────────────────────────────\n * 발생률 `0` 은 \"도착 없음\" 이라는 **뜻 있는 값**이다. 없는 것과 0 은 다르다 — falsy 로 판정해 덮으면\n * \"자극 없이 구조만 보겠다\" 는 선언이 알리지 않고 뒤집힌다. 그래서 **키가 없을 때만** 채운다.\n * `null` 은 없는 것으로 본다(JSON·GraphQL 경로에서 \"주지 않음\" 이 null 로 오는 일이 흔하다).\n *\n * ── 선언 밖 파라미터는 손대지 않는다 ────────────────────────────────────────\n * 템플릿은 노브에 없는 값도 읽는다(예: `geo` 대신 `lat`/`lon` 폴백). 모르는 키를 걷어내면 그 경로가\n * 알리지 않고 끊긴다. 그대로 통과시킨다.\n */\n\n/** 템플릿 노브 — 기본값 적용에 필요한 만큼만 본다. */\nexport interface KnobLike {\n key?: string\n default?: unknown\n}\n\n/**\n * 빠진 노브를 기본값으로 채운다.\n *\n * @param knobs 템플릿이 선언한 노브들(없으면 파라미터를 그대로 돌려준다).\n * @param params 호출자가 준 파라미터.\n * @returns 새 객체 — 입력을 바꾸지 않는다(호출자의 객체가 몰래 변하면 추적이 어렵다).\n */\nexport function applyKnobDefaults(knobs: readonly KnobLike[] | undefined | null, params: Record<string, unknown> | undefined | null): Record<string, unknown> {\n const out: Record<string, unknown> = { ...(params ?? {}) }\n for (const k of knobs ?? []) {\n if (!k?.key) continue\n if (k.default === undefined) continue // 기본값을 선언하지 않은 노브는 채울 것이 없다\n const given = out[k.key]\n if (given === undefined || given === null) out[k.key] = k.default\n }\n return out\n}\n\n/**\n * 기본값으로 채운 노브 이름들 — 호출자에게 \"무엇을 내가 정해 줬는지\" 알릴 때 쓴다.\n *\n * 알리지 않고 채우는 것도 알리지 않고 비우는 것만큼 위험하다: 사용자가 준 적 없는 값으로 트윈이 만들어졌는데\n * 아무 말이 없으면, 나중에 \"왜 이 수치인가\" 를 아무도 답할 수 없다.\n */\nexport function filledKnobKeys(knobs: readonly KnobLike[] | undefined | null, params: Record<string, unknown> | undefined | null): string[] {\n const given = params ?? {}\n return (knobs ?? [])\n .filter(k => k?.key && k.default !== undefined && (given[k.key!] === undefined || given[k.key!] === null))\n .map(k => k.key!)\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"live-cadence.js","sourceRoot":"","sources":["../../../server/service/reference/live-cadence.ts"],"names":[],"mappings":";;AAoFA,8BAEC;AAiBD,8CAyBC;AA7CD,kDAAkD;AAClD,SAAgB,SAAS,CAAC,QAA4C,EAAE,IAAY;IAClF,OAAO,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;AAC7C,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,iBAAiB,CAC/B,OAAgC,EAChC,GAA+G;IAE/G,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAA;IAEvB,MAAM,GAAG,GAAuD,EAAE,CAAA;IAElE,IAAI,OAAO,OAAO,CAAC,iBAAiB,KAAK,QAAQ,IAAI,OAAO,CAAC,iBAAiB,GAAG,CAAC,EAAE,CAAC;QACnF,GAAG,CAAC,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAA;IACnD,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,+CAA+C;QAC/C,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAA;QACnB,OAAO,GAAG,CAAA;IACZ,CAAC;IAED;;;OAGG;IACH,IAAI,OAAO,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,CAAC,QAAQ;QAAE,GAAG,CAAC,QAAQ,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAExF,OAAO,GAAG,CAAA;AACZ,CAAC","sourcesContent":["/*\n * **원천이 축마다 언제·얼마나 자주 내놓나** — 유입 건강이 이 선언으로 견준다.\n *\n * ── 왜 필요한가 (2026-09-04 실측) ──────────────────────────────────────────\n * 유입 건강이 **한 자로 전부를 쟀다.** 10분 안에 아무것도 오지 않으면 「유입 없음」이다. 그런데\n * 원천마다 시간의 결이 다르다.\n *\n * 태양광 발전 일몰에 멈춘다 — 두 사이트가 같은 분 19:25 에 함께\n * 부하 계측 밤에도 온다 · 15분마다\n * 인증서 가격 화·목만 거래된다 · 그 날 하루 한 번\n * MES · 주방 현장 근무시간 밖에는 사실이 안 난다 · 훅이라 「보내면 온다」\n *\n * 그래서 **정상인데 매일 밤 절반이 빨갛게 났다.** 거짓 빨강이 쌓이면 진짜 빨강도 같이 안 읽힌다.\n *\n * ── 두 축이고 서로 다르다 ───────────────────────────────────────────────────\n * window 지금이 그 축의 활동 시각인가 → 아니면 조용한 것이 정상\n * expectedSilenceMs 활동 시각 안에서 이만큼 조용해도 정상 → 넘기면 유입 없음\n *\n * 주기를 넘겼어도 활동 시각 밖이면 정상이다. 한 값으로 접을 수 없다 — 인증서 가격이 그 증거다.\n * 「매일 와야 한다」로 재면 주 5일 중 3일이 빨갛고, 「주 2회」로 재면 이어 쓴 날을 못 본다.\n *\n * ── 커널이 일출·일몰을 계산하지 않는다 ─────────────────────────────────────\n * 굴절·고위도·박명 정의가 다 선택이고, 우리가 정하면 그 순간 방언이다. 그리고 계약이 같은 문제에\n * 이미 답해 뒀다(§`TwinModelDef.utcOffsetMinutes`): 「긴 지평선의 정확한 답은 호스트가 표준대로\n * **절대 구간**을 계산해 넣는 것이다」.\n *\n * 그래서 원천은 `windowKind: 'daylight'` 로 **종류만** 선언하고, 좌표를 아는 호스트가 날마다\n * 구체적인 구간을 채운다. **고정 시각을 선언에 박지 않는다** — 일몰 19:25 는 오늘 잰 값이고 두 달\n * 뒤에는 틀리다. 그것을 박으면 겨울마다 거짓 빨강이 다시 난다.\n *\n * ── 「주기 없음」과 「모른다」는 다르다 ──────────────────────────────────────\n * MES 는 훅으로 밀어 준다 — 몇 분마다가 아니라 「보내면 온다」다. 보낼 것이 없으면 안 보내는 것이\n * 정상이고, 그것을 침묵으로 판정하면 안 된다.\n *\n * 선언하지 않은 축은 **지금까지처럼** 창 하나로 견준다(옛 거동). 「모른다」를 「안 와도 된다」로\n * 접지 않는다 — 그러면 정말 끊긴 원천이 통째로 조용해진다.\n */\nimport type { WorkCalendarEntry } from '@operato/ops-contract'\n\n/**\n * 창의 종류 — **호스트가 채워야 하는 것**만 이름이 있다.\n *\n * 지금은 하나다. 늘리기 전에 「호스트가 그것을 계산할 근거를 갖고 있나」를 먼저 물어야 한다 —\n * 좌표 없이 `daylight` 를 선언하면 호스트가 채울 것이 없고, 그러면 그 축은 창이 없는 것과 같다.\n */\nexport type LiveWindowKind = 'daylight'\n\n/** 한 축의 시간성 — 어느 사실이 언제·얼마나 자주 오나. */\nexport interface LiveCadence {\n /**\n * 어느 축인가 — **`OP_EVENT` 의 값 그대로**(`equipment.status` · `quality.output` …).\n *\n * 새 낱말을 만들지 않는다. 만들면 커넥터가 부르는 이름과 장부가 세는 이름이 갈리고, 그때\n * 선언이 조용히 아무 축에도 안 붙는다.\n */\n axis: string\n /**\n * 활동 시각 안에서 **이만큼 조용해도 정상**(ms). 없으면 창 하나로 견준다.\n *\n * 15분마다 긷는 원천은 이 값을 선언해야 한다 — 안 하면 긷는 사이마다 「유입 없음」으로 보인다.\n */\n expectedSilenceMs?: number\n /**\n * 이 축이 **활동하는 창** — 근무 달력 어휘 그대로(요일·시간대·휴일).\n *\n * 계약이 이미 그 셋을 표현한다. 새 모양을 만들지 않는 이유는 판정하는 함수가 이미 하나\n * 있기 때문이다(`inWorkCalendar`) — 두 벌이면 화면과 커널이 다른 답을 낸다.\n */\n window?: readonly WorkCalendarEntry[]\n /**\n * 창을 **호스트가 채우는 종류** — 좌표·표준으로 계산해야 하는 것.\n *\n * `window` 와 함께 선언하면 호스트가 채운 것이 이긴다(계산이 선언보다 그 날에 맞다).\n */\n windowKind?: LiveWindowKind\n /**\n * **보내면 온다** — 이 축은 침묵으로 판정하지 않는다(훅·구독).\n *\n * 「주기가 없다」와 「모른다」를 가르는 값이다. 훅은 보낼 것이 없으면 안 보내고, 그것이 정상이다.\n */\n pushed?: boolean\n}\n\n/** 그 축의 선언을 찾는다 — 없으면 `undefined`(옛 거동으로 견준다). */\nexport function cadenceOf(cadences: readonly LiveCadence[] | undefined, axis: string): LiveCadence | undefined {\n return cadences?.find(c => c.axis === axis)\n}\n\n/**\n * 유입 건강이 이 축을 어떻게 견줄 것인가 — **판정에 넘길 두 값으로 옮긴다.**\n *\n * `syncVerdictOf` 가 받는 모양(§`ingest-health.ts`)에 맞춘다. 그 함수는 순수하고 달력을 보지\n * 않으므로, 창 판정은 여기서 끝내고 참·거짓만 넘긴다 — `capabilityOf` 가 `requiredTests` 를 받는\n * 것과 같은 규율이다.\n *\n * ── 「보내면 온다」는 침묵으로 판정하지 않는다 ──────────────────────────────\n * `pushed` 면 `quietNow` 를 늘 참으로 낸다. 그러면 침묵이 「유입 없음」이 되지 않는다. **다른\n * 갈래는 그대로 산다** — 버려지는 중·결선 없음은 몇 시든 문제이고 그것들이 이 값보다 앞선다.\n *\n * ── 모르면 아무것도 답하지 않는다 ──────────────────────────────────────────\n * 선언이 없으면 둘 다 비운다. 그때 판정은 지금까지처럼 창 하나로 견준다 — 「모른다」를 「안 와도\n * 된다」로 접으면 정말 끊긴 원천이 통째로 조용해진다.\n */\nexport function ingestExpectation(\n cadence: LiveCadence | undefined,\n ctx: { nowMs: number; utcOffsetMinutes?: number; inWindow?: (window: readonly WorkCalendarEntry[]) => boolean }\n): { expectedSilenceMs?: number; quietNow?: boolean } {\n if (!cadence) return {}\n\n const out: { expectedSilenceMs?: number; quietNow?: boolean } = {}\n\n if (typeof cadence.expectedSilenceMs === 'number' && cadence.expectedSilenceMs > 0) {\n out.expectedSilenceMs = cadence.expectedSilenceMs\n }\n\n if (cadence.pushed) {\n /* 보낼 것이 없으면 안 보내는 것이 정상이다 — 침묵을 근거로 삼지 않는다. */\n out.quietNow = true\n return out\n }\n\n /*\n * 창이 있으면 그것으로 판정한다. **판정할 수단을 안 받았으면 답하지 않는다** — 부르는 쪽이\n * 달력 판정 함수를 주지 않았는데 우리가 「활동 시각이다」로 단언하면 그것이 지어낸 값이다.\n */\n if (cadence.window?.length && ctx.inWindow) out.quietNow = !ctx.inWindow(cadence.window)\n\n return out\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"live-feed-lease.js","sourceRoot":"","sources":["../../../server/service/reference/live-feed-lease.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;;AAmBH,8CAaC;AA9BD,8FAA8F;AAC9F,MAAM,MAAM,GAAG,WAAW,CAAA;AAE1B;;;;;;;;;;;;;GAaG;AACH,SAAgB,iBAAiB,CAAC,QAAgB,EAAE,UAAkB,EAAE,MAAe;IACrF,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI;QAC1B,CAAC,UAAU,EAAE,QAAQ,CAAC;QACtB,CAAC,YAAY,EAAE,UAAU,CAAC;KAClB,EAAE,CAAC;QACX,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,yCAAyC,IAAI,mCAAmC,CAAC,CAAA;QACnG,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;IAE7C,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,QAAQ,KAAK,UAAU,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,QAAQ,KAAK,UAAU,EAAE,CAAA;AAC5G,CAAC","sourcesContent":["/*\n * What a live feed takes its task lease under — pure naming, one place.\n *\n * ── Why the name is built here and not by the adapter ──────────────────────\n * `TaskLease` deliberately has no domain column: the thing made exclusive is a\n * loop, not a tenant's slice of one, and a task that does need to be per-tenant\n * puts the tenant in its name so the unique index keeps meaning what it says.\n *\n * An adapter cannot honour that. It receives `cfg` and `site`, and neither\n * carries a domain — so a name built from `siteId` would let two domains that\n * happen to share a plant code block each other's channel. It would look\n * exactly like \"someone else holds it\", which is the ordinary case on every\n * instance but one, so nothing would be logged and nothing would look wrong.\n *\n * ── Why the source and not the site ───────────────────────────────────────\n * One reference is one connection to one system, and that connection is what a\n * second instance must not open twice. A source that discovers several sites\n * still reads them over that one connection.\n *\n * ── Why the parts are joined with `::` ────────────────────────────────────\n * The same separator the push cursor key already uses (`instanceId::scope`), so\n * a person reading `task_leases` during an incident sees the same shape they\n * see in `liveCursor`.\n */\n\n/** Marks the name as a live feed, so a lease table read tells you what kind of task it is. */\nconst PREFIX = 'twin-live'\n\n/**\n * The lease name for one twin's feed.\n *\n * ── Domain and instance are required; the source is not ───────────────────\n * A feed is registered per instance, so domain plus instance already names it\n * uniquely. The source is appended when known because it is what a person\n * reading `task_leases` wants to see — which system this channel talks to.\n *\n * Missing a domain or an instance is refused rather than filled in. A blank\n * there would collapse two different feeds onto one name, and the failure\n * would be that one of them silently stops: it would look exactly like\n * \"someone else holds it\", which is the ordinary case on every instance but\n * one, so nothing would be logged.\n */\nexport function liveFeedLeaseName(domainId: string, instanceId: string, source?: string): string {\n for (const [what, value] of [\n ['domainId', domainId],\n ['instanceId', instanceId]\n ] as const) {\n if (!value || !String(value).trim()) {\n throw new Error(`cannot name a live feed lease without ${what} — two feeds would share one name`)\n }\n }\n\n const named = source && String(source).trim()\n\n return named ? `${PREFIX}::${domainId}::${instanceId}::${named}` : `${PREFIX}::${domainId}::${instanceId}`\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"live-ingest.js","sourceRoot":"","sources":["../../../server/service/reference/live-ingest.ts"],"names":[],"mappings":";;AAsCA,gCAuDC;AA7ED,oDAAkD;AAClD,0EAAyE;AACzE,oDAA2E;AAC3E,8DAA2D;AAC3D,gDAA8C;AAE9C;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,UAAU,CACxB,QAAgB,EAChB,UAAkB,EAClB,OAAkB,EAClB,GAAW;IAEX,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,IAAA,4CAAsB,EACxE,OAAc,EACd,QAAQ,EACR,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EACxB,qBAAU,CAAC,SAAS,CAAC,QAAQ,EAAE,UAAU,CAAC,CAC3C,CAAA;IACD,2DAA2D;IAC3D,IAAI,UAAU,CAAC,MAAM;QAAE,qBAAU,CAAC,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,UAAU,CAAC,CAAA;IACnF,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,qBAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAA;IAEhG;;;OAGG;IACH,KAAK,IAAA,wBAAa,EAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAA,+BAAoB,GAAE,EAAE,CAAC,CAAA;IAEpF;;;OAGG;IACH,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC,CAAA;IACvE,qBAAU,CAAC,kBAAkB,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,UAAU,CAAC,MAAM,CAAC,CAAA;IAEzH;;;OAGG;IACH;;;;;OAKG;IACH,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAA,8BAAc,EAAC,IAAI,CAAC,CAAC,CAAA;IACpF,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,IAAA,iBAAQ,EACN,kBAAkB,UAAU,oCAAoC;YAC9D,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;YAC/C,2BAA2B,CAC9B,CAAA;IACH,CAAC;IAED,OAAO;QACL,OAAO;QACP,UAAU;QACV,UAAU,EAAE,UAAU,CAAC,MAAM;QAC7B,QAAQ;QACR,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/D,CAAA;AACH,CAAC","sourcesContent":["/*\n * **원본이 준 레코드를 트윈에 넣는다** — 밀어 받는 길과 backfill 이 같이 쓴다.\n *\n * ── 왜 한 곳에 두나 ─────────────────────────────────────────────────────────\n * 이 몸통이 `routes.ts` 의 훅 안에 박혀 있었습니다. backfill 문을 만들면서 복사하면 두 길이 서로\n * 다른 규칙으로 받게 되고, 한쪽만 수정되는 날이 옵니다.\n *\n * 실제로 그 위험이 이 자리에 있습니다. 유입 장부의 네 갈래(앉음 · 되풀이 · 어휘 · 거절)를\n * 2026-09-05 에 고쳤는데, 그때 이 몸통이 두 벌이었다면 한쪽 셈만 닫혔을 것입니다.\n *\n * ── 커널을 직접 부르지 않는 쪽이 넘겨받는다 ────────────────────────────────\n * 훅 판정(`reference-hook.ts`)은 이 함수를 인자로 받습니다. 그 파일이 엔진을 직접 부르면 테스트할\n * 때 엔진이 필요해집니다. 그 규율은 그대로 두고, 이 파일이 엔진을 아는 유일한 자리가 됩니다.\n */\nimport type { IngestResult } from './reference-hook.js'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { ingestCanonicalRecords } from '../../engine/canonical-ingest.js'\nimport { databaseCommandStore, settleEffects } from '../actuation/index.js'\nimport { shouldHaveTime } from '../../engine/event-time.js'\nimport { twinWarn } from '../../engine/log.js'\n\n/**\n * 레코드를 넣고 **네 갈래로 답한다.**\n *\n * 두 갈래로는 셈이 안 닫힙니다(인티그레이션 레인 실측 2026-09-05).\n *\n * ```\n * 두 갈래 157 − 46 − 2 = 109 「설명 못 함」 진실은 되풀이 108 + 어휘 1\n * 네 갈래 157 − 46 − 2 − 108 − 1 = 0\n * ```\n *\n * 109 를 「사라졌다」로 내면 그날부터 아무도 그 수를 안 봅니다. 늘 참인 경고는 벽지가 됩니다.\n *\n * `vocabulary` 는 저널에 안 가는 어휘 요소의 수입니다. 사라진 것이 아닙니다.\n *\n * @param via 유입 장부에 남길 길 이름 — `hook`(밀어 받음) · `fill`(빠진 구간을 다시 받음)\n */\nexport function liveIngest(\n domainId: string,\n instanceId: string,\n records: unknown[],\n via: string\n): IngestResult & { vocabulary: number; undated?: Record<string, number> } {\n const { accepted, rejected, masterData, undated } = ingestCanonicalRecords(\n records as any,\n domainId,\n new Date().toISOString(),\n TwinEngine.factScope(domainId, instanceId)\n )\n /* 사건이 아닌 것은 상태만 세운다 — 두 길이 같은 규율이다(§`applyMasterData`). */\n if (masterData.length) TwinEngine.applyMasterData(domainId, instanceId, masterData)\n const { applied, duplicates } = TwinEngine.ingestLiveResult(domainId, instanceId, accepted, via)\n\n /*\n * **나간 지시와 잇는다** — 폐루프. 기다리지 않습니다: 훅의 응답이 이것 때문에 늦으면 보내는 쪽이\n * 되풀이하고, 그러면 같은 관측이 두 번 옵니다. 실패해도 관측 유입은 멈추지 않습니다.\n */\n void settleEffects({ domainId, envelopes: accepted, store: databaseCommandStore() })\n\n /*\n * 커널이 받지 못한 수를 이름으로 남깁니다 — 트윈이 멈춘 사이 버려진 레코드를 「통과」로 세면\n * 화면이 100% 라고 말합니다. 중복으로 걸러진 것은 버려진 것이 아니므로 빼고 셉니다.\n */\n const undelivered = Math.max(0, accepted.length - applied - duplicates)\n TwinEngine.recordIngestResult(domainId, instanceId, records.length, rejected, Date.now(), undelivered, masterData.length)\n\n /*\n * 떨어진 것을 **수가 아니라 목록으로** 넘깁니다 — 응답에 실어야 보내는 쪽이 그것만 따로 둘 수\n * 있습니다. 수만 주면 배치 전체를 다시 보내거나 전부 버리는 두 갈래밖에 없습니다.\n */\n /*\n * **표에 있는 type 인데 시각을 못 읽었으면 그것은 결함이다.** 그 fact 는 자기 시각을 싣기로 되어\n * 있는데 안 실려 온 것이고, 그대로 두면 ingest 시각이 그 사실의 시각으로 저장된다.\n *\n * 표에 없는 type 은 세기만 한다 — 절대값 snapshot 은 polling 순간이 맞는 시각이다.\n */\n const wrong = Object.entries(undated ?? {}).filter(([type]) => shouldHaveTime(type))\n if (wrong.length) {\n twinWarn(\n `[twin-ingest] \"${instanceId}\" — 시각을 실어야 하는 fact 가 시각 없이 왔습니다: ` +\n wrong.map(([t, n]) => `${t} ${n}건`).join(' · ') +\n `. 그 사실들은 지금 시각으로 저널에 앉습니다`\n )\n }\n\n return {\n applied,\n duplicates,\n vocabulary: masterData.length,\n rejected,\n ...(undated && Object.keys(undated).length ? { undated } : {})\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reference-adapter.js","sourceRoot":"","sources":["../../../server/service/reference/reference-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ApprovedCommand, RefusalCode, RefusalParams } from '@operato/ops-contract'\n\nimport type { ReferenceStep } from './reference-progress.js'\nimport type { ReferenceMaster } from './reference-master.js'\n/*\n * `fetchLinks` 가 돌려주는 관계 그룹. 정의는 이것을 소비하는 쪽에 하나만 둔다\n * (`external-resolver.ts`). 여기서 같은 모양을 다시 선언하면 한쪽만 수정되는 날이 온다.\n */\nimport type { ExternalIncoming as ReferenceLinkGroup } from '../twin-model/external-resolver.js'\nexport type { ReferenceLinkGroup }\nimport { twinWarn } from '../../engine/log.js'\n\n/*\n * 레퍼런스 어댑터 계약 (reference-management P1) — 정적 마스터 등록(`registerReference`)을\n * \"어댑터 타입\" 등록으로 일반화한다. 레퍼런스 = 데이터(TwinReference 행), 어댑터 = 동작.\n * 아웃바운드 액추에이션은 **별개 어댑터가 아니라 이 어댑터의 반대 면**이다(`actuate`) — 같은 연결이\n * 사실을 읽고 조치를 내리므로 인증과 설정이 한 자리에 있어야 한다(command-routing §8.1).\n * seam: openLiveFeed 레코드 → 커널 face2-adapter.ingest → CanonicalEnvelope 까지가 여기,\n * 이후 projector 구동은 face2-inbound-live 소유.\n * 설계 SoT: operato-twin/design/plans/reference-management.md §2.2·§4.2.\n */\n\n/** 연결 설정(어댑터별 스키마) — 엔드포인트·자격·파라미터. */\nexport type ConnectionConfig = Record<string, any>\n\n/** discoverSites 가 반환하는 사이트/공장 서술자 — N 발견(§2.2). 1:1 은 길이 1 배열. */\nexport interface SiteDescriptor {\n /** 안정적 사이트 정체성 키(소스의 plant code 등) — 공간 정합 1차 키. */\n siteId: string\n name?: string\n /** 사이트별 시스템(소스가 시스템을 넘나들 때). 없으면 레퍼런스 system 사용. */\n system?: string\n geo?: { lat: number; lon: number }\n hint?: any\n}\n\n/** 연결 폼 필드 서술 — UI 가 이 스키마로 connectionConfig 입력 폼을 렌더한다(어댑터가 스키마 제공). */\nexport interface AdapterConfigField {\n key: string\n label: string\n placeholder?: string\n required?: boolean\n secret?: boolean // 비밀번호/토큰 — 마스킹 입력\n multiline?: boolean // JSON/매핑 등 여러 줄 입력(textarea)\n help?: string // 필드 아래 도움말\n default?: string // 폼 프리필 기본값(예시 매핑/URL 등)\n /*\n * 값이 정해진 목록 안에 있을 때 — 화면이 고르게 그린다(타이핑하지 않는다).\n *\n * 손으로 적는 칸에 닫힌 값을 받으면 오타가 그대로 저장되고, 그 오타는 오류로 나오지 않는다.\n * 발전소 종류(`sell` | `self-consumption`)가 그렇다 — 틀리면 계량 방향이 반대가 되는데 어디에도\n * 표시되지 않는다.\n *\n * `label` 은 번역 키를 쓸 수 있다(화면이 `meta()` 로 푼다). `value` 는 저장되는 값이라 번역하지 않는다.\n */\n options?: { value: string; label: string }[]\n /*\n * 값의 종류가 정해져 있지만 목록이 너무 커서 `options` 로 실을 수 없을 때 — 화면이 그 종류에 맞는\n * 고르는 칸을 그린다.\n *\n * 'timezone' IANA 시간대. 화면이 `Intl.supportedValuesOf('timeZone')` 로 목록을 만든다.\n * 400개가 넘고 표준이 갱신되므로 우리가 목록을 들고 있지 않는다.\n * 이름이 곧 값이자 표시라 번역하지 않는다.\n *\n * 자유 입력으로 두면 `Asia/seoul` 같은 오타가 저장되고, 그 오타는 오류로 나오지 않는다.\n */\n kind?: 'timezone'\n}\n/** 커넥터 메타데이터 — picker·연결 폼용 표시 정보. */\nexport interface AdapterMeta {\n label?: string\n domain?: string // 'wms' | 'yms' | 'mes' (분류)\n available?: boolean // false = 준비 중(picker 비활성)\n configSchema?: AdapterConfigField[]\n ootbNote?: string // OOTB 매핑 안내 문구\n /*\n * 근거 수준(정직성 1급 표기 — \"실제 API 명세에 근거하는가\"를 UI·피치가 오해 없이 보이게).\n * 'vendor-doc' = 벤더 공개 API 문서의 엔드포인트·필드명에 정합(docUrl 로 출처 명시). 실 시스템 검증은 별도.\n * 'standard' = 개방 표준(EPCIS 2.0 / ISA-95)에 정합 — 벤더 독립.\n * 'facsimile' = 시스템 어휘를 흉내낸 근사치(공개 문서 미정합). 실 연동 시 매핑 재정합 필요.\n * 미지정은 facsimile 로 간주(가장 보수적).\n */\n grounding?: 'vendor-doc' | 'standard' | 'facsimile'\n docUrl?: string // grounding='vendor-doc' 의 근거 문서 URL(출처 추적)\n}\n\n/**\n * 원본이 **선언하는 능력** — 「이 원본으로 무엇을 할 수 있나」 (ADR-0029 §8, 2026-08-19).\n *\n * ── 왜 능력으로 선언하나 ────────────────────────────────────────────────────\n * 실 WMS 는 스스로 사실을 낸다. 시뮬레이터는 **구동해 줘야** 움직인다 — 자극·속도·정지·초기화. 그런데\n * 계약에 그 자리가 없어서 통제가 `TwinEngine` 안에 뚫려 있었다(`controlTwinScenario` 가 런타임의 시나리오\n * 엔진을 직접 잡았다). 그러면 ADR-0029 의 판정 문장(*\"실물이 같은 계약으로 할 수 있는가\"*)에 걸린다:\n * 실 WMS 는 일시정지될 수 없으니 그 통제면은 영원히 「어댑터 규약의 결손」으로 남고, 규칙이 그 면을\n * **숨게** 만든다.\n *\n * 그래서 뒤집는다: 통제는 **선언된 능력**이다. 선언하지 않은 원본에는 그 문이 **아예 열리지 않는다** —\n * 그것이 결손이 아니라 **사실**이다(실 WMS 는 정지되지 않는다). 이 레포가 이미 그 규율로 굴러간다\n * (`capability-contract`: concrete 강결합 대신 능력 선언).\n *\n * · `live` — 돌아가는 피드를 낸다(`openLiveFeed`).\n * · `control` — 구동을 받는다(`control`) — 자극·속도·정지·초기화.\n */\n/**\n * 이 원본에 무엇을 요구할 수 있나.\n *\n * · `live` — 관측을 이어서 낸다\n * · `control` — 시뮬레이터를 움직인다(자극·배속·정지). 실 시스템은 갖지 않는다\n * · `actuate` — **승인된 조치를 받는다.** 현장에 작업지시가 나가는 유일한 축이다\n *\n * ── `actuate` 를 늦게 넣은 이유 (2026-09-02) ────────────────────────────────\n * 어댑터에 `actuate` 구현은 있었는데 이 축에 그 이름이 없었다. 그래서 셋이 동시에 조용했다.\n *\n * 화면 「이 원본이 지시를 받나」를 물을 수 없다 — 능력 목록에 그 이름이 없다\n * 등록 구현했는데 선언 안 한 것을 알려 주지 않는다 — 검사 목록에 그 이름이 없다\n * 넘김 리졸버가 어댑터를 찾지도 않고 무조건 거절했다 — 커넥터는 이미 만들어져 있었다\n *\n * 실제로 MES 커넥터가 `actuate` 를 다 만들어 두었는데, 넘기면 「구현이 없습니다」라고 답했다.\n * 자리는 있고 길이 없는 상태였고, 이 저장소에서 가장 자주 나는 결함 부류다.\n */\n/**\n * 커넥터가 선언하는 능력.\n *\n * `idempotent-actuation` 은 다른 셋과 성질이 다르다 — 무엇을 할 수 있나가 아니라 **두 번 해도\n * 같은가**다. 일꾼이 실패한 조치를 다시 넘길지 정할 때 이것만 본다(§`retryDecisionOf`).\n *\n * 선언하지 않으면 **거짓으로 읽는다.** 이 한 자리에서만 「모르면 안전한 쪽」이 거짓이고, 그\n * 안전한 쪽이 「재시도하지 않음」이다 — 참으로 가정하면 현장에 지시가 두 건 선다.\n *\n * 선언에는 근거가 있어야 한다. operato-plant 는 MES 가 `correlationId` 로 기존 행을 찾아 같은\n * 이름을 돌려주기 때문에 참이다 — 우리가 조심해서가 아니라 저쪽 동작이 그래서다. 저쪽이 그것을\n * 바꾸면 이 선언이 거짓이 된다.\n */\nexport type ReferenceCapability = 'live' | 'control' | 'actuate' | 'idempotent-actuation'\n\n/** 원본에 보내는 구동 명령 — **시뮬레이터를 움직이는 말**이지 트윈을 고치는 말이 아니다. */\nexport interface ControlCommand {\n /**\n * · `stimulus` — 자극을 싣는다(`scenario`). 선언은 원본 설정에 살고 이 명령은 지금 태우는 것이다.\n * · `start`·`pause` — 구동을 켜고 멈춘다.\n * · `speed` — 시각의 배속(`speed`).\n * · `reset` — 씨앗부터 다시(원본의 상태를 되돌린다 — 트윈의 저널은 재기동 정책이 정한다).\n */\n action: 'stimulus' | 'start' | 'pause' | 'speed' | 'reset'\n /** 자극 선언 — `action: 'stimulus'` 에만. 커널 규약(`validateScenario`)으로 검사된 값이어야 한다. */\n scenario?: any\n /** 배속 — `action: 'speed'` 에만. */\n speed?: number\n /**\n * 어느 트윈을 위한 구동인가 — 원본이 여러 트윈에 먹일 수 있으므로 대상을 함께 말한다.\n *\n * 인프로세스 시뮬레이터는 이 둘로 자기 런타임을 찾는다(테넌트까지 있어야 같은 id 의 두 트윈이 갈린다).\n * 원격 원본은 대개 무시한다(자기 세계가 하나다).\n */\n instanceId?: string\n domainId?: string\n}\n\n/** 구동 결과 — **왜 안 됐는지**를 코드로 말한다(화면이 옮긴다). */\nexport interface ControlResult {\n ok: boolean\n /** 언어중립 사유 — `not-declared`(능력 없음) · `unsupported-action` · `invalid`(선언 거절) · `no-target`. */\n code?: string\n detail?: string\n}\n\n/**\n * 한 흐름의 읽기 커서 — **어디까지 읽었나.**\n *\n * `since` 와 `seen` 이 **함께** 있어야 한다. 이어 읽기는 경계 시각을 겹쳐 읽고(`gte`), 그 겹침을\n * `seen` 이 걸러 낸다. `gt` 로 좁히면 같은 밀리초의 다른 행이 사라진다 — 들어온 것이 없는 누락이다.\n *\n * `seen` 이 예상보다 커지면(한 시각에 몰린 행 수가 늘면) 그것은 원본 쪽 신호다. 어댑터가 그 크기를\n * 말해야 한다 — 알리지 않고 커지게 두면 이어 읽기가 막히는 순간까지 아무도 모른다.\n */\nexport interface LiveFeedCursor {\n /** 그 흐름에서 마지막으로 본 시각(원본의 시각, ISO). */\n since?: string\n /** 그 시각에 이미 소비한 행 id — 겹쳐 읽은 것을 걸러 낸다. 저장되는 모양이므로 배열이다. */\n seen: string[]\n}\n\n/**\n * **트윈이 지금 들고 있는 물품 하나** — 붙는 커넥터가 견줄 근거(§`LiveFeedContinuity.observedItems`).\n *\n * 커널 어휘로 낸다. 원본의 낱말(그 시스템의 상태 코드)로 내지 않는 이유가 있다: 원본 낱말은 여러 개가\n * 커널의 한 값으로 모이므로(예: 네 상태가 「진행 중」 하나가 된다) 되돌릴 수 없다. 그리고 되돌릴\n * 필요도 없다 — 두 원본 상태가 커널에서 같은 값이면 **트윈이 든 사실은 바뀌지 않았다.**\n */\nexport interface ObservedItemFact {\n /** 트윈이 든 물품의 키 — 비직렬 재고는 `클래스@자리`. */\n id: string\n /** 클래스 식별자(원문). */\n epcClass?: string\n location?: string\n qty?: number\n uom?: string\n /** 표준 처분(CBV) — 원본 상태 코드가 아니다. */\n disposition?: string\n /** 유통기한(밀리초). */\n expiry?: number\n lot?: string\n}\n\n/**\n * 밀어 받은 본문 하나를 옮긴 결과 (2026-08-31).\n *\n * 레코드만 돌려주던 것을 넓혔다. 번호를 함께 받아야 프레임워크가 빠진 것을 알아챈다.\n *\n * **`scope` 가 없으면 `seq` 도 없는 것으로 다룬다.** 번호만 있고 그 번호가 어느 줄의 것인지 모르면\n * 커서를 무엇으로 잡을지 짐작해야 하고, 짐작한 열쇠는 보내는 쪽이 줄을 하나 더 늘리는 날 어긋난다.\n * 실제로 operato-plant 가 `[domain, channel]` 마다 번호를 매기면서 봉투에 `channel` 을 싣지 않고 있었다.\n */\n/** 번호 하나에 대해 원본이 아는 것. */\nexport interface SeqEntry {\n seq: number\n /**\n * `unknown` 은 결함이 아니다 — 채번을 따로 들지 않는 원본은 「행이 없다」에서 잃은 것과 아직 안\n * 간 것을 가를 수 없다. 그것을 `lost` 로 단정하면 사람이 없는 사실을 찾으러 간다.\n */\n state: 'present' | 'lost' | 'unissued' | 'unknown'\n /** 그 봉투의 발생 시각 — `present` 일 때만. 무엇이었는지 사람이 알아보는 데 쓴다. */\n at?: string\n /** 그 봉투 자체 — `present` 이고 원본이 낼 수 있을 때만. 없어도 `state` 는 참이다. */\n record?: unknown\n}\n\n/** 번호 구간에 대해 원본이 아는 것 — **원본이 아는 것만 낸다. 판정은 사람이 한다.** */\nexport interface SeqReport {\n scope: string\n entries: SeqEntry[]\n /** 상한에 닿아 구간을 다 못 냈다 — 알리지 않고 자르지 않는다. */\n truncated?: boolean\n}\n\nexport interface InboundBatch {\n /**\n * Receiver path for this batch. Omitted is FACTS, preserving every existing\n * connector; ATTENTIONS is a product-level analysis input and must not be\n * folded as a canonical fact.\n */\n lane?: 'FACTS' | 'ATTENTIONS'\n /**\n * 옮긴 것들. 우리와 무관한 본문이면 빈 배열이다.\n *\n * **한 요청에 봉투가 여럿 온다.** 밀어 주는 쪽은 한 건씩 보내지 않는다 — 번호도 봉투마다 붙는다.\n * 배치를 대표하는 번호 하나로는 가운데가 빠진 것을 알아채지 못한다.\n */\n items: InboundItem[]\n /**\n * 이 요청의 번호가 어느 줄의 것인가. 번호를 매기지 않는 원본은 주지 않는다.\n *\n * 요청 하나가 한 줄에만 속한다 — 한 요청에 두 줄을 섞으면 어느 줄의 어디까지 받았는지를 응답\n * 하나로 말할 수 없다.\n */\n scope?: string\n /**\n * (선택) **원본이 지금 가진 번호의 범위** — 우리 커서와 견주려면 이것이 있어야 한다.\n *\n * ── 왜 어댑터가 주나 (인티그레이션 레인 제안, 2026-09-06) ──────────────────\n * 「가장 낮은 번호」를 묻는 방법이 원본마다 다르다. operato-plant 는\n * `mesOutboxEvents(since: 0, limit: 1)` 이고, ppms·chef 는 아웃박스가 없어 번호 범위 자체가\n * 없다. 프레임워크가 이것을 알 방법이 없다.\n *\n * ── 모르면 주지 않는다 ────────────────────────────────────────────────────\n * **0 이나 추측을 넣지 않는다.** 0 을 넣으면 「원본이 1번부터 다 갖고 있다」로 읽히고, 그것이\n * 거짓이면 「우리가 다 받았다」는 잘못된 결론이 나온다.\n *\n * `last` 에는 조건이 하나 붙는다. 쪽 상한에서 멈췄으면 그때의 번호는 **원본의 끝이 아니라\n * 「여기까지 읽었다」**이므로 주지 않는다 — 주면 「원본에 더 없다」로 읽히는데 실제로는 더 있다.\n */\n sourceRange?: { first?: number; last?: number }\n}\n\nexport interface InboundItem {\n /** 우리 어휘로 옮긴 레코드 하나. */\n record: unknown\n /**\n * 원본이 이 봉투에 붙인 id — **떨어졌을 때 그쪽이 자기 행을 찾는 열쇠다.**\n *\n * ── 왜 필요한가 (2026-09-07 측정) ──────────────────────────────────────────\n * 훅 응답의 떨어진 목록이 `{ record, errors }` 였고, plant 은 그것을 **봉투 id 문자열 목록**으로\n * 읽고 있었다. `Array.isArray` 는 통과하니 그쪽 guard 도 안 걸렸다 — id 를 맞추는데 객체라 하나도\n * 안 맞고 **묶음 전체가 「보냈음」으로 찍혔다.** 422 와 `ok: false` 를 보냈는데도 그쪽 행은 재시도\n * 0 · 오류 없음이었다.\n *\n * 이 값을 주면 응답이 `{ eventId, errors }` 로 나가고 그쪽이 그 행만 표시할 수 있다. 안 주면\n * 레코드가 그대로 실려 나간다 — 무엇이 떨어졌는지 알 길이 그것뿐이고, 빈 id 를 실으면 보내는 쪽이\n * 아무 행도 못 찾으면서 「알았다」고 여긴다.\n */\n eventId?: string\n /**\n * 이 봉투의 번호.\n *\n * **한 배치 안에서 있거나 없거나 하나로 통일한다.** 섞이면 빠진 것이 있는지 판단할 근거가 없다.\n */\n seq?: number\n /**\n * 이 봉투가 말하는 **발생 시각** — 레코드가 자기 시각을 안 말할 때 쓴다.\n *\n * ── 왜 필요한가 (2026-09-06 측정) ─────────────────────────────────────────\n * 시각을 안 싣는 레코드는 `defaultEventTime`(= ingest 시각)으로 떨어진다. 그 값이 **실행할 때마다\n * 다르다.** 그런데 fact identity 가 시각을 포함하므로, 같은 사실을 다시 받으면 dedupe 가 안 되고\n * 새 사실로 앉는다.\n *\n * 실제로 그렇게 됐다. `fillTwinScope(from: 0)` 을 두 번 돌렸더니 mes-line-a 저널이\n * 40 → 69건이 되고 중복이 27가지 생겼다. 시각을 말하는 레코드 12건만 되풀이로 걸러졌다.\n *\n * ── 봉투는 그 시각을 알고 있었다 ──────────────────────────────────────────\n * plant 아웃박스가 `eventTime` 을 실어 보낸다. 그것이 갈 자리가 없어서 버려지고 있었다 —\n * connector 주석이 그 위험을 적어 두고 자리를 요청해 두었다(`operato-plant.ts`).\n *\n * **레코드가 자기 시각을 말하면 그것이 이긴다.** 봉투의 시각은 「그 사실을 언제 보냈나」에 가깝고,\n * 레코드의 시각은 「언제 일어났나」다. 둘이 다르면 뒤엣것이 맞다.\n */\n at?: string\n}\n\n/**\n * 재기동을 넘어 이어지는 읽기 상태 — 참조 계층이 소유하고 어댑터에 건넨다(§`openLiveFeed`).\n *\n * 흐름 열쇠는 **어댑터가 정한다**(`Record`). 계약이 이름을 닫으면 새 원본마다 계약을 고치게 된다.\n */\nexport interface LiveFeedContinuity {\n /**\n * The name to take a task lease under, so only one instance polls this feed.\n *\n * ── Why this layer composes it ────────────────────────────────────────────\n * `TaskLease` has no domain column on purpose: what is made exclusive is a\n * loop, not a tenant's slice of one, and a task that does need to be\n * per-tenant puts the tenant in the name so the unique index keeps meaning\n * what it says.\n *\n * An adapter cannot follow that rule. It is handed `cfg` and `site`, and\n * neither carries a domain — so a name built from `siteId` alone would let\n * two domains that happen to use the same plant code block each other's\n * channel. Silently, and looking exactly like \"someone else holds it\".\n *\n * So the name is composed here, where `domainId` and `instanceId` are known,\n * and the rule lives in one place.\n *\n * ── Always present, unlike `cursor` ───────────────────────────────────────\n * A feed needs its lease on the very first attach, before there is any\n * cursor to carry. So this object is now handed over even when nothing has\n * been read yet; `cursor` being absent is what still says \"first attach\",\n * which is how every connector already reads it (`continuity?.cursor`).\n */\n leaseName: string\n /** 지난번에 어디까지 읽었나 — 없으면 첫 붙음이다(그때만 되돌아볼 날수로 창을 만든다). */\n cursor?: { streams?: Record<string, LiveFeedCursor>; firstAttachedAt?: string }\n /**\n * **트윈이 지금 들고 있는 물품** — 붙는 커넥터가 「무엇이 바뀌었나」를 견줄 근거 (2026-08-28).\n *\n * ── 무엇을 고치나 ───────────────────────────────────────────────────────────\n * 전량 절대값을 읽는 커넥터는 지난 주기와 견주어 **바뀐 것만** 낸다. 그 비교 표가 프로세스 안에만\n * 있어서, 재기동하면 커넥터가 자기 기억을 잃고 **전량을 「바뀐 것」으로 판정**한다. 승화푸드에서 그\n * 한 번이 2,665건이었고, 재기동마다 지난 기록을 덮었다.\n *\n * **트윈은 잃지 않는다** — 저장본에서 되세우고 뜬다(실측 로그: `items 2665 · orders 4794`). 잃는\n * 쪽은 커넥터다. 그래서 트윈이 아는 것을 건네면 첫 주기부터 바뀐 것만 나간다.\n *\n * 없으면 `undefined` 다 — 그것이 「트윈이 아무것도 모른다」는 사실이고, 그때만 전량이 새 사실이다.\n * 빈 배열로 메우지 않는다(그러면 「모른다」와 「없다」가 같아진다).\n */\n observedItems?: ObservedItemFact[]\n /**\n * 커서가 움직였다고 알린다 — 이 층이 저장한다.\n *\n * 어댑터가 **매 행마다 부르지 않는다**: 저장은 이 층의 몫이고, 잦으면 이 층이 눌린다. 흐름 하나가\n * 한 묶음을 소비한 뒤 한 번이면 충분하다.\n */\n onCursor?: (cursor: { streams: Record<string, LiveFeedCursor>; firstAttachedAt?: string }) => void\n /**\n * **원본에 닿지 못했다** — 어댑터가 읽기 실패를 알린다(2026-08-23).\n *\n * ── 무엇이 아무 신호도 내지 않았나 ─────────────────────────────────────────────────────────\n * 실 원본이 끊겼을 때(접속 시간 초과) 트윈의 **조회 가능한 상태 어디에도** 그 사실이 없었다.\n * `onRecords` 가 불리지 않으면 유입 장부에 아무 일도 일어나지 않고, `TwinReference.lastError` 는\n * 마스터 동기·접속 시험에만 적힌다. 그래서 화면이 볼 수 있는 것은 「새 사실이 없다」뿐이었고,\n * 그것은 **「연결된 시스템에서 들어온 것이 없다」와 구별되지 않는다.**\n *\n * 어댑터는 로그로 말하고 있었다(재시도 경고). 그러나 **로그는 사람이 볼 때만 값이 있다** — 화면이\n * 말하려면 상태에 있어야 한다. 이 통로가 그 자리다.\n *\n * ── 무엇을 알리고 무엇을 알리지 않나 ────────────────────────────────────────\n * **재시도를 다 쓰고 그 주기를 포기했을 때** 부른다 — 재시도마다 부르면 한 번의 흔들림이 단절로 보인다.\n * 흐름 여럿을 읽는 원본은 흐름 이름을 함께 준다(어느 표가 막혔나가 원인 찾기의 절반이다).\n *\n * **빈 읽기는 실패가 아니다** — 원본이 「새 것이 없다」고 답한 것은 닿았다는 뜻이므로 `onRecords([])`\n * 로 알린다. 그 둘을 섞으면 들어온 것이 없는 연결이 끊긴 원본으로 보인다(고치려던 것의 반대 방향으로 틀린다).\n *\n * ── 무엇을 어댑터가 보내고 무엇을 이 층이 세나 (2026-08-27) ─────────────────\n * 어댑터는 **이유**만 보낸다 — 그리고 물러섰다면 다음에 다시 물을 때까지의 시간을 함께 보낸다.\n *\n * 연달아 몇 번 실패했는지와 언제부터 실패하고 있는지는 **이 층이 이미 센다**(`recordReadFailure`).\n * 그것을 어댑터가 함께 보내면 같은 수를 두 곳에서 세게 되고, 갈라지면 어느 쪽이 사실인지 알 수 없다.\n *\n * `reason` 에 **완성된 문장을 담지 않는다.** 이 값은 화면에 그려지고, 화면은 다섯 언어로 그린다.\n * 「3회 연달아 …부터 읽지 못했습니다」처럼 한 언어의 문장을 넣으면 그 문장은 번역되지 않고, 이 층이\n * 세는 수와 어긋날 수도 있다. 이유 하나만 짧게 준다(접속 시간 초과 · 401 · 형식 오류).\n */\n onReadFailure?: (info: {\n reason: string\n stream?: string\n /** 다음에 다시 물을 때까지 남은 시간(ms) — 물러섰을 때만 준다. */\n nextRetryMs?: number\n }) => void\n /**\n * **끊겼다가 돌아왔다** — 어댑터가 회복을 알린다 (2026-08-27).\n *\n * ── 왜 따로 알려야 하나 ─────────────────────────────────────────────────────\n * 읽기가 다시 성공하면 실패 기록은 지워진다. 그것만 하면 「끊긴 적이 있었다」가 화면에서 사라지고,\n * 밤새 두 시간 끊겼던 연결과 한 번도 끊기지 않은 연결이 아침에 똑같이 보인다.\n *\n * 읽기 성공(`onRecords`)만으로 이 층이 회복을 유추하지 않는다 — 몇 번 실패한 뒤였는지와 얼마나\n * 끊겨 있었는지는 재시도를 관리하는 어댑터가 안다.\n */\n onRecovered?: (info: { afterFailures: number; downMs: number }) => void\n /**\n * **읽었는데 창을 넘길 수 없다** — 어댑터가 커서 정체를 알린다 (2026-08-24).\n *\n * ── 왜 `onReadFailure` 와 갈라야 하나 ───────────────────────────────────────\n * 이 문을 만들기 전에는 두 사실이 **같은 이름으로** 나갔다.\n *\n * 원본에 닿지 못했다 접속 실패·시간 초과·형식 오류 → 기다리면 풀린다\n * 읽었는데 커서가 못 넘어간다 한 시각에 한 페이지보다 많은 행이 몰려 있다 → **기다려도 안 풀린다**\n *\n * 둘째는 **읽기가 성공한 실패**다. 원본은 답했고, 그 답의 모양이 커서를 이긴 것이다. 그런데 화면이\n * 「원본에 닿지 못한다」고 말하면 사람을 반대 방향으로 보낸다 — 원본을 의심하고 기다린다. 실제로\n * 필요한 조치는 **페이지를 키우거나 같은 시각 안에서 순서를 정하는 것**이고, 기다림으로는 영원히\n * 풀리지 않는다.\n *\n * 조치가 반대인 두 사실을 한 이름으로 부르면, 그 이름은 정보가 아니라 오해다.\n *\n * ── 무엇을 알리나 ───────────────────────────────────────────────────────────\n * 그 주기를 포기했을 때 부른다(`onReadFailure` 와 같은 규율). 어느 흐름인지 알면 함께 준다 —\n * 밀도가 높은 표는 원본마다 다르므로 그 이름이 조치의 절반이다.\n *\n * **닿지 못한 것과 섞어 부르지 않는다.** 하나의 주기가 두 이유로 실패할 수는 없다(먼저 닿아야 읽는다).\n */\n onCursorStall?: (info: { reason: string; stream?: string }) => void\n /**\n * **원본에 있는데 세우지 않은 것을 알린다** — 이유와 수.\n *\n * ── 왜 이 통로가 필요한가 (2026-08-24) ─────────────────────────────────────\n * 어댑터가 원본의 사실 일부를 **일부러 받지 않는 일이 정상이다**(입자가 안 맞는다 · 받으면 상태가\n * 거짓이 된다 · 커널 어휘로 옮길 수 없다). 그때 어댑터는 경고를 냈는데, 그 경고가 **프로비저닝\n * 화면에서 한 번 스쳐 지나갈 뿐**이었다 — 그 뒤로는 어디에서도 볼 수 없었다.\n *\n * 그래서 사용자가 「원본보다 이것이 적다」를 물으면 답이 어디에도 없었다. 그 사실은 한 번의 사건이\n * 아니라 **상태의 성질**이다: 세우지 않은 것은 **지금도** 트윈에 없다.\n *\n * **주기마다 불러도 된다** — 장부가 이유로 묶어 세고 마지막 수로 덮는다(누적하지 않는다). 그래서\n * 「지금 세우지 않은 것이 8건」이 8로 남고 800으로 부풀지 않는다.\n *\n * **풀리면 부르지 않는 것으로 끝나지 않는다** — 이유가 사라졌으면 `count: 0` 으로 부르는 것이 아니라\n * 그 이유를 더 이상 보내지 않으면 된다… 가 **아니다.** 장부는 마지막 값을 들고 있으므로, 풀린 것을\n * 알리려면 그 이유로 `count: 0` 을 한 번 보내라(그때 줄이 사라진다). 알리지 않고 그치면 낡은 수가 남는다.\n *\n * 이유는 **자유 문자열**이다 — 무엇을 왜 안 받는지는 원본마다 다르고, 계약이 목록을 닫으면 새 원본을\n * 붙일 때마다 계약을 고치게 된다(흐름 열쇠와 같은 규율). 사람이 읽을 문장으로 적어라.\n */\n onWithheld?: (info: { reason: string; count: number }) => void\n}\n\n/**\n * 저쪽이 조치를 받을 준비가 됐나 — **「모른다」를 「안 된다」로 답하지 않는다.**\n *\n * `ready` 가 거짓이면 `reason` 이 사람의 말로 무엇이 없는지 말하고, `code` 가 화면이 다음 할 일을\n * 가를 값이다(문장을 파싱하지 않게 — 번역하면 문장이 바뀐다).\n */\nexport interface ActuationReadiness {\n /** 받을 수 있어 보이나. **모르면 `undefined`** — 거짓이 아니다. */\n ready?: boolean\n /** 어디까지 봤나. `local` = 우리 설정만 봤다(망을 타지 않았다). `remote` = 저쪽에 물었다. */\n checked: 'local' | 'remote'\n /** 무엇이 없나 — 사람의 말로. */\n reason?: string\n /** 화면이 가를 값. 커넥터가 정한다(예: `no-endpoint` · `no-secret` · `unreachable` · `not-configured`). */\n code?: string\n}\n\nimport type { LiveCadence } from './live-cadence.js'\n\n/**\n * 조치를 넘긴 결과 — **어댑터가 돌려주는 것.**\n *\n * 이름을 붙인 이유는 두 가지다. 익명 타입이라 소비처가 그 모양을 말할 수 없었고(`actuate` 를 감싸는\n * 쪽이 반환형을 다시 적어야 했다), 그리고 **거절 사유를 코드로 실을 자리가 없었다** — `error` 는 사람\n * 문장이라 저장하면 번역과 판독이 함께 굳는다.\n *\n * `errorCode`·`errorParams` 는 `CommandAck` 과 **같은 낱말**이다(ADR-0037 ④ · ADR-0042 ③). 셋째 이름을\n * 만들지 않는다 — 같은 것을 세 이름으로 부르면 매핑 표가 생기고, 그 표는 커넥터가 새 코드를 내는 날\n * 「알 수 없는 사유」로 떨어진다.\n */\nexport interface ActuationResult {\n ok: boolean\n ref?: string\n /**\n * **왜 거절됐나 — 언어 중립 코드.** 저장되는 값은 이것이고 `error` 는 아니다.\n *\n * `lastError` 가 사람 문장을 저장해 두고 화면이 그것을 그대로 뿌렸다. 그러면 번역도 못 하고,\n * 무엇이 났는지 세지도 못하고, 문장을 고치는 순간 옛 행과 새 행이 달라진다.\n */\n errorCode?: RefusalCode\n /** 그 코드가 가리키는 원시값들 — 표현층이 문장에 끼운다. */\n errorParams?: RefusalParams\n /**\n * **받아들였으나 남은 것이 있다** — 실패가 아니다. 사람이 저쪽에서 해야 할 일이 있으면 여기 적는다.\n *\n * `error` 에 적지 말 것 — 실패로 읽혀 커맨드가 `failed` 로 앉고, 다시 넘기게 된다. 실제로 MES 가\n * 「지시서를 못 붙였다」를 답했을 때 커넥터가 적을 칸이 없어 로그로만 남겼고, 트윈 쪽에서는 성공한\n * 조치와 구별되지 않았다.\n */\n note?: string\n /**\n * 그 말 중 **다음에 할 일** 한 줄 — 원인은 위 `note` 다.\n *\n * 붙여 보내지 말 것. 읽는 사람은 「그래서 내가 뭘 해야 하나」를 먼저 찾고, 한 문장으로 오면\n * **화면이 자르게 되며 자르는 규칙이 화면마다 생긴다.** 어댑터는 이미 둘로 알고 있다.\n */\n noteNext?: string\n error?: string\n /**\n * **다시 해서 될 일인가** — 실패했을 때만.\n *\n * again 그대로 다시 해 볼 만하다 못 닿았거나 저쪽이 잠깐 흔들렸다\n * after-fix 사람이 고친 뒤 그대로 나간다 설정 문제\n * never 이 조치로는 영원히 안 된다 지시 내용이 틀렸다\n *\n * 가르는 자리는 **「조치를 다시 낼 필요가 있나」**다. 설정이 틀린 것은 조치가 멀쩡하므로\n * `after-fix`, 지시 내용이 틀린 것은 그 조치가 영원히 틀렸으므로 `never` 다.\n *\n * **문장에 담지 말 것** — 일꾼이 그것을 쓰려면 파싱해야 하고, 번역되면 깨진다.\n *\n * 말하지 않으면 일꾼이 **집지 않는다.** 모르는 것을 `never` 로 접으면 고칠 수 있는 것을 사람이\n * 포기하고, `again` 으로 접으면 없는 자재를 끝없이 두드린다.\n */\n retry?: 'again' | 'after-fix' | 'never'\n}\n\nexport interface ReferenceAdapter {\n /** 레지스트리 키 (예: 'virtual' | 'sap-ewm' | 'custom-rest'). */\n type: string\n /** picker·연결 폼용 메타데이터(선택). 없으면 type 만 노출. */\n meta?: AdapterMeta\n /** 연결 확인 — UI '연결 테스트'. */\n testConnection(cfg: ConnectionConfig): Promise<{ ok: boolean; error?: string }>\n /** N 발견 — 사이트/공장 열거. 단일 소스는 길이 1 반환. */\n discoverSites(cfg: ConnectionConfig): Promise<SiteDescriptor[]>\n /** 사이트별 마스터(구조) — 기존 masterToTwin 이 소비. */\n /**\n * 이 어댑터가 마스터를 읽으며 **보낼 단계를 미리 선언한다** — 그것이 진행률의 분모다.\n *\n * 표준 낱말만 쓴다(`REFERENCE_STEPS`). 선언하지 않으면 화면은 퍼센트를 보이지 않고 단계 이름만\n * 보인다 — **모르는 진행률을 지어내지 않는다.**\n *\n * 원본에 없는 자리는 선언하지 않는다. 그러면 「이 원본으로 채울 수 있는 만큼」이 곧 분모가 된다.\n */\n masterSteps?: readonly ReferenceStep[]\n\n /**\n * 이 원천이 **축마다 언제·얼마나 자주 내놓나** — 유입 건강이 이 선언으로 견준다.\n *\n * 선언하지 않은 축은 **지금까지처럼** 창 하나(10분)로 견준다. 그러면 밤에 발전하지 않는\n * 태양광과 업무시간만 도는 원천이 **정상인데 매일 밤 빨갛게** 난다 — 거짓 빨강이 쌓이면 진짜\n * 빨강도 같이 안 읽힌다.\n *\n * `masterSteps` 와 같은 규율이다: **어댑터만 자기 시간의 결을 안다.** 호스트가 짐작하면 그것은\n * 지어낸 판정이다.\n *\n * 자세한 것은 §`live-cadence.ts` — 특히 「해 있는 동안」을 **고정 시각으로 박지 말 것**\n * (오늘 잰 일몰은 두 달 뒤에 틀리다).\n */\n liveCadence?: readonly LiveCadence[]\n\n /**\n * @param onStep 진행을 알리는 통로 — **선택이다.** 주지 않으면 예전과 같이 동작한다.\n *\n * 마스터 읽기는 원본에 여러 질의를 보내는 긴 작업이고(창고·자리·설비·공정·경로·품목·BOM), 화면은 그\n * 사이에 아무것도 알 수 없었다. 어댑터만 자기 단계를 아므로 어댑터가 말해야 한다 — 호스트가 짐작하면\n * 그것은 지어낸 진행률이다.\n *\n * `total` 은 **그 어댑터가 보낼 단계 수**다. 모르면 주지 않는다(화면이 퍼센트를 만들지 않는다).\n */\n fetchMaster(\n cfg: ConnectionConfig,\n site: SiteDescriptor,\n onStep?: (step: { key: ReferenceStep; done: number; total?: number; detail?: string }) => void\n ): Promise<ReferenceMaster>\n /**\n * (선택) 사이트별 실 이벤트 스트림 — 없으면 sim. onRecords 로 레코드 push, unsubscribe 반환.\n *\n * ── 네 번째 인자: **읽기 커서** (2026-08-23) ────────────────────────────────\n *\n * 왜 이 층이 커서를 드나: 커널이 그 갈림을 이미 적어 두었다(§`hydrateContinuity`) — 「원천이 **애초에\n * 다시 말해 주지 않는 축**」은 재기동 연속성으로 이어받는다. **「우리가 어디까지 읽었나」가 정확히 그\n * 성질이다.** 원본은 그것을 되풀어 주지 않는다.\n *\n * 커서가 없던 동안 어댑터는 붙을 때마다 `Date.now() − 되돌아볼 날수` 로 창을 새로 만들었다. 그래서\n * **재기동마다 미러의 과거가 잘렸다** — 실측으로 작업 2,855 → 2,820(8시간 흐른 만큼). 미러가 아는\n * 것이 「원본의 사실」이 아니라 「창의 함수」였다.\n *\n * 그런데 미러의 상태는 **원천으로 서야 한다**(같은 절: 「미러의 진실은 원천이다 … 심으면 떠난 물건이\n * 되살아난다」). 그러니 저널을 계산해 상태를 세우는 것으로 고칠 일이 아니다 — 그 선언의 **전제**가\n * 「원천이 다시 말해 준다」이고, 창이 좁아지면 그 전제가 깨진다. 창을 고치는 것이 그 원칙을 지키는 길이다.\n *\n * ── 계약의 모양 ─────────────────────────────────────────────────────────────\n * · **흐름 열쇠는 어댑터가 정한다.** 원본마다 흐름 수와 뜻이 다르다(이 원본은 재고·오더·로트·투입 넷).\n * 계약이 이름을 닫으면 새 원본마다 계약을 고치게 된다 — 원본의 스키마가 계약을 끌고 가는 그 모양이다.\n * · **저장되는 모양으로 넘긴다**(`seen` 은 배열). 이 층은 저장·복원만 하고 안을 해석하지 않는다.\n * `Set` 을 받으면 이 층이 직렬화를 알게 되고, 커서 모양이 바뀔 때 두 곳을 고친다.\n * · `firstAttachedAt` — **첫 창을 만든 시각.** 이후 재기동에서 바뀌지 않는다. 커널은 아는 구간의\n * 상한만 안다(`nowTime` = 마지막으로 들은 시각). 이 값이 하한이고, 둘이 「이 트윈이 아는 구간」이다.\n * 화면이 수를 보일 때 그 구간을 함께 말해야 한다 — 말하지 않으면 알리지 않고 자르는 것이 된다.\n * · **커서가 없을 때만** 되돌아볼 날수를 쓴다. 그 순간이 `firstAttachedAt` 이다.\n *\n * 주지 않는 호출자와도 함께 선다(선택 인자) — 커서를 모르는 배포는 예전처럼 동작한다.\n */\n /**\n * (선택) **연결된 시스템이 밀어 준 것**을 우리 레코드로 옮긴다 — 웹훅.\n *\n * ── 왜 `openLiveFeed` 와 나누나 (2026-08-26) ───────────────────────────────\n * `openLiveFeed` 는 「가져오기」와 「옮기기」를 한 함수에 담고 있다. 그래서 가져오는 방식이 바뀌면\n * (그쪽이 밀어 주면) 옮기는 코드를 쓸 수 없었다 — 같은 파일 안에 있는데 밖에서 부를 수 없다.\n *\n * 이 함수는 **옮기기만** 한다. 밖에서 받은 본문을 받아 우리 레코드 목록을 돌려준다. 입구·인증·유입은\n * 프레임워크가 맡는다(§`reference-hook`).\n *\n * ── 옮길 수 없으면 던진다 ─────────────────────────────────────────────────\n * 빈 목록과 「옮길 수 없다」는 다르다. 빈 목록은 「우리와 무관한 것이 왔다」이고, 던지는 것은 「그쪽\n * 모양이 우리가 아는 것과 다르다」다. 그 둘을 같게 답하면 그쪽 모양이 바뀐 것을 아무도 모른다.\n *\n * ── 판단은 여기서 한다 ────────────────────────────────────────────────────\n * 통신이 끊긴 설비의 값을 보내지 않는 것, 잰 시각이 나아가지 않은 줄을 거르는 것 — 폴링에서 하던\n * 판단을 여기서도 한다. 그래야 두 길이 같은 답을 낸다.\n *\n * ── 번호는 프레임워크가 본다 (2026-08-31) ─────────────────────────────────\n * 빠진 번호를 알아채는 일은 밀어 주는 모든 연결에 같은 규율이라야 한다. 커넥터마다 만들면 한 곳이\n * 빠지고, 빠진 그 연결에서만 사실이 없어진다. 그래서 커넥터는 **번호를 꺼내 주기만** 하고 판정은\n * 프레임워크가 한다(§`reference-hook`).\n */\n handleInbound?(cfg: ConnectionConfig, site: { siteId: string }, body: unknown, headers: Record<string, unknown>): InboundBatch\n\n /**\n * **현장에 조치를 내린다** — 인바운드의 반대 면(§`command-routing.md` §8.1).\n *\n * 별개 어댑터를 두지 않는다. 같은 연결이 사실을 읽고 조치를 내리므로, 인증과 설정이 한 자리에 있어야\n * 한다. 둘로 나누면 같은 시스템에 두 벌의 연결 설정이 생긴다.\n *\n * ── 승인은 이미 지났다 (2026-08-31) ───────────────────────────────────────\n * 인자가 `ApprovedCommand` 다 — 그 표식은 승인을 지난 커맨드에만 붙고, 디스패처가 붙인다\n * (§`command-dispatcher.ts`). **어댑터는 승인을 다시 확인하지 않는다.** 확인이 두 곳에 있으면\n * 규칙이 두 벌이 되고 한쪽만 고쳐지는 날이 온다.\n *\n * 이 자리를 게이트보다 먼저 열지 않은 이유가 있다 — 자리가 있으면 누군가 부르고, 그때 사람 승인 없이\n * 현장에 작업지시가 나간다. 작업지시는 취소해도 이미 만든 것이 남는다.\n *\n * ── 돌려줄 것 ─────────────────────────────────────────────────────────────\n * `ref` 를 **반드시** 돌려준다. 그것이 없으면 넘긴 것이 저쪽에서 무엇이 되었는지 되짚을 수 없고,\n * 되돌릴 때 무엇을 되돌릴지 말할 수 없다.\n */\n actuate?(cfg: ConnectionConfig, site: SiteDescriptor, command: ApprovedCommand): Promise<ActuationResult>\n\n /**\n * **저쪽이 조치를 받을 준비가 됐나** — 보내기 전에 묻는다. 선택이다.\n *\n * ── 왜 필요한가 (2026-09-03 실측) ──────────────────────────────────────────\n * 승인된 조치를 넘겼더니 저쪽이 503 을 답했다 — **저쪽 프로세스에 비밀값이 실려 있지 않았다.**\n * 트윈 쪽 연결 설정에는 있었고, 주소도 맞았고, 서명도 맞았다. 저쪽이 재기동되면서 환경 변수에만\n * 살던 값을 잃은 것이다.\n *\n * 그것을 보내 보기 전에는 알 수 없었다. 그래서 사람이 승인 화면까지 가서 누르고, 실패를 보고,\n * 다시 로그인했다. **승인이 사라지지는 않는다**(`failed → dispatch` 가 열려 있다) — 없어진 것은\n * 사람의 시간이다.\n *\n * ── 문이 아니다. 알림이다 ──────────────────────────────────────────────────\n * **이 답으로 넘김을 막지 않는다.** 막으면 새 실패 방식이 생긴다 — 점검이 틀렸거나 잠깐 못 닿은\n * 사이에, 성공할 수 있었던 조치가 못 나간다. 화면이 「지금 저쪽이 못 받는 것으로 보입니다」를\n * 미리 말하는 데까지가 이 얼굴의 일이다.\n *\n * ── 두 겹을 구별한다 ──────────────────────────────────────────────────────\n * 우리 쪽에 무엇이 없는 것(주소·비밀값을 설정하지 않았다)은 **망을 타지 않고** 알 수 있다.\n * 저쪽이 받을 준비가 됐는지는 물어야 안다. 앞엣것만 답하고 뒤엣것을 안 물어도 되고, 그때\n * `checked: 'local'` 로 그 사실을 말한다 — 「살아 있다」고 말한 적 없는 것과 「죽었다」는 다르다.\n *\n * 화면이 그릴 때마다 부를 수 있으므로 **망을 타는 구현은 짧게 끝내야 한다.**\n */\n actuationReadiness?(cfg: ConnectionConfig, site: SiteDescriptor): Promise<ActuationReadiness>\n\n /**\n * **놓친 구간을 backfill 한다** — 밀어 주는 길의 짝.\n *\n * ── 왜 필요한가 (2026-08-31, 인티그레이션 레인 지적) ──────────────────────\n * 훅은 반드시 놓친다 — 우리가 내려가 있을 때, 그쪽이 못 보냈을 때, 네트워크가 끊겼을 때. 받는 쪽이\n * 번호로 그것을 **알아채게** 되었지만(§`takeInSequence`), 알아챈 뒤 **그 사이를 채우는 길이 없었다.**\n * 설계가 그 길을 적어 두었는데 만들지 않았다.\n *\n * ── 왜 `since` 가 번호인가 ────────────────────────────────────────────────\n * 커서에 사는 것이 번호이고, 보내는 쪽이 되풀어 주는 단위도 번호다. 시각으로 두면 두 축이 섞이고,\n * 같은 밀리초의 두 행이 갈리지 않는다.\n *\n * ── 왜 `scope` 를 프레임워크가 주나 ───────────────────────────────────────\n * 그것이 커서 열쇠의 절반이다. 커넥터가 만들면 열쇠를 만드는 자리가 둘이 되고, 어긋난 그곳은 조용하다.\n * 그래서 프레임워크가 커서에서 읽어 그대로 넘긴다.\n *\n * 답이 `InboundBatch` 인 이유도 하나다 — **backfill 한 것이 밀어 받은 것과 같은 번호 검사를 지난다.**\n * 다른 모양으로 돌려주면 그 검사를 다시 만들게 되고, 두 길이 다른 규칙으로 받는다.\n */\n fetchSince?(cfg: ConnectionConfig, site: SiteDescriptor, scope: string, since: number): Promise<InboundBatch>\n\n /**\n * (선택) **원본에만 있는 관계를 조회한다** — 트윈으로 복제하지 않고 그때그때 물어본다.\n *\n * ── 왜 필요한가 ─────────────────────────────────────────────────────────────\n * 트윈은 표준 어휘로 옮길 수 있는 것만 담는다. 원본에는 그 밖의 관계가 있다 — MES 의 생산 실적,\n * 설비 상태 구간, 정비 계획. 설비 상세 화면에서 사람이 실제로 묻는 것이 그런 것들이다.\n *\n * 이것을 트윈으로 복제하면 원본의 표를 전부 미러링하게 된다. 조회용 참조는 복제하지 않고 그때\n * 물어보는 것이 맞다.\n *\n * ── 왜 어댑터가 답하나 ──────────────────────────────────────────────────────\n * 어느 테이블에 무엇이 있는지는 원본마다 다르다. 커널이 그것을 알면 원본 하나의 스키마에 묶이고,\n * 다음 원본이 다른 구조를 쓰면 커널을 또 고친다.\n *\n * 어댑터는 이미 그 원본에 붙는 방법과 인증을 갖고 있다. 연결 설정이 한 자리에 있어야 한다는 것은\n * `actuate` 를 별개 어댑터로 두지 않은 것과 같은 이유다.\n *\n * ── 실패하면 그것을 알린다 ──────────────────────────────────────────────────\n * 예외를 던져도 됩니다. 호출하는 쪽이 잡아서 「조회 실패」로 표시하고, 나머지 관계는 그대로\n * 표시합니다. 빈 배열은 「가리키는 것이 없다」는 뜻이고 실패와 다릅니다.\n *\n * @param axis `equipment` · `locations` 등 계약의 축 이름\n * @param itemId 그 축에서의 식별자. 원본이 아는 이름이다(설비는 `ops_equipment.name`)\n */\n fetchLinks?(cfg: ConnectionConfig, site: SiteDescriptor | undefined, axis: string, itemId: string): Promise<ReferenceLinkGroup[]>\n\n /**\n * (선택) **그 번호가 무엇이었나** — 구멍을 만났을 때 묻는다.\n *\n * ── 왜 필요한가 (2026-09-06 실물) ──────────────────────────────────────────\n * 커넥터가 구멍을 만나면 「418 다음이 420」이라고만 말한다. **419 가 무엇이었는지 알 방법이\n * 없다.** 그날 plant 레인이 자기 아웃박스를 손으로 뒤져서 그것이 부하 시험의 흔적임을 찾았다.\n *\n * 구멍은 채널을 세운다 — 그 뒤의 사실이 통째로 못 온다. 그래서 「이게 무엇이었나」가 급한 물음인데\n * 물을 자리가 없었다.\n *\n * ── 세 가지를 가른다. 넷째는 「모른다」다 ──────────────────────────────────\n * ```\n * present 그 행이 있다 — 봉투를 함께 낸다\n * lost 번호는 나갔는데 행이 없다 — 쓰기 하나를 잃었다\n * unissued 아직 그 번호까지 안 갔다 — 잃은 것이 아니다\n * unknown 원본이 그 셋을 구별하지 못한다\n * ```\n *\n * **`unknown` 이 있어야 한다.** 채번을 따로 들지 않는 원본은 「행이 없다」에서 `lost` 와\n * `unissued` 를 가를 수 없다. 그때 `lost` 로 단정하면 사람이 없는 사실을 찾으러 간다.\n *\n * ── 판정을 여기서 하지 않는다 ─────────────────────────────────────────────\n * 이 문은 **원본이 아는 것을 그대로 낸다.** 「그러니 이 번호는 포기해도 된다」는 판정은 사람이\n * 한다 — 그 판단은 되돌릴 수 없어서(커서가 지나가면 끝이다) 코드가 대신할 자리가 아니다.\n *\n * @param from 이 번호부터(포함)\n * @param to 이 번호까지(포함). 원본이 상한을 두면 그만큼만 내고 `truncated` 로 말한다\n */\n seqReport?(cfg: ConnectionConfig, site: SiteDescriptor | undefined, scope: string, from: number, to: number): Promise<SeqReport>\n\n openLiveFeed?(\n cfg: ConnectionConfig,\n site: SiteDescriptor,\n onRecords: (records: unknown[]) => void,\n continuity?: LiveFeedContinuity\n ): () => void\n /**\n * 이 원본이 **선언하는 능력** — 없으면 「구조만 준다」는 뜻이다(ADR-0029 §8).\n *\n * 선언과 구현이 어긋나지 않게 `capabilitiesOf` 가 둘을 함께 본다: `control` 을 선언했는데 `control`\n * 구현이 없으면 그 능력은 **없는 것으로 읽는다**(선언만 있는 능력은 사용자에게 거짓말이 된다).\n */\n capabilities?: ReferenceCapability[]\n /**\n * 이 커넥터가 **무엇에 대고 확인됐나** — 능력과 다른 축이다(2026-08-23).\n *\n * ── 왜 필요한가 ─────────────────────────────────────────────────────────────\n * `capabilities` 는 「무엇을 할 수 있다고 선언하나」이고, 이 자리는 **「그 선언이 무엇으로 확인됐나」**다.\n * 둘이 갈리는 것이 지금 상태다: 등록된 커넥터 열넷 중 실 시스템에 붙어 본 것은 **하나**이고, 나머지는\n * 목(mock)에 대고 만들었다. 그런데 화면의 커넥터 목록에 그 구분이 없어서 **목으로 확인한 것이 확인으로\n * 보인다.**\n *\n * 그것은 이 저장소가 다른 자리에서 계속 거절하는 모양이다 — 「선언만 있는 능력은 사용자에게\n * 거짓말이 된다」(§`capabilitiesOf`). 능력에 세운 그 규율을 근거에도 세운다.\n *\n * ── 왜 계약이고 배포 노트가 아닌가 ──────────────────────────────────────────\n * 노트는 다음 사람이 보지 않는다. 계약에 있으면 새 커넥터가 그 값을 **빠뜨릴 수 없고**, 빠뜨리면\n * `groundingOf` 가 `unknown` 으로 답해 화면이 그 사실을 말한다.\n *\n * ── 자동으로 알 수 없는 값이다 ──────────────────────────────────────────────\n * 능력은 구현이 있는지 보면 알 수 있다. 「실 시스템에 붙여 봤나」는 **볼 수 없다** — 사람이 아는 사실이다.\n * 그래서 이 선언은 낡을 수 있고, 그것을 막는 것이 `verifiedAgainst` 다: **무엇에 대고 확인했는지 적지\n * 않으면 그 주장은 주장이 아니다**(`groundingOf` 가 `unknown` 으로 내린다).\n */\n grounding?: ReferenceGrounding\n /**\n * (선택) 구동 — 자극·속도·정지·초기화. **`capabilities` 에 `control` 을 선언한 원본만** 가진다.\n *\n * 실 시스템은 이것을 갖지 않는다(실 WMS 는 일시정지되지 않는다). 그 사실이 결손이 아니라 이 축의 요점이다.\n */\n control?(cfg: ConnectionConfig, site: SiteDescriptor | undefined, command: ControlCommand): Promise<ControlResult>\n}\n\n/**\n * 이 원본이 **실제로 할 수 있는 것** — 선언과 구현의 교집합.\n *\n * 선언만 있고 구현이 없으면 화면이 「할 수 있다」고 말한 뒤 아무 일도 일어나지 않는다. 반대로 구현이\n * 있는데 선언이 없으면 그 능력은 **의도적으로 닫아 둔 것**이므로 열지 않는다 — 선언이 권위다.\n */\n/**\n * 근거 수준 — **무엇에 대고 확인했나.** 넷이고 닫혀 있다.\n *\n * `synthetic` 실 시스템이 아니다 — 스스로 만들어 낸다(가상 원본·시뮬레이터). 한계가 아니라 성질이다.\n * `mock` 목·목 REST 에 대고만 확인했다. **실 시스템에 붙어 본 적이 없다.**\n * `instance` 실 시스템 **한 배포**에 붙여 확인했다. 그 배포의 성질과 제품의 성질을 아직 가르지 못했다.\n * `product` 실 시스템 **여러 배포**에 붙여 확인했다 — 그때 비로소 제품의 성질이라 말할 수 있다.\n *\n * `instance` 와 `product` 를 가르는 이유: 첫 실 연동에서 그 배포만의 성질이 넷 드러났고(관계 리솔버가\n * 다른 테넌트의 행을 답한다 · 모르는 필터를 알리지 않고 무시한다 · 지워진 품목의 BOM 이 남아 있다 ·\n * 사실상 무한을 뜻하는 표식) **그것이 그 제품의 성질인지 그 인스턴스의 성질인지 아무도 모른다.**\n * 두 번째 배포가 붙는 날 갈린다. 그 사실을 어휘가 담는다.\n */\nexport type ReferenceGroundingLevel = 'synthetic' | 'mock' | 'instance' | 'product'\n\nexport interface ReferenceGrounding {\n level: ReferenceGroundingLevel\n /**\n * **무엇에 대고 확인했나** — 사람이 읽는 한 줄(원본의 버전·인스턴스·목의 이름).\n *\n * `instance`·`product` 를 주장하면서 이것이 비어 있으면 `groundingOf` 가 `unknown` 으로 내린다 —\n * 가리키는 대상이 없는 주장은 주장이 아니고, 그런 주장은 낡아도 아무도 알 수 없다.\n */\n verifiedAgainst?: string\n}\n\n/**\n * 이 커넥터의 근거 — **주장을 그대로 믿지 않는다**(`capabilitiesOf` 와 같은 규율).\n *\n * 능력은 구현이 있는지 보아 선언을 검증할 수 있다. 근거는 **볼 수 없다** — 사람이 아는 사실이다.\n * 그래서 검증할 수 있는 것 하나만 검증한다: **실 시스템을 주장하면 무엇에 대고인지 말해야 한다.**\n *\n * 선언이 없으면 `unknown` 이다 — 「목이다」가 아니라 **「모른다」**다. 둘을 같게 두면 아직 선언하지 않은\n * 새 커넥터가 목으로 낙인찍히고, 그것도 없는 사실이다.\n */\nexport function groundingOf(adapter: ReferenceAdapter | undefined): {\n level: ReferenceGroundingLevel | 'unknown'\n verifiedAgainst?: string\n /** 주장이 내려갔으면 그 이유 — 화면이 그대로 옮긴다. */\n downgraded?: string\n} {\n const g = adapter?.grounding\n if (!g?.level) return { level: 'unknown' }\n const against = g.verifiedAgainst?.trim()\n if ((g.level === 'instance' || g.level === 'product') && !against) {\n return {\n level: 'unknown',\n downgraded: `claims \"${g.level}\" but does not say what it was verified against`\n }\n }\n return { level: g.level, ...(against ? { verifiedAgainst: against } : {}) }\n}\n\n/**\n * 능력마다 **그것을 증명하는 메서드들** — 하나라도 있으면 성립한다.\n *\n * ── `live` 의 증명이 왜 둘인가 (2026-09-16, ADR-0036 ③ 개정) ─────────────────\n * 예전에는 `openLiveFeed` 하나였다. 그런데 사실이 흘러 들어오는 방식은 **상대 시스템의 성질**이지\n * 우리 능력의 종류가 아니다 — 우리가 당겨 오면 `openLiveFeed`, 상대가 웹훅으로 밀어 넣으면\n * `handleInbound`(계약 일급 문)다. 둘 다 「이 원본에서 사실이 계속 온다」를 증명한다.\n *\n * 실측: `operato-plant` 은 outbox 가 밀어 넣는 쪽이라 `openLiveFeed` 가 없다. 그래서 **실 생산\n * 사실을 나르는 유일한 커넥터가 판정에서 live 가 아니었다.** 등록 경고는 「구현했는데 선언 안 함」\n * 한 방향만 봐서 아무 말도 하지 않았다.\n */\nconst PROVED_BY: Record<ReferenceCapability, readonly string[]> = {\n live: ['openLiveFeed', 'handleInbound'],\n control: ['control'],\n actuate: ['actuate'],\n /*\n * **선언만으로 성립한다** — 부를 메서드가 없다. 「두 번 해도 같은가」는 어댑터가 하는 일이 아니고\n * 저쪽 시스템의 성질이다. 그래서 구현 검사를 하지 않는다.\n */\n 'idempotent-actuation': []\n}\n\nconst CAPABILITIES = Object.keys(PROVED_BY) as ReferenceCapability[]\n\n/** 그 능력을 증명하는 메서드가 실제로 붙어 있나. 증명이 필요 없는 능력은 언제나 참이다. */\nfunction implemented(adapter: ReferenceAdapter, capability: ReferenceCapability): boolean {\n const methods = PROVED_BY[capability]\n if (methods.length === 0) return true\n return methods.some(name => typeof (adapter as any)[name] === 'function')\n}\n\nexport function capabilitiesOf(adapter: ReferenceAdapter | undefined): ReferenceCapability[] {\n if (!adapter) return []\n const declared = new Set(adapter.capabilities ?? [])\n return CAPABILITIES.filter(capability => declared.has(capability) && implemented(adapter, capability))\n}\n\n/* 어댑터 \"타입\" 레지스트리(전역·인메모리) — 레퍼런스 데이터는 DB(TwinReference), 어댑터 동작은 여기. */\nconst adapters = new Map<string, ReferenceAdapter>()\n\n/**\n * 어댑터를 등록한다 — 그리고 **선언과 구현이 어긋나면 그 자리에서 말한다.**\n *\n * ── 왜 필요한가 (2026-08-26 실측) ──────────────────────────────────────────\n * 능력 판정(`capabilitiesOf`)은 **한 방향만** 본다: 선언했는데 구현이 없는 것은 걸러 내고, **구현했는데\n * 선언이 없는 것은 아무 말도 하지 않는다.** 그래서 다음이 오류 없이 일어난다.\n *\n * 커넥터가 `openLiveFeed` 를 구현한다 → `capabilities: ['live']` 를 잊는다\n * → 연결 화면이 「이 원본은 라이브를 못 낸다」로 읽는다\n * → 재기동 정책을 정할 수 없어 트윈 생성이 거절된다\n * → 사람이 보는 것은 「재기동 정책이 없습니다」뿐이다\n *\n * 실제로 그렇게 막혔고(태양광 발전소), 원인을 찾는 데 코드를 뒤져야 했다. 지금 저장소에서 같은 상태인\n * 커넥터가 일곱이다.\n *\n * ── 왜 막지 않고 알리기만 하나 ──────────────────────────────────────────────\n * 등록에서 던지면 커넥터 하나 때문에 호스트가 못 뜬다. 만들다 만 커넥터를 들고 있는 사람이 아무것도\n * 못 하게 된다. 그리고 이 어긋남은 **고칠 수 있는 것**이지 위험한 것이 아니다 — 말해 주면 된다.\n */\nexport function registerAdapterType(adapter: ReferenceAdapter): void {\n const declared = new Set(adapter.capabilities ?? [])\n\n /*\n * **어긋남은 양쪽으로 본다** (2026-09-16, ADR-0036 ③ 개정).\n *\n * 이 경고는 「구현했는데 선언 안 함」 한 방향만 봤다. 그래서 반대쪽 — 선언했는데 증명이 없는 것 —\n * 은 `capabilitiesOf` 가 조용히 걸러 내기만 하고 아무도 그 사실을 듣지 못했다. 한 방향만 보면\n * **있는 것을 없다고(이번 plant) 또는 없는 것을 있다고** 말하게 된다. 같은 무게로 말한다.\n */\n const undeclared = CAPABILITIES.filter(c => PROVED_BY[c].length > 0 && implemented(adapter, c) && !declared.has(c))\n const unproven = CAPABILITIES.filter(c => PROVED_BY[c].length > 0 && declared.has(c) && !implemented(adapter, c))\n\n if (undeclared.length) {\n twinWarn(\n `[reference] connector \"${adapter.type}\" implements ${undeclared.join(' and ')} but does not declare it — ` +\n `add capabilities: [${undeclared.map(c => `'${c}'`).join(', ')}] or remove the implementation. ` +\n 'Until then the screens treat this connector as unable to do it, and a live twin cannot be created from it.'\n )\n }\n if (unproven.length) {\n twinWarn(\n `[reference] connector \"${adapter.type}\" declares ${unproven.join(' and ')} but implements none of ` +\n `${unproven.map(c => PROVED_BY[c].map(m => `${m}()`).join(' or ')).join(', ')} — ` +\n 'the declaration is dropped, so the screens treat this connector as unable to do it. ' +\n 'Implement one of those methods or remove the capability.'\n )\n }\n\n adapters.set(adapter.type, adapter)\n}\nexport function getAdapter(type: string): ReferenceAdapter | undefined {\n return adapters.get(type)\n}\nexport function listAdapterTypes(): string[] {\n return [...adapters.keys()]\n}\n/** 커넥터 메타데이터 목록(picker·연결 폼용). */\n/**\n * 등록된 어댑터 목록 — **능력은 판정값이다. 선언 원본이 아니다.**\n *\n * 능력은 이 목록을 읽는 화면이 「이 원본에 무엇을 요구할 수 있나」를 판단하는 근거다.\n *\n * ── 왜 선언을 그대로 내지 않나 (2026-09-16, ADR-0036 ③ 개정) ────────────────\n * 여기서 `a.capabilities` 를 그대로 냈다. 그래서 `capabilitiesOf` — 선언과 구현이 어긋나면 **없는\n * 것으로 읽는다**는 그 문 — 의 판정값을 **읽는 자리가 하나도 없었다.** 화면은 선언을 보고 있었고,\n * 어느 커넥터든 구현 없이 능력을 적기만 하면 가능한 것으로 보였다. 선언을 믿지 않으려고 만든 문을\n * 아무도 지나지 않은 셈이다.\n *\n * 커넥터 쪽에도 흔적이 남아 있었다 — `operato-plant` 주석이 「어댑터 목록은 선언한 값을 그대로\n * 보여 주므로 남겨 두면 화면이 없는 능력을 있다고 말한다」며 선언을 손으로 줄여 두었다. 사람이\n * 기억해서 맞추는 것은 다음 커넥터에서 끊긴다.\n */\nexport function listAdapters(): { type: string; meta?: AdapterMeta; capabilities?: ReferenceCapability[] }[] {\n return [...adapters.values()].map(a => ({ type: a.type, meta: a.meta, capabilities: capabilitiesOf(a) }))\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reference-assessment.js","sourceRoot":"","sources":["../../../server/service/reference/reference-assessment.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IngestWarning, ReferenceMaster } from './reference-master.js'\n\n/*\n * **원본에서 읽은 뒤의 총평** — 사람이 한 장으로 읽는다.\n *\n * ── 왜 필요한가 (2026-08-22) ────────────────────────────────────────────────\n * 읽기가 끝나면 화면에 남는 것은 경고 열 줄이었고, 그 안에 이유가 문장으로 묻혀 있고 UUID 수백 개가\n * 붙어 있었다. 그래서 사람이 「가져올 것은 다 들어왔나」·「무엇이 비었나」·「다음에 무엇을 하면\n * 나아지나」를 스스로 세어야 했다 — 실제로 그 셋을 손으로 세는 대화가 하루를 채웠다.\n *\n * 그 셋은 이미 우리가 아는 사실이다. 세어 두었으니 **정리해서 내면 된다.**\n *\n * ── 두 층으로 나눈다 ────────────────────────────────────────────────────────\n * 여기는 **사실 층**이다: 센 것과 그 사유 코드만 담고 문장을 만들지 않는다. 화면이 표로 그리고,\n * 사람이 더 알고 싶으면 그때 AI 가 이 사실**만** 근거로 서술한다(말 층).\n *\n * **AI 는 로드베어링이 아니다.** 호출이 꺼져 있거나 실패해도 이 표가 그대로 화면에 남아야 한다 —\n * 그래서 사실 층이 스스로 완결이다.\n *\n * ── 무엇을 지어내지 않는가 ──────────────────────────────────────────────────\n * 「없음」의 뜻을 셋으로 구분해 둔다. 뭉치면 조치가 달라지는 것들이 한 칸에 섞인다.\n * · `gaps` — 원본에 있는데 우리가 옮기지 못했다. 고칠 대상이 있다(원본의 빈 칸이거나 우리 매핑).\n * · `absent` — 이 원본에 그 **개념이 없다.** 빠진 것이 아니므로 고칠 것이 없다.\n * · `grounding` — 값이 들어왔지만 **근거가 저장된 값이 아니다**(원본의 계산 규칙에서 왔다).\n */\n\n/** 센 것 — 화면이 그대로 표로 그린다. */\nexport interface AssessmentCounts {\n locations: number\n areas: number\n equipment: number\n materialDefinitions: number\n operations: number\n /** 생산 선언이 없으면 없다 — 0 과 구별한다(선언 자체가 없는 것과 비어 있는 것은 다르다). */\n materials?: number\n materialsWithHome?: number\n routes?: number\n recipes?: number\n recipesWithRoute?: number\n /** 공정을 말하는 투입 / 전체 투입 — 공정별 소요를 아는 정도. */\n taggedInputs?: number\n totalInputs?: number\n /** 시험 명세(점검표)의 수 — 선언되지 않았으면 없다(0 과 구별한다). */\n testSpecifications?: number\n /** 그 명세들이 선언한 판정 기준의 수. */\n criteria?: number\n /**\n * 그중 **아무 한계도 말하지 않는** 기준의 수 — 커널의 `criterionSaysNothing` 과 같은 판정이다.\n *\n * 이 수가 0 이 아니면 「관리점이 있다」가 「판정된다」를 뜻하지 않는다. 세지 않으면 그 구별이 사라진다.\n */\n criteriaWithoutLimit?: number\n}\n\n/** 옮기지 못한 것 한 갈래 — 건수 큰 것부터. */\nexport interface AssessmentGap {\n /** 무엇을 옮기다 못 옮겼나 — 커넥터가 붙인 이름(`material home warehouse`). */\n what: string\n count: number\n /** 못 옮긴 원본 값 몇 개 — 전부가 아니라 표본이다(수백 개가 붙던 자리다). */\n sample?: string\n /** 그 자리에 대신 들어간 값. */\n fallback?: string\n}\n\nexport interface ImportAssessment {\n source: string\n siteId: string\n siteName?: string\n system?: string\n built: AssessmentCounts\n /**\n * **원본이 가지고 있던 수** — 커넥터가 말해 준 경우에만 있다.\n *\n * 이름을 `origin` 이라 하지 않는다 — 화면에서 그 낱말이 이미 **트윈의 출처**(`summary.origin`:\n * 템플릿인가 원본인가)를 뜻한다. 같은 이름을 두 뜻으로 쓰면 읽는 사람이 헷갈린다.\n *\n * `built` 와 비교해 「전부 옮겼는가」를 사람이 직접 셀 수 있게 한다. 커넥터가 말하지 않으면 이 자리는\n * 비어 있고, 화면은 분모 없이 옮긴 수만 보인다 — **분모를 지어내지 않는다.**\n */\n originCounts?: Record<string, number>\n gaps: AssessmentGap[]\n /** 이 원본에 개념이 없는 자리 — 커널 낱말로 적는다(화면이 번역한다). */\n absent: string[]\n /**\n * 값이 들어왔지만 **근거가 원본의 저장된 값이 아닌 것** — 파생·유추된 값이다.\n *\n * 이것을 `gaps` 에 섞으면 안 된다. 빈 자리가 아니라 **채워진 자리**이고, 사람이 물을 것은\n * 「왜 비었나」가 아니라 「그 값을 믿어도 되나」다. 조치도 다르다 — 원본을 채우는 것이 아니라\n * 그 규칙이 바뀌면 이 값도 바뀐다는 사실을 아는 것이다.\n */\n derived: AssessmentGap[]\n /** 되물은 횟수·잘린 목록 같은 운영 사실. */\n notes: IngestWarning[]\n}\n\n/** 이 마스터가 개념을 선언했나 — 「없다」와 「비어 있다」를 구분한다. */\nconst declared = (v: unknown): boolean => Array.isArray(v) ? v.length > 0 : v !== undefined && v !== null\n\n/**\n * 마스터 한 벌 → 총평의 사실 층.\n *\n * 경고를 두 갈래로 나눈다: `connector-mapping-fallback` 은 **옮기지 못한 것**이므로 `gaps` 로,\n * 나머지(파생된 값·되물은 횟수·잘린 목록)는 `notes` 로 그대로 나른다. 판단하지 않는다 — 무엇이\n * 심각한지는 건수와 그 자리의 뜻이 말하고, 그 해석은 사람이나 말 층의 몫이다.\n */\nexport function assessMaster(master: ReferenceMaster, siteId: string): ImportAssessment {\n const m: any = master\n const d = m.productionSpec?.definition\n const inputs = (d?.recipes ?? []).flatMap((r: any) => r.inputs ?? [])\n\n const built: AssessmentCounts = {\n locations: (m.locations ?? []).length,\n areas: (m.locations ?? []).filter((l: any) => l.role === 'area' || l.role === 'both').length,\n equipment: (m.equipment ?? []).length,\n materialDefinitions: (m.materialDefinitions ?? []).length,\n operations: (m.operations ?? []).length,\n ...(declared(m.testSpecifications)\n ? {\n testSpecifications: m.testSpecifications!.length,\n criteria: m.testSpecifications!.reduce((n, t) => n + (t.criteria?.length ?? 0), 0),\n /*\n * **한계를 말하지 않는 기준** — 커널의 `criterionSaysNothing` 과 같은 판정이다. 기준이\n * 선언됐다는 사실만 있고 판정할 재료가 없는 자리이고, 그것을 세지 않으면 「관리점이 있다」가\n * 「판정된다」로 읽힌다.\n */\n criteriaWithoutLimit: m.testSpecifications!.reduce(\n (n, t) => n + (t.criteria ?? []).filter(c => !c.expression && c.limit?.minimum === undefined && c.limit?.maximum === undefined).length,\n 0\n )\n }\n : {}),\n ...(d\n ? {\n materials: (d.materials ?? []).length,\n materialsWithHome: (d.materials ?? []).filter((x: any) => x.locationType).length,\n routes: (d.routes ?? []).length,\n recipes: (d.recipes ?? []).length,\n recipesWithRoute: (d.recipes ?? []).filter((x: any) => x.route).length,\n totalInputs: inputs.length,\n taggedInputs: inputs.filter((i: any) => i.operation).length\n }\n : {})\n }\n\n /* 원본의 수는 커넥터가 말한 그대로 나른다 — 여기서 고치거나 채우지 않는다. */\n const originCounts = m.originCounts && Object.keys(m.originCounts).length ? { ...m.originCounts } : undefined\n\n const gaps: AssessmentGap[] = []\n const derived: AssessmentGap[] = []\n const notes: IngestWarning[] = []\n /*\n * 커넥터가 「대체」 수집기로 남기는 것 중 일부는 **빈 자리가 아니라 파생된 값**이다(산출량이 원본의\n * 계산 규칙에서 온 경우처럼). 같은 통로로 오지만 뜻이 달라 구분해 담는다 — 이름에 그 사실이 적혀 있다.\n */\n const isDerived = (field: string) => /derived|not a stored field/i.test(field)\n for (const w of m.warnings ?? []) {\n if (w?.code === 'connector-mapping-fallback' && w.params?.field) {\n const values = String(w.params.values ?? '')\n const bin = isDerived(String(w.params.field)) ? derived : gaps\n bin.push({\n what: String(w.params.field),\n count: Number(w.params.count ?? 0),\n /* 값은 표본만 — 이 자리에 UUID 수백 개가 붙어 화면을 밀어냈다. 전체는 경고 원문에 남는다. */\n ...(values ? { sample: values.length > 120 ? `${values.slice(0, 120)}…` : values } : {}),\n ...(w.params.fallback ? { fallback: String(w.params.fallback) } : {})\n })\n continue\n }\n notes.push(w)\n }\n gaps.sort((a, b) => b.count - a.count)\n derived.sort((a, b) => b.count - a.count)\n\n /*\n * 이 원본에 개념이 없는 자리. **커널 낱말로 적는다** — 화면이 그것으로 번역하고, 개념 지도가 이미\n * 같은 낱말을 쓴다(두 화면이 다른 말을 하면 사람이 둘을 다른 사실로 읽는다).\n */\n const absent: string[] = []\n if (!declared(m.persons)) absent.push('persons')\n if (!declared(m.assets)) absent.push('assets')\n if (!declared(m.testSpecifications)) absent.push('testSpecifications')\n if (!declared(m.personnelClasses)) absent.push('personnelClasses')\n if (!declared(m.assetClasses)) absent.push('assetClasses')\n if (!declared(m.materialClasses)) absent.push('materialClasses')\n if (!d) absent.push('productionSpec')\n\n return {\n source: m.source,\n siteId,\n siteName: m.siteName,\n system: m.system,\n built,\n ...(originCounts ? { originCounts } : {}),\n gaps,\n derived,\n absent,\n notes\n }\n}\n\n/** 사람이 읽는 줄 — 터미널·로그가 읽는다(화면은 구조를 그대로 그린다). */\nexport function formatAssessment(a: ImportAssessment): string {\n const c = a.built\n const out: string[] = []\n /**\n * 분모가 있으면 함께 적는다 — 「1,261」과 「원본 1,408 중 1,261」은 다른 말이다.\n * 커넥터가 그 수를 말하지 않았으면 옮긴 수만 적는다(지어내지 않는다).\n */\n const n = (key: keyof AssessmentCounts, v?: number) => {\n if (v === undefined) return '없음'\n const o = a.originCounts?.[key as string]\n return o === undefined ? String(v) : o === v ? `${v}(원본 전부)` : `${v} / 원본 ${o}`\n }\n out.push(`총평 ${a.siteName ?? a.siteId} (${a.system ?? '?'}) — 원본 ${a.source}`)\n out.push(\n ` 선 것: 로케이션 ${n('locations', c.locations)}(존 ${c.areas}) · 설비 ${n('equipment', c.equipment)} · ` +\n `품목 정의 ${n('materialDefinitions', c.materialDefinitions)} · 공정 ${n('operations', c.operations)}`\n )\n if (c.materials !== undefined) {\n out.push(\n ` 자재 ${n('materials', c.materials)}(보관 로케이션 있는 것 ${c.materialsWithHome}) · ` +\n `경로 ${n('routes', c.routes)} · 레시피 ${n('recipes', c.recipes)}(경로 붙은 것 ${c.recipesWithRoute}) · ` +\n `투입 ${n('totalInputs', c.totalInputs)}(공정을 말하는 것 ${c.taggedInputs})`\n )\n } else {\n out.push(' 생산 선언 없음 — 레시피가 하나도 성립하지 않았다')\n }\n if (a.gaps.length) {\n out.push(' 옮기지 못한 것(건수 순):')\n for (const g of a.gaps) out.push(` ${String(g.count).padStart(6)} ${g.what}${g.sample ? ` — ${g.sample}` : ''}`)\n }\n if (a.derived.length) {\n out.push(' 파생된 값(저장된 값이 아니다):')\n for (const g of a.derived) out.push(` ${g.what}${g.sample ? ` — ${g.sample}` : ''}`)\n }\n if (c.testSpecifications) {\n /*\n * 관리 기준은 계측이 오는지와 무관하게 값이 있다 — 「관리점이 셋인데 둘은 아무 한계도 말하지\n * 않는다」를 여기서 볼 수 있어야 판정에 근거가 있는지 사람이 안다.\n */\n out.push(\n ` 관리 기준: 점검표 ${n('testSpecifications', c.testSpecifications)} · 기준 ${c.criteria ?? 0}` +\n (c.criteriaWithoutLimit ? ` (한계를 말하지 않는 것 ${c.criteriaWithoutLimit})` : '')\n )\n }\n if (a.absent.length) out.push(` 이 원본에 개념이 없는 자리: ${a.absent.join(', ')}`)\n for (const n of a.notes) out.push(` 참고 ${n.code}: ${n.message}`)\n return out.join('\\n')\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reference-fill-run.js","sourceRoot":"","sources":["../../../server/service/reference/reference-fill-run.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **빠진 구간을 다시 받는 일을 실제로 부른다** — `fillSince` 에 연결과 어댑터를 물려 주는 자리.\n *\n * ── 왜 이 파일이 없었나 ─────────────────────────────────────────────────────\n * `fillSince` 는 2026-08-31 에 만들어졌고 **부르는 곳이 0곳이었습니다.** 2026-09-05 에 저는 그것을\n * 못 보고 같은 일을 하는 것을 하나 더 만들었습니다(`backfillScope`). 두 벌이 되었고 둘 다 아무도\n * 부르지 않았습니다.\n *\n * 그래서 이런 상태였습니다.\n *\n * ```\n * 번호가 비면 어디서부터 다시 받을지 안다 ✔ takeInSequence 가 판정한다\n * 그 빈 것을 메운다 ✖ 부를 곳이 없다\n * ```\n *\n * 실제로 plant 아웃박스의 1~20 을 잃고 되찾을 방법이 없는 상태를 만났습니다.\n *\n * ── 같은 검사를 지난다 ──────────────────────────────────────────────────────\n * `fillSince` 가 `takeInSequence` 를 지나고, 유입은 `liveIngest` 를 지납니다 — 밀어 받는 길과 같은\n * 함수입니다. 여기서 따로 만들면 두 길이 다른 규칙으로 받고, 한쪽만 수정되는 날이 옵니다.\n *\n * ── 커서 열쇠는 프레임워크가 만든다 ─────────────────────────────────────────\n * 열쇠는 `instanceId::scope` 이고 그것을 아는 것은 프레임워크입니다. 어댑터가 만들면 두 곳이 같은\n * 규칙을 갖게 되고, 어긋나는 날 **다시 받기가 자기 커서를 0부터 새로 셉니다.**\n */\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinReference } from './twin-reference.js'\nimport { getAdapter, type SeqReport, type SiteDescriptor } from './reference-adapter.js'\nimport { firstSeqOf, pushCursorKey, seqOf, withSeq } from './hook-contract.js'\nimport { fillSince, type FillOutcome } from './reference-fill.js'\nimport { liveIngest } from './live-ingest.js'\nimport { recordReferenceReconciliation } from './reference-reconciliation-writer.js'\nimport { journalIdentities, spanOf } from './fill-dedupe.js'\nimport { TwinEngine } from '../../engine/index.js'\nimport { twinLog, twinWarn } from '../../engine/log.js'\n\n/** 한 연결의 한 단위를 다시 받은 결과. */\nexport interface FillRunResult extends Partial<FillOutcome> {\n instanceId: string\n scope: string\n /**\n * 못 한 이유 — 성공이면 없습니다.\n *\n * `not-supported` 는 결함이 아닙니다. 주기로 물어보는 원본(chef · ppms)은 다음 주기가 곧 다시\n * 받는 것입니다.\n */\n skipped?: 'unknown-twin' | 'no-source' | 'no-adapter' | 'not-supported'\n}\n\n/**\n * 그 연결의 한 단위를 다시 받는다.\n *\n * **커서를 되돌리지 않습니다.** 받은 것이 번호 검사를 지나야 커서가 나아가고, 그 판정은 밀어 받는\n * 길과 같은 함수가 합니다.\n *\n * @param from 여기서부터 다시 받는다 — 주면 커서를 무시합니다. 잃은 구간을 되찾을 때만 씁니다.\n */\nexport async function runFill(args: {\n domainId: string\n instanceId: string\n scope: string\n from?: number\n maxPages?: number\n}): Promise<FillRunResult> {\n const { domainId, instanceId, scope, from, maxPages } = args\n const base: FillRunResult = { instanceId, scope }\n\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!inst) return { ...base, skipped: 'unknown-twin' }\n\n const repo = getRepository(TwinReference)\n const ref = await repo.findOne({ where: { domain: { id: domainId }, source: instanceId } })\n if (!ref) return { ...base, skipped: 'no-source' }\n\n const adapter = getAdapter(ref.adapterType)\n if (!adapter) return { ...base, skipped: 'no-adapter' }\n /* 다시 받기를 안 내는 원본이 있습니다 — 주기로 물어보는 쪽은 다음 주기가 곧 그것입니다. */\n if (typeof adapter.fetchSince !== 'function') return { ...base, skipped: 'not-supported' }\n\n const siteId = (inst.origin as any)?.siteId\n const site: SiteDescriptor = siteId ? { siteId } : { siteId: instanceId }\n const cursorKey = pushCursorKey(instanceId, scope)\n\n /*\n * `from` 을 주면 커서를 그 값으로 갈아 끼웁니다 — **저장된 커서를 건드리지 않습니다.** `fillSince`\n * 가 나아간 만큼만 적으므로, 잃은 구간을 되찾은 뒤 커서는 원래 자리보다 앞서지 않습니다.\n */\n const cursor = from === undefined ? ((ref as any).liveCursor ?? {}) : withSeq((ref as any).liveCursor, cursorKey, from)\n /* fillSince가 커서를 저장할 때 이 지역 값도 같이 바꾼다. 저장본을 다시 읽어 대사를 늦추지 않는다. */\n let persistedCursor: unknown = cursor\n\n const outcome = await fillSince({\n cfg: ref.connectionConfig ?? {},\n site,\n scope,\n cursorKey,\n cursor,\n fetch: (cfg, s, sc, since) => adapter.fetchSince!(cfg, s as SiteDescriptor, sc, since),\n ingest: records => liveIngest(domainId, instanceId, records, 'fill'),\n /*\n * ── 넣기 전에 기억을 채운다 (2026-09-06) ──────────────────────────────────\n * 중복 판정 기억은 프로세스 안에만 있다. 재기동하면 비워지고, 그러면 backfill 이 **이미 저널에\n * 있는 사실도 다시 앉힌다.** 실측으로 39건이 통째로 다시 앉았다(저널 44 → 83).\n *\n * push 경로에서는 안 한다 — 거기서는 재전송이 창 안에 들어와 이미 걸리고, 봉투마다 저널을\n * 조회하면 뜨거운 길이 느려진다.\n */\n beforeIngest: async records => {\n const span = spanOf(records)\n if (!span) return\n const { identities, scanned, truncated } = await journalIdentities(domainId, instanceId, span)\n const added = TwinEngine.warmDeduper(domainId, instanceId, identities)\n if (truncated) {\n twinWarn(\n `[twin-fill] \"${instanceId}\" 의 저널을 ${scanned}줄까지만 읽었습니다 — 그보다 앞의 사실은 ` +\n '다시 앉을 수 있습니다. 구간을 좁혀 부르거나 정체 색인이 필요합니다'\n )\n }\n if (added) twinLog(`[twin-fill] \"${instanceId}\" — 저널에서 정체 ${added}개를 기억에 채웠습니다`)\n },\n saveCursor: async next => {\n await repo.update({ id: (ref as any).id }, { liveCursor: next } as any)\n persistedCursor = next\n },\n ...(maxPages ? { maxPages } : {})\n })\n\n /* 원본이 실제로 답한 범위가 있을 때만 기록한다. 범위를 모르는 것은 \"정상\"으로 덮지 않는다. */\n await recordReferenceReconciliation({\n domainId,\n instanceId,\n scope,\n sourceRange: outcome.sourceRange,\n cursorSeq: seqOf(persistedCursor, cursorKey),\n firstSeqSeen: firstSeqOf(persistedCursor, cursorKey)\n }).catch(err => twinWarn(`[twin-reconcile] \"${instanceId}\" · ${scope} 상태를 적지 못했습니다: ${err?.message ?? err}`))\n\n const say = `[twin-fill] \"${instanceId}\" · ${scope} — ${outcome.applied}건 넣음 · ${outcome.pages}쪽 · ${outcome.stopped}${outcome.detail ? ` (${outcome.detail})` : ''}`\n /* 끝난 것과 막힌 것을 다른 소리로 남깁니다 — 구멍은 사람이 원본을 봐야 합니다. */\n if (outcome.stopped === 'done' || outcome.stopped === 'no-progress') twinLog(say)\n else twinWarn(say)\n\n return { ...base, ...outcome }\n}\n\n/**\n * **그 번호들이 무엇이었나** — 구멍을 만났을 때 사람이 묻는다.\n *\n * 커넥터는 「418 다음이 420」까지만 말한다. 그 419 가 무엇이었는지 알려면 원본에 물어야 하고,\n * 그 길이 없어서 사람이 아웃박스를 손으로 뒤졌다(2026-09-06).\n *\n * **판정하지 않는다.** 원본이 아는 것을 그대로 낸다 — 「그러니 포기해도 된다」는 되돌릴 수 없는\n * 판단이라 사람의 몫이다.\n */\nexport async function seqReportOf(args: {\n domainId: string\n instanceId: string\n scope: string\n from: number\n to: number\n}): Promise<{ instanceId: string; scope: string; report?: SeqReport; skipped?: 'unknown-twin' | 'no-source' | 'no-adapter' | 'not-supported' }> {\n const { domainId, instanceId, scope, from, to } = args\n const base = { instanceId, scope }\n\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!inst) return { ...base, skipped: 'unknown-twin' }\n const ref = await getRepository(TwinReference).findOne({ where: { domain: { id: domainId }, source: instanceId } })\n if (!ref) return { ...base, skipped: 'no-source' }\n const adapter = getAdapter(ref.adapterType)\n if (!adapter) return { ...base, skipped: 'no-adapter' }\n /* 번호를 매기지 않는 원본은 물을 것이 없다 — 결함이 아니다. */\n if (typeof adapter.seqReport !== 'function') return { ...base, skipped: 'not-supported' }\n\n const siteId = (inst.origin as any)?.siteId\n const site: SiteDescriptor | undefined = siteId ? { siteId } : undefined\n return { ...base, report: await adapter.seqReport(ref.connectionConfig ?? {}, site, scope, from, to) }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reference-fill.js","sourceRoot":"","sources":["../../../server/service/reference/reference-fill.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * **놓친 구간을 backfill 해 채운다** — 밀어 받는 길의 짝.\n *\n * ── 왜 필요한가 (2026-08-31, 인티그레이션 레인 지적) ──────────────────────\n * 훅은 반드시 놓친다 — 우리가 내려가 있을 때, 그쪽이 못 보냈을 때, 네트워크가 끊겼을 때. 받는 쪽이\n * 번호로 그것을 **알아채게** 되었지만(§`takeInSequence`), 알아챈 뒤 **그 사이를 채우는 길이 없었다.**\n * 설계가 그 길을 적어 두었는데 만들지 않았다.\n *\n * ── 같은 검사를 지난다 ───────────────────────────────────────────────────\n * backfill 한 것도 `takeInSequence` 를 지난다. 여기서 따로 만들면 두 길이 다른 규칙으로 받고, 한쪽만\n * 고쳐지는 날 어긋난 그 길에서만 사실이 사라진다.\n *\n * ── 멈추는 조건을 셋 둔다 ────────────────────────────────────────────────\n * 되풀이해서 요청하는 경로는 **끝나지 않는 것**이 가장 흔한 결함이다.\n *\n * 빈 답 더 줄 것이 없다. 끝\n * 번호가 안 나아간다 같은 것을 다시 받았다 — 무한히 요청하는 것을 막는다\n * 구멍 원본의 아웃박스에 구멍이 있다. 이어 받으면 커서가 그 번호를 지나쳐 적힌다\n *\n * 그리고 **쪽 상한**을 둔다. 상한에 닿았으면 조용히 끝내지 않고 말한다 — 조용히 자르면 사람은 다 받은\n * 줄로 읽는다.\n */\nimport { seqOf, takeInSequence, withSeq } from './hook-contract.js'\nimport type { InboundBatch } from './reference-adapter.js'\nimport { twinWarn } from '../../engine/log.js'\n\n/** 한 번 채우고 나서 남는 사실. */\nexport interface FillOutcome {\n /** 트윈에 넣은 레코드 수. */\n applied: number\n /**\n * ingest 가 낸 네 갈래의 합 — **두 갈래로는 셈이 안 닫힌다.**\n *\n * ```\n * 두 갈래 157 − 46 − 2 = 109 「설명 못 함」 진실은 되풀이 108 + 어휘 1\n * 네 갈래 157 − 46 − 2 − 108 − 1 = 0\n * ```\n *\n * 이 값이 없으면 원본 쪽에서 「보낸 것 20개 중 event 가 9개인데 나머지는 어디 갔나」를 셀 수\n * 없다. 실제로 인티그레이션 레인이 그것을 물었고, 그 수가 트윈에 저장되지 않아 답을 못 했다.\n */\n duplicates: number\n rejected: number\n /**\n * 왜 거절됐나 — **사유별 수.** 수만 내면 「26건이 안 들어온다」에서 멈춘다.\n *\n * 목록 전체가 아니라 사유별로 센다. 같은 이유로 거절된 26건이 26줄이면 읽는 사람이 그 줄을\n * 세어야 하고, 다른 이유 하나가 그 안에 묻힌다.\n */\n rejectedReasons?: Record<string, number>\n vocabulary: number\n /** 시각을 못 읽어 ingest 시각으로 떨어진 것 — 종류별. 비어 있으면 전부 자기 시각을 말했다. */\n undated?: Record<string, number>\n /** 몇 쪽을 받았나. */\n pages: number\n /** 커서가 어디까지 갔나 — 아무것도 못 받았으면 없다. */\n lastSeq?: number\n /**\n * 이 실행에서 **가장 낮게 받아들인 번호** — 밑에서부터 다시 받았을 때 이것이 곧 「우리가 가진\n * 가장 낮은 번호」다.\n */\n lowestSeq?: number\n /** 왜 멈췄나 — `done` · `gap` · `no-progress` · `page-limit` · `refused`. */\n stopped: 'done' | 'gap' | 'no-progress' | 'page-limit' | 'refused'\n /** 멈춘 이유의 사람 말. 알리지 않고 끝내지 않는다. */\n detail?: string\n /**\n * 마지막으로 받은 묶음이 말한 **원본의 번호 범위** — 원본이 안 말하면 없다.\n *\n * 우리 커서와 견주는 자리가 이 값을 쓴다. 없으면 「맞다」가 아니라 **「모른다」**로 답해야 한다.\n */\n sourceRange?: { first?: number; last?: number }\n}\n\n/** 몇 쪽까지 받나 — 한 번의 채우기가 서버를 오래 붙잡지 않게. */\nconst MAX_PAGES = 50\n\n/**\n * 그 단위의 놓친 구간을 채운다.\n *\n * `ingest` 를 부르는 쪽이 넘겨 준다 — 훅과 같은 이유다(이 파일이 엔진을 직접 부르면 시험이 엔진을\n * 세워야 한다).\n */\nexport async function fillSince(args: {\n cfg: Record<string, any>\n site: { siteId: string }\n /** 번호를 매기는 단위 — 커서 열쇠의 절반. 커넥터가 만들지 않는다. */\n scope: string\n /** 커서에 적힌 열쇠 — `pushCursorKey(instanceId, scope)`. */\n cursorKey: string\n /** 지금 커서 — 이 함수가 읽고, 나아간 만큼 되돌려 준다. */\n cursor: unknown\n fetch: (cfg: Record<string, any>, site: { siteId: string }, scope: string, since: number) => Promise<InboundBatch>\n ingest: (records: unknown[]) => { applied: number; duplicates: number; rejected: unknown[] }\n /**\n * 넣기 **직전에** 부른다 — 이 쪽의 레코드를 넘긴다.\n *\n * 중복 판정 기억을 저널에서 채우는 자리다(§`fill-dedupe.ts`). 그 기억은 프로세스 안에만 있어서\n * 재기동하면 비워지고, 그러면 이미 저널에 있는 사실도 「처음 본 것」이 된다.\n *\n * `ingest` 는 동기라 여기서 채울 수 없다. 이 훅은 비동기이므로 저널을 읽을 수 있다.\n *\n * 실패해도 넣기를 멈추지 않는다 — 기억을 못 채운 것이 사실을 못 받을 이유는 아니다.\n */\n beforeIngest?: (records: unknown[]) => Promise<void>\n saveCursor: (cursor: Record<string, unknown>) => Promise<void>\n maxPages?: number\n}): Promise<FillOutcome> {\n const { cfg, site, scope, cursorKey, fetch, ingest, saveCursor , beforeIngest } = args\n const maxPages = args.maxPages ?? MAX_PAGES\n\n let cursor = args.cursor\n let applied = 0\n let duplicates = 0\n let rejectedCount = 0\n const rejectedReasons: Record<string, number> = {}\n let vocabulary = 0\n const undated: Record<string, number> = {}\n /* 원본이 말한 번호 범위 — 마지막으로 받은 묶음의 것을 들고 나간다. 안 말하면 끝까지 없다. */\n let sourceRange: { first?: number; last?: number } | undefined\n /* 이 실행에서 가장 낮게 받아들인 번호 — 커서에 firstSeq 로 적는다. */\n let lowestSeq: number | undefined\n let pages = 0\n\n for (; pages < maxPages; pages++) {\n const since = seqOf(cursor, cursorKey) ?? 0\n\n let batch: InboundBatch\n try {\n batch = await fetch(cfg, site, scope, since)\n } catch (e: any) {\n return { applied, duplicates, rejected: rejectedCount, ...(Object.keys(rejectedReasons).length ? { rejectedReasons } : {}), vocabulary, ...(Object.keys(undated).length ? { undated } : {}), pages, ...seqPart(cursor, cursorKey), ...(sourceRange ? { sourceRange } : {}), ...(lowestSeq !== undefined ? { lowestSeq } : {}), stopped: 'refused', detail: String(e?.message ?? e) }\n }\n\n if (batch?.sourceRange) sourceRange = batch.sourceRange\n\n const items = batch?.items ?? []\n /* 빈 답은 더 줄 것이 없다는 뜻이다. 오류가 아니다. */\n if (!items.length) return { applied, duplicates, rejected: rejectedCount, ...(Object.keys(rejectedReasons).length ? { rejectedReasons } : {}), vocabulary, ...(Object.keys(undated).length ? { undated } : {}), pages, ...seqPart(cursor, cursorKey), ...(sourceRange ? { sourceRange } : {}), ...(lowestSeq !== undefined ? { lowestSeq } : {}), stopped: 'done' }\n\n const taken = takeInSequence({ items, scope, lastSeq: seqOf(cursor, cursorKey) })\n if (taken.refused) {\n return { applied, duplicates, rejected: rejectedCount, ...(Object.keys(rejectedReasons).length ? { rejectedReasons } : {}), vocabulary, ...(Object.keys(undated).length ? { undated } : {}), pages, ...seqPart(cursor, cursorKey), ...(sourceRange ? { sourceRange } : {}), ...(lowestSeq !== undefined ? { lowestSeq } : {}), stopped: 'refused', detail: taken.refused }\n }\n\n if (taken.records.length) {\n if (beforeIngest) {\n await beforeIngest(taken.records).catch(e =>\n twinWarn(`[twin-fill] 중복 판정 기억을 못 채웠다 — ${String(e?.message ?? e)}. 넣기는 그대로 간다`)\n )\n }\n const result = ingest(taken.records)\n applied += result.applied\n duplicates += result.duplicates ?? 0\n rejectedCount += result.rejected?.length ?? 0\n for (const r of result.rejected ?? []) {\n /* 사유가 여럿이면 첫 줄로 묶는다 — 뒤는 대개 같은 원인의 곁가지다. */\n const why = String((r as any)?.errors?.[0] ?? '(사유 없음)').slice(0, 120)\n rejectedReasons[why] = (rejectedReasons[why] ?? 0) + 1\n }\n vocabulary += (result as any).vocabulary ?? 0\n for (const [k, n] of Object.entries((result as any).undated ?? {})) undated[k] = (undated[k] ?? 0) + (n as number)\n }\n\n /*\n * **번호가 나아가야 다음 쪽을 요청한다.** 원본이 빈 묶음에 물어본 번호를 그대로 돌려주는 계약이면,\n * 나아감을 보지 않고 요청하면 무한히 돈다.\n */\n if (taken.advancedSeq === undefined || taken.advancedSeq <= since) {\n if (taken.gap) return { applied, duplicates, rejected: rejectedCount, ...(Object.keys(rejectedReasons).length ? { rejectedReasons } : {}), vocabulary, ...(Object.keys(undated).length ? { undated } : {}), pages: pages + 1, ...seqPart(cursor, cursorKey), ...(sourceRange ? { sourceRange } : {}), ...(lowestSeq !== undefined ? { lowestSeq } : {}), stopped: 'gap', detail: gapWord(taken.gap) }\n return {\n applied,\n duplicates,\n rejected: rejectedCount, ...(Object.keys(rejectedReasons).length ? { rejectedReasons } : {}),\n vocabulary,\n ...(Object.keys(undated).length ? { undated } : {}),\n pages: pages + 1,\n ...seqPart(cursor, cursorKey),\n ...(sourceRange ? { sourceRange } : {}), ...(lowestSeq !== undefined ? { lowestSeq } : {}),\n stopped: 'no-progress',\n detail: `번호가 ${since} 에서 나아가지 않았다 — 같은 것을 다시 받았다`\n }\n }\n\n if (lowestSeq === undefined) lowestSeq = taken.lowestSeq\n /* 밑에서부터 다시 받았으면 그 번호가 곧 우리가 가진 가장 낮은 번호다. */\n cursor = withSeq(cursor, cursorKey, taken.advancedSeq, taken.firstSeq ?? lowestSeq)\n await saveCursor(cursor as Record<string, unknown>).catch(err =>\n twinWarn(`[twin-fill] 번호를 커서에 적지 못했다 (${cursorKey}=${taken.advancedSeq}) — ${err?.message ?? err}`)\n )\n\n /*\n * **구멍이 있으면 멈춘다.** 원본의 아웃박스에 구멍이 있는 것이고, 이어 받으면 커서가 그 번호를\n * 지나쳐 적혀 빠진 사실이 다시 오지 않는다.\n */\n if (taken.gap) {\n return { applied, duplicates, rejected: rejectedCount, ...(Object.keys(rejectedReasons).length ? { rejectedReasons } : {}), vocabulary, ...(Object.keys(undated).length ? { undated } : {}), pages: pages + 1, ...seqPart(cursor, cursorKey), ...(sourceRange ? { sourceRange } : {}), ...(lowestSeq !== undefined ? { lowestSeq } : {}), stopped: 'gap', detail: gapWord(taken.gap) }\n }\n }\n\n /* 상한에 닿았다 — 조용히 끝내지 않는다. 사람이 다 받은 줄로 읽으면 안 된다. */\n return {\n applied,\n duplicates,\n rejected: rejectedCount, ...(Object.keys(rejectedReasons).length ? { rejectedReasons } : {}),\n vocabulary,\n ...(Object.keys(undated).length ? { undated } : {}),\n pages,\n ...seqPart(cursor, cursorKey),\n ...(sourceRange ? { sourceRange } : {}), ...(lowestSeq !== undefined ? { lowestSeq } : {}),\n stopped: 'page-limit',\n detail: `${maxPages}쪽까지 받고 멈췄다 — 남은 것이 있을 수 있다. 다음 주기가 이어 받는다`\n }\n}\n\nfunction seqPart(cursor: unknown, key: string): { lastSeq?: number } {\n const seq = seqOf(cursor, key)\n return seq === undefined ? {} : { lastSeq: seq }\n}\n\nfunction gapWord(gap: { expectedSeq: number; lastSeq: number }): string {\n return `번호가 비었다 — ${gap.lastSeq} 다음이 ${gap.expectedSeq} 이어야 한다. 원본의 아웃박스에 구멍이 있다`\n}\n"]}