@things-factory/headless-twin 10.0.5 → 10.0.7

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 (161) hide show
  1. package/README.md +37 -24
  2. package/dist-server/engine/entity-delta.d.ts +13 -1
  3. package/dist-server/engine/entity-delta.js +130 -16
  4. package/dist-server/engine/entity-delta.js.map +1 -1
  5. package/dist-server/engine/index.d.ts +4 -0
  6. package/dist-server/engine/index.js +4 -0
  7. package/dist-server/engine/index.js.map +1 -1
  8. package/dist-server/engine/kpi-fold.d.ts +20 -3
  9. package/dist-server/engine/kpi-fold.js +45 -53
  10. package/dist-server/engine/kpi-fold.js.map +1 -1
  11. package/dist-server/engine/kpi-query.d.ts +16 -0
  12. package/dist-server/engine/kpi-query.js +129 -19
  13. package/dist-server/engine/kpi-query.js.map +1 -1
  14. package/dist-server/engine/kpi-target.d.ts +141 -0
  15. package/dist-server/engine/kpi-target.js +137 -0
  16. package/dist-server/engine/kpi-target.js.map +1 -0
  17. package/dist-server/engine/live-attentions.d.ts +4 -2
  18. package/dist-server/engine/live-attentions.js +5 -1
  19. package/dist-server/engine/live-attentions.js.map +1 -1
  20. package/dist-server/engine/measured-estimator.d.ts +50 -0
  21. package/dist-server/engine/measured-estimator.js +78 -0
  22. package/dist-server/engine/measured-estimator.js.map +1 -0
  23. package/dist-server/engine/model-basis.d.ts +23 -0
  24. package/dist-server/engine/model-basis.js +100 -0
  25. package/dist-server/engine/model-basis.js.map +1 -0
  26. package/dist-server/engine/oee-accumulator.d.ts +2 -2
  27. package/dist-server/engine/oee-accumulator.js +4 -4
  28. package/dist-server/engine/oee-accumulator.js.map +1 -1
  29. package/dist-server/engine/spec-coverage.d.ts +49 -0
  30. package/dist-server/engine/spec-coverage.js +60 -0
  31. package/dist-server/engine/spec-coverage.js.map +1 -0
  32. package/dist-server/engine/structure-diff.d.ts +25 -0
  33. package/dist-server/engine/structure-diff.js +63 -0
  34. package/dist-server/engine/structure-diff.js.map +1 -0
  35. package/dist-server/engine/travel-estimator.d.ts +57 -0
  36. package/dist-server/engine/travel-estimator.js +120 -0
  37. package/dist-server/engine/travel-estimator.js.map +1 -0
  38. package/dist-server/engine/twin-engine.d.ts +181 -18
  39. package/dist-server/engine/twin-engine.js +545 -96
  40. package/dist-server/engine/twin-engine.js.map +1 -1
  41. package/dist-server/engine/warm-start.d.ts +39 -0
  42. package/dist-server/engine/warm-start.js +37 -0
  43. package/dist-server/engine/warm-start.js.map +1 -0
  44. package/dist-server/index.js +8 -0
  45. package/dist-server/index.js.map +1 -1
  46. package/dist-server/service/index.d.ts +4 -2
  47. package/dist-server/service/index.js +21 -14
  48. package/dist-server/service/index.js.map +1 -1
  49. package/dist-server/service/reference/reference-live.js +2 -1
  50. package/dist-server/service/reference/reference-live.js.map +1 -1
  51. package/dist-server/service/reference/reference-master.d.ts +307 -1
  52. package/dist-server/service/reference/reference-master.js +96 -8
  53. package/dist-server/service/reference/reference-master.js.map +1 -1
  54. package/dist-server/service/reference/reference-resolver.js +3 -3
  55. package/dist-server/service/reference/reference-resolver.js.map +1 -1
  56. package/dist-server/service/reference/template-registry.d.ts +1 -1
  57. package/dist-server/service/reference/template-registry.js.map +1 -1
  58. package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
  59. package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
  60. package/dist-server/service/twin-control/twin-control-mutation.js +1 -1
  61. package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
  62. package/dist-server/service/twin-event/backfill-keys.d.ts +11 -0
  63. package/dist-server/service/twin-event/backfill-keys.js +63 -0
  64. package/dist-server/service/twin-event/backfill-keys.js.map +1 -0
  65. package/dist-server/service/twin-event/twin-event-keys.d.ts +35 -0
  66. package/dist-server/service/twin-event/twin-event-keys.js +95 -0
  67. package/dist-server/service/twin-event/twin-event-keys.js.map +1 -0
  68. package/dist-server/service/twin-event/twin-event-type.d.ts +6 -0
  69. package/dist-server/service/twin-event/twin-event-type.js +32 -0
  70. package/dist-server/service/twin-event/twin-event-type.js.map +1 -0
  71. package/dist-server/service/twin-event/twin-event.d.ts +16 -0
  72. package/dist-server/service/twin-event/twin-event.js +50 -0
  73. package/dist-server/service/twin-event/twin-event.js.map +1 -1
  74. package/dist-server/service/twin-forecast/twin-forecast-query.js +50 -7
  75. package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
  76. package/dist-server/service/twin-instance/twin-instance.js +1 -1
  77. package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
  78. package/dist-server/service/twin-journal/twin-journal-query.d.ts +28 -0
  79. package/dist-server/service/twin-journal/twin-journal-query.js +127 -2
  80. package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
  81. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +1 -1
  82. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +4 -3
  83. package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
  84. package/dist-server/service/twin-space/twin-space-area.js +1 -1
  85. package/dist-server/service/twin-space/twin-space-area.js.map +1 -1
  86. package/dist-server/service/twin-space/twin-space-resolver.js +3 -3
  87. package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
  88. package/dist-server/service/twin-space/twin-space.d.ts +11 -0
  89. package/dist-server/service/twin-space/twin-space.js +5 -0
  90. package/dist-server/service/twin-space/twin-space.js.map +1 -1
  91. package/dist-server/service/twin-structure/index.d.ts +2 -0
  92. package/dist-server/service/twin-structure/index.js +6 -0
  93. package/dist-server/service/twin-structure/index.js.map +1 -0
  94. package/dist-server/service/twin-structure/twin-structure.d.ts +28 -0
  95. package/dist-server/service/twin-structure/twin-structure.js +97 -0
  96. package/dist-server/service/twin-structure/twin-structure.js.map +1 -0
  97. package/dist-server/service/twin-target/index.d.ts +4 -0
  98. package/dist-server/service/twin-target/index.js +8 -0
  99. package/dist-server/service/twin-target/index.js.map +1 -0
  100. package/dist-server/service/twin-target/twin-target-resolver.d.ts +7 -0
  101. package/dist-server/service/twin-target/twin-target-resolver.js +143 -0
  102. package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -0
  103. package/dist-server/service/twin-target/twin-target.d.ts +25 -0
  104. package/dist-server/service/twin-target/twin-target.js +112 -0
  105. package/dist-server/service/twin-target/twin-target.js.map +1 -0
  106. package/dist-server/tsconfig.tsbuildinfo +1 -1
  107. package/package.json +6 -6
  108. package/server/engine/entity-delta.ts +135 -15
  109. package/server/engine/index.ts +4 -0
  110. package/server/engine/kpi-fold.ts +62 -59
  111. package/server/engine/kpi-query.ts +140 -19
  112. package/server/engine/kpi-target.ts +226 -0
  113. package/server/engine/live-attentions.ts +12 -2
  114. package/server/engine/measured-estimator.ts +91 -0
  115. package/server/engine/model-basis.ts +94 -0
  116. package/server/engine/oee-accumulator.ts +5 -5
  117. package/server/engine/spec-coverage.ts +85 -0
  118. package/server/engine/structure-diff.ts +88 -0
  119. package/server/engine/travel-estimator.ts +133 -0
  120. package/server/engine/twin-engine.ts +587 -106
  121. package/server/engine/warm-start.ts +53 -0
  122. package/server/index.ts +9 -0
  123. package/server/service/index.ts +7 -0
  124. package/server/service/reference/reference-live.ts +2 -1
  125. package/server/service/reference/reference-master.ts +383 -10
  126. package/server/service/reference/reference-resolver.ts +3 -3
  127. package/server/service/reference/template-registry.ts +1 -1
  128. package/server/service/twin-attention/twin-attention-query.ts +1 -1
  129. package/server/service/twin-control/twin-control-mutation.ts +1 -1
  130. package/server/service/twin-event/backfill-keys.ts +72 -0
  131. package/server/service/twin-event/twin-event-keys.ts +102 -0
  132. package/server/service/twin-event/twin-event-type.ts +27 -0
  133. package/server/service/twin-event/twin-event.ts +62 -0
  134. package/server/service/twin-forecast/twin-forecast-query.ts +56 -8
  135. package/server/service/twin-instance/twin-instance.ts +1 -1
  136. package/server/service/twin-journal/twin-journal-query.ts +129 -5
  137. package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +4 -3
  138. package/server/service/twin-space/twin-space-area.ts +1 -1
  139. package/server/service/twin-space/twin-space-resolver.ts +3 -3
  140. package/server/service/twin-space/twin-space.ts +14 -0
  141. package/server/service/twin-structure/index.ts +3 -0
  142. package/server/service/twin-structure/twin-structure.ts +93 -0
  143. package/server/service/twin-target/index.ts +5 -0
  144. package/server/service/twin-target/twin-target-resolver.ts +124 -0
  145. package/server/service/twin-target/twin-target.ts +97 -0
  146. package/test/capability-mapping.test.ts +5 -5
  147. package/test/duration-estimators.test.ts +144 -0
  148. package/test/entity-delta.test.ts +150 -19
  149. package/test/ingest-bench.test.ts +9 -9
  150. package/test/kpi-fold.test.ts +254 -3
  151. package/test/live-mirror-parity.test.ts +53 -19
  152. package/test/master-to-twin.test.ts +87 -13
  153. package/test/model-basis.test.ts +86 -0
  154. package/test/oee-accumulator.test.ts +9 -9
  155. package/test/scale-twin-bench.test.ts +22 -22
  156. package/test/spec-coverage.test.ts +113 -0
  157. package/test/streamline-e2e.test.ts +10 -10
  158. package/test/structure-revision-db.test.ts +310 -0
  159. package/test/twin-event-keys.test.ts +108 -0
  160. package/test/vocabulary-guard.test.ts +43 -0
  161. package/test/warm-start.test.ts +78 -0
