@operato/twin-kernel 0.2.3 → 0.3.0

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.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  물류창고·야드·스마트팩토리의 **비즈니스 실행**을 시뮬레이션·모니터링하는 **헤드리스·프레임워크 무관·zero-dep** TS 커널. UI/3D/DOM 없이 순수 Node 에서 도는 것이 목적(sim=능동 생산, live=수동 미러, 계약 동일).
4
4
 
5
5
  - 실행: `node --test test/*.test.ts` (Node 25 네이티브 TS, **무의존**)
6
- - 상태: **38 tests green**, 3 버티컬(WMS/YMS/MES)
6
+ - 상태: **v0.2.3 · 261 tests green**, 3 버티컬(WMS/YMS/MES), ISA-95 4대 자원
7
7
 
8
8
  ## 계층 (아래로만 의존, 무방언)
9
9
 
@@ -12,7 +12,9 @@ contract.ts 3채널 계약(State/Command/Scenario) + 운영 델타 + TwinKe
12
12
  epcis.ts GS1 EPCIS 2.0 이벤트 machinery(타입·빌더·검증기·URI·CBV disp) ← 표준(도메인 무관)
13
13
  flow-engine.ts FlowEngine base — mechanics(RNG·clock·tick·자원배정·태스크진행·emit·snapshot)
14
14
  allocation-policy.ts AllocationPolicy 확장 시임(selectPlacement/selectStock)
15
- state-projector.ts 이벤트 스트림 → State 투영(모니터링 미러) ← 도메인 무관
15
+ observed-reducer.ts 관측 이벤트 → State 접기(**두 구동이 쓰는 유일한 규칙**) ← 도메인 무관
16
+ domain-definition.ts 공정 명세(ISA-95 OperationsSegment: 소요·변동·수율·자원 요구)
17
+ iso-duration.ts xsd:duration 파서(달력 의존 단위는 거절)
16
18
  runtime.ts TwinRuntime — host-facing facade + 구독 프로토콜 ← 도메인/전송 무관
17
19
  face2-adapter.ts 레거시 레코드 → 정규 EPCIS(선언적 매핑 + 검증) ← ACL
18
20
 
@@ -20,7 +22,7 @@ face2-adapter.ts 레거시 레코드 → 정규 EPCIS(선언적 매핑 + 검증)
20
22
  {wms,yms,mes}-kernel.ts 도메인 flow 동사(4 hook) — FlowEngine 확장
