@things-factory/headless-twin 10.0.7 → 10.0.9

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 (143) hide show
  1. package/dist-server/engine/attention-digest.d.ts +52 -0
  2. package/dist-server/engine/attention-digest.js +76 -0
  3. package/dist-server/engine/attention-digest.js.map +1 -0
  4. package/dist-server/engine/board-vocabulary.d.ts +17 -0
  5. package/dist-server/engine/board-vocabulary.js +63 -0
  6. package/dist-server/engine/board-vocabulary.js.map +1 -0
  7. package/dist-server/engine/command-routing.d.ts +33 -0
  8. package/dist-server/engine/command-routing.js +53 -0
  9. package/dist-server/engine/command-routing.js.map +1 -0
  10. package/dist-server/engine/entity-delta.d.ts +13 -6
  11. package/dist-server/engine/entity-delta.js +38 -12
  12. package/dist-server/engine/entity-delta.js.map +1 -1
  13. package/dist-server/engine/index.d.ts +6 -0
  14. package/dist-server/engine/index.js +10 -0
  15. package/dist-server/engine/index.js.map +1 -1
  16. package/dist-server/engine/kpi-baseline.d.ts +78 -0
  17. package/dist-server/engine/kpi-baseline.js +123 -0
  18. package/dist-server/engine/kpi-baseline.js.map +1 -0
  19. package/dist-server/engine/kpi-broadcast.d.ts +4 -0
  20. package/dist-server/engine/kpi-broadcast.js +16 -0
  21. package/dist-server/engine/kpi-broadcast.js.map +1 -0
  22. package/dist-server/engine/kpi-query.d.ts +31 -0
  23. package/dist-server/engine/kpi-query.js +50 -2
  24. package/dist-server/engine/kpi-query.js.map +1 -1
  25. package/dist-server/engine/live-attentions.d.ts +1 -0
  26. package/dist-server/engine/live-attentions.js +7 -1
  27. package/dist-server/engine/live-attentions.js.map +1 -1
  28. package/dist-server/engine/runtime-key.d.ts +15 -0
  29. package/dist-server/engine/runtime-key.js +64 -0
  30. package/dist-server/engine/runtime-key.js.map +1 -0
  31. package/dist-server/engine/state-axes.d.ts +16 -0
  32. package/dist-server/engine/state-axes.js +54 -0
  33. package/dist-server/engine/state-axes.js.map +1 -0
  34. package/dist-server/engine/twin-engine.d.ts +92 -16
  35. package/dist-server/engine/twin-engine.js +335 -77
  36. package/dist-server/engine/twin-engine.js.map +1 -1
  37. package/dist-server/engine/twin-level.d.ts +23 -0
  38. package/dist-server/engine/twin-level.js +52 -0
  39. package/dist-server/engine/twin-level.js.map +1 -0
  40. package/dist-server/engine/warm-start.d.ts +58 -12
  41. package/dist-server/engine/warm-start.js +80 -9
  42. package/dist-server/engine/warm-start.js.map +1 -1
  43. package/dist-server/service/reference/discovery-result.d.ts +34 -0
  44. package/dist-server/service/reference/discovery-result.js +84 -0
  45. package/dist-server/service/reference/discovery-result.js.map +1 -0
  46. package/dist-server/service/reference/ingest-space.d.ts +30 -0
  47. package/dist-server/service/reference/ingest-space.js +63 -0
  48. package/dist-server/service/reference/ingest-space.js.map +1 -0
  49. package/dist-server/service/reference/knob-defaults.d.ts +20 -0
  50. package/dist-server/service/reference/knob-defaults.js +59 -0
  51. package/dist-server/service/reference/knob-defaults.js.map +1 -0
  52. package/dist-server/service/reference/reference-live.js +2 -2
  53. package/dist-server/service/reference/reference-live.js.map +1 -1
  54. package/dist-server/service/reference/reference-master.d.ts +37 -2
  55. package/dist-server/service/reference/reference-master.js +55 -5
  56. package/dist-server/service/reference/reference-master.js.map +1 -1
  57. package/dist-server/service/reference/reference-resolver.d.ts +2 -2
  58. package/dist-server/service/reference/reference-resolver.js +71 -15
  59. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  60. package/dist-server/service/twin-attention/twin-attention-query.d.ts +8 -1
  61. package/dist-server/service/twin-attention/twin-attention-query.js +39 -8
  62. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  63. package/dist-server/service/twin-control/twin-control-mutation.d.ts +2 -0
  64. package/dist-server/service/twin-control/twin-control-mutation.js +29 -10
  65. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  66. package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
  67. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  68. package/dist-server/service/twin-instance/twin-instance.js +4 -2
  69. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  70. package/dist-server/service/twin-journal/twin-journal-query.d.ts +6 -2
  71. package/dist-server/service/twin-journal/twin-journal-query.js +25 -7
  72. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  73. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +8 -0
  74. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +23 -1
  75. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  76. package/dist-server/service/twin-metrics/twin-metrics-query.js +1 -1
  77. package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
  78. package/dist-server/service/twin-space/twin-space-resolver.js +9 -0
  79. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  80. package/dist-server/service/twin-state/twin-state-subscription.js +1 -1
  81. package/dist-server/service/twin-state/twin-state-subscription.js.map +1 -1
  82. package/dist-server/service/twin-structure/twin-structure.js +2 -1
  83. package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
  84. package/dist-server/service/twin-target/twin-target-resolver.js +21 -4
  85. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
  86. package/dist-server/tsconfig.tsbuildinfo +1 -1
  87. package/package.json +6 -6
  88. package/server/engine/attention-digest.ts +102 -0
  89. package/server/engine/board-vocabulary.ts +61 -0
  90. package/server/engine/command-routing.ts +67 -0
  91. package/server/engine/entity-delta.ts +33 -11
  92. package/server/engine/index.ts +10 -0
  93. package/server/engine/kpi-baseline.ts +202 -0
  94. package/server/engine/kpi-broadcast.ts +13 -0
  95. package/server/engine/kpi-query.ts +81 -3
  96. package/server/engine/live-attentions.ts +7 -2
  97. package/server/engine/runtime-key.ts +58 -0
  98. package/server/engine/state-axes.ts +55 -0
  99. package/server/engine/twin-engine.ts +350 -77
  100. package/server/engine/twin-level.ts +48 -0
  101. package/server/engine/warm-start.ts +130 -16
  102. package/server/service/reference/discovery-result.ts +95 -0
  103. package/server/service/reference/ingest-space.ts +70 -0
  104. package/server/service/reference/knob-defaults.ts +59 -0
  105. package/server/service/reference/reference-live.ts +2 -2
  106. package/server/service/reference/reference-master.ts +94 -8
  107. package/server/service/reference/reference-resolver.ts +80 -16
  108. package/server/service/twin-attention/twin-attention-query.ts +43 -6
  109. package/server/service/twin-control/twin-control-mutation.ts +31 -12
  110. package/server/service/twin-forecast/twin-forecast-query.ts +3 -2
  111. package/server/service/twin-instance/twin-instance.ts +6 -2
  112. package/server/service/twin-journal/twin-journal-query.ts +35 -5
  113. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +18 -2
  114. package/server/service/twin-metrics/twin-metrics-query.ts +1 -1
  115. package/server/service/twin-space/twin-space-resolver.ts +10 -1
  116. package/server/service/twin-state/twin-state-subscription.ts +1 -1
  117. package/server/service/twin-structure/twin-structure.ts +4 -1
  118. package/server/service/twin-target/twin-target-resolver.ts +22 -4
  119. package/test/attention-digest.test.ts +135 -0
  120. package/test/board-vocabulary.test.ts +114 -0
  121. package/test/capability-mapping.test.ts +4 -4
  122. package/test/command-routing.test.ts +61 -0
  123. package/test/discovery-result.test.ts +75 -0
  124. package/test/entity-delta.test.ts +25 -25
  125. package/test/ingest-bench.test.ts +3 -3
  126. package/test/ingest-space.test.ts +50 -0
  127. package/test/knob-defaults.test.ts +72 -0
  128. package/test/kpi-baseline-db.test.ts +214 -0
  129. package/test/kpi-baseline.test.ts +196 -0
  130. package/test/kpi-query-bench.test.ts +128 -0
  131. package/test/live-mirror-parity.test.ts +35 -2
  132. package/test/master-to-twin.test.ts +7 -3
  133. package/test/mutation-gate.test.ts +108 -0
  134. package/test/oee-accumulator.test.ts +65 -1
  135. package/test/registry-key-guard.test.ts +80 -0
  136. package/test/runtime-key.test.ts +66 -0
  137. package/test/scale-twin-bench.test.ts +2 -2
  138. package/test/state-axes.test.ts +74 -0
  139. package/test/streamline-e2e.test.ts +2 -2
  140. package/test/structure-revision-db.test.ts +5 -4
  141. package/test/tenant-registry-db.test.ts +149 -0
  142. package/test/warm-start-seam.test.ts +140 -0
  143. package/test/warm-start.test.ts +138 -4