@@ -1,4 +1,6 @@
1
1
  import { type ReferenceMaster } from '../service/reference/reference-master.js';
2
+ import { type ModelBasis } from './model-basis.js';
3
+ import { type StructureDiff } from './structure-diff.js';
2
4
  import { OeeAccumulator } from './oee-accumulator.js';
3
5
  import type { BoardDef, TwinRuntime as TwinRuntimeType, CanonicalEnvelope } from '@operato/twin-kernel';
4
6
  interface TwinMetrics {
@@ -25,6 +27,10 @@ interface InstanceRuntime {
25
27
  runtime?: TwinRuntimeType;
26
28
  kernel?: any;
27
29
  mode?: 'sim' | 'live';
30
+ /**
31
+ * live 의 상태 접근 어댑터 — 이제 **관측 모드 커널**을 가리킨다(`kernel` 과 같은 것).
32
+ * 이름은 소비처 호환으로 남았고, P3 에서 커널 어휘로 정리하면 사라진다.
33
+ */
28
34
  projector?: any;
29
35
  oee?: OeeAccumulator;
30
36
  unsub: () => void;
@@ -63,14 +69,122 @@ export declare class TwinEngine {
63
69
  * 라이브 tick 재개(sim 결정적 re-run / live 인제스트 지속)는 후속. 여기선 상태 복원 + 노출.
64
70
  */
65
71
  static bootstrap(): Promise<void>;
66
- /** 트윈 인스턴스 시작 — 커널 생성 + 보드 로드 + State 스트림 브리지 + 워커 tick + 레지스트리 영속. */
67
- static start(id: string, domainId: string, kind: string, board: BoardDef, realityMode?: RealityMode): InstanceRuntime;
68
72
  /**
69
- * live-모드 기동(face2-inbound-live §1.1) 커널 tick 대신 StateProjector 실 이벤트를 미러.
70
- * 관측 상태(점유·위치·아이템)는 projector 가 재구성 → sim 과 동일 data(tag) payload → **보드 컴포넌트 동일 렌더**
71
- * (live-mirror-parity payload 층 검증됨). 계산 층(attentions·OEE)은 이벤트에 없어 부재 — derive 층은 후속.
73
+ * 웜스타트기동하는 커널에 **직전 관측 상태**를 심는다.
74
+ *
75
+ * ── 필요한가 ─────────────────────────────────────────────────────────────
76
+ * `loadBoard` 는 **구조만** 싣는다(노드·무버). 상태(무엇이 어디에 얼마나)는 없다. 그래서 재기동한
77
+ * 트윈은 저널에 입고 540건이 남아 있어도 재고가 0 이었고, 화면은 "보유 중인 것이 없습니다" 라고
78
+ * 말했다 — 있는 재고를 없다고 하는 셈이다(2026-07-31 hatiolab-wms 실측으로 확인).
79
+ * `bootstrap()` 이 이미 체크포인트 캐시(없으면 저널 replay)로 상태를 복구해 `recovered` 에 담아 두는데,
80
+ * 기동 순간 그걸 **버리고** 있었다. 반만 연결돼 있던 장치를 잇는다.
81
+ *
82
+ * ── 정직한 한계 ─────────────────────────────────────────────────────────────
83
+ * · **오더는 복원하지 않는다.** `hydrateObserved` 의 오더 인자는 requested/fulfilled/lines 를 요구하는데
84
+ * 스냅샷의 `OrderState` 에는 `progress` 밖에 없다. progress 에서 역산하면 없는 숫자를 지어내는 것이라
85
+ * 넘기지 않는다 — 재고·노드·무버만 복원되고 진행 중 오더는 비어서 시작한다.
86
+ * · 진행 중 개별 task 의 내부 상태도 관측만으로는 복원되지 않는다(커널이 명시한 한계, 재계획에 맡김).
87
+ * · 근본 해법(상태 영속 계약·revision 이어붙임)은 별도 과제.
88
+ *
89
+ * ── 벤치는 시드하지 않는다 ──────────────────────────────────────────────────
90
+ * 부하 벤치는 **새 시작에서 용량을 재는 것**이 목적이라 현재 상태를 심으면 측정이 오염된다.
91
+ */
92
+ private static warmStart;
93
+ /** 트윈 인스턴스 시작 — 커널 생성 + 보드 로드 + 직전 상태 웜스타트 + State 스트림 브리지 + 워커 tick + 레지스트리 영속. */
94
+ /**
95
+ * 공정 명세를 커널에 싣는다 — **시뮬레이션의 시간을 데이터가 말하게 하는 마지막 한 칸.**
96
+ *
97
+ * `board.operations`(마스터 인제스트가 통과시킨 ISA-95 OperationsSegment 명세)를 커널이 소비한다.
98
+ * 없으면 커널 기본 상수로 굴러가고, 커널 `specCoverage()` 가 무엇을 기본값으로 썼는지 보고한다.
99
+ *
100
+ * 커널이 아직 이 API 를 갖지 않은 버전이면(발행 이전) **조용히 넘어가지 않고 경고한다** — 명세를
101
+ * 선언했는데 반영되지 않는 상태를 모르고 지나가면, 예측이 상수로 돌아간 것을 아무도 알 수 없다.
102
+ */
103
+ private static applyOperations;
104
+ /**
105
+ * 소요시간 추정기 주입 — **상수를 데이터로 바꾸는 두 번째·세 번째 층.**
106
+ *
107
+ * 커널 `durationOf` 의 우선순위는 추정기 > 명세 > 상수다. 그 추정기 자리에 두 가지를 사슬로 넣는다:
108
+ * ① **실측**(저널의 작업 종류별 작업시간 p50) — 그 현장에서 실제로 얼마 걸렸나. 가장 강한 근거.
109
+ * ② **거리 × 속도**(board.layout + 설비 속도 속성) — 이동은 거리에 비례한다. 커널은 좌표를 모르므로
110
+ * 호스트가 계산해 넣는다.
111
+ * 둘 다 못 만들면 주입하지 않는다 — 커널이 명세·상수로 굴러가고 `specCoverage()` 가 그 사실을 남긴다.
112
+ *
113
+ * 실측은 DB 조회라 비동기다. 그래서 이 함수는 **await 하지 않는 쪽에서도 안전**하도록 실패를 삼키되,
114
+ * 무엇을 왜 못 넣었는지는 로그로 남긴다(조용한 무효화 금지).
115
+ */
116
+ static installEstimators(kernel: any, domainId: string, instanceId: string, board: any): Promise<void>;
117
+ /**
118
+ * 실측 추정기 — **예측 요청마다 저널을 다시 접지 않는다.**
119
+ *
120
+ * 예측 커널은 요청마다 새로 세워지고(미러 예측·백테스트), 화면은 시각을 긁으면 계속 재예측한다.
121
+ * 거기에 KPI 조회를 그대로 달면 요청당 저널 스캔이 하나씩 붙는다 — 실측은 분 단위로 바뀌지 않으므로
122
+ * 짧은 TTL 로 재사용한다. 캐시는 인스턴스별이고, 만료 전에는 같은 값을 쓴다(예측 간 일관성도 얻는다).
123
+ */
124
+ private static measuredCache;
125
+ private static readonly MEASURED_TTL_MS;
126
+ private static measuredEstimator;
127
+ /**
128
+ * 지금 이 트윈이 어떤 모델로 굴러가는가 — 예측 출력 보정이 **자기가 배운 모델**에만 적용되도록
129
+ * 비교하는 지문. 재료는 호스트가 아는 것(실측·속도·선언 명세)이라 커널 발행 상태와 무관하다.
130
+ * 실패하면 undefined — 그때는 비교를 하지 않는다(모르는 것을 근거로 보정을 끊지 않는다).
131
+ */
132
+ static modelBasis(domainId: string, instanceId: string): Promise<ModelBasis | undefined>;
133
+ /**
134
+ * **이 트윈이 하루 몇 대를 낼 수 있는가** — 굴려 보지 않고 답한다.
135
+ *
136
+ * 계산은 커널이 자기 상태에서 한다(`kernel.capacity`). 여기서 하는 일은 **기준 주를 정해 주는
137
+ * 것**뿐이다: 공휴일이 없는 평상주여야 한다 — 공휴일은 연간 가용량을 따로 깎지, 이 공장의 평상시
138
+ * 천장을 정하지 않는다. 커널이 임의로 고르게 두면 그 주에 공휴일이 끼었을 때 천장이 조용히 낮아진다.
139
+ *
140
+ * 트윈이 안 떠 있으면 `undefined` 다 — 0 이 아니다. 안 뜬 트윈의 천장을 0 이라고 답하면 화면은
141
+ * "이 공장은 아무것도 못 만든다" 고 말한다.
142
+ */
143
+ static capacity(domainId: string, target: {
144
+ instanceId?: string;
145
+ spaceId?: string;
146
+ }, unitsPerDay: number, sampleWeekStartMs: number): Promise<{
147
+ instanceId: string;
148
+ kind?: string;
149
+ analysis?: any;
150
+ reason?: 'not-running' | 'no-operations';
151
+ }[]>;
152
+ static start(id: string, domainId: string, kind: string, board: BoardDef, realityMode?: RealityMode, purpose?: string, resumeFrom?: number): InstanceRuntime;
153
+ /**
154
+ * live-모드 기동 — **관측 모드 커널**로 실 이벤트를 미러한다(통합 P2).
155
+ *
156
+ * 예전에는 `StateProjector` 를 세웠다. 그래서 라이브에는 **커널이 없었고**, 그 하나 때문에 우회가
157
+ * 줄줄이 생겼다: 예측하려면 임시 커널을 세워야 했고(`buildForecastKernel`), 주목 신호를 호스트가
158
+ * 덧붙여야 했고(`withLiveAttentions`), AI 예측 도구는 미러에서 "찾을 수 없다" 로 끝났다.
159
+ *
160
+ * 이제 라이브 인스턴스도 **커널이다.** 같은 규칙(`ObservedReducer`)으로 이벤트를 접고, 주목 신호를
161
+ * 스스로 내고, 그 자리에서 `fork` 해 예측한다. 구동만 다르다 — sim 은 `tick`, live 는 `apply`.
162
+ *
72
163
  * 실 이벤트원 = reference 어댑터 openLiveFeed → face2-adapter.ingest → CanonicalEnvelope → ingestLive().
73
164
  */
165
+ /**
166
+ * 보드가 실은 **생산 정의**(레시피·라우트·바인딩)를 꺼낸다 — 커널의 정의-구동 모드 입구.
167
+ *
168
+ * ── 없을 때 무엇이 일어났나 ──────────────────────────────────────────────
169
+ * 커널에는 정의-구동 MES 경로가 있는데 **호스트가 그것을 한 번도 넘기지 않았다.** 그래서 모든 MES
170
+ * 트윈이 **하드코딩된 레거시 흐름**으로 돌았다 — 현장 레시피를 아무리 정성껏 적어도 커널은 그것을
171
+ * 보지 못하고 토이 부품으로 토이 제품을 만들었다. 선언과 실행이 갈라져 있던 자리다.
172
+ *
173
+ * 보드에 없으면 `undefined` — 레거시 경로 그대로다(기존 트윈의 거동을 바꾸지 않는다).
174
+ */
175
+ private static mesSpecOf;
176
+ /**
177
+ * 현장(공간)의 **시각 기준**을 보드에 얹는다 — 커널이 교대의 `HH:MM` 을 읽을 기준.
178
+ *
179
+ * **테넌트가 아니라 공간이 권위다.** 한 테넌트가 Rosarito(태평양)와 한국 공장을 함께 가질 수 있고,
180
+ * 테넌트 단위(`Domain.timezone`)로 두면 둘 중 하나는 반드시 틀린다. 그래서 인스턴스가 묶인 공간의
181
+ * `timezone` 을 읽는다. 공간이 말하지 않으면 **테넌트로 내려가지 않는다** — 잘못된 입자로 답하는 것이
182
+ * 모르는 것보다 나쁘다(그때는 커널이 UTC 로 읽고, 그 기본값은 계약에 밝혀져 있다).
183
+ *
184
+ * 커널은 zero-dep 이라 시간대 데이터베이스를 갖지 않으므로 **분 오프셋**으로 풀어 넘긴다. 그 값은
185
+ * 지금 계절의 것이다(일광절약시간) — 계절을 넘는 긴 예측은 한 시간 어긋난다(계약에 명시).
186
+ */
187
+ static withSpaceTimeBase(board: BoardDef, domainId: string, spaceId?: string): Promise<BoardDef>;
74
188
  static startLive(id: string, domainId: string, kind: string, board: BoardDef): InstanceRuntime;
75
189
  /**
76
190
  * live 이벤트 인제스트 — projector 구동 + data(tag) 방송(폐루프의 인바운드 도착 지점, command-routing §8.2).
@@ -85,25 +199,69 @@ export declare class TwinEngine {
85
199
  private static ensureBroadcastCoalescer;
86
200
  /** dirty live 인스턴스 방송 flush(주기 tick 또는 명시 호출). 테스트/즉시 방송용으로 public. */
87
201
  static flushLiveBroadcasts(): void;
202
+ /**
203
+ * 저널 행 한 줄 — **기록 경로가 둘이라(라이브 벌크·심 단건) 행 모양은 반드시 한 곳에서 만든다.**
204
+ * 두 곳에 각자 적으면 승격 검색 키가 한쪽에만 채워지고, 반쯤 빈 색인은 "저널에는 있는데
205
+ * 검색으로는 안 나오는 이벤트" 를 만든다 — 저널에서 가장 나쁜 종류의 결함이다.
206
+ */
207
+ private static journalRow;
208
+ /**
209
+ * 이 트윈이 지금 어느 구조로 도는가 — 이벤트에 찍을 번호. 한 번 읽고 캐시한다(쓰기마다 조회 금지).
210
+ *
211
+ * 없으면 `undefined` 다 — 0 이 아니다. 구조 리비전이 생기기 전에 만들어진 트윈은 아직 리비전이
212
+ * 없고, 그 사실을 0 이라는 **유효해 보이는 번호**로 위장하면 안 된다.
213
+ */
214
+ private static structureRevCache;
215
+ static structureRevOf(domainId: string, instanceId: string): Promise<number | undefined>;
88
216
  /** live 저널 배치 기록 — 모아둔 CanonicalEnvelope 들에 startRevision+1.. 을 부여해 벌크 저장(coalescer tick 당 1회). */
89
217
  static persistBatch(domainId: string, instanceId: string, envelopes: any[], startRevision: number): Promise<void>;
90
218
  /** 레지스트리 upsert(도메인+instanceId 유니크). status 인자로 provision(stopped)/start(running) 공용. */
91
219
  static register(domainId: string, instanceId: string, kind: string, board: BoardDef, status?: 'running' | 'stopped', realityMode?: RealityMode): Promise<void>;
92
220
  /**
93
- * 라이브 구조 변이(resource.add 등)를 저장된 board 에 반영 — 런타임 커널의 무버를 registry board.movers 에 동기.
221
+ * 라이브 구조 변이(resource.add 등)를 저장된 board 에 반영 — 런타임 커널의 무버를 registry board.equipment 에 동기.
94
222
  * 이게 없으면 런타임엔 추가돼도(상태·저널엔 반영) 저장 board 는 원본이라 프로비저닝 편집기·재기동(loadBoard)이
95
- * 추가분을 잃는다. 기존 board.movers 항목은 보존(homeNode 유지)하고 새 id 만 append(추가 시점 location=homeNode).
96
- * 좌표(layout)만 다루는 register 와 달리 movers 집합을 갱신하나, 재프로비전(purge)이 아니라 in-place 갱신이라
223
+ * 추가분을 잃는다. 기존 board.equipment 항목은 보존(homeLocation 유지)하고 새 id 만 append(추가 시점 location=homeLocation).
224
+ * 좌표(layout)만 다루는 register 와 달리 equipment 집합을 갱신하나, 재프로비전(purge)이 아니라 in-place 갱신이라
97
225
  * 저널은 보존된다(추가는 이미 equipment 델타로 저널됨 → replay 는 id-keyed upsert 라 이중계산 없음).
98
226
  */
99
- static syncBoardMovers(domainId: string, instanceId: string): Promise<void>;
227
+ static syncBoardEquipment(domainId: string, instanceId: string): Promise<void>;
100
228
  /**
101
229
  * 프로비저닝 — 레지스트리에 구조(board)를 등록만 하고 기동하지 않음(status='stopped').
102
230
  * 재프로비전 시 구조 시그니처가 바뀌면(노드·무버 집합/속성 변경) 기존 저널을 purge 한다(ADR-0015):
103
231
  * board 는 replay 의 마스터라 구조가 바뀌면 과거 이벤트의 전제가 깨진다. 좌표(layout)만 바뀌면 저널 보존.
104
232
  */
105
- static provision(domainId: string, instanceId: string, kind: string, board: BoardDef): Promise<void>;
233
+ static provision(domainId: string, instanceId: string, kind: string, board: BoardDef, comment?: string): Promise<void>;
234
+ /**
235
+ * 이 구조를 리비전으로 남기고 그 번호를 돌려준다 — **바뀌었을 때만** 새 번호가 생긴다.
236
+ *
237
+ * 프로비저닝은 부팅마다 다시 도는데, 그때마다 리비전이 늘면 이력이 뜻 없는 마디로 잘게 쪼개진다.
238
+ * 그래서 **구조 서명이 같으면 있던 리비전을 그대로 쓴다.**
239
+ */
240
+ static recordStructure(domainId: string, instanceId: string, board: BoardDef, comment?: string): Promise<number>;
241
+ /**
242
+ * 이 트윈이 거쳐 온 구조들 — **무엇이 언제 달라졌나.**
243
+ *
244
+ * 리비전은 그 시절 공장을 통째로 담지만(재생에 필요하다), 사람이 보고 싶은 것은 통째가 아니라
245
+ * 차이다. 보드는 빼고 **차이 요약만** 내보낸다 — 62KB 짜리 보드를 화면에 실어 보낼 이유가 없다.
246
+ */
247
+ static structureHistory(domainId: string, instanceId: string): Promise<{
248
+ rev: number;
249
+ createdAt?: Date;
250
+ comment?: string;
251
+ events: number;
252
+ diff: StructureDiff;
253
+ }[]>;
254
+ /** 지금 쓰이는 구조 리비전 — 이벤트에 찍을 번호. 아직 없으면 기록하며 만든다. */
255
+ static currentStructureRev(domainId: string, instanceId: string, board?: BoardDef): Promise<number | undefined>;
106
256
  /** 구조 동일성 지문 — 좌표(layout) 등 뷰 관심사는 제외하고 커널이 보는 위상·용량·무버만. */
257
+ /**
258
+ * 지문을 **64자로 줄인다** — 컬럼이 varchar(64) 다.
259
+ *
260
+ * 원문 지문은 자리·설비를 전부 이어 붙인 문자열이라 큰 공장에서는 수만 자가 된다. sqlite 는
261
+ * 길이를 강제하지 않아 그냥 들어가지만 **Postgres 는 거기서 터진다** — 개발에서는 멀쩡하고
262
+ * 운영에서만 죽는 종류의 실패다. 비교에만 쓰는 값이므로 해시로 충분하다.
263
+ */
264
+ private static structureFingerprint;
107
265
  private static structureSignature;
108
266
  /** 레지스트리 board 로 기동(프로비전된 인스턴스 start). board 인자 없이 저장된 구조로 재기동. */
109
267
  static startFromRegistry(domainId: string, instanceId: string, realityMode?: RealityMode): Promise<InstanceRuntime>;
@@ -135,8 +293,8 @@ export declare class TwinEngine {
135
293
  spaceId: string;
136
294
  name: string;
137
295
  instances: number;
138
- nodes: number;
139
- movers: number;
296
+ locations: number;
297
+ equipment: number;
140
298
  }[]>;
141
299
  /** 인스턴스 목적 표식(운영/벤치) 설정 — 벤치 사본을 1급 속성으로 구별(이름 접두사 아님). */
142
300
  static setPurpose(domainId: string, instanceId: string, purpose: 'operational' | 'bench', copyOf?: string): Promise<void>;
@@ -204,18 +362,23 @@ export declare class TwinEngine {
204
362
  * 검증 없이 접근하면 크로스테넌트 읽기/정지가 가능해진다.
205
363
  */
206
364
  static owns(domainId: string, id: string): boolean;
365
+ /**
366
+ * 이 트윈이 이 테넌트 것인가 — **떠 있든 아니든.**
367
+ *
368
+ * `owns()` 는 **떠 있는** 런타임만 안다. 그것을 이력 조회의 관문으로 쓰면, 꺼진 트윈의 저널을
369
+ * 읽으려 할 때 "이 테넌트에 없다" 는 답이 돌아온다 — 두 가지가 틀렸다. 첫째, 저널은 트윈이 꺼져
370
+ * 있을 때 **가장 필요한 것**이다(그게 이력의 존재 이유다). 둘째, 그 문장은 남의 것이라는 뜻이라
371
+ * 사용자가 권한 문제로 오해한다. 실제로는 그냥 안 돌고 있을 뿐이다.
372
+ *
373
+ * 소유는 **등록부**가 안다. 이력·집계처럼 런타임과 무관한 질문은 이쪽에 묻는다.
374
+ */
375
+ static ownsRegistered(domainId: string, instanceId: string): Promise<boolean>;
207
376
  /** 라이브 커널(ForecastTwin) — forecast/divergence 예측 연산용. */
208
377
  static kernel(id: string): any;
209
378
  /** 라이브 처리량 계측 스냅샷(모니터, ④-1) — 내부 누적(_acc*) 제외한 공개 지표. live 아니면 null. */
210
379
  static metrics(id: string): any;
211
380
  /** 전체 라이브 인스턴스 계측(모니터 대시보드용). */
212
381
  static allMetrics(domainId?: string): Promise<any[]>;
213
- /**
214
- * 라이브 예측용 임시 커널(kernel-unification P1) — 라이브는 projector 미러라 커널이 없어 예측을 못 한다.
215
- * 관측 스냅샷(재고·무버·노드) + 저널의 오더 원값·라인을 hydrate 해 예측 가능한 임시 커널을 만든다.
216
- * 라이브 런타임은 무간섭(이 커널은 fork 대상 임시본, instances 에 넣지 않음). sim 인스턴스면 null(그쪽은 kernel() 사용).
217
- */
218
- static buildForecastKernel(domainId: string, instanceId: string): Promise<any | null>;
219
382
  /**
220
383
  * 예측용 커널을 **임의 시각 T 기준**으로 재구성 — 과거-vantage 예측(백테스트)·"그때 서서 본 미래".
221
384
  * recover(untilTime)로 T 시점 상태를, T 이하 오더 관측을 모아 hydrate → monteCarloForecast 가 T 에서 앞으로 굴린다.