@things-factory/headless-twin 10.0.12 → 10.0.13

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 (312) hide show
  1. package/dist-server/engine/canonical-ingest.d.ts +57 -2
  2. package/dist-server/engine/canonical-ingest.js +43 -2
  3. package/dist-server/engine/canonical-ingest.js.map +1 -1
  4. package/dist-server/engine/command-routing.js +1 -1
  5. package/dist-server/engine/command-routing.js.map +1 -1
  6. package/dist-server/engine/index.d.ts +3 -0
  7. package/dist-server/engine/index.js +4 -0
  8. package/dist-server/engine/index.js.map +1 -1
  9. package/dist-server/engine/ingest-health.d.ts +335 -0
  10. package/dist-server/engine/ingest-health.js +434 -0
  11. package/dist-server/engine/ingest-health.js.map +1 -0
  12. package/dist-server/engine/integration-coverage.d.ts +76 -0
  13. package/dist-server/engine/integration-coverage.js +73 -0
  14. package/dist-server/engine/integration-coverage.js.map +1 -0
  15. package/dist-server/engine/integration-probes.d.ts +65 -0
  16. package/dist-server/engine/integration-probes.js +100 -0
  17. package/dist-server/engine/integration-probes.js.map +1 -0
  18. package/dist-server/engine/integration-runner.d.ts +45 -0
  19. package/dist-server/engine/integration-runner.js +59 -0
  20. package/dist-server/engine/integration-runner.js.map +1 -0
  21. package/dist-server/engine/integration-target-profile.d.ts +57 -0
  22. package/dist-server/engine/integration-target-profile.js +79 -0
  23. package/dist-server/engine/integration-target-profile.js.map +1 -0
  24. package/dist-server/engine/kpi-fold.d.ts +27 -0
  25. package/dist-server/engine/kpi-fold.js +47 -4
  26. package/dist-server/engine/kpi-fold.js.map +1 -1
  27. package/dist-server/engine/kpi-query.js +128 -14
  28. package/dist-server/engine/kpi-query.js.map +1 -1
  29. package/dist-server/engine/live-feed-registry.js +2 -16
  30. package/dist-server/engine/live-feed-registry.js.map +1 -1
  31. package/dist-server/engine/load-meter.d.ts +1 -1
  32. package/dist-server/engine/load-meter.js +12 -3
  33. package/dist-server/engine/load-meter.js.map +1 -1
  34. package/dist-server/engine/log.d.ts +18 -0
  35. package/dist-server/engine/log.js +80 -0
  36. package/dist-server/engine/log.js.map +1 -0
  37. package/dist-server/engine/loop-lag.d.ts +54 -0
  38. package/dist-server/engine/loop-lag.js +87 -0
  39. package/dist-server/engine/loop-lag.js.map +1 -0
  40. package/dist-server/engine/model-gap.d.ts +108 -0
  41. package/dist-server/engine/model-gap.js +95 -0
  42. package/dist-server/engine/model-gap.js.map +1 -0
  43. package/dist-server/engine/restart-policy.d.ts +3 -3
  44. package/dist-server/engine/restart-policy.js +4 -4
  45. package/dist-server/engine/restart-policy.js.map +1 -1
  46. package/dist-server/engine/runtime-key.js +1 -1
  47. package/dist-server/engine/runtime-key.js.map +1 -1
  48. package/dist-server/engine/stage-path.d.ts +83 -0
  49. package/dist-server/engine/stage-path.js +118 -0
  50. package/dist-server/engine/stage-path.js.map +1 -0
  51. package/dist-server/engine/twin-engine.d.ts +352 -16
  52. package/dist-server/engine/twin-engine.js +1037 -166
  53. package/dist-server/engine/twin-engine.js.map +1 -1
  54. package/dist-server/index.js +34 -2
  55. package/dist-server/index.js.map +1 -1
  56. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.d.ts +5 -0
  57. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +58 -0
  58. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -0
  59. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.d.ts +5 -0
  60. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +55 -0
  61. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -0
  62. package/dist-server/migrations/index.js +7 -1
  63. package/dist-server/migrations/index.js.map +1 -1
  64. package/dist-server/service/index.d.ts +5 -2
  65. package/dist-server/service/index.js +18 -7
  66. package/dist-server/service/index.js.map +1 -1
  67. package/dist-server/service/reference/discovery-result.d.ts +1 -1
  68. package/dist-server/service/reference/discovery-result.js +1 -1
  69. package/dist-server/service/reference/discovery-result.js.map +1 -1
  70. package/dist-server/service/reference/index.d.ts +4 -1
  71. package/dist-server/service/reference/index.js +6 -1
  72. package/dist-server/service/reference/index.js.map +1 -1
  73. package/dist-server/service/reference/reference-adapter.d.ts +184 -3
  74. package/dist-server/service/reference/reference-adapter.js +28 -4
  75. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  76. package/dist-server/service/reference/reference-assessment.d.ts +68 -0
  77. package/dist-server/service/reference/reference-assessment.js +136 -0
  78. package/dist-server/service/reference/reference-assessment.js.map +1 -0
  79. package/dist-server/service/reference/reference-live.d.ts +12 -1
  80. package/dist-server/service/reference/reference-live.js +116 -11
  81. package/dist-server/service/reference/reference-live.js.map +1 -1
  82. package/dist-server/service/reference/reference-master.d.ts +47 -1
  83. package/dist-server/service/reference/reference-master.js +34 -4
  84. package/dist-server/service/reference/reference-master.js.map +1 -1
  85. package/dist-server/service/reference/reference-probe.d.ts +92 -0
  86. package/dist-server/service/reference/reference-probe.js +186 -0
  87. package/dist-server/service/reference/reference-probe.js.map +1 -0
  88. package/dist-server/service/reference/reference-progress-subscription.d.ts +17 -0
  89. package/dist-server/service/reference/reference-progress-subscription.js +94 -0
  90. package/dist-server/service/reference/reference-progress-subscription.js.map +1 -0
  91. package/dist-server/service/reference/reference-progress.d.ts +38 -0
  92. package/dist-server/service/reference/reference-progress.js +71 -0
  93. package/dist-server/service/reference/reference-progress.js.map +1 -0
  94. package/dist-server/service/reference/reference-resolver.js +210 -43
  95. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  96. package/dist-server/service/reference/twin-reference.d.ts +32 -0
  97. package/dist-server/service/reference/twin-reference.js +10 -0
  98. package/dist-server/service/reference/twin-reference.js.map +1 -1
  99. package/dist-server/service/twin-control/twin-control-mutation.js +4 -3
  100. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  101. package/dist-server/service/twin-event/journal-count.d.ts +21 -0
  102. package/dist-server/service/twin-event/journal-count.js +26 -0
  103. package/dist-server/service/twin-event/journal-count.js.map +1 -0
  104. package/dist-server/service/twin-event/twin-event-keys.d.ts +9 -0
  105. package/dist-server/service/twin-event/twin-event-keys.js +15 -18
  106. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  107. package/dist-server/service/twin-event/twin-event-type.d.ts +3 -1
  108. package/dist-server/service/twin-event/twin-event-type.js +18 -1
  109. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  110. package/dist-server/service/twin-event/twin-event.d.ts +1 -0
  111. package/dist-server/service/twin-event/twin-event.js +9 -1
  112. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  113. package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
  114. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  115. package/dist-server/service/twin-ingest-window/index.d.ts +5 -0
  116. package/dist-server/service/twin-ingest-window/index.js +10 -0
  117. package/dist-server/service/twin-ingest-window/index.js.map +1 -0
  118. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +12 -0
  119. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +123 -0
  120. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -0
  121. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.d.ts +1 -0
  122. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +51 -0
  123. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -0
  124. package/dist-server/service/twin-ingest-window/twin-ingest-window.d.ts +16 -0
  125. package/dist-server/service/twin-ingest-window/twin-ingest-window.js +108 -0
  126. package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +1 -0
  127. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  128. package/dist-server/service/twin-journal/twin-journal-query.js +58 -5
  129. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  130. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +14 -0
  131. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +72 -2
  132. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  133. package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
  134. package/dist-server/service/twin-metrics/twin-metrics-query.js +78 -3
  135. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  136. package/dist-server/service/twin-model/axis-journal-evidence.d.ts +2 -0
  137. package/dist-server/service/twin-model/axis-journal-evidence.js +89 -0
  138. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -0
  139. package/dist-server/service/twin-model/epcis-coverage.d.ts +1 -1
  140. package/dist-server/service/twin-model/epcis-coverage.js +19 -7
  141. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  142. package/dist-server/service/twin-model/iec61850-coverage.d.ts +1 -1
  143. package/dist-server/service/twin-model/iec61850-coverage.js +18 -7
  144. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  145. package/dist-server/service/twin-model/isa95-coverage.d.ts +14 -0
  146. package/dist-server/service/twin-model/isa95-coverage.js +23 -9
  147. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  148. package/dist-server/service/twin-model/item-ref.d.ts +24 -0
  149. package/dist-server/service/twin-model/item-ref.js +88 -0
  150. package/dist-server/service/twin-model/item-ref.js.map +1 -0
  151. package/dist-server/service/twin-model/name-index.d.ts +36 -0
  152. package/dist-server/service/twin-model/name-index.js +116 -0
  153. package/dist-server/service/twin-model/name-index.js.map +1 -0
  154. package/dist-server/service/twin-model/project-structure.js +1 -1
  155. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  156. package/dist-server/service/twin-model/status-tally.d.ts +9 -0
  157. package/dist-server/service/twin-model/status-tally.js +37 -0
  158. package/dist-server/service/twin-model/status-tally.js.map +1 -0
  159. package/dist-server/service/twin-model/twin-lineage-query.js +40 -12
  160. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  161. package/dist-server/service/twin-model/twin-model-item-query.js +38 -39
  162. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  163. package/dist-server/service/twin-model/twin-model-query.js +156 -4
  164. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  165. package/dist-server/service/twin-model/twin-model-tree-query.js +7 -0
  166. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  167. package/dist-server/service/twin-readiness/index.d.ts +2 -0
  168. package/dist-server/service/twin-readiness/index.js +6 -0
  169. package/dist-server/service/twin-readiness/index.js.map +1 -0
  170. package/dist-server/service/twin-readiness/twin-readiness-query.d.ts +3 -0
  171. package/dist-server/service/twin-readiness/twin-readiness-query.js +103 -0
  172. package/dist-server/service/twin-readiness/twin-readiness-query.js.map +1 -0
  173. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  174. package/dist-server/service/twin-space/twin-space-resolver.js +5 -4
  175. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  176. package/dist-shared/entity-delta.d.ts +23 -3
  177. package/dist-shared/entity-delta.js +12 -8
  178. package/dist-shared/entity-delta.js.map +1 -1
  179. package/dist-shared/kpi-broadcast.js +1 -1
  180. package/dist-shared/kpi-broadcast.js.map +1 -1
  181. package/dist-shared/touched-items.d.ts +7 -0
  182. package/dist-shared/touched-items.js +80 -0
  183. package/dist-shared/touched-items.js.map +1 -0
  184. package/package.json +7 -7
  185. package/server/engine/canonical-ingest.ts +92 -7
  186. package/server/engine/command-routing.ts +1 -1
  187. package/server/engine/index.ts +4 -0
  188. package/server/engine/ingest-health.ts +704 -0
  189. package/server/engine/integration-coverage.ts +147 -0
  190. package/server/engine/integration-probes.ts +144 -0
  191. package/server/engine/integration-runner.ts +95 -0
  192. package/server/engine/integration-target-profile.ts +103 -0
  193. package/server/engine/kpi-fold.ts +72 -4
  194. package/server/engine/kpi-query.ts +129 -15
  195. package/server/engine/live-feed-registry.ts +2 -1
  196. package/server/engine/load-meter.ts +12 -3
  197. package/server/engine/log.ts +72 -0
  198. package/server/engine/loop-lag.ts +120 -0
  199. package/server/engine/model-gap.ts +168 -0
  200. package/server/engine/restart-policy.ts +4 -4
  201. package/server/engine/runtime-key.ts +1 -1
  202. package/server/engine/stage-path.ts +172 -0
  203. package/server/engine/twin-engine.ts +1135 -162
  204. package/server/index.ts +35 -2
  205. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +54 -0
  206. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +53 -0
  207. package/server/migrations/index.ts +7 -1
  208. package/server/service/index.ts +11 -0
  209. package/server/service/reference/discovery-result.ts +1 -1
  210. package/server/service/reference/index.ts +6 -1
  211. package/server/service/reference/reference-adapter.ts +197 -5
  212. package/server/service/reference/reference-assessment.ts +215 -0
  213. package/server/service/reference/reference-live.ts +123 -11
  214. package/server/service/reference/reference-master.ts +64 -4
  215. package/server/service/reference/reference-probe.ts +264 -0
  216. package/server/service/reference/reference-progress-subscription.ts +73 -0
  217. package/server/service/reference/reference-progress.ts +95 -0
  218. package/server/service/reference/reference-resolver.ts +200 -12
  219. package/server/service/reference/twin-reference.ts +39 -1
  220. package/server/service/twin-control/twin-control-mutation.ts +4 -3
  221. package/server/service/twin-event/journal-count.ts +53 -0
  222. package/server/service/twin-event/twin-event-keys.ts +16 -1
  223. package/server/service/twin-event/twin-event-type.ts +26 -2
  224. package/server/service/twin-event/twin-event.ts +7 -0
  225. package/server/service/twin-forecast/twin-forecast-query.ts +2 -1
  226. package/server/service/twin-ingest-window/index.ts +7 -0
  227. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +125 -0
  228. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +57 -0
  229. package/server/service/twin-ingest-window/twin-ingest-window.ts +113 -0
  230. package/server/service/twin-instance/twin-instance.ts +1 -1
  231. package/server/service/twin-journal/twin-journal-query.ts +59 -5
  232. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +77 -3
  233. package/server/service/twin-metrics/twin-metrics-query.ts +81 -3
  234. package/server/service/twin-model/axis-journal-evidence.ts +57 -0
  235. package/server/service/twin-model/epcis-coverage.ts +2 -8
  236. package/server/service/twin-model/iec61850-coverage.ts +2 -8
  237. package/server/service/twin-model/isa95-coverage.ts +22 -9
  238. package/server/service/twin-model/item-ref.ts +80 -0
  239. package/server/service/twin-model/name-index.ts +94 -0
  240. package/server/service/twin-model/project-structure.ts +1 -1
  241. package/server/service/twin-model/status-tally.ts +37 -0
  242. package/server/service/twin-model/twin-lineage-query.ts +38 -9
  243. package/server/service/twin-model/twin-model-item-query.ts +26 -27
  244. package/server/service/twin-model/twin-model-query.ts +155 -4
  245. package/server/service/twin-model/twin-model-tree-query.ts +7 -0
  246. package/server/service/twin-readiness/index.ts +3 -0
  247. package/server/service/twin-readiness/twin-readiness-query.ts +96 -0
  248. package/server/service/twin-space/twin-space-resolver.ts +5 -4
  249. package/shared/entity-delta.ts +31 -7
  250. package/shared/kpi-broadcast.ts +1 -1
  251. package/shared/touched-items.ts +73 -0
  252. package/test/axis-journal-evidence.test.ts +71 -0
  253. package/test/axis-read.test.ts +101 -1
  254. package/test/boot-resume.test.ts +92 -26
  255. package/test/broadcast-cost-baseline.test.ts +200 -0
  256. package/test/broadcast-period.test.ts +58 -0
  257. package/test/canonical-ingest-vocabularies.test.ts +36 -1
  258. package/test/canonical-quantity-door.test.ts +61 -0
  259. package/test/command-routing.test.ts +1 -1
  260. package/test/declared-location-types.test.ts +89 -0
  261. package/test/discovery-result.test.ts +1 -1
  262. package/test/duration-estimators.test.ts +1 -1
  263. package/test/entity-delta.test.ts +2 -2
  264. package/test/ingest-bench.test.ts +3 -3
  265. package/test/ingest-health-engine.test.ts +248 -0
  266. package/test/ingest-health-wiring.test.ts +119 -0
  267. package/test/ingest-health.test.ts +306 -0
  268. package/test/ingest-history.test.ts +247 -0
  269. package/test/integration-probes.test.ts +103 -0
  270. package/test/integration-runner.test.ts +95 -0
  271. package/test/item-ref.test.ts +78 -0
  272. package/test/journal-read-discipline.test.ts +177 -0
  273. package/test/journal-retention.test.ts +133 -0
  274. package/test/journal-sort-axis.test.ts +142 -0
  275. package/test/journal-write-door.test.ts +110 -0
  276. package/test/journal-write-trend.test.ts +115 -0
  277. package/test/kernel-kind-guard.test.ts +4 -4
  278. package/test/kpi-fold.test.ts +90 -3
  279. package/test/kpi-query-bench.test.ts +2 -2
  280. package/test/lineage-survives-restart.test.ts +17 -1
  281. package/test/live-cursor-wiring.test.ts +87 -0
  282. package/test/live-feed-registry.test.ts +9 -1
  283. package/test/live-kernel-facts.test.ts +7 -7
  284. package/test/live-mirror-parity.test.ts +6 -0
  285. package/test/load-meter.test.ts +29 -15
  286. package/test/local-declarations.test.ts +1 -1
  287. package/test/log-stamp.test.ts +59 -0
  288. package/test/loop-lag.test.ts +82 -0
  289. package/test/model-gap.test.ts +153 -0
  290. package/test/oee-accumulator.test.ts +4 -0
  291. package/test/operational-vocabulary.test.ts +4 -4
  292. package/test/read-failure-visible.test.ts +145 -0
  293. package/test/reference-grounding.test.ts +70 -0
  294. package/test/resolve-ts-siblings.mjs +52 -0
  295. package/test/restart-policy.test.ts +7 -7
  296. package/test/revision-axis.test.ts +93 -0
  297. package/test/runtime-key.test.ts +2 -2
  298. package/test/scale-twin-bench.test.ts +2 -2
  299. package/test/spec-coverage.test.ts +1 -1
  300. package/test/stage-path.test.ts +95 -0
  301. package/test/standard-coverage.test.ts +15 -3
  302. package/test/status-tally.test.ts +55 -0
  303. package/test/structure-revision-db.test.ts +4 -0
  304. package/test/tenant-registry-db.test.ts +1 -1
  305. package/test/time-range.test.ts +152 -0
  306. package/test/touched-items.test.ts +61 -0
  307. package/test/twin-event-keys.test.ts +10 -2
  308. package/test/twin-model-item-db.test.ts +2 -0
  309. package/test/warm-start-seam.test.ts +4 -0
  310. package/test/yield-loop.test.ts +57 -2
  311. package/tsconfig.shared.tsbuildinfo +1 -1
  312. package/tsconfig.tsbuildinfo +1 -1