@@ -8,7 +8,7 @@
8
8
  import { test } from 'node:test'
9
9
  import assert from 'node:assert/strict'
10
10
 
11
- import { planWarmStart } from '../server/engine/warm-start.ts'
11
+ import { planWarmStart, unwrapState } from '../server/engine/warm-start.ts'
12
12
 
13
13
  const STATE = {
14
14
  locations: [{ id: 'dock-recv' }, { id: 'rack-1' }],
@@ -27,13 +27,82 @@ test('상태가 있으면 심는다 — 재고·노드·무버', () => {
27
27
  assert.deepEqual(plan.seed.locations, STATE.locations)
28
28
  })
29
29
 
30
- test('오더는 심지 않는다 — 스냅샷에 requested/fulfilled 없어 지어내야 하기 때문', () => {
30
+ test('원값 없는 오더는 심지 않는다 — 남은 수량을 없어 지어내야 하기 때문', () => {
31
+ /* STATE 의 오더는 progress 만 들고 있다 — 커널 판정(requested !== undefined)과 같은 기준으로 뺀다. */
31
32
  const plan = planWarmStart(STATE, undefined, true)
32
33
  assert.equal(plan.action, 'hydrate')
33
34
  if (plan.action !== 'hydrate') return
35
+ assert.equal(plan.seed.orders, undefined)
36
+ assert.equal(plan.orderCount, 0)
37
+ assert.equal(plan.ordersWithoutDemand, 1)
38
+ })
39
+
40
+ test('뺀 오더의 수를 센다 — "주문이 없다" 와 "주문을 못 심었다" 가 같아지면 안 된다', () => {
41
+ /*
42
+ * 이 숫자가 로그의 근거다. 세지 않고 조용히 빼면, 재기동 후 빈 주문 목록을 보고
43
+ * 사용자는 "원래 없었다" 로 읽는다. 실제로 그 화면이 오래 남아 있었다.
44
+ */
45
+ const mixed = {
46
+ ...STATE,
47
+ orders: [
48
+ { id: 'SO-1', kind: 'outbound', status: 'created', progress: 0.5 },
49
+ { id: 'SO-2', kind: 'outbound', status: 'created', requested: 100, fulfilled: 40 },
50
+ { id: 'SO-3', kind: 'outbound', status: 'created', requested: 30 }
51
+ ]
52
+ }
53
+ const plan = planWarmStart(mixed, undefined, true)
54
+ assert.equal(plan.action, 'hydrate')
55
+ if (plan.action !== 'hydrate') return
56
+ assert.equal(plan.orderCount, 2)
57
+ assert.equal(plan.ordersWithoutDemand, 1)
58
+ assert.deepEqual((plan.seed.orders as any[]).map(o => o.id), ['SO-2', 'SO-3'])
59
+ })
60
+
61
+ test('진행 중 주문을 심는다 — 재기동하면 주문이 사라졌던 결함의 회귀 방지', () => {
62
+ /*
63
+ * 예전에는 씨앗이 자리·물품·설비 셋만 넘겨, 재고는 맞는데 주문 목록이 비었다. 원값이 있는
64
+ * 오더는 반드시 씨앗에 실려야 한다 — 이 단언이 그 결함으로 되돌아가는 것을 막는다.
65
+ */
66
+ const plan = planWarmStart(
67
+ { ...STATE, orders: [{ id: 'SO-9', kind: 'outbound', status: 'created', requested: 50, fulfilled: 10, lines: [{ gtin: 'g1', requested: 50 }] }] },
68
+ undefined,
69
+ true
70
+ )
71
+ assert.equal(plan.action, 'hydrate')
72
+ if (plan.action !== 'hydrate') return
73
+ assert.equal(plan.orderCount, 1)
74
+ assert.equal(plan.ordersWithoutDemand, 0)
75
+ assert.equal((plan.seed.orders as any[])[0].requested, 50)
76
+ assert.equal((plan.seed.orders as any[])[0].fulfilled, 10)
77
+ })
78
+
79
+ test('작업·사람·자산도 함께 심는다 — 커널이 심을 수 있는 축을 호스트가 버리지 않는다', () => {
80
+ const plan = planWarmStart(
81
+ { ...STATE, persons: [{ id: 'p1' }, { id: 'p2' }], assets: [{ id: 'pallet-1' }] },
82
+ undefined,
83
+ true
84
+ )
85
+ assert.equal(plan.action, 'hydrate')
86
+ if (plan.action !== 'hydrate') return
87
+ assert.equal(plan.taskCount, 1)
88
+ assert.equal(plan.personCount, 2)
89
+ assert.equal(plan.assetCount, 1)
90
+ assert.deepEqual(plan.seed.tasks, STATE.tasks)
91
+ assert.equal((plan.seed.persons as any[]).length, 2)
92
+ })
93
+
94
+ test('없는 축은 씨앗에 키를 만들지 않는다 — 빈 배열로 커널 판정을 흐리지 않는다', () => {
95
+ const plan = planWarmStart({ locations: [{ id: 'n1' }], items: [], equipment: [] }, undefined, true)
96
+ assert.equal(plan.action, 'hydrate')
97
+ if (plan.action !== 'hydrate') return
34
98
  assert.deepEqual(Object.keys(plan.seed).sort(), ['equipment', 'items', 'locations'])
35
- assert.equal((plan.seed as any).orders, undefined)
36
- assert.equal((plan.seed as any).tasks, undefined)
99
+ })
100
+
101
+ test('오더만 있어도 심는다 — 구조가 보드에서 오는 경우가 있다', () => {
102
+ const plan = planWarmStart({ orders: [{ id: 'SO-1', requested: 10 }] } as any, undefined, true)
103
+ assert.equal(plan.action, 'hydrate')
104
+ if (plan.action !== 'hydrate') return
105
+ assert.equal(plan.orderCount, 1)
37
106
  })
38
107
 
39
108
  test('벤치 트윈에는 심지 않는다 — 새 시작에서 용량을 재는 것이 목적이다', () => {
@@ -76,3 +145,68 @@ test('빠진 배열은 빈 배열로 다뤄지되 없는 것을 지어내지 않
76
145
  assert.deepEqual(plan.seed.equipment, [])
77
146
  assert.equal(plan.itemCount, 1)
78
147
  })
148
+
149
+ test('확인해 둔 주목 신호를 이어받는다 — 재기동하면 다시 빨개지던 것', () => {
150
+ /*
151
+ * 확인 처리는 상태에서 파생되지 않는 유일한 축이다. 씨앗에서 빠지면 재기동 후 확인해 둔 신호가
152
+ * 전부 다시 활성으로 뜬다 — 오류는 나지 않고, 사용자는 "또 터졌다" 로 읽는다.
153
+ */
154
+ const plan = planWarmStart({ ...STATE, acked: ['bottleneck:bin-1', 'breakdown:fk-1'] } as any, undefined, true)
155
+ assert.equal(plan.action, 'hydrate')
156
+ if (plan.action !== 'hydrate') return
157
+ assert.deepEqual(plan.seed.acked, ['bottleneck:bin-1', 'breakdown:fk-1'])
158
+ assert.equal(plan.ackedCount, 2)
159
+ })
160
+
161
+ test('확인 처리만 있어도 심는다 — 신호를 확인해 둔 사실 자체가 이어받을 값어치가 있다', () => {
162
+ const plan = planWarmStart({ acked: ['bottleneck:bin-1'] } as any, undefined, true)
163
+ assert.equal(plan.action, 'hydrate')
164
+ if (plan.action !== 'hydrate') return
165
+ assert.equal(plan.ackedCount, 1)
166
+ })
167
+
168
+ test('확인 처리가 문자열 목록이 아니면 없는 것으로 본다 — 지어내지 않는다', () => {
169
+ const plan = planWarmStart({ ...STATE, acked: 'nope' } as any, undefined, true)
170
+ assert.equal(plan.action, 'hydrate')
171
+ if (plan.action !== 'hydrate') return
172
+ assert.equal(plan.ackedCount, 0)
173
+ assert.equal(plan.seed.acked, undefined)
174
+ })
175
+
176
+ /*
177
+ * ── 겹포장 회귀 방지 (2026-08-05) ──────────────────────────────────────────
178
+ * 체크포인트가 `{revision, state:{revision, state:{실제}}}` 로 저장돼, 꺼낸 값에 축이 하나도 없었다.
179
+ * 판정은 "심을 것이 없다"(no-state)로 답하고 **조용히** 넘어갔다 — 저널에 수천 건이 있고 캐시도
180
+ * 최신인데 트윈이 빈 채로 떴다. 오류도 로그도 없어서 이 경로가 처음부터 죽어 있었다.
181
+ */
182
+ test('봉투로 감싸여 와도 벗겨서 심는다 — 체크포인트가 겹포장돼 복원이 죽었던 자리', () => {
183
+ const enveloped = { revision: 2684, state: { revision: 2684, state: STATE } }
184
+ const plan = planWarmStart(enveloped as any, undefined, true)
185
+ assert.equal(plan.action, 'hydrate', '봉투를 받으면 심을 것이 없다고 답하면 안 된다')
186
+ if (plan.action !== 'hydrate') return
187
+ assert.equal(plan.itemCount, 3)
188
+ assert.deepEqual(plan.seed.locations, STATE.locations)
189
+ })
190
+
191
+ test('한 겹 봉투도 벗긴다 — 저장 방식이 바뀌는 동안 두 모양이 함께 남는다', () => {
192
+ const plan = planWarmStart({ revision: 1, state: STATE } as any, undefined, true)
193
+ assert.equal(plan.action, 'hydrate')
194
+ if (plan.action !== 'hydrate') return
195
+ assert.equal(plan.itemCount, 3)
196
+ })
197
+
198
+ test('벗길 것이 없으면 그대로 쓴다 — 상태를 봉투로 착각하지 않는다', () => {
199
+ const plan = planWarmStart(STATE, undefined, true)
200
+ assert.equal(plan.action, 'hydrate')
201
+ if (plan.action !== 'hydrate') return
202
+ assert.equal(plan.itemCount, 3)
203
+ })
204
+
205
+ test('빈 봉투는 여전히 no-state — 벗겼는데도 아무 축이 없으면 심을 것이 없다', () => {
206
+ assert.deepEqual(planWarmStart({ revision: 5, state: { revision: 5 } } as any, undefined, true), { action: 'skip', reason: 'no-state' })
207
+ })
208
+
209
+ test('unwrapState 는 상태를 먼저 알아본다 — state 라는 이름의 축이 있어도 벗기지 않는다', () => {
210
+ const st = { items: [{ epc: 'a' }], state: 'whatever' }
211
+ assert.equal(unwrapState(st as any), st, '축이 있으면 그것이 상태다')
212
+ })