@things-factory/headless-twin 10.0.15 → 10.0.18

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 (513) hide show
  1. package/dist-server/engine/attention-digest.d.ts +4 -4
  2. package/dist-server/engine/attention-digest.js +7 -7
  3. package/dist-server/engine/attention-digest.js.map +1 -1
  4. package/dist-server/engine/canonical-ingest.d.ts +18 -82
  5. package/dist-server/engine/canonical-ingest.js +183 -18
  6. package/dist-server/engine/canonical-ingest.js.map +1 -1
  7. package/dist-server/engine/command-routing.js +1 -1
  8. package/dist-server/engine/command-routing.js.map +1 -1
  9. package/dist-server/engine/declared-stimulus.js +1 -1
  10. package/dist-server/engine/declared-stimulus.js.map +1 -1
  11. package/dist-server/engine/energy-topology.js +4 -4
  12. package/dist-server/engine/energy-topology.js.map +1 -1
  13. package/dist-server/engine/ingest-dedupe.d.ts +38 -0
  14. package/dist-server/engine/ingest-dedupe.js +145 -0
  15. package/dist-server/engine/ingest-dedupe.js.map +1 -0
  16. package/dist-server/engine/ingest-health.d.ts +90 -10
  17. package/dist-server/engine/ingest-health.js +125 -16
  18. package/dist-server/engine/ingest-health.js.map +1 -1
  19. package/dist-server/engine/integration-probes.d.ts +3 -3
  20. package/dist-server/engine/integration-probes.js +6 -6
  21. package/dist-server/engine/integration-probes.js.map +1 -1
  22. package/dist-server/engine/kpi-baseline.js +4 -4
  23. package/dist-server/engine/kpi-baseline.js.map +1 -1
  24. package/dist-server/engine/kpi-fold.d.ts +212 -14
  25. package/dist-server/engine/kpi-fold.js +374 -21
  26. package/dist-server/engine/kpi-fold.js.map +1 -1
  27. package/dist-server/engine/kpi-query.d.ts +11 -11
  28. package/dist-server/engine/kpi-query.js +281 -36
  29. package/dist-server/engine/kpi-query.js.map +1 -1
  30. package/dist-server/engine/live-feed-registry.d.ts +1 -1
  31. package/dist-server/engine/live-feed-registry.js +1 -1
  32. package/dist-server/engine/live-feed-registry.js.map +1 -1
  33. package/dist-server/engine/load-meter.d.ts +1 -1
  34. package/dist-server/engine/load-meter.js.map +1 -1
  35. package/dist-server/engine/local-declarations.d.ts +7 -7
  36. package/dist-server/engine/local-declarations.js +15 -14
  37. package/dist-server/engine/local-declarations.js.map +1 -1
  38. package/dist-server/engine/log.d.ts +1 -1
  39. package/dist-server/engine/log.js +1 -1
  40. package/dist-server/engine/log.js.map +1 -1
  41. package/dist-server/engine/measured-estimator.js +1 -1
  42. package/dist-server/engine/measured-estimator.js.map +1 -1
  43. package/dist-server/engine/measured-yield.js +1 -1
  44. package/dist-server/engine/measured-yield.js.map +1 -1
  45. package/dist-server/engine/model-basis.d.ts +1 -1
  46. package/dist-server/engine/model-basis.js +1 -1
  47. package/dist-server/engine/model-basis.js.map +1 -1
  48. package/dist-server/engine/model-gap.d.ts +3 -3
  49. package/dist-server/engine/model-gap.js +1 -1
  50. package/dist-server/engine/model-gap.js.map +1 -1
  51. package/dist-server/engine/model-vocabulary.js +3 -3
  52. package/dist-server/engine/model-vocabulary.js.map +1 -1
  53. package/dist-server/engine/oee-accumulator.d.ts +6 -5
  54. package/dist-server/engine/oee-accumulator.js +6 -5
  55. package/dist-server/engine/oee-accumulator.js.map +1 -1
  56. package/dist-server/engine/property-effects.js +2 -2
  57. package/dist-server/engine/property-effects.js.map +1 -1
  58. package/dist-server/engine/read-time.d.ts +22 -0
  59. package/dist-server/engine/read-time.js +81 -0
  60. package/dist-server/engine/read-time.js.map +1 -0
  61. package/dist-server/engine/restart-policy.js +1 -1
  62. package/dist-server/engine/restart-policy.js.map +1 -1
  63. package/dist-server/engine/runtime-key.js +1 -1
  64. package/dist-server/engine/runtime-key.js.map +1 -1
  65. package/dist-server/engine/spec-coverage.d.ts +2 -2
  66. package/dist-server/engine/spec-coverage.js +1 -1
  67. package/dist-server/engine/spec-coverage.js.map +1 -1
  68. package/dist-server/engine/stage-path.d.ts +4 -4
  69. package/dist-server/engine/stage-path.js +1 -1
  70. package/dist-server/engine/stage-path.js.map +1 -1
  71. package/dist-server/engine/state-axes.d.ts +1 -1
  72. package/dist-server/engine/state-axes.js +2 -2
  73. package/dist-server/engine/state-axes.js.map +1 -1
  74. package/dist-server/engine/travel-estimator.d.ts +2 -2
  75. package/dist-server/engine/travel-estimator.js +1 -1
  76. package/dist-server/engine/travel-estimator.js.map +1 -1
  77. package/dist-server/engine/twin-engine.d.ts +307 -54
  78. package/dist-server/engine/twin-engine.js +822 -196
  79. package/dist-server/engine/twin-engine.js.map +1 -1
  80. package/dist-server/engine/warm-start.d.ts +2 -2
  81. package/dist-server/engine/warm-start.js +5 -5
  82. package/dist-server/engine/warm-start.js.map +1 -1
  83. package/dist-server/index.js +1 -1
  84. package/dist-server/index.js.map +1 -1
  85. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +1 -1
  86. package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -1
  87. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +3 -3
  88. package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -1
  89. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +2 -2
  90. package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -1
  91. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.d.ts +5 -0
  92. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js +54 -0
  93. package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +1 -0
  94. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.d.ts +5 -0
  95. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js +44 -0
  96. package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +1 -0
  97. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.d.ts +6 -0
  98. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js +84 -0
  99. package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +1 -0
  100. package/dist-server/migrations/index.js +7 -1
  101. package/dist-server/migrations/index.js.map +1 -1
  102. package/dist-server/routes.d.ts +1 -0
  103. package/dist-server/routes.js +69 -5
  104. package/dist-server/routes.js.map +1 -1
  105. package/dist-server/service/index.d.ts +2 -2
  106. package/dist-server/service/index.js +30 -24
  107. package/dist-server/service/index.js.map +1 -1
  108. package/dist-server/service/reference/control-routing.d.ts +1 -1
  109. package/dist-server/service/reference/control-routing.js +1 -1
  110. package/dist-server/service/reference/control-routing.js.map +1 -1
  111. package/dist-server/service/reference/discovery-result.d.ts +2 -2
  112. package/dist-server/service/reference/discovery-result.js +3 -3
  113. package/dist-server/service/reference/discovery-result.js.map +1 -1
  114. package/dist-server/service/reference/hook-contract.d.ts +43 -0
  115. package/dist-server/service/reference/hook-contract.js +59 -0
  116. package/dist-server/service/reference/hook-contract.js.map +1 -0
  117. package/dist-server/service/reference/ingest-space.d.ts +1 -1
  118. package/dist-server/service/reference/ingest-space.js +4 -4
  119. package/dist-server/service/reference/ingest-space.js.map +1 -1
  120. package/dist-server/service/reference/knob-defaults.d.ts +1 -1
  121. package/dist-server/service/reference/knob-defaults.js +4 -4
  122. package/dist-server/service/reference/knob-defaults.js.map +1 -1
  123. package/dist-server/service/reference/reference-adapter.d.ts +116 -9
  124. package/dist-server/service/reference/reference-adapter.js +27 -0
  125. package/dist-server/service/reference/reference-adapter.js.map +1 -1
  126. package/dist-server/service/reference/reference-hook.d.ts +20 -0
  127. package/dist-server/service/reference/reference-hook.js +133 -0
  128. package/dist-server/service/reference/reference-hook.js.map +1 -0
  129. package/dist-server/service/reference/reference-live.d.ts +2 -2
  130. package/dist-server/service/reference/reference-live.js +76 -13
  131. package/dist-server/service/reference/reference-live.js.map +1 -1
  132. package/dist-server/service/reference/reference-master.d.ts +25 -16
  133. package/dist-server/service/reference/reference-master.js +35 -13
  134. package/dist-server/service/reference/reference-master.js.map +1 -1
  135. package/dist-server/service/reference/reference-resolver.d.ts +1 -1
  136. package/dist-server/service/reference/reference-resolver.js +12 -12
  137. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  138. package/dist-server/service/reference/template-registry.d.ts +1 -1
  139. package/dist-server/service/reference/template-registry.js.map +1 -1
  140. package/dist-server/service/reference/twin-reference.d.ts +3 -3
  141. package/dist-server/service/reference/twin-reference.js.map +1 -1
  142. package/dist-server/service/twin-attention/twin-attention-query.d.ts +1 -1
  143. package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
  144. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  145. package/dist-server/service/twin-audit/command-audit.d.ts +1 -1
  146. package/dist-server/service/twin-audit/command-audit.js.map +1 -1
  147. package/dist-server/service/twin-audit/twin-audit-event.d.ts +1 -1
  148. package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
  149. package/dist-server/service/twin-audit/twin-audit-query.js +1 -1
  150. package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -1
  151. package/dist-server/service/twin-backfill/backfill-period-facts.d.ts +24 -0
  152. package/dist-server/service/twin-backfill/backfill-period-facts.js +96 -0
  153. package/dist-server/service/twin-backfill/backfill-period-facts.js.map +1 -0
  154. package/dist-server/service/twin-backfill/backfill-shape.d.ts +34 -0
  155. package/dist-server/service/twin-backfill/backfill-shape.js +75 -0
  156. package/dist-server/service/twin-backfill/backfill-shape.js.map +1 -0
  157. package/dist-server/service/twin-backfill/index.d.ts +2 -0
  158. package/dist-server/service/twin-backfill/index.js +6 -0
  159. package/dist-server/service/twin-backfill/index.js.map +1 -0
  160. package/dist-server/service/twin-backfill/twin-backfill-resolver.d.ts +10 -0
  161. package/dist-server/service/twin-backfill/twin-backfill-resolver.js +44 -0
  162. package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +1 -0
  163. package/dist-server/service/twin-control/twin-control-mutation.js +3 -3
  164. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  165. package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
  166. package/dist-server/service/twin-event/twin-event-keys.js +2 -2
  167. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
  168. package/dist-server/service/twin-event/twin-event-type.d.ts +1 -0
  169. package/dist-server/service/twin-event/twin-event-type.js +8 -1
  170. package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
  171. package/dist-server/service/twin-event/twin-event.d.ts +2 -2
  172. package/dist-server/service/twin-event/twin-event.js +32 -4
  173. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  174. package/dist-server/service/twin-forecast/forecast-metrics.js +1 -1
  175. package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -1
  176. package/dist-server/service/twin-forecast/gap-analytics.d.ts +1 -1
  177. package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
  178. package/dist-server/service/twin-forecast/twin-forecast-query.js +29 -11
  179. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  180. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +1 -1
  181. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +1 -1
  182. package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -1
  183. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +1 -1
  184. package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -1
  185. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  186. package/dist-server/service/twin-journal/event-type-split-shape.d.ts +50 -0
  187. package/dist-server/service/twin-journal/event-type-split-shape.js +111 -0
  188. package/dist-server/service/twin-journal/event-type-split-shape.js.map +1 -0
  189. package/dist-server/service/twin-journal/event-type-split.d.ts +15 -0
  190. package/dist-server/service/twin-journal/event-type-split.js +102 -0
  191. package/dist-server/service/twin-journal/event-type-split.js.map +1 -0
  192. package/dist-server/service/twin-journal/journal-order.d.ts +15 -0
  193. package/dist-server/service/twin-journal/journal-order.js +17 -0
  194. package/dist-server/service/twin-journal/journal-order.js.map +1 -0
  195. package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -5
  196. package/dist-server/service/twin-journal/twin-journal-query.js +232 -21
  197. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  198. package/dist-server/service/twin-lifecycle/domain-catalog.js +2 -1
  199. package/dist-server/service/twin-lifecycle/domain-catalog.js.map +1 -1
  200. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +27 -1
  201. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +59 -3
  202. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  203. package/dist-server/service/twin-metrics/twin-metrics-query.js +14 -1
  204. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  205. package/dist-server/service/twin-model/axis-journal-evidence.js +1 -1
  206. package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -1
  207. package/dist-server/service/twin-model/epcis-coverage.js +34 -3
  208. package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
  209. package/dist-server/service/twin-model/iec61850-coverage.js +1 -1
  210. package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
  211. package/dist-server/service/twin-model/isa95-coverage.d.ts +12 -9
  212. package/dist-server/service/twin-model/isa95-coverage.js +33 -9
  213. package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
  214. package/dist-server/service/twin-model/item-ref.js +2 -2
  215. package/dist-server/service/twin-model/item-ref.js.map +1 -1
  216. package/dist-server/service/twin-model/name-index.d.ts +1 -1
  217. package/dist-server/service/twin-model/name-index.js +2 -2
  218. package/dist-server/service/twin-model/name-index.js.map +1 -1
  219. package/dist-server/service/twin-model/project-structure.d.ts +1 -1
  220. package/dist-server/service/twin-model/project-structure.js +7 -6
  221. package/dist-server/service/twin-model/project-structure.js.map +1 -1
  222. package/dist-server/service/twin-model/twin-equipment.d.ts +2 -2
  223. package/dist-server/service/twin-model/twin-equipment.js +3 -3
  224. package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
  225. package/dist-server/service/twin-model/twin-lineage-query.js +16 -5
  226. package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
  227. package/dist-server/service/twin-model/twin-location.d.ts +1 -1
  228. package/dist-server/service/twin-model/twin-location.js.map +1 -1
  229. package/dist-server/service/twin-model/twin-model-item-query.d.ts +3 -3
  230. package/dist-server/service/twin-model/twin-model-item-query.js +15 -15
  231. package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
  232. package/dist-server/service/twin-model/twin-model-mutation.d.ts +2 -2
  233. package/dist-server/service/twin-model/twin-model-mutation.js +4 -4
  234. package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -1
  235. package/dist-server/service/twin-model/twin-model-query.js +51 -30
  236. package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
  237. package/dist-server/service/twin-model/twin-model-tree-query.js +23 -1
  238. package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
  239. package/dist-server/service/twin-model/twin-operation.d.ts +1 -1
  240. package/dist-server/service/twin-model/twin-operation.js +1 -1
  241. package/dist-server/service/twin-model/twin-operation.js.map +1 -1
  242. package/dist-server/service/twin-space/move-space.js +2 -2
  243. package/dist-server/service/twin-space/move-space.js.map +1 -1
  244. package/dist-server/service/twin-space/representation-role.d.ts +2 -0
  245. package/dist-server/service/twin-space/representation-role.js +18 -0
  246. package/dist-server/service/twin-space/representation-role.js.map +1 -0
  247. package/dist-server/service/twin-space/space-integrity.d.ts +1 -1
  248. package/dist-server/service/twin-space/space-integrity.js.map +1 -1
  249. package/dist-server/service/twin-space/twin-space-representation.d.ts +2 -0
  250. package/dist-server/service/twin-space/twin-space-representation.js +16 -0
  251. package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
  252. package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
  253. package/dist-server/service/twin-space/twin-space-resolver.js +37 -8
  254. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  255. package/dist-server/service/twin-structure/twin-structure.js +3 -3
  256. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  257. package/dist-server/service/twin-subject/event-subjects.d.ts +43 -0
  258. package/dist-server/service/twin-subject/event-subjects.js +137 -0
  259. package/dist-server/service/twin-subject/event-subjects.js.map +1 -0
  260. package/dist-server/service/twin-subject/index.d.ts +9 -0
  261. package/dist-server/service/twin-subject/index.js +16 -0
  262. package/dist-server/service/twin-subject/index.js.map +1 -0
  263. package/dist-server/service/twin-subject/item-resolver.d.ts +10 -0
  264. package/dist-server/service/twin-subject/item-resolver.js +93 -0
  265. package/dist-server/service/twin-subject/item-resolver.js.map +1 -0
  266. package/dist-server/service/twin-subject/subject-query-shape.d.ts +22 -0
  267. package/dist-server/service/twin-subject/subject-query-shape.js +88 -0
  268. package/dist-server/service/twin-subject/subject-query-shape.js.map +1 -0
  269. package/dist-server/service/twin-subject/twin-subject-backfill.d.ts +23 -0
  270. package/dist-server/service/twin-subject/twin-subject-backfill.js +159 -0
  271. package/dist-server/service/twin-subject/twin-subject-backfill.js.map +1 -0
  272. package/dist-server/service/twin-subject/twin-subject-event.d.ts +14 -0
  273. package/dist-server/service/twin-subject/twin-subject-event.js +111 -0
  274. package/dist-server/service/twin-subject/twin-subject-event.js.map +1 -0
  275. package/dist-server/service/twin-subject/twin-subject-fast-path.d.ts +39 -0
  276. package/dist-server/service/twin-subject/twin-subject-fast-path.js +295 -0
  277. package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +1 -0
  278. package/dist-server/service/twin-subject/twin-subject-query.d.ts +27 -0
  279. package/dist-server/service/twin-subject/twin-subject-query.js +232 -0
  280. package/dist-server/service/twin-subject/twin-subject-query.js.map +1 -0
  281. package/dist-server/service/twin-subject/twin-subject-writer.d.ts +27 -0
  282. package/dist-server/service/twin-subject/twin-subject-writer.js +166 -0
  283. package/dist-server/service/twin-subject/twin-subject-writer.js.map +1 -0
  284. package/dist-server/service/twin-target/twin-target-resolver.js +1 -1
  285. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
  286. package/dist-shared/entity-delta.d.ts +1 -1
  287. package/dist-shared/entity-delta.js +4 -4
  288. package/dist-shared/entity-delta.js.map +1 -1
  289. package/dist-shared/touched-items.js +2 -2
  290. package/dist-shared/touched-items.js.map +1 -1
  291. package/dist-shared/twin-level.js +1 -1
  292. package/dist-shared/twin-level.js.map +1 -1
  293. package/package.json +7 -6
  294. package/server/engine/attention-digest.ts +9 -9
  295. package/server/engine/canonical-ingest.ts +202 -87
  296. package/server/engine/command-routing.ts +1 -1
  297. package/server/engine/declared-stimulus.ts +1 -1
  298. package/server/engine/energy-topology.ts +2 -2
  299. package/server/engine/ingest-dedupe.ts +149 -0
  300. package/server/engine/ingest-health.ts +171 -22
  301. package/server/engine/integration-probes.ts +7 -7
  302. package/server/engine/kpi-baseline.ts +4 -4
  303. package/server/engine/kpi-fold.ts +592 -41
  304. package/server/engine/kpi-query.ts +307 -48
  305. package/server/engine/live-feed-registry.ts +1 -1
  306. package/server/engine/load-meter.ts +1 -1
  307. package/server/engine/local-declarations.ts +15 -14
  308. package/server/engine/log.ts +1 -1
  309. package/server/engine/measured-estimator.ts +1 -1
  310. package/server/engine/measured-yield.ts +1 -1
  311. package/server/engine/model-basis.ts +2 -2
  312. package/server/engine/model-gap.ts +3 -3
  313. package/server/engine/model-vocabulary.ts +2 -2
  314. package/server/engine/oee-accumulator.ts +8 -6
  315. package/server/engine/property-effects.ts +2 -2
  316. package/server/engine/read-time.ts +77 -0
  317. package/server/engine/restart-policy.ts +1 -1
  318. package/server/engine/runtime-key.ts +1 -1
  319. package/server/engine/spec-coverage.ts +3 -3
  320. package/server/engine/stage-path.ts +5 -5
  321. package/server/engine/state-axes.ts +2 -2
  322. package/server/engine/travel-estimator.ts +2 -2
  323. package/server/engine/twin-engine.ts +906 -205
  324. package/server/engine/warm-start.ts +6 -6
  325. package/server/index.ts +1 -1
  326. package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +1 -1
  327. package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +3 -3
  328. package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +2 -2
  329. package/server/migrations/1786300000000-IndexEventTypeByTime.ts +48 -0
  330. package/server/migrations/1786400000000-IndexEventCreatedAt.ts +38 -0
  331. package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +93 -0
  332. package/server/migrations/index.ts +7 -1
  333. package/server/routes.ts +73 -5
  334. package/server/service/index.ts +7 -1
  335. package/server/service/reference/control-routing.ts +1 -1
  336. package/server/service/reference/discovery-result.ts +3 -3
  337. package/server/service/reference/hook-contract.ts +103 -0
  338. package/server/service/reference/ingest-space.ts +4 -4
  339. package/server/service/reference/knob-defaults.ts +4 -4
  340. package/server/service/reference/reference-adapter.ts +145 -10
  341. package/server/service/reference/reference-hook.ts +145 -0
  342. package/server/service/reference/reference-live.ts +87 -15
  343. package/server/service/reference/reference-master.ts +51 -20
  344. package/server/service/reference/reference-resolver.ts +11 -11
  345. package/server/service/reference/template-registry.ts +1 -1
  346. package/server/service/reference/twin-reference.ts +3 -3
  347. package/server/service/twin-attention/twin-attention-query.ts +1 -1
  348. package/server/service/twin-audit/command-audit.ts +1 -1
  349. package/server/service/twin-audit/twin-audit-event.ts +2 -2
  350. package/server/service/twin-audit/twin-audit-query.ts +1 -1
  351. package/server/service/twin-backfill/backfill-period-facts.ts +168 -0
  352. package/server/service/twin-backfill/backfill-shape.ts +86 -0
  353. package/server/service/twin-backfill/index.ts +3 -0
  354. package/server/service/twin-backfill/twin-backfill-resolver.ts +35 -0
  355. package/server/service/twin-control/twin-control-mutation.ts +2 -2
  356. package/server/service/twin-event/twin-event-keys.ts +2 -2
  357. package/server/service/twin-event/twin-event-type.ts +18 -1
  358. package/server/service/twin-event/twin-event.ts +32 -6
  359. package/server/service/twin-forecast/forecast-metrics.ts +1 -1
  360. package/server/service/twin-forecast/gap-analytics.ts +1 -1
  361. package/server/service/twin-forecast/twin-forecast-query.ts +30 -11
  362. package/server/service/twin-ingest-window/twin-ingest-window-query.ts +1 -1
  363. package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +1 -1
  364. package/server/service/twin-instance/twin-instance.ts +4 -4
  365. package/server/service/twin-journal/event-type-split-shape.ts +112 -0
  366. package/server/service/twin-journal/event-type-split.ts +144 -0
  367. package/server/service/twin-journal/journal-order.ts +20 -0
  368. package/server/service/twin-journal/twin-journal-query.ts +224 -20
  369. package/server/service/twin-lifecycle/domain-catalog.ts +2 -1
  370. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +58 -4
  371. package/server/service/twin-metrics/twin-metrics-query.ts +14 -1
  372. package/server/service/twin-model/axis-journal-evidence.ts +1 -1
  373. package/server/service/twin-model/epcis-coverage.ts +34 -3
  374. package/server/service/twin-model/iec61850-coverage.ts +1 -1
  375. package/server/service/twin-model/isa95-coverage.ts +43 -16
  376. package/server/service/twin-model/item-ref.ts +2 -2
  377. package/server/service/twin-model/name-index.ts +2 -2
  378. package/server/service/twin-model/project-structure.ts +7 -6
  379. package/server/service/twin-model/twin-equipment.ts +16 -4
  380. package/server/service/twin-model/twin-lineage-query.ts +16 -5
  381. package/server/service/twin-model/twin-location.ts +1 -1
  382. package/server/service/twin-model/twin-model-item-query.ts +15 -15
  383. package/server/service/twin-model/twin-model-mutation.ts +4 -4
  384. package/server/service/twin-model/twin-model-query.ts +49 -28
  385. package/server/service/twin-model/twin-model-tree-query.ts +26 -2
  386. package/server/service/twin-model/twin-operation.ts +2 -2
  387. package/server/service/twin-space/move-space.ts +3 -3
  388. package/server/service/twin-space/representation-role.ts +15 -0
  389. package/server/service/twin-space/space-integrity.ts +1 -1
  390. package/server/service/twin-space/twin-space-representation.ts +37 -0
  391. package/server/service/twin-space/twin-space-resolver.ts +40 -8
  392. package/server/service/twin-structure/twin-structure.ts +3 -3
  393. package/server/service/twin-subject/event-subjects.ts +155 -0
  394. package/server/service/twin-subject/index.ts +10 -0
  395. package/server/service/twin-subject/item-resolver.ts +92 -0
  396. package/server/service/twin-subject/subject-query-shape.ts +84 -0
  397. package/server/service/twin-subject/twin-subject-backfill.ts +196 -0
  398. package/server/service/twin-subject/twin-subject-event.ts +101 -0
  399. package/server/service/twin-subject/twin-subject-fast-path.ts +346 -0
  400. package/server/service/twin-subject/twin-subject-query.ts +210 -0
  401. package/server/service/twin-subject/twin-subject-writer.ts +189 -0
  402. package/server/service/twin-target/twin-target-resolver.ts +1 -1
  403. package/shared/entity-delta.ts +4 -4
  404. package/shared/touched-items.ts +2 -2
  405. package/shared/twin-level.ts +1 -1
  406. package/test/adopt-structure-live.test.ts +3 -3
  407. package/test/aggregate-not-findone.test.ts +112 -0
  408. package/test/attention-digest.test.ts +6 -6
  409. package/test/axis-read.test.ts +6 -5
  410. package/test/backfill-shape.test.ts +89 -0
  411. package/test/boot-resume.test.ts +20 -7
  412. package/test/broadcast-cost-baseline.test.ts +2 -1
  413. package/test/broadcast-period.test.ts +2 -2
  414. package/test/broadcast-queue-overflow.test.ts +244 -0
  415. package/test/canonical-ingest-vocabularies.test.ts +259 -8
  416. package/test/canonical-quantity-door.test.ts +2 -2
  417. package/test/capability-mapping.test.ts +1 -1
  418. package/test/checkpoint-refuses-empty.test.ts +2 -2
  419. package/test/connector-capability-declaration.test.ts +88 -0
  420. package/test/contract-layer-guard.test.ts +48 -0
  421. package/test/control-capability.test.ts +1 -1
  422. package/test/cursor-stall-not-read-failure.test.ts +2 -2
  423. package/test/declaration-reaches-model.test.ts +7 -5
  424. package/test/declared-stimulus.test.ts +4 -4
  425. package/test/discovery-result.test.ts +2 -2
  426. package/test/entity-delta.test.ts +4 -4
  427. package/test/equipment-identity-reaches.test.ts +57 -0
  428. package/test/event-subjects.test.ts +147 -0
  429. package/test/event-time-column.test.ts +3 -3
  430. package/test/event-type-split.test.ts +155 -0
  431. package/test/fact-scope-wiring.test.ts +96 -0
  432. package/test/fold-gap-speaks.test.ts +53 -0
  433. package/test/forecast-metrics.test.ts +1 -1
  434. package/test/forecast-tuning.test.ts +1 -1
  435. package/test/generated-not-counted.test.ts +99 -0
  436. package/test/ingest-bench.test.ts +2 -1
  437. package/test/ingest-health-engine.test.ts +2 -2
  438. package/test/ingest-health-wiring.test.ts +7 -5
  439. package/test/ingest-health.test.ts +6 -6
  440. package/test/ingest-history.test.ts +2 -2
  441. package/test/ingest-idempotent.test.ts +132 -0
  442. package/test/ingest-space.test.ts +3 -3
  443. package/test/ingest-wiring-guard.test.ts +197 -0
  444. package/test/integration-probes.test.ts +3 -3
  445. package/test/integration-runner.test.ts +1 -1
  446. package/test/item-ref.test.ts +1 -1
  447. package/test/journal-order-axis.test.ts +80 -0
  448. package/test/journal-read-discipline.test.ts +16 -6
  449. package/test/journal-retention.test.ts +171 -29
  450. package/test/journal-sort-axis.test.ts +1 -1
  451. package/test/journal-write-door.test.ts +3 -3
  452. package/test/journal-write-trend.test.ts +1 -1
  453. package/test/kernel-kind-guard.test.ts +3 -3
  454. package/test/knob-defaults.test.ts +1 -1
  455. package/test/kpi-baseline-db.test.ts +2 -2
  456. package/test/kpi-fold.test.ts +266 -14
  457. package/test/kpi-query-bench.test.ts +3 -3
  458. package/test/live-cursor-wiring.test.ts +6 -6
  459. package/test/live-kernel-facts.test.ts +11 -11
  460. package/test/live-mirror-parity.test.ts +9 -6
  461. package/test/load-meter.test.ts +1 -1
  462. package/test/local-declarations.test.ts +13 -12
  463. package/test/log-stamp.test.ts +1 -1
  464. package/test/master-to-twin.test.ts +2 -2
  465. package/test/measured-yield.test.ts +2 -2
  466. package/test/merge-streams.test.ts +70 -0
  467. package/test/mirror-resumes-from-checkpoint.test.ts +13 -13
  468. package/test/model-basis.test.ts +2 -2
  469. package/test/model-gap.test.ts +4 -4
  470. package/test/model-vocabulary.test.ts +2 -2
  471. package/test/move-space.test.ts +1 -1
  472. package/test/oee-accumulator.test.ts +20 -5
  473. package/test/operational-vocabulary.test.ts +2 -2
  474. package/test/project-structure-db.test.ts +2 -2
  475. package/test/projection-reaches-screen.test.ts +7 -2
  476. package/test/projection-reads-declared.test.ts +109 -0
  477. package/test/property-effects.test.ts +10 -6
  478. package/test/read-failure-ledger.test.ts +106 -0
  479. package/test/read-failure-visible.test.ts +7 -7
  480. package/test/read-time.test.ts +118 -0
  481. package/test/reference-grounding.test.ts +1 -1
  482. package/test/reference-hook.test.ts +86 -0
  483. package/test/registry-key-guard.test.ts +1 -1
  484. package/test/rename-twin.test.ts +81 -0
  485. package/test/restart-policy.test.ts +3 -3
  486. package/test/resync-origin-site.test.ts +3 -3
  487. package/test/revision-axis.test.ts +14 -8
  488. package/test/runtime-key.test.ts +3 -3
  489. package/test/scale-twin-bench.test.ts +2 -1
  490. package/test/snapshot-freshness.test.ts +4 -4
  491. package/test/source-outcome-audit.test.ts +1 -1
  492. package/test/space-integrity.test.ts +1 -1
  493. package/test/space-representation.test.ts +66 -0
  494. package/test/space-scope-includes-stopped.test.ts +55 -0
  495. package/test/spec-coverage.test.ts +1 -1
  496. package/test/standard-coverage.test.ts +19 -21
  497. package/test/status-tally.test.ts +1 -1
  498. package/test/structure-revision-db.test.ts +7 -7
  499. package/test/subject-fast-path.test.ts +148 -0
  500. package/test/subject-read-model.test.ts +175 -0
  501. package/test/touched-items.test.ts +2 -2
  502. package/test/twin-audit.test.ts +1 -1
  503. package/test/twin-event-keys.test.ts +1 -1
  504. package/test/twin-model-item-db.test.ts +5 -5
  505. package/test/twin-model-tree-db.test.ts +35 -2
  506. package/test/twin-origin-resync.test.ts +1 -1
  507. package/test/vocabulary-guard.test.ts +1 -1
  508. package/test/warm-start-seam.test.ts +1 -1
  509. package/test/warm-start.test.ts +6 -6
  510. package/test/withheld-door.test.ts +2 -2
  511. package/test/yield-loop.test.ts +4 -4
  512. package/tsconfig.shared.tsbuildinfo +1 -1
  513. package/tsconfig.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"spec-coverage.js","sourceRoot":"","sources":["../../server/engine/spec-coverage.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AA2CH,oDAqCC;AAOD,4CAOC;AAvDD;;;GAGG;AACH,SAAgB,oBAAoB,CAAC,GAWpC;IACC,MAAM,GAAG,GAAG,GAAG,EAAE,UAAU,CAAA;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM;QAAE,OAAO,SAAS,CAAA;IAClC,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE;QAC9B,2DAA2D;QAC3D,GAAG,CAAC,CAAC,CAAC,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1G,CAAC,CAAC,CAAA;IACH,MAAM,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IAC3G,MAAM,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IAC3G,MAAM,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAA;IACxG,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IACrG,iGAAiG;IACjG,MAAM,QAAQ,GAAG,iBAAiB,GAAG,iBAAiB,CAAA;IACtD,MAAM,aAAa,GACjB,QAAQ,KAAK,CAAC;QACZ,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,gBAAgB,GAAG,CAAC;YACpB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,iBAAiB,KAAK,CAAC;gBACvB,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,kBAAkB,CAAA;IAC5B,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAA;AAChH,CAAC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,MAAW;IAC1C,IAAI,OAAO,MAAM,EAAE,YAAY,KAAK,UAAU;QAAE,OAAO,SAAS,CAAA;IAChE,IAAI,CAAC;QACH,OAAO,oBAAoB,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAA;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC","sourcesContent":["/*\n * 시뮬 명세 자기보고 — **\"이 예측으로 무엇을 말할 수 있나\" 를 예측과 함께 내보낸다.**\n *\n * 배경(operato-twin design/plans/simulation-spec.md): 공정 소요·수율이 커널 소스의 상수였다. 이제\n * 마스터가 명세를 실어 오면 커널이 그것으로 실행하고, `specCoverage()` 가 무엇을 선언값으로 무엇을\n * 기본값으로 썼는지 보고한다. 그 보고를 **예측 응답에 붙이지 않으면** 받는 쪽은 상수로 계산한 숫자와\n * 현장 명세로 계산한 숫자를 구별할 수 없다 — 똑같이 \"p50 168\" 로 보인다.\n *\n * 언어 중립: 여기서는 **코드와 수치만** 낸다(`qualification` + 카운트). 사람이 읽는 문장은 표현\n * 계층(클라 i18next / AI 답변)이 만든다 — attention 과 같은 규율.\n */\n\n/** 예측 자격 — 선언된 명세가 얼마나 덮는지에 따라 이 예측으로 정당하게 말할 수 있는 범위가 달라진다. */\nexport type ForecastQualification =\n /** 모든 소요가 커널 기본 상수 → **상대 비교만**(\"자원 2대 더 넣으면 나아지나\"). 절대 시각 주장 불가. */\n | 'relative'\n /** 일부만 선언·실측 → 그 구간은 접지, 나머지는 상수. 혼합. */\n | 'partial'\n /** 소요 전부 선언 → 절대 예측 시도 가능. 단 **정확도는 이력이 확인한다**(선언 != 검증). */\n | 'absolute-capable'\n /**\n * 소요 전부가 **이 현장의 이력에서 온 값**(추정기) → 가장 강한 근거. 선언은 사람이 적은 값이고\n * 이것은 실제로 일어난 값이다. 그래도 \"정확하다\" 가 아니라 \"관측에 접지됐다\" 는 뜻이다.\n */\n | 'calibrated'\n\nexport interface SpecCoverageReport {\n operations: {\n kind: string\n duration: 'measured' | 'declared' | 'default'\n variability?: string\n parameters: string[]\n /**\n * 그 모수를 **무엇으로** 읽었나(`id → measured|declared`) — 없는 id 는 코드 상수를 썼다는 뜻이다.\n *\n * 소요시간이 근거를 밝히듯 모수도 밝힌다(2026-08-19): 수율이 이력에서 온 값인지 사람이 적은 값인지\n * 상수인지에 따라 「불량이 늘었다」는 화면 문장의 무게가 다르다.\n */\n parameterBasis?: Record<string, 'measured' | 'declared'>\n }[]\n /** 이력·계산에서 온 소요(추정기) — 선언값보다 강한 근거. */\n measuredDurations: number\n declaredDurations: number\n defaultDurations: number\n /** 변동(분포)을 가진 작업 종류 수. **0 이면 모든 소요가 고정값**이라 대기·병목이 과소평가된다. */\n varyingDurations: number\n qualification: ForecastQualification\n}\n\n/**\n * 명세 자격 판정(순수) — 커널 보고를 받아 분류한다. 커널을 모르므로 테스트가 쉽다.\n * 관측된 작업이 하나도 없으면(`operations` 빈 배열) 판정할 근거가 없으므로 **undefined**(모른다).\n */\nexport function classifySpecCoverage(cov: {\n operations?: {\n kind: string\n duration: 'measured' | 'declared' | 'default'\n variability?: string\n parameters?: string[]\n parameterBasis?: Record<string, 'measured' | 'declared'>\n }[]\n measuredDurations?: number\n declaredDurations?: number\n defaultDurations?: number\n}): SpecCoverageReport | undefined {\n const ops = cov?.operations\n if (!ops?.length) return undefined\n const operations = ops.map(o => ({\n kind: o.kind,\n duration: o.duration,\n ...(o.variability ? { variability: o.variability } : {}),\n parameters: o.parameters ?? [],\n /* 근거는 커널이 말한 것만 나른다 — 없으면 필드를 만들지 않는다(상수를 썼다는 뜻이 빈칸이다). */\n ...(o.parameterBasis && Object.keys(o.parameterBasis).length ? { parameterBasis: o.parameterBasis } : {})\n }))\n const measuredDurations = cov.measuredDurations ?? operations.filter(o => o.duration === 'measured').length\n const declaredDurations = cov.declaredDurations ?? operations.filter(o => o.duration === 'declared').length\n const defaultDurations = cov.defaultDurations ?? operations.filter(o => o.duration === 'default').length\n const varyingDurations = operations.filter(o => o.variability && o.variability !== 'constant').length\n /* 사다리: 전부 이력 → calibrated · 상수 없음 → absolute-capable · 접지가 하나도 없음 → relative · 나머지 → partial. */\n const grounded = measuredDurations + declaredDurations\n const qualification: ForecastQualification =\n grounded === 0\n ? 'relative'\n : defaultDurations > 0\n ? 'partial'\n : declaredDurations === 0\n ? 'calibrated'\n : 'absolute-capable'\n return { operations, measuredDurations, declaredDurations, defaultDurations, varyingDurations, qualification }\n}\n\n/**\n * 커널에서 읽는다 — **아직 이 API 가 없는 커널(발행 이전)이면 undefined.**\n * 그때 `undefined` 는 \"명세가 없다\" 가 아니라 \"모른다\" 다. 소비처가 그 둘을 섞지 않도록 주석을 남긴다:\n * 없는 것을 \"전부 기본값\" 이라고 단정하지 않는다(단정하려면 커널이 말해 줘야 한다).\n */\nexport function readSpecCoverage(kernel: any): SpecCoverageReport | undefined {\n if (typeof kernel?.specCoverage !== 'function') return undefined\n try {\n return classifySpecCoverage(kernel.specCoverage())\n } catch {\n return undefined\n }\n}\n"]}