21
23
  ```
22
24
 
23
- > **무방언 원칙**: 코어(contract/epcis/flow-engine/policy/projector/runtime)에 특정 도메인 용어를 넣지 않는다. EPCIS 는 표준이라 `epcis.ts`(≠wms). order.kind·task.resourceType 등은 도메인이 소유. 정책 인터페이스는 `selectPlacement`/`selectStock`(≠putaway/pallets).
25
+ > **무방언 원칙**: 코어(contract/epcis/flow-engine/policy/reducer/runtime)에 특정 도메인 용어를 넣지 않는다. EPCIS 는 표준이라 `epcis.ts`(≠wms). order.kind·task.resourceType 등은 도메인이 소유. 정책 인터페이스는 `selectPlacement`/`selectStock`(≠putaway/pallets).
24
26
 
25
27
  ## FlowEngine — 단일 base, 도메인은 4 hook 만
26
28
 
@@ -45,6 +47,31 @@ base 는 task **생명주기**(자원 배정·진행·완료·해제·델타)만
45
47
 
46
48
  두 진화 축: 시간창 스케줄링=흡수, 자원-타입 매칭=최소 확장. 둘 다 backward-compatible(기존 도메인 무영향, 예제 바이트 동일로 검증).
47
49
 
50
+ ## 4대 자원 (ISA-95)
51
+
52
+ 작업이 요구하는 자원을 표준대로 넷으로 나눠 든다. **한 그릇에 담지 않는 이유**: 사람은 고장 나지 않고 교대로 살고, 설비는 고장 나며 OEE 로 평가받고, 팔레트는 돌아온다. 섞으면 한쪽 어휘가 다른 쪽에 붙어 둘 다 거짓이 된다.
53
+
54
+ | ISA-95 | 커널 | 요구 명세 |
55
+ |---|---|---|
56
+ | Personnel | `persons` · `PersonState` | `personnelSpecification` = 등급 + 인원 수 |
57
+ | Equipment | `movers` · `MoverState` | `equipmentSpecification` = 등급 + 대수 |
58
+ | PhysicalAsset | `assets` · `AssetState` (GS1 **GRAI**) | `physicalAssetSpecification` |
59
+ | Material | `items` · `ItemState` (EPCIS) | — |
60
+
61
+ 배정 규율: **부분 확보로 시작하지 않는다.** 한 종류라도 모자라면 기다린다(반쯤 잡고 실패하면 자원이 일 없이 묶인다). 확보(`claim*`)와 확정(`assign*`)이 나뉜 이유. 완료 시 함께 해제하되 **자산은 도착 자리에 남는다**(물건이므로 — 회수의 출발점).
62
+
63
+ > 팔레트를 물품으로 두지 않은 이유: GS1 에서 **SSCC**(물류단위)와 **GRAI**(돌아오는 팔레트 자체)는 다른 것이다. 같은 GRAI 가 오늘은 이 SSCC 를, 내일은 다른 SSCC 를 싣는다. 그래서 **신설하고 연결**한다(`AssetState.carrying` ↔ `ItemState.carriedBy`).
64
+
65
+ ## 공정 명세와 소요시간 세 층
66
+
67
+ `OperationDef` 가 ISA-95 `OperationsSegment` 를 담는다 — `duration`(xsd:duration) · `variability`(퍼짐) · `parameters`(수율·준비시간) · 자원 요구 셋. 소요시간은 **강한 근거부터** 찾는다:
68
+
69
+ **실측 추정기(호스트 주입) > 선언된 명세 > 커널 상수**
70
+
71
+ `specCoverage()` 가 무엇을 썼는지 세어 예측의 **자격**을 낸다 — `relative`(전부 상수: 대안 비교만) · `partial` · `absolute-capable` · `calibrated`. 근거가 없다고 거절하지 않고, **무엇에 근거했는지 함께 말한다.**
72
+
73
+ > 표준이 정하지 않은 것: ISA-95 는 자원 구조는 정하지만 파라미터 ID 어휘는 정하지 않는다. 그래서 `OP_PARAM`(yield·setupDuration) **한 곳**에서 정하고, 발명한 자리임을 숨기지 않는다.
74
+
48
75
  ## 3채널 계약 (Face 1) + 구독
49
76
 
50
77
  - **State**: `getSnapshot()`(스냅샷) + 델타 이벤트 스트림. 델타 = EPCIS(재고/위치/조립/변환) + 운영 델타(`task/equipment/order.status`, EPCIS 로 재구성 불가한 절반).
@@ -55,7 +82,15 @@ base 는 task **생명주기**(자원 배정·진행·완료·해제·델타)만
55
82
  ## State 이원 모델
56
83
  - **EPCIS 저널**(이산): 재고·위치·조립·변환. `epcis.ts` 로 정규 방출, `validateEpcisEvent` 검증.
57
84
  - **운영·키네마틱**(연속): 무버 motion(from/to/progress)·태스크·오더 진척. 운영 델타로 미러.
58
- - **StateProjector** 가 둘을 접어 재구성 → sim 이벤트든 live 이벤트든 같은 State(데이터원 스왑).
85
+ - **`ObservedReducer`** 가 둘을 접어 재구성 → sim 이벤트든 live 이벤트든 같은 State(데이터원 스왑). `StateProjector` 는 같은 것의 옛 이름(별칭 유지).
86
+
87
+ ### 구동은 둘, 규칙은 하나 — 적합성 하네스
88
+
89
+ 상태를 만드는 구동이 둘이다(시뮬 `tick()` / 관측 `apply()`). 각자 계약으로 옮기는 코드가 두 벌이면 한쪽만 고쳤을 때 **조용히 갈라진다** — 계약 필드가 거의 다 선택이라 "안 채우는 것이 합법"이고, 컴파일러가 잡아 주지 않는다.
90
+
91
+ > **불변식: 커널 상태의 모든 사실은 이벤트로 나가야 한다.**
92
+
93
+ 나가지 않는 사실은 미러가 모르고, 저널로 복원되지도 않고(시간여행), 미러에서 세운 예측 씨앗에도 실리지 않는다. `test/driver-conformance.test.ts` 가 시뮬을 굴려 **그 이벤트를 그대로 미러에 흘리고** 두 스냅샷을 대조한다(WMS·YMS·MES). 정당한 예외는 주석이 아니라 **상수로** 들고 이유를 적는다(호스트가 적분하는 `oee`, 매 tick 가지 않는 보간값 — 대신 **앵커는 반드시 같다**).
59
94
 
60
95
  ## 트윈 본연 — 현재로부터 예측 + 정합 (mirror+sim 과 구별짓는 기능)
61
96
  관측·예측·행위가 따로 있는 건 mirror+simulator. 트윈의 본질은 그 **커플링**:
@@ -77,4 +112,10 @@ base 는 task **생명주기**(자원 배정·진행·완료·해제·델타)만
77
112
  - **host 결합**: `TwinRuntime` 를 GraphQL sub·서비스로 래핑(전송은 얇은 host 계층).
78
113
 
79
114
  ## 미구현(범위 밖)
80
- host 결합(things-factory 전송/퍼시스턴스/커넥터) · 보드 바인딩(컴포넌트↔SGLN) · 도메인 깊이(WMS 멀티라인, YMS 상하차 AggregationEvent, MES BOM/수율). 설계 SoT: `operato-twin/design/`.
115
+ host 결합(things-factory 전송/퍼시스턴스/커넥터) · 보드 바인딩(컴포넌트↔SGLN) · 도메인 깊이(WMS 멀티라인, YMS 상하차 AggregationEvent, MES BOM).
116
+
117
+ **모델의 남은 공백**(정직하게): 인원 자격·숙련도(등급만 있고 자격 매칭·숙련도별 소요 차이 없음) · 자산 회수 작업(빈 팔레트가 도착 자리에 남을 뿐) · `movers`/`nodes` 계층 겸직(이동설비↔워크센터, 위치↔워크센터) · next-event 도약(지금은 고정 간격 tick).
118
+
119
+ **1.0 게이트**: `movers`/`nodes` 계층을 정리하거나 가산적으로만 가능하도록 확정한 뒤. 계약 파괴가 예정된 채로 1.0 을 주면 다음 라운드가 곧 2.0 이 된다.
120
+
121
+ 설계 SoT: `operato-twin/design/plans/` — `simulation-spec.md`(명세·추정기·예측 자격) · `four-resources-and-conformance.md`(4대 자원·불변식) · `kernel-unification-live-observe.md`(구동 통합).
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * things-scene 정합: 이건 "의도(intent)" 계층(관측). 씬 기제 믹스(Capacity/Transferable/CarrierLine …)가
11
11
  * 이 의도를 *실현*한다 — 공유 계약은 관측(계층 B)뿐, 기제 methods(계층 A)는 공유 안 함.
12
- * ⚠ Mobile ≠ Transferable: Mobile=자원 자신이 이동, Transferable=노드가 아이템 이동(씬 기제).
12
+ * ⚠ Mobile ≠ Transferable: Mobile=자원 자신이 이동, Transferable=자리가 아이템 이동(씬 기제).
13
13
  */
14
14
  export const CAPABILITIES = {
15
15
  operable: {
@@ -21,7 +21,7 @@ export const CAPABILITIES = {
21
21
  stateFields: ['occupancy', 'capacity'], invariants: ['0 <= occupancy <= capacity (capacity>0)'], results: ['occupancyChanged']
22
22
  },
23
23
  mobile: {
24
- key: 'mobile', label: '이동', semantics: '자원 자신이 노드 간 이동. Transferable(아이템 이동)과 다름. (씬 기제: CarrierLine)',
24
+ key: 'mobile', label: '이동', semantics: '자원 자신이 자리 간 이동. Transferable(아이템 이동)과 다름. (씬 기제: CarrierLine)',
25
25
  stateFields: ['location', 'motion'], models: ['Motion'], results: ['moved', 'motionTick']
26
26
  },
27
27
  processable: {