@@ -1,3 +1,4 @@
1
+ import type { ReferenceStep } from './reference-progress.js';
1
2
  import type { ReferenceMaster } from './reference-master.js';
2
3
  /** 연결 설정(어댑터별 스키마) — 엔드포인트·자격·파라미터. */
3
4
  export type ConnectionConfig = Record<string, any>;
@@ -82,6 +83,66 @@ export interface ControlResult {
82
83
  code?: string;
83
84
  detail?: string;
84
85
  }
86
+ /**
87
+ * 한 흐름의 읽기 커서 — **어디까지 읽었나.**
88
+ *
89
+ * `since` 와 `seen` 이 **함께** 있어야 한다. 이어 읽기는 경계 시각을 겹쳐 읽고(`gte`), 그 겹침을
90
+ * `seen` 이 걸러 낸다. `gt` 로 좁히면 같은 밀리초의 다른 행이 사라진다 — 조용한 누락이다.
91
+ *
92
+ * `seen` 이 예상보다 커지면(한 시각에 몰린 행 수가 늘면) 그것은 원본 쪽 신호다. 어댑터가 그 크기를
93
+ * 말해야 한다 — 조용히 커지게 두면 이어 읽기가 막히는 순간까지 아무도 모른다.
94
+ */
95
+ export interface LiveFeedCursor {
96
+ /** 그 흐름에서 마지막으로 본 시각(원본의 시각, ISO). */
97
+ since?: string;
98
+ /** 그 시각에 이미 소비한 행 id — 겹쳐 읽은 것을 걸러 낸다. 저장되는 모양이므로 배열이다. */
99
+ seen: string[];
100
+ }
101
+ /**
102
+ * 재기동을 넘어 이어지는 읽기 상태 — 참조 계층이 소유하고 어댑터에 건넨다(§`openLiveFeed`).
103
+ *
104
+ * 흐름 열쇠는 **어댑터가 정한다**(`Record`). 계약이 이름을 닫으면 새 원본마다 계약을 고치게 된다.
105
+ */
106
+ export interface LiveFeedContinuity {
107
+ /** 지난번에 어디까지 읽었나 — 없으면 첫 붙음이다(그때만 되돌아볼 날수로 창을 만든다). */
108
+ cursor?: {
109
+ streams?: Record<string, LiveFeedCursor>;
110
+ firstAttachedAt?: string;
111
+ };
112
+ /**
113
+ * 커서가 움직였다고 알린다 — 이 층이 저장한다.
114
+ *
115
+ * 어댑터가 **매 행마다 부르지 않는다**: 저장은 이 층의 몫이고, 잦으면 이 층이 눌린다. 흐름 하나가
116
+ * 한 묶음을 소비한 뒤 한 번이면 충분하다.
117
+ */
118
+ onCursor?: (cursor: {
119
+ streams: Record<string, LiveFeedCursor>;
120
+ firstAttachedAt?: string;
121
+ }) => void;
122
+ /**
123
+ * **원본에 닿지 못했다** — 어댑터가 읽기 실패를 알린다(2026-08-23).
124
+ *
125
+ * ── 무엇이 조용했나 ─────────────────────────────────────────────────────────
126
+ * 실 원본이 끊겼을 때(접속 시간 초과) 트윈의 **조회 가능한 상태 어디에도** 그 사실이 없었다.
127
+ * `onRecords` 가 불리지 않으면 유입 장부에 아무 일도 일어나지 않고, `TwinReference.lastError` 는
128
+ * 마스터 동기·접속 시험에만 적힌다. 그래서 화면이 볼 수 있는 것은 「새 사실이 없다」뿐이었고,
129
+ * 그것은 **「원본이 조용하다」와 구별되지 않는다.**
130
+ *
131
+ * 어댑터는 로그로 말하고 있었다(재시도 경고). 그러나 **로그는 사람이 볼 때만 값이 있다** — 화면이
132
+ * 말하려면 상태에 있어야 한다. 이 통로가 그 자리다.
133
+ *
134
+ * ── 무엇을 알리고 무엇을 알리지 않나 ────────────────────────────────────────
135
+ * **재시도를 다 쓰고 그 주기를 포기했을 때** 부른다 — 재시도마다 부르면 한 번의 흔들림이 단절로 보인다.
136
+ * 흐름 여럿을 읽는 원본은 흐름 이름을 함께 준다(어느 표가 막혔나가 원인 찾기의 절반이다).
137
+ *
138
+ * **빈 읽기는 실패가 아니다** — 원본이 「새 것이 없다」고 답한 것은 닿았다는 뜻이므로 `onRecords([])`
139
+ * 로 알린다. 그 둘을 섞으면 조용한 원본이 끊긴 원본으로 보인다(고치려던 것의 반대 방향으로 틀린다).
140
+ */
141
+ onReadFailure?: (info: {
142
+ reason: string;
143
+ stream?: string;
144
+ }) => void;
145
+ }
85
146
  export interface ReferenceAdapter {
86
147
  /** 레지스트리 키 (예: 'virtual' | 'sap-ewm' | 'custom-rest'). */
87
148
  type: string;
@@ -95,9 +156,60 @@ export interface ReferenceAdapter {
95
156
  /** N 발견 — 사이트/공장 열거. 단일 소스는 길이 1 반환. */
96
157
  discoverSites(cfg: ConnectionConfig): Promise<SiteDescriptor[]>;
97
158
  /** 사이트별 마스터(구조) — 기존 masterToTwin 이 소비. */
98
- fetchMaster(cfg: ConnectionConfig, site: SiteDescriptor): Promise<ReferenceMaster>;
99
- /** (선택) 사이트별 이벤트 스트림 없으면 sim. onRecords 레코드 push, unsubscribe 반환. */
100
- openLiveFeed?(cfg: ConnectionConfig, site: SiteDescriptor, onRecords: (records: unknown[]) => void): () => void;
159
+ /**
160
+ * 어댑터가 마스터를 읽으며 **보낼 단계를 미리 선언한다** 그것이 진행률의 분모다.
161
+ *
162
+ * 표준 낱말만 쓴다(`REFERENCE_STEPS`). 선언하지 않으면 화면은 퍼센트를 보이지 않고 단계 이름만
163
+ * 보인다 — **모르는 진행률을 지어내지 않는다.**
164
+ *
165
+ * 원본에 없는 자리는 선언하지 않는다. 그러면 「이 원본으로 채울 수 있는 만큼」이 곧 분모가 된다.
166
+ */
167
+ masterSteps?: readonly ReferenceStep[];
168
+ /**
169
+ * @param onStep 진행을 알리는 통로 — **선택이다.** 주지 않으면 예전과 같이 동작한다.
170
+ *
171
+ * 마스터 읽기는 원본에 여러 질의를 보내는 긴 작업이고(창고·자리·설비·공정·경로·품목·BOM), 화면은 그
172
+ * 사이에 아무것도 알 수 없었다. 어댑터만 자기 단계를 아므로 어댑터가 말해야 한다 — 호스트가 짐작하면
173
+ * 그것은 지어낸 진행률이다.
174
+ *
175
+ * `total` 은 **그 어댑터가 보낼 단계 수**다. 모르면 주지 않는다(화면이 퍼센트를 만들지 않는다).
176
+ */
177
+ fetchMaster(cfg: ConnectionConfig, site: SiteDescriptor, onStep?: (step: {
178
+ key: ReferenceStep;
179
+ done: number;
180
+ total?: number;
181
+ detail?: string;
182
+ }) => void): Promise<ReferenceMaster>;
183
+ /**
184
+ * (선택) 사이트별 실 이벤트 스트림 — 없으면 sim. onRecords 로 레코드 push, unsubscribe 반환.
185
+ *
186
+ * ── 네 번째 인자: **읽기 커서** (2026-08-23) ────────────────────────────────
187
+ *
188
+ * 왜 이 층이 커서를 드나: 커널이 그 갈림을 이미 적어 두었다(§`hydrateContinuity`) — 「원천이 **애초에
189
+ * 다시 말해 주지 않는 축**」은 재기동 연속성으로 이어받는다. **「우리가 어디까지 읽었나」가 정확히 그
190
+ * 성질이다.** 원본은 그것을 되풀어 주지 않는다.
191
+ *
192
+ * 커서가 없던 동안 어댑터는 붙을 때마다 `Date.now() − 되돌아볼 날수` 로 창을 새로 만들었다. 그래서
193
+ * **재기동마다 미러의 과거가 잘렸다** — 실측으로 작업 2,855 → 2,820(8시간 흐른 만큼). 미러가 아는
194
+ * 것이 「원본의 사실」이 아니라 「창의 함수」였다.
195
+ *
196
+ * 그런데 미러의 상태는 **원천으로 서야 한다**(같은 절: 「미러의 진실은 원천이다 … 심으면 떠난 물건이
197
+ * 되살아난다」). 그러니 저널을 접어 상태를 세우는 것으로 고칠 일이 아니다 — 그 선언의 **전제**가
198
+ * 「원천이 다시 말해 준다」이고, 창이 좁아지면 그 전제가 깨진다. 창을 고치는 것이 그 원칙을 지키는 길이다.
199
+ *
200
+ * ── 계약의 모양 ─────────────────────────────────────────────────────────────
201
+ * · **흐름 열쇠는 어댑터가 정한다.** 원본마다 흐름 수와 뜻이 다르다(이 원본은 재고·오더·로트·투입 넷).
202
+ * 계약이 이름을 닫으면 새 원본마다 계약을 고치게 된다 — 원본의 스키마가 계약을 끌고 가는 그 모양이다.
203
+ * · **저장되는 모양으로 넘긴다**(`seen` 은 배열). 이 층은 저장·복원만 하고 안을 해석하지 않는다.
204
+ * `Set` 을 받으면 이 층이 직렬화를 알게 되고, 커서 모양이 바뀔 때 두 곳을 고친다.
205
+ * · `firstAttachedAt` — **첫 창을 만든 시각.** 이후 재기동에서 바뀌지 않는다. 커널은 아는 구간의
206
+ * 상한만 안다(`nowTime` = 마지막으로 들은 시각). 이 값이 하한이고, 둘이 「이 트윈이 아는 구간」이다.
207
+ * 화면이 수를 보일 때 그 구간을 함께 말해야 한다 — 말하지 않으면 조용히 자르는 것이 된다.
208
+ * · **커서가 없을 때만** 되돌아볼 날수를 쓴다. 그 순간이 `firstAttachedAt` 이다.
209
+ *
210
+ * 주지 않는 호출자와도 함께 선다(선택 인자) — 커서를 모르는 배포는 예전처럼 동작한다.
211
+ */
212
+ openLiveFeed?(cfg: ConnectionConfig, site: SiteDescriptor, onRecords: (records: unknown[]) => void, continuity?: LiveFeedContinuity): () => void;
101
213
  /**
102
214
  * 이 원본이 **선언하는 능력** — 없으면 「구조만 준다」는 뜻이다(ADR-0029 §8).
103
215
  *
@@ -105,6 +217,28 @@ export interface ReferenceAdapter {
105
217
  * 구현이 없으면 그 능력은 **없는 것으로 읽는다**(선언만 있는 능력은 사용자에게 거짓말이 된다).
106
218
  */
107
219
  capabilities?: ReferenceCapability[];
220
+ /**
221
+ * 이 커넥터가 **무엇에 대고 확인됐나** — 능력과 다른 축이다(2026-08-23).
222
+ *
223
+ * ── 왜 필요한가 ─────────────────────────────────────────────────────────────
224
+ * `capabilities` 는 「무엇을 할 수 있다고 선언하나」이고, 이 자리는 **「그 선언이 무엇으로 확인됐나」**다.
225
+ * 둘이 갈리는 것이 지금 상태다: 등록된 커넥터 열넷 중 실 시스템에 붙어 본 것은 **하나**이고, 나머지는
226
+ * 목(mock)에 대고 만들었다. 그런데 화면의 커넥터 목록에 그 구분이 없어서 **목으로 확인한 것이 확인으로
227
+ * 보인다.**
228
+ *
229
+ * 그것은 이 저장소가 다른 자리에서 계속 거절하는 모양이다 — 「선언만 있는 능력은 사용자에게
230
+ * 거짓말이 된다」(§`capabilitiesOf`). 능력에 세운 그 규율을 근거에도 세운다.
231
+ *
232
+ * ── 왜 계약이고 배포 노트가 아닌가 ──────────────────────────────────────────
233
+ * 노트는 다음 사람이 보지 않는다. 계약에 있으면 새 커넥터가 그 값을 **빠뜨릴 수 없고**, 빠뜨리면
234
+ * `groundingOf` 가 `unknown` 으로 답해 화면이 그 사실을 말한다.
235
+ *
236
+ * ── 자동으로 알 수 없는 값이다 ──────────────────────────────────────────────
237
+ * 능력은 구현이 있는지 보면 알 수 있다. 「실 시스템에 붙여 봤나」는 **볼 수 없다** — 사람이 아는 사실이다.
238
+ * 그래서 이 선언은 낡을 수 있고, 그것을 막는 것이 `verifiedAgainst` 다: **무엇에 대고 확인했는지 적지
239
+ * 않으면 그 주장은 주장이 아니다**(`groundingOf` 가 `unknown` 으로 내린다).
240
+ */
241
+ grounding?: ReferenceGrounding;
108
242
  /**
109
243
  * (선택) 구동 — 자극·속도·정지·초기화. **`capabilities` 에 `control` 을 선언한 원본만** 가진다.
110
244
  *
@@ -118,12 +252,59 @@ export interface ReferenceAdapter {
118
252
  * 선언만 있고 구현이 없으면 화면이 「할 수 있다」고 말한 뒤 아무 일도 일어나지 않는다. 반대로 구현이
119
253
  * 있는데 선언이 없으면 그 능력은 **의도적으로 닫아 둔 것**이므로 열지 않는다 — 선언이 권위다.
120
254
  */
255
+ /**
256
+ * 근거 수준 — **무엇에 대고 확인했나.** 넷이고 닫혀 있다.
257
+ *
258
+ * `synthetic` 실 시스템이 아니다 — 스스로 만들어 낸다(가상 원본·시뮬레이터). 한계가 아니라 성질이다.
259
+ * `mock` 목·목 REST 에 대고만 확인했다. **실 시스템에 붙어 본 적이 없다.**
260
+ * `instance` 실 시스템 **한 배포**에 붙여 확인했다. 그 배포의 성질과 제품의 성질을 아직 가르지 못했다.
261
+ * `product` 실 시스템 **여러 배포**에 붙여 확인했다 — 그때 비로소 제품의 성질이라 말할 수 있다.
262
+ *
263
+ * `instance` 와 `product` 를 가르는 이유: 첫 실 연동에서 그 배포만의 성질이 넷 드러났고(관계 리솔버가
264
+ * 다른 테넌트의 행을 답한다 · 모르는 필터를 조용히 무시한다 · 지워진 품목의 BOM 이 남아 있다 ·
265
+ * 사실상 무한을 뜻하는 표식) **그것이 그 제품의 성질인지 그 인스턴스의 성질인지 아무도 모른다.**
266
+ * 두 번째 배포가 붙는 날 갈린다. 그 사실을 어휘가 담는다.
267
+ */
268
+ export type ReferenceGroundingLevel = 'synthetic' | 'mock' | 'instance' | 'product';
269
+ export interface ReferenceGrounding {
270
+ level: ReferenceGroundingLevel;
271
+ /**
272
+ * **무엇에 대고 확인했나** — 사람이 읽는 한 줄(원본의 판·인스턴스·목의 이름).
273
+ *
274
+ * `instance`·`product` 를 주장하면서 이것이 비어 있으면 `groundingOf` 가 `unknown` 으로 내린다 —
275
+ * 가리키는 대상이 없는 주장은 주장이 아니고, 그런 주장은 낡아도 아무도 알 수 없다.
276
+ */
277
+ verifiedAgainst?: string;
278
+ }
279
+ /**
280
+ * 이 커넥터의 근거 — **주장을 그대로 믿지 않는다**(`capabilitiesOf` 와 같은 규율).
281
+ *
282
+ * 능력은 구현이 있는지 보아 선언을 검증할 수 있다. 근거는 **볼 수 없다** — 사람이 아는 사실이다.
283
+ * 그래서 검증할 수 있는 것 하나만 검증한다: **실 시스템을 주장하면 무엇에 대고인지 말해야 한다.**
284
+ *
285
+ * 선언이 없으면 `unknown` 이다 — 「목이다」가 아니라 **「모른다」**다. 둘을 같게 두면 아직 선언하지 않은
286
+ * 새 커넥터가 목으로 낙인찍히고, 그것도 없는 사실이다.
287
+ */
288
+ export declare function groundingOf(adapter: ReferenceAdapter | undefined): {
289
+ level: ReferenceGroundingLevel | 'unknown';
290
+ verifiedAgainst?: string;
291
+ /** 주장이 내려갔으면 그 이유 — 화면이 그대로 옮긴다. */
292
+ downgraded?: string;
293
+ };
121
294
  export declare function capabilitiesOf(adapter: ReferenceAdapter | undefined): ReferenceCapability[];
122
295
  export declare function registerAdapterType(adapter: ReferenceAdapter): void;
123
296
  export declare function getAdapter(type: string): ReferenceAdapter | undefined;
124
297
  export declare function listAdapterTypes(): string[];
125
298
  /** 커넥터 메타데이터 목록(picker·연결 폼용). */
299
+ /**
300
+ * 등록된 어댑터 목록 — **선언한 능력도 함께 낸다.**
301
+ *
302
+ * 예전에는 `{ type, meta }` 만 냈습니다. 그런데 소비처(어댑터 목록 질의)가 `capabilities` 를 함께
303
+ * 내보내고 있어 타입검사가 막혔고, 그 상태로는 개발 서버 빌드가 실패합니다. 능력은 이 목록을 읽는
304
+ * 화면이 「이 원본에 무엇을 요구할 수 있나」를 판단하는 근거이므로, 빼지 않고 여기서 함께 냅니다.
305
+ */
126
306
  export declare function listAdapters(): {
127
307
  type: string;
128
308
  meta?: AdapterMeta;
309
+ capabilities?: ReferenceCapability[];
129
310
  }[];
@@ -1,16 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.groundingOf = groundingOf;
3
4
  exports.capabilitiesOf = capabilitiesOf;
4
5
  exports.registerAdapterType = registerAdapterType;
5
6
  exports.getAdapter = getAdapter;
6
7
  exports.listAdapterTypes = listAdapterTypes;
7
8
  exports.listAdapters = listAdapters;
8
9
  /**
9
- * 이 원본이 **실제로 있는 것** 선언과 구현의 교집합.
10
+ * 이 커넥터의 근거 **주장을 그대로 믿지 않는다**(`capabilitiesOf` 같은 규율).
10
11
  *
11
- * 선언만 있고 구현이 없으면 화면이 「할있다」고 말한 아무 일도 일어나지 않는다. 반대로 구현이
12
- * 있는데 선언이 없으면 능력은 **의도적으로 닫아 것**이므로 열지 않는다 선언이 권위다.
12
+ * 능력은 구현이 있는지 보아 선언을 검증할 있다. 근거는 **볼 없다** 사람이 아는 사실이다.
13
+ * 그래서 검증할 있는 하나만 검증한다: **실 시스템을 주장하면 무엇에 대고인지 말해야 한다.**
14
+ *
15
+ * 선언이 없으면 `unknown` 이다 — 「목이다」가 아니라 **「모른다」**다. 둘을 같게 두면 아직 선언하지 않은
16
+ * 새 커넥터가 목으로 낙인찍히고, 그것도 없는 사실이다.
13
17
  */
18
+ function groundingOf(adapter) {
19
+ const g = adapter?.grounding;
20
+ if (!g?.level)
21
+ return { level: 'unknown' };
22
+ const against = g.verifiedAgainst?.trim();
23
+ if ((g.level === 'instance' || g.level === 'product') && !against) {
24
+ return {
25
+ level: 'unknown',
26
+ downgraded: `claims "${g.level}" but does not say what it was verified against`
27
+ };
28
+ }
29
+ return { level: g.level, ...(against ? { verifiedAgainst: against } : {}) };
30
+ }
14
31
  function capabilitiesOf(adapter) {
15
32
  if (!adapter)
16
33
  return [];
@@ -34,7 +51,14 @@ function listAdapterTypes() {
34
51
  return [...adapters.keys()];
35
52
  }
36
53
  /** 커넥터 메타데이터 목록(picker·연결 폼용). */
54
+ /**
55
+ * 등록된 어댑터 목록 — **선언한 능력도 함께 낸다.**
56
+ *
57
+ * 예전에는 `{ type, meta }` 만 냈습니다. 그런데 소비처(어댑터 목록 질의)가 `capabilities` 를 함께
58
+ * 내보내고 있어 타입검사가 막혔고, 그 상태로는 개발 서버 빌드가 실패합니다. 능력은 이 목록을 읽는
59
+ * 화면이 「이 원본에 무엇을 요구할 수 있나」를 판단하는 근거이므로, 빼지 않고 여기서 함께 냅니다.
60
+ */
37
61
  function listAdapters() {
38
- return [...adapters.values()].map(a => ({ type: a.type, meta: a.meta }));
62
+ return [...adapters.values()].map(a => ({ type: a.type, meta: a.meta, capabilities: a.capabilities }));
39
63
  }
40
64
  //# sourceMappingURL=reference-adapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reference-adapter.js","sourceRoot":"","sources":["../../../server/service/reference/reference-adapter.ts"],"names":[],"mappings":";;AA0IA,wCAOC;AAKD,kDAEC;AACD,gCAEC;AACD,4CAEC;AAED,oCAEC;AA9BD;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,OAAqC;IAClE,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAA;IACvB,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAA;IACpD,MAAM,GAAG,GAA0B,EAAE,CAAA;IACrC,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,OAAO,CAAC,YAAY,KAAK,UAAU;QAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACxF,IAAI,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,UAAU;QAAE,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACzF,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,wEAAwE;AACxE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAA;AAEpD,SAAgB,mBAAmB,CAAC,OAAyB;IAC3D,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AACrC,CAAC;AACD,SAAgB,UAAU,CAAC,IAAY;IACrC,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AAC3B,CAAC;AACD,SAAgB,gBAAgB;IAC9B,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;AAC7B,CAAC;AACD,kCAAkC;AAClC,SAAgB,YAAY;IAC1B,OAAO,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;AAC1E,CAAC","sourcesContent":["import type { ReferenceMaster } from './reference-master.js'\n\n/*\n * 레퍼런스 어댑터 계약 (reference-management P1) — 정적 마스터 등록(`registerReference`)을\n * \"어댑터 타입\" 등록으로 일반화한다. 레퍼런스 = 데이터(TwinReference 행), 어댑터 = 동작.\n * inbound 전용(구조·상태 유입). 아웃바운드 액추에이션은 command-routing(별도 ActuationAdapter) 소유.\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/** 커넥터 메타데이터 — 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 */\nexport type ReferenceCapability = 'live' | 'control'\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\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 fetchMaster(cfg: ConnectionConfig, site: SiteDescriptor): Promise<ReferenceMaster>\n /** (선택) 사이트별 실 이벤트 스트림 — 없으면 sim. onRecords 로 레코드 push, unsubscribe 반환. */\n openLiveFeed?(cfg: ConnectionConfig, site: SiteDescriptor, onRecords: (records: unknown[]) => void): () => void\n /**\n * 이 원본이 **선언하는 능력** — 없으면 「구조만 준다」는 뜻이다(ADR-0029 §8).\n *\n * 선언과 구현이 어긋나지 않게 `capabilitiesOf` 가 둘을 함께 본다: `control` 을 선언했는데 `control`\n * 구현이 없으면 그 능력은 **없는 것으로 읽는다**(선언만 있는 능력은 사용자에게 거짓말이 된다).\n */\n capabilities?: ReferenceCapability[]\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 */\nexport function capabilitiesOf(adapter: ReferenceAdapter | undefined): ReferenceCapability[] {\n if (!adapter) return []\n const declared = new Set(adapter.capabilities ?? [])\n const out: ReferenceCapability[] = []\n if (declared.has('live') && typeof adapter.openLiveFeed === 'function') out.push('live')\n if (declared.has('control') && typeof adapter.control === 'function') out.push('control')\n return out\n}\n\n/* 어댑터 \"타입\" 레지스트리(전역·인메모리) — 레퍼런스 데이터는 DB(TwinReference), 어댑터 동작은 여기. */\nconst adapters = new Map<string, ReferenceAdapter>()\n\nexport function registerAdapterType(adapter: ReferenceAdapter): void {\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·연결 폼용). */\nexport function listAdapters(): { type: string; meta?: AdapterMeta }[] {\n return [...adapters.values()].map(a => ({ type: a.type, meta: a.meta }))\n}\n"]}
1
+ {"version":3,"file":"reference-adapter.js","sourceRoot":"","sources":["../../../server/service/reference/reference-adapter.ts"],"names":[],"mappings":";;AAiTA,kCAgBC;AAED,wCAOC;AAKD,kDAEC;AACD,gCAEC;AACD,4CAEC;AASD,oCAEC;AA1DD;;;;;;;;GAQG;AACH,SAAgB,WAAW,CAAC,OAAqC;IAM/D,MAAM,CAAC,GAAG,OAAO,EAAE,SAAS,CAAA;IAC5B,IAAI,CAAC,CAAC,EAAE,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;IAC1C,MAAM,OAAO,GAAG,CAAC,CAAC,eAAe,EAAE,IAAI,EAAE,CAAA;IACzC,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,UAAU,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAClE,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,UAAU,EAAE,WAAW,CAAC,CAAC,KAAK,iDAAiD;SAChF,CAAA;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AAC7E,CAAC;AAED,SAAgB,cAAc,CAAC,OAAqC;IAClE,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAA;IACvB,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAA;IACpD,MAAM,GAAG,GAA0B,EAAE,CAAA;IACrC,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,OAAO,CAAC,YAAY,KAAK,UAAU;QAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACxF,IAAI,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,UAAU;QAAE,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACzF,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,wEAAwE;AACxE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAA;AAEpD,SAAgB,mBAAmB,CAAC,OAAyB;IAC3D,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AACrC,CAAC;AACD,SAAgB,UAAU,CAAC,IAAY;IACrC,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AAC3B,CAAC;AACD,SAAgB,gBAAgB;IAC9B,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;AAC7B,CAAC;AACD,kCAAkC;AAClC;;;;;;GAMG;AACH,SAAgB,YAAY;IAC1B,OAAO,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAA;AACxG,CAAC","sourcesContent":["import type { ReferenceStep } from './reference-progress.js'\nimport type { ReferenceMaster } from './reference-master.js'\n\n/*\n * 레퍼런스 어댑터 계약 (reference-management P1) — 정적 마스터 등록(`registerReference`)을\n * \"어댑터 타입\" 등록으로 일반화한다. 레퍼런스 = 데이터(TwinReference 행), 어댑터 = 동작.\n * inbound 전용(구조·상태 유입). 아웃바운드 액추에이션은 command-routing(별도 ActuationAdapter) 소유.\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/** 커넥터 메타데이터 — 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 */\nexport type ReferenceCapability = 'live' | 'control'\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 * 재기동을 넘어 이어지는 읽기 상태 — 참조 계층이 소유하고 어댑터에 건넨다(§`openLiveFeed`).\n *\n * 흐름 열쇠는 **어댑터가 정한다**(`Record`). 계약이 이름을 닫으면 새 원본마다 계약을 고치게 된다.\n */\nexport interface LiveFeedContinuity {\n /** 지난번에 어디까지 읽었나 — 없으면 첫 붙음이다(그때만 되돌아볼 날수로 창을 만든다). */\n cursor?: { streams?: Record<string, LiveFeedCursor>; firstAttachedAt?: string }\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 onReadFailure?: (info: { reason: string; stream?: string }) => void\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 * @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 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\nexport function capabilitiesOf(adapter: ReferenceAdapter | undefined): ReferenceCapability[] {\n if (!adapter) return []\n const declared = new Set(adapter.capabilities ?? [])\n const out: ReferenceCapability[] = []\n if (declared.has('live') && typeof adapter.openLiveFeed === 'function') out.push('live')\n if (declared.has('control') && typeof adapter.control === 'function') out.push('control')\n return out\n}\n\n/* 어댑터 \"타입\" 레지스트리(전역·인메모리) — 레퍼런스 데이터는 DB(TwinReference), 어댑터 동작은 여기. */\nconst adapters = new Map<string, ReferenceAdapter>()\n\nexport function registerAdapterType(adapter: ReferenceAdapter): void {\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 * 예전에는 `{ type, meta }` 만 냈습니다. 그런데 소비처(어댑터 목록 질의)가 `capabilities` 를 함께\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: a.capabilities }))\n}\n"]}
@@ -0,0 +1,68 @@
1
+ import type { IngestWarning, ReferenceMaster } from './reference-master.js';
2
+ /** 센 것 — 화면이 그대로 표로 그린다. */
3
+ export interface AssessmentCounts {
4
+ locations: number;
5
+ areas: number;
6
+ equipment: number;
7
+ materialDefinitions: number;
8
+ operations: number;
9
+ /** 생산 선언이 없으면 없다 — 0 과 구별한다(선언 자체가 없는 것과 비어 있는 것은 다르다). */
10
+ materials?: number;
11
+ materialsWithHome?: number;
12
+ routes?: number;
13
+ recipes?: number;
14
+ recipesWithRoute?: number;
15
+ /** 공정을 말하는 투입 / 전체 투입 — 공정별 소요를 아는 정도. */
16
+ taggedInputs?: number;
17
+ totalInputs?: number;
18
+ }
19
+ /** 옮기지 못한 것 한 갈래 — 건수 큰 것부터. */
20
+ export interface AssessmentGap {
21
+ /** 무엇을 옮기다 못 옮겼나 — 커넥터가 붙인 이름(`material home warehouse`). */
22
+ what: string;
23
+ count: number;
24
+ /** 못 옮긴 원본 값 몇 개 — 전부가 아니라 표본이다(수백 개가 붙던 자리다). */
25
+ sample?: string;
26
+ /** 그 자리에 대신 들어간 값. */
27
+ fallback?: string;
28
+ }
29
+ export interface ImportAssessment {
30
+ source: string;
31
+ siteId: string;
32
+ siteName?: string;
33
+ system?: string;
34
+ built: AssessmentCounts;
35
+ /**
36
+ * **원본이 가지고 있던 수** — 커넥터가 말해 준 경우에만 있다.
37
+ *
38
+ * 이름을 `origin` 이라 하지 않는다 — 화면에서 그 낱말이 이미 **트윈의 출처**(`summary.origin`:
39
+ * 템플릿인가 원본인가)를 뜻한다. 같은 이름을 두 뜻으로 쓰면 읽는 사람이 헷갈린다.
40
+ *
41
+ * `built` 와 견주어 「전부 옮겼는가」를 사람이 직접 셀 수 있게 한다. 커넥터가 말하지 않으면 이 자리는
42
+ * 비어 있고, 화면은 분모 없이 옮긴 수만 보인다 — **분모를 지어내지 않는다.**
43
+ */
44
+ originCounts?: Record<string, number>;
45
+ gaps: AssessmentGap[];
46
+ /** 이 원본에 개념이 없는 자리 — 커널 낱말로 적는다(화면이 번역한다). */
47
+ absent: string[];
48
+ /**
49
+ * 값이 들어왔지만 **근거가 원본의 저장된 값이 아닌 것** — 파생·유추된 값이다.
50
+ *
51
+ * 이것을 `gaps` 에 섞으면 안 된다. 빈 자리가 아니라 **채워진 자리**이고, 사람이 물을 것은
52
+ * 「왜 비었나」가 아니라 「그 값을 믿어도 되나」다. 조치도 다르다 — 원본을 채우는 것이 아니라
53
+ * 그 규칙이 바뀌면 이 값도 바뀐다는 사실을 아는 것이다.
54
+ */
55
+ derived: AssessmentGap[];
56
+ /** 되물은 횟수·잘린 목록 같은 운영 사실. */
57
+ notes: IngestWarning[];
58
+ }
59
+ /**
60
+ * 마스터 한 벌 → 총평의 사실 층.
61
+ *
62
+ * 경고를 두 갈래로 나눈다: `connector-mapping-fallback` 은 **옮기지 못한 것**이므로 `gaps` 로,
63
+ * 나머지(파생된 값·되물은 횟수·잘린 목록)는 `notes` 로 그대로 나른다. 판단하지 않는다 — 무엇이
64
+ * 심각한지는 건수와 그 자리의 뜻이 말하고, 그 해석은 사람이나 말 층의 몫이다.
65
+ */
66
+ export declare function assessMaster(master: ReferenceMaster, siteId: string): ImportAssessment;
67
+ /** 사람이 읽는 줄 — 터미널·로그가 읽는다(화면은 구조를 그대로 그린다). */
68
+ export declare function formatAssessment(a: ImportAssessment): string;
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assessMaster = assessMaster;
4
+ exports.formatAssessment = formatAssessment;
5
+ /** 이 마스터가 개념을 선언했나 — 「없다」와 「비어 있다」를 가른다. */
6
+ const declared = (v) => Array.isArray(v) ? v.length > 0 : v !== undefined && v !== null;
7
+ /**
8
+ * 마스터 한 벌 → 총평의 사실 층.
9
+ *
10
+ * 경고를 두 갈래로 나눈다: `connector-mapping-fallback` 은 **옮기지 못한 것**이므로 `gaps` 로,
11
+ * 나머지(파생된 값·되물은 횟수·잘린 목록)는 `notes` 로 그대로 나른다. 판단하지 않는다 — 무엇이
12
+ * 심각한지는 건수와 그 자리의 뜻이 말하고, 그 해석은 사람이나 말 층의 몫이다.
13
+ */
14
+ function assessMaster(master, siteId) {
15
+ const m = master;
16
+ const d = m.productionSpec?.definition;
17
+ const inputs = (d?.recipes ?? []).flatMap((r) => r.inputs ?? []);
18
+ const built = {
19
+ locations: (m.locations ?? []).length,
20
+ areas: (m.locations ?? []).filter((l) => l.role === 'area' || l.role === 'both').length,
21
+ equipment: (m.equipment ?? []).length,
22
+ materialDefinitions: (m.materialDefinitions ?? []).length,
23
+ operations: (m.operations ?? []).length,
24
+ ...(d
25
+ ? {
26
+ materials: (d.materials ?? []).length,
27
+ materialsWithHome: (d.materials ?? []).filter((x) => x.locationType).length,
28
+ routes: (d.routes ?? []).length,
29
+ recipes: (d.recipes ?? []).length,
30
+ recipesWithRoute: (d.recipes ?? []).filter((x) => x.route).length,
31
+ totalInputs: inputs.length,
32
+ taggedInputs: inputs.filter((i) => i.operation).length
33
+ }
34
+ : {})
35
+ };
36
+ /* 원본의 수는 커넥터가 말한 그대로 나른다 — 여기서 고치거나 채우지 않는다. */
37
+ const originCounts = m.originCounts && Object.keys(m.originCounts).length ? { ...m.originCounts } : undefined;
38
+ const gaps = [];
39
+ const derived = [];
40
+ const notes = [];
41
+ /*
42
+ * 커넥터가 「대체」 수집기로 남기는 것 중 일부는 **빈 자리가 아니라 파생된 값**이다(산출량이 원본의
43
+ * 계산 규칙에서 온 경우처럼). 같은 통로로 오지만 뜻이 달라 갈라 담는다 — 이름에 그 사실이 적혀 있다.
44
+ */
45
+ const isDerived = (field) => /derived|not a stored field/i.test(field);
46
+ for (const w of m.warnings ?? []) {
47
+ if (w?.code === 'connector-mapping-fallback' && w.params?.field) {
48
+ const values = String(w.params.values ?? '');
49
+ const bin = isDerived(String(w.params.field)) ? derived : gaps;
50
+ bin.push({
51
+ what: String(w.params.field),
52
+ count: Number(w.params.count ?? 0),
53
+ /* 값은 표본만 — 이 자리에 UUID 수백 개가 붙어 화면을 밀어냈다. 전체는 경고 원문에 남는다. */
54
+ ...(values ? { sample: values.length > 120 ? `${values.slice(0, 120)}…` : values } : {}),
55
+ ...(w.params.fallback ? { fallback: String(w.params.fallback) } : {})
56
+ });
57
+ continue;
58
+ }
59
+ notes.push(w);
60
+ }
61
+ gaps.sort((a, b) => b.count - a.count);
62
+ derived.sort((a, b) => b.count - a.count);
63
+ /*
64
+ * 이 원본에 개념이 없는 자리. **커널 낱말로 적는다** — 화면이 그것으로 번역하고, 개념 지도가 이미
65
+ * 같은 낱말을 쓴다(두 화면이 다른 말을 하면 사람이 둘을 다른 사실로 읽는다).
66
+ */
67
+ const absent = [];
68
+ if (!declared(m.persons))
69
+ absent.push('persons');
70
+ if (!declared(m.assets))
71
+ absent.push('assets');
72
+ if (!declared(m.testSpecifications))
73
+ absent.push('testSpecifications');
74
+ if (!declared(m.personnelClasses))
75
+ absent.push('personnelClasses');
76
+ if (!declared(m.assetClasses))
77
+ absent.push('assetClasses');
78
+ if (!declared(m.materialClasses))
79
+ absent.push('materialClasses');
80
+ if (!d)
81
+ absent.push('productionSpec');
82
+ return {
83
+ source: m.source,
84
+ siteId,
85
+ siteName: m.siteName,
86
+ system: m.system,
87
+ built,
88
+ ...(originCounts ? { originCounts } : {}),
89
+ gaps,
90
+ derived,
91
+ absent,
92
+ notes
93
+ };
94
+ }
95
+ /** 사람이 읽는 줄 — 터미널·로그가 읽는다(화면은 구조를 그대로 그린다). */
96
+ function formatAssessment(a) {
97
+ const c = a.built;
98
+ const out = [];
99
+ /**
100
+ * 분모가 있으면 함께 적는다 — 「1,261」과 「원본 1,408 중 1,261」은 다른 말이다.
101
+ * 커넥터가 그 수를 말하지 않았으면 옮긴 수만 적는다(지어내지 않는다).
102
+ */
103
+ const n = (key, v) => {
104
+ if (v === undefined)
105
+ return '없음';
106
+ const o = a.originCounts?.[key];
107
+ return o === undefined ? String(v) : o === v ? `${v}(원본 전부)` : `${v} / 원본 ${o}`;
108
+ };
109
+ out.push(`총평 ${a.siteName ?? a.siteId} (${a.system ?? '?'}) — 원본 ${a.source}`);
110
+ out.push(` 선 것: 로케이션 ${n('locations', c.locations)}(존 ${c.areas}) · 설비 ${n('equipment', c.equipment)} · ` +
111
+ `품목 정의 ${n('materialDefinitions', c.materialDefinitions)} · 공정 ${n('operations', c.operations)}`);
112
+ if (c.materials !== undefined) {
113
+ out.push(` 자재 ${n('materials', c.materials)}(보관 로케이션 있는 것 ${c.materialsWithHome}) · ` +
114
+ `경로 ${n('routes', c.routes)} · 레시피 ${n('recipes', c.recipes)}(경로 붙은 것 ${c.recipesWithRoute}) · ` +
115
+ `투입 ${n('totalInputs', c.totalInputs)}(공정을 말하는 것 ${c.taggedInputs})`);
116
+ }
117
+ else {
118
+ out.push(' 생산 선언 없음 — 레시피가 하나도 성립하지 않았다');
119
+ }
120
+ if (a.gaps.length) {
121
+ out.push(' 옮기지 못한 것(건수 순):');
122
+ for (const g of a.gaps)
123
+ out.push(` ${String(g.count).padStart(6)} ${g.what}${g.sample ? ` — ${g.sample}` : ''}`);
124
+ }
125
+ if (a.derived.length) {
126
+ out.push(' 파생된 값(저장된 값이 아니다):');
127
+ for (const g of a.derived)
128
+ out.push(` ${g.what}${g.sample ? ` — ${g.sample}` : ''}`);
129
+ }
130
+ if (a.absent.length)
131
+ out.push(` 이 원본에 개념이 없는 자리: ${a.absent.join(', ')}`);
132
+ for (const n of a.notes)
133
+ out.push(` 참고 ${n.code}: ${n.message}`);
134
+ return out.join('\n');
135
+ }
136
+ //# sourceMappingURL=reference-assessment.js.map
@@ -0,0 +1 @@
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}\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 ...(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 (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"]}
@@ -3,7 +3,18 @@ import { type ConnectionConfig, type SiteDescriptor } from './reference-adapter.
3
3
  * 커넥터 라이브 기동 — 어댑터 openLiveFeed 를 인스턴스에 결선.
4
4
  * 반환: 라이브 결선 성공 여부(어댑터가 openLiveFeed 미지원이면 false → 호출측이 sim 폴백).
5
5
  */
6
- export declare function startReferenceLiveFeed(domainId: string, adapterType: string, cfg: ConnectionConfig, site: SiteDescriptor, instanceId: string): Promise<boolean>;
6
+ export declare function startReferenceLiveFeed(domainId: string, adapterType: string, cfg: ConnectionConfig, site: SiteDescriptor, instanceId: string,
7
+ /**
8
+ * **어느 레퍼런스에서 읽는 피드인가** — 읽기 커서를 저장할 자리를 가리킨다(§`loadLiveCursor`).
9
+ *
10
+ * 부르는 쪽이 `TwinReference` 를 이미 들고 있으므로 그 이름을 넘긴다. 처음에는 `cfg`·`site` 에서
11
+ * 찾으려 했는데 **둘 다 그 값을 가질 수 없다**: `cfg` 는 사람이 화면에서 입력한 접속 설정이고
12
+ * (`endpoint`·`token`…), `site` 는 커넥터가 원본에서 발견한 것이다 — **레퍼런스의 이름은 호스트가
13
+ * 붙이는 것이고 원본은 그것을 모른다.** 커넥터가 채울 수 없는 값을 커넥터에서 읽으려 한 것이 잘못이었다.
14
+ *
15
+ * 주지 않으면 커서를 잇지 않고 **그 사실을 말한다**(조용히 창이 미끄러지지 않게).
16
+ */
17
+ source?: string): Promise<boolean>;
7
18
  /** 라이브 피드 중지(unsubscribe). */
8
19
  export declare function stopReferenceLiveFeed(instanceId: string): boolean;
9
20
  /** 활성 피드 목록(관측). */