1
+ {"version":3,"file":"spec-coverage.js","sourceRoot":"","sources":["../../server/engine/spec-coverage.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AA2CH,oDAqCC;AAOD,4CAOC;AAvDD;;;GAGG;AACH,SAAgB,oBAAoB,CAAC,GAWpC;IACC,MAAM,GAAG,GAAG,GAAG,EAAE,UAAU,CAAA;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM;QAAE,OAAO,SAAS,CAAA;IAClC,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE;QAC9B,2DAA2D;QAC3D,GAAG,CAAC,CAAC,CAAC,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1G,CAAC,CAAC,CAAA;IACH,MAAM,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IAC3G,MAAM,iBAAiB,GAAG,GAAG,CAAC,iBAAiB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IAC3G,MAAM,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAA;IACxG,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,MAAM,CAAA;IACrG,mGAAmG;IACnG,MAAM,QAAQ,GAAG,iBAAiB,GAAG,iBAAiB,CAAA;IACtD,MAAM,aAAa,GACjB,QAAQ,KAAK,CAAC;QACZ,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,gBAAgB,GAAG,CAAC;YACpB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,iBAAiB,KAAK,CAAC;gBACvB,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,kBAAkB,CAAA;IAC5B,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAA;AAChH,CAAC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,MAAW;IAC1C,IAAI,OAAO,MAAM,EAAE,YAAY,KAAK,UAAU;QAAE,OAAO,SAAS,CAAA;IAChE,IAAI,CAAC;QACH,OAAO,oBAAoB,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAA;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC","sourcesContent":["/*\n * 시뮬 명세 자기보고 — **\"이 예측으로 무엇을 말할 수 있나\" 를 예측과 함께 내보낸다.**\n *\n * 배경(operato-twin design/plans/simulation-spec.md): 공정 소요·수율이 커널 소스의 상수였다. 이제\n * 마스터가 명세를 실어 오면 커널이 그것으로 실행하고, `specCoverage()` 가 무엇을 선언값으로 무엇을\n * 기본값으로 썼는지 보고한다. 그 보고를 **예측 응답에 붙이지 않으면** 받는 쪽은 상수로 계산한 숫자와\n * 현장 명세로 계산한 숫자를 구별할 수 없다 — 똑같이 \"p50 168\" 로 보인다.\n *\n * 언어 중립: 여기서는 **코드와 수치만** 낸다(`qualification` + 카운트). 사람이 읽는 문장은 표현\n * 계층(클라 i18next / AI 답변)이 만든다 — attention 과 같은 규율.\n */\n\n/** 예측 자격 — 선언된 명세가 얼마나 덮는지에 따라 이 예측으로 정당하게 말할 수 있는 범위가 달라진다. */\nexport type ForecastQualification =\n /** 모든 소요가 커널 기본 상수 → **상대 비교만**(\"자원 2대 더 넣으면 나아지나\"). 절대 시각 주장 불가. */\n | 'relative'\n /** 일부만 선언·실측 → 그 구간은 계산하지, 나머지는 상수. 혼합. */\n | 'partial'\n /** 소요 전부 선언 → 절대 예측 시도 가능. 단 **정확도는 이력이 확인한다**(선언 != 검증). */\n | 'absolute-capable'\n /**\n * 소요 전부가 **이 현장의 이력에서 온 값**(추정기) → 가장 강한 근거. 선언은 사람이 적은 값이고\n * 이것은 실제로 일어난 값이다. 그래도 \"정확하다\" 가 아니라 \"관측에 계산하지됐다\" 는 뜻이다.\n */\n | 'calibrated'\n\nexport interface SpecCoverageReport {\n operations: {\n kind: string\n duration: 'measured' | 'declared' | 'default'\n variability?: string\n parameters: string[]\n /**\n * 그 모수를 **무엇으로** 읽었나(`id → measured|declared`) — 없는 id 는 코드 상수를 썼다는 뜻이다.\n *\n * 소요시간이 근거를 밝히듯 모수도 밝힌다(2026-08-19): 수율이 이력에서 온 값인지 사람이 적은 값인지\n * 상수인지에 따라 「불량이 늘었다」는 화면 문장의 무게가 다르다.\n */\n parameterBasis?: Record<string, 'measured' | 'declared'>\n }[]\n /** 이력·계산에서 온 소요(추정기) — 선언값보다 강한 근거. */\n measuredDurations: number\n declaredDurations: number\n defaultDurations: number\n /** 변동(분포)을 가진 작업 종류 수. **0 이면 모든 소요가 고정값**이라 대기·병목이 과소평가된다. */\n varyingDurations: number\n qualification: ForecastQualification\n}\n\n/**\n * 명세 자격 판정(순수) — 커널 보고를 받아 분류한다. 커널을 모르므로 테스트가 쉽다.\n * 관측된 작업이 하나도 없으면(`operations` 빈 배열) 판정할 근거가 없으므로 **undefined**(모른다).\n */\nexport function classifySpecCoverage(cov: {\n operations?: {\n kind: string\n duration: 'measured' | 'declared' | 'default'\n variability?: string\n parameters?: string[]\n parameterBasis?: Record<string, 'measured' | 'declared'>\n }[]\n measuredDurations?: number\n declaredDurations?: number\n defaultDurations?: number\n}): SpecCoverageReport | undefined {\n const ops = cov?.operations\n if (!ops?.length) return undefined\n const operations = ops.map(o => ({\n kind: o.kind,\n duration: o.duration,\n ...(o.variability ? { variability: o.variability } : {}),\n parameters: o.parameters ?? [],\n /* 근거는 커널이 말한 것만 나른다 — 없으면 필드를 만들지 않는다(상수를 썼다는 뜻이 빈칸이다). */\n ...(o.parameterBasis && Object.keys(o.parameterBasis).length ? { parameterBasis: o.parameterBasis } : {})\n }))\n const measuredDurations = cov.measuredDurations ?? operations.filter(o => o.duration === 'measured').length\n const declaredDurations = cov.declaredDurations ?? operations.filter(o => o.duration === 'declared').length\n const defaultDurations = cov.defaultDurations ?? operations.filter(o => o.duration === 'default').length\n const varyingDurations = operations.filter(o => o.variability && o.variability !== 'constant').length\n /* 사다리: 전부 이력 → calibrated · 상수 없음 → absolute-capable · 계산하지가 하나도 없음 → relative · 나머지 → partial. */\n const grounded = measuredDurations + declaredDurations\n const qualification: ForecastQualification =\n grounded === 0\n ? 'relative'\n : defaultDurations > 0\n ? 'partial'\n : declaredDurations === 0\n ? 'calibrated'\n : 'absolute-capable'\n return { operations, measuredDurations, declaredDurations, defaultDurations, varyingDurations, qualification }\n}\n\n/**\n * 커널에서 읽는다 — **아직 이 API 가 없는 커널(발행 이전)이면 undefined.**\n * 그때 `undefined` 는 \"명세가 없다\" 가 아니라 \"모른다\" 다. 소비처가 그 둘을 섞지 않도록 주석을 남긴다:\n * 없는 것을 \"전부 기본값\" 이라고 단정하지 않는다(단정하려면 커널이 말해 줘야 한다).\n */\nexport function readSpecCoverage(kernel: any): SpecCoverageReport | undefined {\n if (typeof kernel?.specCoverage !== 'function') return undefined\n try {\n return classifySpecCoverage(kernel.specCoverage())\n } catch {\n return undefined\n }\n}\n"]}
@@ -9,17 +9,17 @@ export interface StageEvidence {
9
9
  * 마스터를 옮기며 **못 옮긴 값의 수**. `null` = 세지 않았다.
10
10
  *
11
11
  * 0 이면 통과이고 n 이어도 통과다 — 이 판정이 요구하는 것은 「없다」가 아니라 **「말할 수 있다」**다.
12
- * 못 옮긴 값을 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도 조용히 성공한다.
12
+ * 못 옮긴 값을 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도 성공이라 답하면서 아무 일도 하지 않는다.
13
13
  */
14
14
  masterWarnings: number | null;
15
15
  /**
16
16
  * 이 축이 이 트윈에 **해당하나**. 레퍼런스 없이 템플릿으로 세운 트윈에는 옮겨 온 마스터가 없으므로
17
- * 「못 옮긴 값」을 물을 대상이 아니다 — 그걸 미측정으로 접으면 영원히 배선을 기다린다.
17
+ * 「못 옮긴 값」을 물을 대상이 아니다 — 그걸 미측정으로 처리하면 영원히 배선을 기다린다.
18
18
  */
19
19
  masterWarningsApplicable?: boolean;
20
20
  /** 인제스트 누적 건수. */
21
21
  liveIngestTotal: number;
22
- /** **반출(remove)을 접은 누적 건수.** `null` = 계측되지 않음. 0 = 세었으나 없었다(→ 스냅샷 의심). */
22
+ /** **반출(remove)을 계산한 누적 건수.** `null` = 계측되지 않음. 0 = 세었으나 없었다(→ 스냅샷 의심). */
23
23
  removalsObserved: number | null;
24
24
  /** 이 트윈에서 예측이 한 번이라도 돌았나. */
25
25
  forecastRan: boolean;
@@ -32,7 +32,7 @@ export interface StageEvidence {
32
32
  * 이 트윈이 **무엇인가** — 시뮬레이션은 원천에서 받는 것이 없다.
33
33
  *
34
34
  * 시뮬에 라이브를 요구하면 영원히 빨갛고, 그 빨강은 결함이 아니다. 도입 단계는 **연결된 트윈**의
35
- * 축이므로 시뮬은 라이브 이후를 「해당 없음」으로 말한다(달성으로도, 미측정으로도 접지 않는다).
35
+ * 축이므로 시뮬은 라이브 이후를 「해당 없음」으로 말한다(달성으로도, 미측정으로도 계산하지 않는다).
36
36
  */
37
37
  subject?: 'live' | 'sim';
38
38
  /** 피드 성격 — `snapshot-poll` 이면 반출 0 은 「아직」이 아니라 **구조적으로 불가**다. */
@@ -11,7 +11,7 @@
11
11
  * 위조할 수 없다. 특히:
12
12
  *
13
13
  * · **반출 관측**이 라이브의 판별식이다. 스냅샷을 되풀어 읽는 방식은 **원리적으로** 반출을 낼 수 없다
14
- * (사라진 것은 그냥 없어질 뿐이라 `OBSERVE` 만 남는다). 그러므로 「반출을 한 번이라도 접었는가」는
14
+ * (사라진 것은 그냥 없어질 뿐이라 `OBSERVE` 만 남는다). 그러므로 「반출을 한 번이라도 계산했는가」는
15
15
  * 진짜 delta 의 증거이고, 폴링을 실시간이라 부르는 것을 막는다.
16
16
  * · **선언 적재량**이 예측의 판별식이다. 커널 기본값으로 돌아간 예측도 숫자를 내므로, 화면만 보면
17
17
  * 구별되지 않는다. 그 숫자가 무엇 위에 섰는지는 `specCoverage` 만 안다.
@@ -1 +1 @@
1
- {"version":3,"file":"stage-path.js","sourceRoot":"","sources":["../../server/engine/stage-path.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 단계 판정 — **가벼운 성공을 최종 성공으로 잇는 길을, 사람의 체크가 아니라 증거로 센다.**\n *\n * ── 왜 이 모듈이 있나 ───────────────────────────────────────────────────────\n * 트윈 도입은 「구조 → 라이브 → 예측 → 폐루프」로 간다. 그런데 이 진행을 **체크리스트**로 관리하면\n * 사람이 체크하는 순간 참이 된다. 실제로 가장 자주 건너뛰어지는 자리가 구조 다음이다 — 구조가 서면\n * 화면이 그럴듯해지므로 「실시간」이라고 말해 버리고, 되풀어 읽는 스냅샷이 그 말을 뒷받침하지 못한다.\n *\n * 그래서 단계를 **판정식**으로 둔다. 각 단계는 시스템이 이미 내놓는 사실로만 판정되고, 선언으로\n * 위조할 수 없다. 특히:\n *\n * · **반출 관측**이 라이브의 판별식이다. 스냅샷을 되풀어 읽는 방식은 **원리적으로** 반출을 낼 수 없다\n * (사라진 것은 그냥 없어질 뿐이라 `OBSERVE` 만 남는다). 그러므로 「반출을 한 번이라도 접었는가」는\n * 진짜 delta 의 증거이고, 폴링을 실시간이라 부르는 것을 막는다.\n * · **선언 적재량**이 예측의 판별식이다. 커널 기본값으로 돌아간 예측도 숫자를 내므로, 화면만 보면\n * 구별되지 않는다. 그 숫자가 무엇 위에 섰는지는 `specCoverage` 만 안다.\n *\n * ── 이 모듈의 정수: 「모른다」를 「아니다」로도 「그렇다」로도 바꾸지 않는다 ──────────────\n * 계측되지 않은 신호는 `null` 로 온다. `null` 은 0 이 아니다. 계측되지 않았으면 그 단계는 **달성이\n * 아니고**, 그 사실을 `unmeasured` 로 말한다 — 신호를 배선하지 않은 채 단계를 주장할 길을 남기지\n * 않는다. (0 으로 대체하면 「반출 0건」과 「반출을 세지 않았다」가 같아진다.)\n *\n * 순수 함수다(프레임워크·DB 무의존) — 증거 수집은 호출자 몫이다.\n */\n\nexport type StageId = 'structure' | 'live' | 'forecast' | 'closed-loop'\n\n/** 판정에 쓰는 사실. `null` = **계측되지 않았다**(0 이 아니다). */\nexport interface StageEvidence {\n /* ── S1 구조 ── */\n /** 이 트윈이 든 자리 수. */\n locationCount: number\n /** 자리에 놓인 물품 수(비직렬 수량 물품 포함 — 식품·화학은 개수가 아니라 총량으로 온다). */\n itemCount: number\n /**\n * 마스터를 옮기며 **못 옮긴 값의 수**. `null` = 세지 않았다.\n *\n * 0 이면 통과이고 n 이어도 통과다 — 이 판정이 요구하는 것은 「없다」가 아니라 **「말할 수 있다」**다.\n * 못 옮긴 값을 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도 조용히 성공한다.\n */\n masterWarnings: number | null\n /**\n * 이 축이 이 트윈에 **해당하나**. 레퍼런스 없이 템플릿으로 세운 트윈에는 옮겨 온 마스터가 없으므로\n * 「못 옮긴 값」을 물을 대상이 아니다 — 그걸 미측정으로 접으면 영원히 배선을 기다린다.\n */\n masterWarningsApplicable?: boolean\n\n /* ── S2 라이브 ── */\n /** 인제스트 누적 건수. */\n liveIngestTotal: number\n /** **반출(remove)을 접은 누적 건수.** `null` = 계측되지 않음. 0 = 세었으나 없었다(→ 스냅샷 의심). */\n removalsObserved: number | null\n\n /* ── S3 예측 ── */\n /** 이 트윈에서 예측이 한 번이라도 돌았나. */\n forecastRan: boolean\n /** 선언 적재량 — `declared`/`total`. `null` = 알 수 없음. */\n specCoverage: { declared: number; total: number } | null\n\n /* ── S4 폐루프 ── */\n /**\n * 이 트윈이 **무엇인가** — 시뮬레이션은 원천에서 받는 것이 없다.\n *\n * 시뮬에 라이브를 요구하면 영원히 빨갛고, 그 빨강은 결함이 아니다. 도입 단계는 **연결된 트윈**의\n * 축이므로 시뮬은 라이브 이후를 「해당 없음」으로 말한다(달성으로도, 미측정으로도 접지 않는다).\n */\n subject?: 'live' | 'sim'\n /** 피드 성격 — `snapshot-poll` 이면 반출 0 은 「아직」이 아니라 **구조적으로 불가**다. */\n feed?: 'push' | 'delta-poll' | 'snapshot-poll' | 'none' | 'unknown'\n /** 원본이 **선언∩구현**으로 액추에이션 능력을 갖나(`capabilitiesOf`). */\n actuateCapability: boolean\n /** 내 명령의 효과가 인바운드로 **상관되어** 돌아온 누적 건수. `null` = 계측되지 않음. */\n correlatedEffectObserved: number | null\n}\n\nexport interface StageVerdict {\n id: StageId\n achieved: boolean\n /** 아직 아닌 이유 — 언어중립 코드(화면이 옮긴다). 달성이면 빈 배열. */\n blockedBy: string[]\n /** 판정할 수 없었던 신호 — **배선되지 않은 계측**. 있으면 달성이 아니다. */\n unmeasured: string[]\n}\n\nexport interface StagePath {\n stages: StageVerdict[]\n /** 지금 서 있는 칸 — 연속으로 달성된 마지막 단계. 하나도 없으면 `null`. */\n reached: StageId | null\n /**\n * 다음 한 걸음 — 사람이 오늘 할 수 있는 일 하나. 없으면(전부 달성) `null`.\n * 「무엇이 부족한지」의 목록이 아니라 **다음 하나**다. 목록은 사람을 멈추게 한다.\n */\n next: { stage: StageId; blockedBy: string[]; unmeasured: string[] } | null\n}\n\nconst ORDER: StageId[] = ['structure', 'live', 'forecast', 'closed-loop']\n\n/**\n * 증거로 단계를 판정한다.\n *\n * **단계는 앞선 단계를 전제한다**(구조 없이 라이브를 달성했다고 말할 수 없다). 앞이 막히면 뒤는\n * 자기 조건을 따지지 않고 `prerequisite` 으로 막는다 — 뒤 단계의 조건을 나열하면 사람이 순서를\n * 건너뛴다.\n */\nexport function assessStagePath(e: StageEvidence): StagePath {\n const stages: StageVerdict[] = []\n let prereqOk = true\n\n for (const id of ORDER) {\n const blockedBy: string[] = []\n const unmeasured: string[] = []\n\n if (!prereqOk) {\n blockedBy.push('prerequisite')\n } else if (id === 'structure') {\n if (!(e.locationCount > 0)) blockedBy.push('no-locations')\n if (!(e.itemCount > 0)) blockedBy.push('no-items')\n if (e.masterWarningsApplicable !== false && e.masterWarnings === null) unmeasured.push('master-warnings')\n } else if (id === 'live') {\n if (e.subject === 'sim') blockedBy.push('subject-is-simulation')\n else {\n if (!(e.liveIngestTotal > 0)) blockedBy.push('no-ingest')\n if (e.removalsObserved === null) unmeasured.push('removals')\n /*\n * 세었는데 없다. 이유가 둘이고 **처방이 정반대**다: 스냅샷을 되풀어 읽는 방식이면 더 기다려도\n * 오지 않으므로 방식을 바꿔야 하고, 그 밖이면 아직 반출이 일어나지 않은 것일 수 있다.\n */\n else if (e.removalsObserved === 0) blockedBy.push(e.feed === 'snapshot-poll' ? 'snapshot-only-feed' : 'no-removal-observed')\n }\n } else if (id === 'forecast') {\n if (!e.forecastRan) blockedBy.push('no-forecast-run')\n if (e.specCoverage === null) unmeasured.push('spec-coverage')\n /* 선언이 0 이면 예측은 커널 기본값 위에 섰다 — 숫자는 나오지만 이 현장의 숫자가 아니다. */\n else if (!(e.specCoverage.declared > 0)) blockedBy.push('declarations-empty')\n } else {\n if (!e.actuateCapability) blockedBy.push('no-actuate-capability')\n if (e.correlatedEffectObserved === null) unmeasured.push('correlated-effect')\n /* 접수만으로는 폐루프가 아니다 — 효과가 관측으로 돌아와야 닫힌다. */\n else if (!(e.correlatedEffectObserved > 0)) blockedBy.push('effect-not-observed')\n }\n\n const achieved = blockedBy.length === 0 && unmeasured.length === 0\n stages.push({ id, achieved, blockedBy, unmeasured })\n if (!achieved) prereqOk = false\n }\n\n const achievedIds = stages.filter(s => s.achieved).map(s => s.id)\n const firstUnmet = stages.find(s => !s.achieved)\n return {\n stages,\n reached: achievedIds.length ? achievedIds[achievedIds.length - 1] : null,\n next: firstUnmet ? { stage: firstUnmet.id, blockedBy: firstUnmet.blockedBy, unmeasured: firstUnmet.unmeasured } : null\n }\n}\n\n/**\n * 이 단계에 이르지 못했다면 **쓰지 않기로 한 말**.\n *\n * 근거 수준 규율(`connector-developer-guide` §8)을 판정과 같은 자리에 둔다. 문장이 문서에만 있으면\n * 제안서를 쓰는 사람이 그 문서를 읽지 않는다. 값이 되면 화면이 그 자리에서 말할 수 있다.\n */\nexport const CLAIM_GATE: Record<StageId, string> = {\n structure: 'claim.structure-synced',\n live: 'claim.realtime',\n forecast: 'claim.prediction',\n 'closed-loop': 'claim.automatic-control'\n}\n\n/** 아직 쓸 수 없는 주장들 — 도달하지 못한 단계의 게이트 전부. */\nexport function forbiddenClaims(path: StagePath): string[] {\n return path.stages.filter(s => !s.achieved).map(s => CLAIM_GATE[s.id])\n}\n"]}
1
+ {"version":3,"file":"stage-path.js","sourceRoot":"","sources":["../../server/engine/stage-path.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 단계 판정 — **가벼운 성공을 최종 성공으로 잇는 길을, 사람의 체크가 아니라 증거로 센다.**\n *\n * ── 왜 이 모듈이 있나 ───────────────────────────────────────────────────────\n * 트윈 도입은 「구조 → 라이브 → 예측 → 폐루프」로 간다. 그런데 이 진행을 **체크리스트**로 관리하면\n * 사람이 체크하는 순간 참이 된다. 실제로 가장 자주 건너뛰어지는 자리가 구조 다음이다 — 구조가 서면\n * 화면이 그럴듯해지므로 「실시간」이라고 말해 버리고, 되풀어 읽는 스냅샷이 그 말을 뒷받침하지 못한다.\n *\n * 그래서 단계를 **판정식**으로 둔다. 각 단계는 시스템이 이미 내놓는 사실로만 판정되고, 선언으로\n * 위조할 수 없다. 특히:\n *\n * · **반출 관측**이 라이브의 판별식이다. 스냅샷을 되풀어 읽는 방식은 **원리적으로** 반출을 낼 수 없다\n * (사라진 것은 그냥 없어질 뿐이라 `OBSERVE` 만 남는다). 그러므로 「반출을 한 번이라도 계산했는가」는\n * 진짜 delta 의 증거이고, 폴링을 실시간이라 부르는 것을 막는다.\n * · **선언 적재량**이 예측의 판별식이다. 커널 기본값으로 돌아간 예측도 숫자를 내므로, 화면만 보면\n * 구별되지 않는다. 그 숫자가 무엇 위에 섰는지는 `specCoverage` 만 안다.\n *\n * ── 이 모듈의 정수: 「모른다」를 「아니다」로도 「그렇다」로도 바꾸지 않는다 ──────────────\n * 계측되지 않은 신호는 `null` 로 온다. `null` 은 0 이 아니다. 계측되지 않았으면 그 단계는 **달성이\n * 아니고**, 그 사실을 `unmeasured` 로 말한다 — 신호를 배선하지 않은 채 단계를 주장할 길을 남기지\n * 않는다. (0 으로 대체하면 「반출 0건」과 「반출을 세지 않았다」가 같아진다.)\n *\n * 순수 함수다(프레임워크·DB 무의존) — 증거 수집은 호출자 몫이다.\n */\n\nexport type StageId = 'structure' | 'live' | 'forecast' | 'closed-loop'\n\n/** 판정에 쓰는 사실. `null` = **계측되지 않았다**(0 이 아니다). */\nexport interface StageEvidence {\n /* ── S1 구조 ── */\n /** 이 트윈이 든 자리 수. */\n locationCount: number\n /** 자리에 놓인 물품 수(비직렬 수량 물품 포함 — 식품·화학은 개수가 아니라 총량으로 온다). */\n itemCount: number\n /**\n * 마스터를 옮기며 **못 옮긴 값의 수**. `null` = 세지 않았다.\n *\n * 0 이면 통과이고 n 이어도 통과다 — 이 판정이 요구하는 것은 「없다」가 아니라 **「말할 수 있다」**다.\n * 못 옮긴 값을 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도 성공이라 답하면서 아무 일도 하지 않는다.\n */\n masterWarnings: number | null\n /**\n * 이 축이 이 트윈에 **해당하나**. 레퍼런스 없이 템플릿으로 세운 트윈에는 옮겨 온 마스터가 없으므로\n * 「못 옮긴 값」을 물을 대상이 아니다 — 그걸 미측정으로 처리하면 영원히 배선을 기다린다.\n */\n masterWarningsApplicable?: boolean\n\n /* ── S2 라이브 ── */\n /** 인제스트 누적 건수. */\n liveIngestTotal: number\n /** **반출(remove)을 계산한 누적 건수.** `null` = 계측되지 않음. 0 = 세었으나 없었다(→ 스냅샷 의심). */\n removalsObserved: number | null\n\n /* ── S3 예측 ── */\n /** 이 트윈에서 예측이 한 번이라도 돌았나. */\n forecastRan: boolean\n /** 선언 적재량 — `declared`/`total`. `null` = 알 수 없음. */\n specCoverage: { declared: number; total: number } | null\n\n /* ── S4 폐루프 ── */\n /**\n * 이 트윈이 **무엇인가** — 시뮬레이션은 원천에서 받는 것이 없다.\n *\n * 시뮬에 라이브를 요구하면 영원히 빨갛고, 그 빨강은 결함이 아니다. 도입 단계는 **연결된 트윈**의\n * 축이므로 시뮬은 라이브 이후를 「해당 없음」으로 말한다(달성으로도, 미측정으로도 계산하지 않는다).\n */\n subject?: 'live' | 'sim'\n /** 피드 성격 — `snapshot-poll` 이면 반출 0 은 「아직」이 아니라 **구조적으로 불가**다. */\n feed?: 'push' | 'delta-poll' | 'snapshot-poll' | 'none' | 'unknown'\n /** 원본이 **선언∩구현**으로 액추에이션 능력을 갖나(`capabilitiesOf`). */\n actuateCapability: boolean\n /** 내 명령의 효과가 인바운드로 **상관되어** 돌아온 누적 건수. `null` = 계측되지 않음. */\n correlatedEffectObserved: number | null\n}\n\nexport interface StageVerdict {\n id: StageId\n achieved: boolean\n /** 아직 아닌 이유 — 언어중립 코드(화면이 옮긴다). 달성이면 빈 배열. */\n blockedBy: string[]\n /** 판정할 수 없었던 신호 — **배선되지 않은 계측**. 있으면 달성이 아니다. */\n unmeasured: string[]\n}\n\nexport interface StagePath {\n stages: StageVerdict[]\n /** 지금 서 있는 칸 — 연속으로 달성된 마지막 단계. 하나도 없으면 `null`. */\n reached: StageId | null\n /**\n * 다음 한 걸음 — 사람이 오늘 할 수 있는 일 하나. 없으면(전부 달성) `null`.\n * 「무엇이 부족한지」의 목록이 아니라 **다음 하나**다. 목록은 사람을 멈추게 한다.\n */\n next: { stage: StageId; blockedBy: string[]; unmeasured: string[] } | null\n}\n\nconst ORDER: StageId[] = ['structure', 'live', 'forecast', 'closed-loop']\n\n/**\n * 증거로 단계를 판정한다.\n *\n * **단계는 앞선 단계를 전제한다**(구조 없이 라이브를 달성했다고 말할 수 없다). 앞이 막히면 뒤는\n * 자기 조건을 따지지 않고 `prerequisite` 으로 막는다 — 뒤 단계의 조건을 나열하면 사람이 순서를\n * 건너뛴다.\n */\nexport function assessStagePath(e: StageEvidence): StagePath {\n const stages: StageVerdict[] = []\n let prereqOk = true\n\n for (const id of ORDER) {\n const blockedBy: string[] = []\n const unmeasured: string[] = []\n\n if (!prereqOk) {\n blockedBy.push('prerequisite')\n } else if (id === 'structure') {\n if (!(e.locationCount > 0)) blockedBy.push('no-locations')\n if (!(e.itemCount > 0)) blockedBy.push('no-items')\n if (e.masterWarningsApplicable !== false && e.masterWarnings === null) unmeasured.push('master-warnings')\n } else if (id === 'live') {\n if (e.subject === 'sim') blockedBy.push('subject-is-simulation')\n else {\n if (!(e.liveIngestTotal > 0)) blockedBy.push('no-ingest')\n if (e.removalsObserved === null) unmeasured.push('removals')\n /*\n * 세었는데 없다. 이유가 둘이고 **처방이 정반대**다: 스냅샷을 되풀어 읽는 방식이면 더 기다려도\n * 오지 않으므로 방식을 바꿔야 하고, 그 밖이면 아직 반출이 일어나지 않은 것일 수 있다.\n */\n else if (e.removalsObserved === 0) blockedBy.push(e.feed === 'snapshot-poll' ? 'snapshot-only-feed' : 'no-removal-observed')\n }\n } else if (id === 'forecast') {\n if (!e.forecastRan) blockedBy.push('no-forecast-run')\n if (e.specCoverage === null) unmeasured.push('spec-coverage')\n /* 선언이 0 이면 예측은 커널 기본값 위에 섰다 — 숫자는 나오지만 이 현장의 숫자가 아니다. */\n else if (!(e.specCoverage.declared > 0)) blockedBy.push('declarations-empty')\n } else {\n if (!e.actuateCapability) blockedBy.push('no-actuate-capability')\n if (e.correlatedEffectObserved === null) unmeasured.push('correlated-effect')\n /* 접수만으로는 폐루프가 아니다 — 효과가 관측으로 돌아와야 닫힌다. */\n else if (!(e.correlatedEffectObserved > 0)) blockedBy.push('effect-not-observed')\n }\n\n const achieved = blockedBy.length === 0 && unmeasured.length === 0\n stages.push({ id, achieved, blockedBy, unmeasured })\n if (!achieved) prereqOk = false\n }\n\n const achievedIds = stages.filter(s => s.achieved).map(s => s.id)\n const firstUnmet = stages.find(s => !s.achieved)\n return {\n stages,\n reached: achievedIds.length ? achievedIds[achievedIds.length - 1] : null,\n next: firstUnmet ? { stage: firstUnmet.id, blockedBy: firstUnmet.blockedBy, unmeasured: firstUnmet.unmeasured } : null\n }\n}\n\n/**\n * 이 단계에 이르지 못했다면 **쓰지 않기로 한 말**.\n *\n * 근거 수준 규율(`connector-developer-guide` §8)을 판정과 같은 자리에 둔다. 문장이 문서에만 있으면\n * 제안서를 쓰는 사람이 그 문서를 읽지 않는다. 값이 되면 화면이 그 자리에서 말할 수 있다.\n */\nexport const CLAIM_GATE: Record<StageId, string> = {\n structure: 'claim.structure-synced',\n live: 'claim.realtime',\n forecast: 'claim.prediction',\n 'closed-loop': 'claim.automatic-control'\n}\n\n/** 아직 쓸 수 없는 주장들 — 도달하지 못한 단계의 게이트 전부. */\nexport function forbiddenClaims(path: StagePath): string[] {\n return path.stages.filter(s => !s.achieved).map(s => CLAIM_GATE[s.id])\n}\n"]}
@@ -16,6 +16,6 @@ export interface PickedAxes {
16
16
  * 요청한 축만 남긴다.
17
17
  *
18
18
  * `axes` 가 비어 있으면 **자르지 않는다** — 기본값이 "전부" 여야 기존 호출부가 그대로 동작한다
19
- * (자르는 쪽이 기본이면 축을 말하지 않은 화면이 조용히 비어 버린다).
19
+ * (자르는 쪽이 기본이면 축을 말하지 않은 화면이 알리지 않고 비어 버린다).
20
20
  */
21
21
  export declare function pickAxes(state: any, axes: readonly string[] | undefined | null): PickedAxes;
@@ -15,7 +15,7 @@
15
15
  * `twinMapState` 같은 화면 전용 질의를 만들지 않는다. 호출부가 **필요한 축을 말하고** 서버가 자른다 —
16
16
  * 새 화면이 생길 때마다 질의가 늘지 않고, 어휘도 화면 이름이 아니라 상태의 축이다(무방언).
17
17
  *
18
- * 모르는 축 이름은 **조용히 버리지 않고 무시하되 그 사실을 돌려준다** — 오타 하나로 화면이 빈
18
+ * 모르는 축 이름은 **알리지 않고 버리지 않고 무시하되 그 사실을 돌려준다** — 오타 하나로 화면이 빈
19
19
  * 채로 뜨는 것이 가장 나쁘다.
20
20
  */
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -34,7 +34,7 @@ const ALWAYS = ['revision', 'nowTime', 'simClockMs', 'corrections', 'unhandled']
34
34
  * 요청한 축만 남긴다.
35
35
  *
36
36
  * `axes` 가 비어 있으면 **자르지 않는다** — 기본값이 "전부" 여야 기존 호출부가 그대로 동작한다
37
- * (자르는 쪽이 기본이면 축을 말하지 않은 화면이 조용히 비어 버린다).
37
+ * (자르는 쪽이 기본이면 축을 말하지 않은 화면이 알리지 않고 비어 버린다).
38
38
  */
39
39
  function pickAxes(state, axes) {
40
40
  if (!state || typeof state !== 'object')
@@ -1 +1 @@
1
- {"version":3,"file":"state-axes.js","sourceRoot":"","sources":["../../server/engine/state-axes.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;AA2BH,4BAcC;AAvCD;;;;;GAKG;AACU,QAAA,UAAU,GAAG,CAAC,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,CAAU,CAAA;AAG/I,gDAAgD;AAChD,MAAM,MAAM,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,CAAU,CAAA;AASzF;;;;;GAKG;AACH,SAAgB,QAAQ,CAAC,KAAU,EAAE,IAA0C;IAC7E,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;IACtE,IAAI,CAAC,IAAI,EAAE,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;IAEhD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAS,kBAA+B,CAAC,CAAA;IAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/C,uDAAuD;IACvD,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;IAE7C,MAAM,GAAG,GAAQ,EAAE,CAAA;IACnB,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,IAAI,CAAC,IAAI,KAAK;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACzD,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,IAAI,CAAC,IAAI,KAAK;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACzD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,CAAA;AAChC,CAAC","sourcesContent":["/*\n * 상태 축 고르기 — **순수**. \"요청한 축만 남긴다.\"\n *\n * ── 왜 있나 (2026-08-05) ────────────────────────────────────────────────────\n * 지도는 구독으로 \"바뀌었다\" 신호를 받고 상태를 다시 읽는다. 그 신호는 의도적으로 가볍고(`{kind,\n * revision}`) 리프레시도 250ms 로 합쳐진다 — 거기까지는 설계대로다. 문제는 **리프레시가 통째로**\n * 라는 것이다: 실 저널로 재 보니 트윈 하나가 최대 710KB 이고, 같은 공간에 셋이면 한 번에 약 2MB 다.\n *\n * 그런데 지도가 실제로 쓰는 것은 `locations`·`attentions`·`nowTime` **셋뿐**이다(코드 확인). 물품\n * 2,278건·작업·오더를 받아 놓고 쓰지 않는다. 축을 골라 받으면 트윈에 따라 1.7~15배 줄어든다\n * (물품이 많은 트윈은 15배, 신호가 많은 트윈은 2배 — 신호가 큰 쪽은 그것이 다음 과제다).\n *\n * ── 화면 이름을 API 에 넣지 않는다 ──────────────────────────────────────────\n * `twinMapState` 같은 화면 전용 질의를 만들지 않는다. 호출부가 **필요한 축을 말하고** 서버가 자른다 —\n * 새 화면이 생길 때마다 질의가 늘지 않고, 어휘도 화면 이름이 아니라 상태의 축이다(무방언).\n *\n * 모르는 축 이름은 **조용히 버리지 않고 무시하되 그 사실을 돌려준다** — 오타 하나로 화면이 빈\n * 채로 뜨는 것이 가장 나쁘다.\n */\n\n/**\n * 상태가 들고 있는 축 — 이 목록 밖의 이름은 고를 수 없다.\n *\n * `energy` 는 에너지 트윈만 채운다(계량 지점·수요 구간·피크). `ALWAYS` 에 두지 않는다: 마감 구간을\n * 하루치 들고 있어 작지 않고, 그것이 필요한 화면이 스스로 이름을 말하는 편이 맞다.\n */\nexport const STATE_AXES = ['locations', 'items', 'equipment', 'persons', 'assets', 'tasks', 'orders', 'attentions', 'acked', 'energy'] as const\nexport type StateAxis = (typeof STATE_AXES)[number]\n\n/** 축과 무관하게 늘 남는 것 — 이것이 없으면 무엇을 받았는지 알 수 없다. */\nconst ALWAYS = ['revision', 'nowTime', 'simClockMs', 'corrections', 'unhandled'] as const\n\nexport interface PickedAxes {\n /** 고른 축만 남은 상태. `axes` 가 비었으면 원본 그대로(자르지 않는다). */\n state: any\n /** 상태 축이 아닌 이름 — 호출부가 오타를 알 수 있게 돌려준다. */\n unknown: string[]\n}\n\n/**\n * 요청한 축만 남긴다.\n *\n * `axes` 가 비어 있으면 **자르지 않는다** — 기본값이 \"전부\" 여야 기존 호출부가 그대로 동작한다\n * (자르는 쪽이 기본이면 축을 말하지 않은 화면이 조용히 비어 버린다).\n */\nexport function pickAxes(state: any, axes: readonly string[] | undefined | null): PickedAxes {\n if (!state || typeof state !== 'object') return { state, unknown: [] }\n if (!axes?.length) return { state, unknown: [] }\n\n const known = new Set<string>(STATE_AXES as readonly string[])\n const wanted = axes.filter(a => known.has(a))\n const unknown = axes.filter(a => !known.has(a))\n /* 아는 축이 하나도 없으면 자르지 않는다 — 전부 오타인 경우에 빈 화면을 만들지 않는다. */\n if (!wanted.length) return { state, unknown }\n\n const out: any = {}\n for (const k of ALWAYS) if (k in state) out[k] = state[k]\n for (const a of wanted) if (a in state) out[a] = state[a]\n return { state: out, unknown }\n}\n"]}
1
+ {"version":3,"file":"state-axes.js","sourceRoot":"","sources":["../../server/engine/state-axes.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;AA2BH,4BAcC;AAvCD;;;;;GAKG;AACU,QAAA,UAAU,GAAG,CAAC,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,CAAU,CAAA;AAG/I,gDAAgD;AAChD,MAAM,MAAM,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,CAAU,CAAA;AASzF;;;;;GAKG;AACH,SAAgB,QAAQ,CAAC,KAAU,EAAE,IAA0C;IAC7E,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;IACtE,IAAI,CAAC,IAAI,EAAE,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;IAEhD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAS,kBAA+B,CAAC,CAAA;IAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/C,uDAAuD;IACvD,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;IAE7C,MAAM,GAAG,GAAQ,EAAE,CAAA;IACnB,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,IAAI,CAAC,IAAI,KAAK;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACzD,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,IAAI,CAAC,IAAI,KAAK;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACzD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,CAAA;AAChC,CAAC","sourcesContent":["/*\n * 상태 축 고르기 — **순수**. \"요청한 축만 남긴다.\"\n *\n * ── 왜 있나 (2026-08-05) ────────────────────────────────────────────────────\n * 지도는 구독으로 \"바뀌었다\" 신호를 받고 상태를 다시 읽는다. 그 신호는 의도적으로 가볍고(`{kind,\n * revision}`) 리프레시도 250ms 로 합쳐진다 — 거기까지는 설계대로다. 문제는 **리프레시가 통째로**\n * 라는 것이다: 실 저널로 재 보니 트윈 하나가 최대 710KB 이고, 같은 공간에 셋이면 한 번에 약 2MB 다.\n *\n * 그런데 지도가 실제로 쓰는 것은 `locations`·`attentions`·`nowTime` **셋뿐**이다(코드 확인). 물품\n * 2,278건·작업·오더를 받아 놓고 쓰지 않는다. 축을 골라 받으면 트윈에 따라 1.7~15배 줄어든다\n * (물품이 많은 트윈은 15배, 신호가 많은 트윈은 2배 — 신호가 큰 쪽은 그것이 다음 과제다).\n *\n * ── 화면 이름을 API 에 넣지 않는다 ──────────────────────────────────────────\n * `twinMapState` 같은 화면 전용 질의를 만들지 않는다. 호출부가 **필요한 축을 말하고** 서버가 자른다 —\n * 새 화면이 생길 때마다 질의가 늘지 않고, 어휘도 화면 이름이 아니라 상태의 축이다(무방언).\n *\n * 모르는 축 이름은 **알리지 않고 버리지 않고 무시하되 그 사실을 돌려준다** — 오타 하나로 화면이 빈\n * 채로 뜨는 것이 가장 나쁘다.\n */\n\n/**\n * 상태가 들고 있는 축 — 이 목록 밖의 이름은 고를 수 없다.\n *\n * `energy` 는 에너지 트윈만 채운다(계량 지점·수요 구간·피크). `ALWAYS` 에 두지 않는다: 마감 구간을\n * 하루치 들고 있어 작지 않고, 그것이 필요한 화면이 스스로 이름을 말하는 편이 맞다.\n */\nexport const STATE_AXES = ['locations', 'items', 'equipment', 'persons', 'assets', 'tasks', 'orders', 'attentions', 'acked', 'energy'] as const\nexport type StateAxis = (typeof STATE_AXES)[number]\n\n/** 축과 무관하게 늘 남는 것 — 이것이 없으면 무엇을 받았는지 알 수 없다. */\nconst ALWAYS = ['revision', 'nowTime', 'simClockMs', 'corrections', 'unhandled'] as const\n\nexport interface PickedAxes {\n /** 고른 축만 남은 상태. `axes` 가 비었으면 원본 그대로(자르지 않는다). */\n state: any\n /** 상태 축이 아닌 이름 — 호출부가 오타를 알 수 있게 돌려준다. */\n unknown: string[]\n}\n\n/**\n * 요청한 축만 남긴다.\n *\n * `axes` 가 비어 있으면 **자르지 않는다** — 기본값이 \"전부\" 여야 기존 호출부가 그대로 동작한다\n * (자르는 쪽이 기본이면 축을 말하지 않은 화면이 알리지 않고 비어 버린다).\n */\nexport function pickAxes(state: any, axes: readonly string[] | undefined | null): PickedAxes {\n if (!state || typeof state !== 'object') return { state, unknown: [] }\n if (!axes?.length) return { state, unknown: [] }\n\n const known = new Set<string>(STATE_AXES as readonly string[])\n const wanted = axes.filter(a => known.has(a))\n const unknown = axes.filter(a => !known.has(a))\n /* 아는 축이 하나도 없으면 자르지 않는다 — 전부 오타인 경우에 빈 화면을 만들지 않는다. */\n if (!wanted.length) return { state, unknown }\n\n const out: any = {}\n for (const k of ALWAYS) if (k in state) out[k] = state[k]\n for (const a of wanted) if (a in state) out[a] = state[a]\n return { state: out, unknown }\n}\n"]}
@@ -3,7 +3,7 @@ import type { MeasuredEstimate } from './measured-estimator.js';
3
3
  * UN/CEFACT 권고 20 공통코드 → m/s 변환. 모르는 코드는 변환하지 않는다(발명 금지).
4
4
  *
5
5
  * **내보내는 이유**: 선언 화면·AI 가 「어떤 단위를 받나」를 말해야 하고, 그 목록이 두 곳에 적히면
6
- * 화면이 받아 준 단위를 추정기가 모르는 일이 생긴다(그때 공정 시간이 조용히 사라진다).
6
+ * 화면이 받아 준 단위를 추정기가 모르는 일이 생긴다(그때 공정 시간이 오류 없이 사라진다).
7
7
  */
8
8
  export declare const SPEED_TO_MPS: Record<string, number>;
9
9
  /** 우리가 읽는 canonical 설비 속성 ID — 표준이 속성 ID 어휘를 정하지 않으므로 한 곳에서 정한다. */
@@ -47,7 +47,7 @@ export interface TravelEstimatorResult {
47
47
  estimator?: {
48
48
  estimate(ctx: DurationCtx): number | undefined;
49
49
  };
50
- /** 왜 못 만들었나(만들었으면 비어 있음) — 조용히 없는 상태로 두지 않는다. */
50
+ /** 왜 못 만들었나(만들었으면 비어 있음) — 알리지 않고 없는 상태로 두지 않는다. */
51
51
  reasons: string[];
52
52
  /** 속도를 읽어낸 자원 종류들 — 무엇이 계산 대상인지 드러낸다. */
53
53
  speedsByKind: Record<string, number>;
@@ -22,7 +22,7 @@ exports.chainEstimators = chainEstimators;
22
22
  * UN/CEFACT 권고 20 공통코드 → m/s 변환. 모르는 코드는 변환하지 않는다(발명 금지).
23
23
  *
24
24
  * **내보내는 이유**: 선언 화면·AI 가 「어떤 단위를 받나」를 말해야 하고, 그 목록이 두 곳에 적히면
25
- * 화면이 받아 준 단위를 추정기가 모르는 일이 생긴다(그때 공정 시간이 조용히 사라진다).
25
+ * 화면이 받아 준 단위를 추정기가 모르는 일이 생긴다(그때 공정 시간이 오류 없이 사라진다).
26
26
  */
27
27
  exports.SPEED_TO_MPS = {
28
28
  MTS: 1, // metre per second
@@ -1 +1 @@
1
- {"version":3,"file":"travel-estimator.js","sourceRoot":"","sources":["../../server/engine/travel-estimator.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 이동시간 추정기 — **거리 × 속도.** 커널의 `TRAVEL_MS` 상수를 대체하는 유일한 정당한 길.\n *\n * 커널은 좌표를 모른다(설계: 좌표-free — \"얼마 걸리나\" 만 소비하고 \"어떤 경로로\" 는 씬 소관).\n * 그래서 거리 기반 계산은 **호스트가 추정기로 주입**한다(`DurationEstimator` 시임).\n *\n * 좌표는 이미 있다: 마스터의 `RefLocation.footprint` → `masterToTwin` 이 `model.layout`(노드 중심)으로\n * 낸다. 없던 것은 **속도**다 — 속도 없이 거리만으로는 시간이 나오지 않는다. 그래서 설비 속성\n * (ISA-95 `EquipmentSpecificationProperty`)으로 받는다.\n *\n * 정직 규율: **셋 중 하나라도 모르면 답하지 않는다**(undefined → 커널이 명세·상수로 떨어진다).\n * ① 두 노드의 좌표 ② 그 작업을 할 자원 종류의 속도 ③ 좌표의 단위가 미터인지\n * 그리고 같은 노드 안의 작업(처리·체류)은 이동이 아니므로 다루지 않는다 — 거리 0 을 \"0초\" 로\n * 만들면 공정 시간이 사라진다.\n */\n\nimport type { MeasuredEstimate } from './measured-estimator.js'\n\n/**\n * UN/CEFACT 권고 20 공통코드 → m/s 변환. 모르는 코드는 변환하지 않는다(발명 금지).\n *\n * **내보내는 이유**: 선언 화면·AI 가 「어떤 단위를 받나」를 말해야 하고, 그 목록이 두 곳에 적히면\n * 화면이 받아 준 단위를 추정기가 모르는 일이 생긴다(그때 공정 시간이 조용히 사라진다).\n */\nexport const SPEED_TO_MPS: Record<string, number> = {\n MTS: 1, // metre per second\n KMH: 1000 / 3600, // kilometre per hour\n '2M': 0.3048, // foot per second\n HM: 0.44704 // mile per hour\n}\n\n/** 우리가 읽는 canonical 설비 속성 ID — 표준이 속성 ID 어휘를 정하지 않으므로 한 곳에서 정한다. */\nexport const EQUIPMENT_PROPERTY = { speed: 'speed' } as const\n\n/** 추정기 문맥 — 커널 `DurationContext` 와 같은 모양(커널 타입에 의존하지 않기 위해 구조로 맞춘다). */\nexport interface DurationCtx {\n kind: string\n fromNode: string\n toNode: string\n resourceKind?: string\n}\n\n/**\n * 추정기 — 숫자(평균만) 또는 추정치 객체(퍼짐까지). 커널 `DurationEstimator` 와 같은 모양.\n * 거리 계산은 숫자를 내고, 실측은 관측된 퍼짐을 함께 낸다.\n */\nexport interface DurationEstimatorLike {\n estimate(ctx: DurationCtx): number | MeasuredEstimate | undefined\n}\n\nexport interface TravelEstimatorInput {\n /** 노드 중심 좌표(masterToTwin 산출 `model.layout`). */\n layout?: Record<string, { x: number; y: number }>\n /** 무버(자원) — kind 와 속성. `model.equipment`. */\n equipment?: { id: string; kind: string; properties?: { id: string; value: string; uom?: string }[] }[]\n /** 좌표 단위(공간 `content.unit`). 미터가 아니면 계산하지 않는다(환산을 추측하지 않는다). */\n unit?: string\n}\n\nexport interface TravelEstimatorResult {\n estimator?: { estimate(ctx: DurationCtx): number | undefined }\n /** 왜 못 만들었나(만들었으면 비어 있음) — 조용히 없는 상태로 두지 않는다. */\n reasons: string[]\n /** 속도를 읽어낸 자원 종류들 — 무엇이 계산 대상인지 드러낸다. */\n speedsByKind: Record<string, number>\n}\n\n/** 자원 종류별 속도(m/s) — 같은 종류에 값이 여럿이면 첫 유효값. 단위 없는 속도는 받지 않는다. */\nfunction readSpeeds(equipment: TravelEstimatorInput['equipment'], reasons: string[]): Record<string, number> {\n const out: Record<string, number> = {}\n for (const m of equipment ?? []) {\n if (out[m.kind] !== undefined) continue\n const p = m.properties?.find(x => x.id === EQUIPMENT_PROPERTY.speed)\n if (!p) continue\n const n = Number(p.value)\n if (!Number.isFinite(n) || n <= 0) { reasons.push(`equipment \"${m.id}\": speed \"${p.value}\" is not a positive number`); continue }\n /* 단위가 없으면 m/s 라고 **가정하지 않는다** — 3 이 초속 3m 인지 시속 3km 인지 뜻이 완전히 다르다. */\n if (!p.uom) { reasons.push(`equipment \"${m.id}\": speed has no unit of measure (expected UN/CEFACT code such as MTS or KMH)`); continue }\n const factor = SPEED_TO_MPS[p.uom]\n if (factor === undefined) { reasons.push(`equipment \"${m.id}\": unknown speed unit \"${p.uom}\"`); continue }\n out[m.kind] = n * factor\n }\n return out\n}\n\n/**\n * 이동시간 추정기를 만든다 — 만들 수 없으면 **이유와 함께** 비운다.\n * `estimator` 가 undefined 면 커널은 명세·상수로 굴러가고, `reasons` 가 왜 그런지 말한다.\n */\nexport function buildTravelEstimator(input: TravelEstimatorInput): TravelEstimatorResult {\n const reasons: string[] = []\n const layout = input.layout\n const unit = input.unit ?? 'm'\n if (unit !== 'm') reasons.push(`space unit is \"${unit}\", not metres — travel time is not computed (no assumed conversion)`)\n if (!layout || !Object.keys(layout).length) reasons.push('no location coordinates (model.layout empty) — travel time cannot be derived from distance')\n const speedsByKind = readSpeeds(input.equipment, reasons)\n if (!Object.keys(speedsByKind).length) reasons.push('no resource declares a speed property — distance alone cannot produce a duration')\n if (reasons.length) return { reasons, speedsByKind }\n\n const lay = layout as Record<string, { x: number; y: number }>\n return {\n reasons,\n speedsByKind,\n estimator: {\n estimate(ctx) {\n /* 같은 자리에서 벌어지는 일(처리·체류)은 이동이 아니다 — 다루지 않고 넘긴다. */\n if (!ctx.fromNode || !ctx.toNode || ctx.fromNode === ctx.toNode) return undefined\n const a = lay[ctx.fromNode]\n const b = lay[ctx.toNode]\n if (!a || !b) return undefined // 좌표를 모르는 노드(관측으로 알게 된 자리 등)\n const speed = ctx.resourceKind ? speedsByKind[ctx.resourceKind] : undefined\n if (!speed) return undefined // 자원 종류를 모르거나 그 종류의 속도가 없다\n const dist = Math.hypot(b.x - a.x, b.y - a.y)\n if (!Number.isFinite(dist) || dist <= 0) return undefined\n return Math.round((dist / speed) * 1000)\n }\n }\n }\n}\n\n/**\n * 추정기 사슬 — 앞선 것이 답하면 그 값, 아니면 다음. **실측 → 계산 → (커널이) 명세 → 상수** 순서를\n * 이 사슬과 커널 `durationOf` 가 함께 만든다. 앞에 둘수록 강한 근거다.\n */\nexport function chainEstimators(estimators: (DurationEstimatorLike | undefined)[]): DurationEstimatorLike | undefined {\n const live = estimators.filter(Boolean) as DurationEstimatorLike[]\n if (!live.length) return undefined\n if (live.length === 1) return live[0]\n return {\n estimate(ctx) {\n for (const e of live) {\n const v = e.estimate(ctx)\n if (v !== undefined) return v\n }\n return undefined\n }\n }\n}\n"]}
1
+ {"version":3,"file":"travel-estimator.js","sourceRoot":"","sources":["../../server/engine/travel-estimator.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 이동시간 추정기 — **거리 × 속도.** 커널의 `TRAVEL_MS` 상수를 대체하는 유일한 정당한 길.\n *\n * 커널은 좌표를 모른다(설계: 좌표-free — \"얼마 걸리나\" 만 소비하고 \"어떤 경로로\" 는 씬 소관).\n * 그래서 거리 기반 계산은 **호스트가 추정기로 주입**한다(`DurationEstimator` 시임).\n *\n * 좌표는 이미 있다: 마스터의 `RefLocation.footprint` → `masterToTwin` 이 `model.layout`(노드 중심)으로\n * 낸다. 없던 것은 **속도**다 — 속도 없이 거리만으로는 시간이 나오지 않는다. 그래서 설비 속성\n * (ISA-95 `EquipmentSpecificationProperty`)으로 받는다.\n *\n * 정직 규율: **셋 중 하나라도 모르면 답하지 않는다**(undefined → 커널이 명세·상수로 떨어진다).\n * ① 두 노드의 좌표 ② 그 작업을 할 자원 종류의 속도 ③ 좌표의 단위가 미터인지\n * 그리고 같은 노드 안의 작업(처리·체류)은 이동이 아니므로 다루지 않는다 — 거리 0 을 \"0초\" 로\n * 만들면 공정 시간이 사라진다.\n */\n\nimport type { MeasuredEstimate } from './measured-estimator.js'\n\n/**\n * UN/CEFACT 권고 20 공통코드 → m/s 변환. 모르는 코드는 변환하지 않는다(발명 금지).\n *\n * **내보내는 이유**: 선언 화면·AI 가 「어떤 단위를 받나」를 말해야 하고, 그 목록이 두 곳에 적히면\n * 화면이 받아 준 단위를 추정기가 모르는 일이 생긴다(그때 공정 시간이 오류 없이 사라진다).\n */\nexport const SPEED_TO_MPS: Record<string, number> = {\n MTS: 1, // metre per second\n KMH: 1000 / 3600, // kilometre per hour\n '2M': 0.3048, // foot per second\n HM: 0.44704 // mile per hour\n}\n\n/** 우리가 읽는 canonical 설비 속성 ID — 표준이 속성 ID 어휘를 정하지 않으므로 한 곳에서 정한다. */\nexport const EQUIPMENT_PROPERTY = { speed: 'speed' } as const\n\n/** 추정기 문맥 — 커널 `DurationContext` 와 같은 모양(커널 타입에 의존하지 않기 위해 구조로 맞춘다). */\nexport interface DurationCtx {\n kind: string\n fromNode: string\n toNode: string\n resourceKind?: string\n}\n\n/**\n * 추정기 — 숫자(평균만) 또는 추정치 객체(퍼짐까지). 커널 `DurationEstimator` 와 같은 모양.\n * 거리 계산은 숫자를 내고, 실측은 관측된 퍼짐을 함께 낸다.\n */\nexport interface DurationEstimatorLike {\n estimate(ctx: DurationCtx): number | MeasuredEstimate | undefined\n}\n\nexport interface TravelEstimatorInput {\n /** 노드 중심 좌표(masterToTwin 산출 `model.layout`). */\n layout?: Record<string, { x: number; y: number }>\n /** 무버(자원) — kind 와 속성. `model.equipment`. */\n equipment?: { id: string; kind: string; properties?: { id: string; value: string; uom?: string }[] }[]\n /** 좌표 단위(공간 `content.unit`). 미터가 아니면 계산하지 않는다(환산을 추측하지 않는다). */\n unit?: string\n}\n\nexport interface TravelEstimatorResult {\n estimator?: { estimate(ctx: DurationCtx): number | undefined }\n /** 왜 못 만들었나(만들었으면 비어 있음) — 알리지 않고 없는 상태로 두지 않는다. */\n reasons: string[]\n /** 속도를 읽어낸 자원 종류들 — 무엇이 계산 대상인지 드러낸다. */\n speedsByKind: Record<string, number>\n}\n\n/** 자원 종류별 속도(m/s) — 같은 종류에 값이 여럿이면 첫 유효값. 단위 없는 속도는 받지 않는다. */\nfunction readSpeeds(equipment: TravelEstimatorInput['equipment'], reasons: string[]): Record<string, number> {\n const out: Record<string, number> = {}\n for (const m of equipment ?? []) {\n if (out[m.kind] !== undefined) continue\n const p = m.properties?.find(x => x.id === EQUIPMENT_PROPERTY.speed)\n if (!p) continue\n const n = Number(p.value)\n if (!Number.isFinite(n) || n <= 0) { reasons.push(`equipment \"${m.id}\": speed \"${p.value}\" is not a positive number`); continue }\n /* 단위가 없으면 m/s 라고 **가정하지 않는다** — 3 이 초속 3m 인지 시속 3km 인지 뜻이 완전히 다르다. */\n if (!p.uom) { reasons.push(`equipment \"${m.id}\": speed has no unit of measure (expected UN/CEFACT code such as MTS or KMH)`); continue }\n const factor = SPEED_TO_MPS[p.uom]\n if (factor === undefined) { reasons.push(`equipment \"${m.id}\": unknown speed unit \"${p.uom}\"`); continue }\n out[m.kind] = n * factor\n }\n return out\n}\n\n/**\n * 이동시간 추정기를 만든다 — 만들 수 없으면 **이유와 함께** 비운다.\n * `estimator` 가 undefined 면 커널은 명세·상수로 굴러가고, `reasons` 가 왜 그런지 말한다.\n */\nexport function buildTravelEstimator(input: TravelEstimatorInput): TravelEstimatorResult {\n const reasons: string[] = []\n const layout = input.layout\n const unit = input.unit ?? 'm'\n if (unit !== 'm') reasons.push(`space unit is \"${unit}\", not metres — travel time is not computed (no assumed conversion)`)\n if (!layout || !Object.keys(layout).length) reasons.push('no location coordinates (model.layout empty) — travel time cannot be derived from distance')\n const speedsByKind = readSpeeds(input.equipment, reasons)\n if (!Object.keys(speedsByKind).length) reasons.push('no resource declares a speed property — distance alone cannot produce a duration')\n if (reasons.length) return { reasons, speedsByKind }\n\n const lay = layout as Record<string, { x: number; y: number }>\n return {\n reasons,\n speedsByKind,\n estimator: {\n estimate(ctx) {\n /* 같은 자리에서 벌어지는 일(처리·체류)은 이동이 아니다 — 다루지 않고 넘긴다. */\n if (!ctx.fromNode || !ctx.toNode || ctx.fromNode === ctx.toNode) return undefined\n const a = lay[ctx.fromNode]\n const b = lay[ctx.toNode]\n if (!a || !b) return undefined // 좌표를 모르는 노드(관측으로 알게 된 자리 등)\n const speed = ctx.resourceKind ? speedsByKind[ctx.resourceKind] : undefined\n if (!speed) return undefined // 자원 종류를 모르거나 그 종류의 속도가 없다\n const dist = Math.hypot(b.x - a.x, b.y - a.y)\n if (!Number.isFinite(dist) || dist <= 0) return undefined\n return Math.round((dist / speed) * 1000)\n }\n }\n }\n}\n\n/**\n * 추정기 사슬 — 앞선 것이 답하면 그 값, 아니면 다음. **실측 → 계산 → (커널이) 명세 → 상수** 순서를\n * 이 사슬과 커널 `durationOf` 가 함께 만든다. 앞에 둘수록 강한 근거다.\n */\nexport function chainEstimators(estimators: (DurationEstimatorLike | undefined)[]): DurationEstimatorLike | undefined {\n const live = estimators.filter(Boolean) as DurationEstimatorLike[]\n if (!live.length) return undefined\n if (live.length === 1) return live[0]\n return {\n estimate(ctx) {\n for (const e of live) {\n const v = e.estimate(ctx)\n if (v !== undefined) return v\n }\n return undefined\n }\n }\n}\n"]}