@operato/twin-kernel 0.6.13 → 0.7.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.
@@ -135,7 +135,7 @@ export interface FlowTask {
135
135
  * **실제로 들어가고 나온 자재** — ISA-95 `JobResponse.MaterialActual`.
136
136
  *
137
137
  * 명세(계획)가 아니라 **일어난 일**이다. 투입 인원·설비가 이미 작업 델타에 타고 있으니 자재도 같은
138
- * 채널에 실어야 실적을 **한 곳에서** 읽는다(EPCIS 이벤트에서 되짚으려면 작업과 잇는 끈이 없다).
138
+ * 채널에 실어야 실적을 **한 곳에서** 읽는다(EPCIS 이벤트에서 다시 계산하려면 작업과 잇는 끈이 없다).
139
139
  */
140
140
  materialActual?: {
141
141
  definitionId: string;
@@ -340,7 +340,7 @@ export declare abstract class FlowEngine implements TwinKernel {
340
340
  */
341
341
  observe(): void;
342
342
  /**
343
- * 돌면서 공장을 갈아탄다 — **현실이 안 멈추므로 미러도 멈출 수 없다.**
343
+ * 돌면서 공장을 전환한다 — **현실이 안 멈추므로 미러도 멈출 수 없다.**
344
344
  *
345
345
  * ── 왜 관측 구동에만 여는가 ────────────────────────────────────────────────
346
346
  * 미러는 이미 있는 현실을 따라갈 뿐이라, 설비가 한 대 늘었다고 멈췄다 서는 것은 그 사이의
@@ -353,7 +353,7 @@ export declare abstract class FlowEngine implements TwinKernel {
353
353
  * 자원은 버리되 **몇 개를 버렸는지 돌려준다.** 여기서는 그 판단을 커널 자신의 선언 표에도
354
354
  * 그대로 옮긴다. 관측층만 갈면 사라진 설비가 커널 표에 남아 **화면이 없는 설비를 그린다.**
355
355
  *
356
- * 관측으로 알게 된 자리는 선언에 없어도 지운 뒤 다시 심는다 — 다음 스냅샷에서 관측층이
356
+ * 관측으로 알게 된 자리는 선언에 없어도 지운 뒤 다시 주입한다 — 다음 스냅샷에서 관측층이
357
357
  * 되살리므로(`settleObserved` → `hydrateObserved`), 여기서 지켜야 할 것은 **선언분**뿐이다.
358
358
  */
359
359
  adoptStructure(def: TwinModelDef): StructureShift;
@@ -432,7 +432,7 @@ export declare abstract class FlowEngine implements TwinKernel {
432
432
  protected computeAttentions(): Attention[];
433
433
  /**
434
434
  * fork — 현재 상태를 정확히 복제한 새 엔진 (디지털트윈 본연: "현재로부터 예측").
435
- * 원본(live/sim)은 계속 진행, fork 는 what-if 를 앞으로 굴려 forecast·발산(predicted vs actual) 검사에 쓴다.
435
+ * 원본(live/sim)은 계속 진행, fork 는 what-if 를 앞으로 시뮬레이션해 forecast·발산(predicted vs actual) 검사에 쓴다.
436
436
  * fork 는 자기 구독자·시나리오를 갖고 원본과 격리(handlers·gens 비움, generating=false).
437
437
  * rng 는 fork 의 시나리오 load 시 재시드(드레인 예측은 생성 없어 rng 무관·결정적).
438
438
  */
@@ -441,7 +441,7 @@ export declare abstract class FlowEngine implements TwinKernel {
441
441
  * 이 커널의 **지금**(ms) — 시각으로 바뀌는 모든 판정의 단일 기준.
442
442
  *
443
443
  * ── 관측 모드에서 시계가 멈춰 있었다 ────────────────────────────────────
444
- * `apply()` 는 `clockMs` 를 밀지 않는다(관측은 시간을 굴리지 않는다). 그래서 라이브 트윈의 "지금" 이
444
+ * `apply()` 는 `clockMs` 를 밀지 않는다(관측은 시간을 진행시키지 않는다). 그래서 라이브 트윈의 "지금" 이
445
445
  * **BASE_EPOCH(2026-01-01)에 얼어 있었다.** 실 이벤트는 실제 시각을 달고 오므로 결과가 이렇게 된다:
446
446
  * · **납기 초과를 영원히 보고하지 않는다** — 실 납기가 항상 미래로 보인다
447
447
  * · 폐기·도입예정 판정이 틀린다(§EffectivePeriod)
@@ -472,14 +472,14 @@ export declare abstract class FlowEngine implements TwinKernel {
472
472
  };
473
473
  protected randInt(min: number, max: number): number;
474
474
  /**
475
- * 관측 구동(P0 스파이크) — **이벤트로 커널을 굴린다.**
475
+ * 관측 구동(P0 스파이크) — **이벤트로 커널을 실행한다.**
476
476
  *
477
477
  * 상태를 만드는 구동이 둘인데(시뮬 `tick` / 미러 `apply`) 지금은 **모델도 둘**이라 한쪽만 고치면
478
- * 갈라진다(2026-08-01 하루에 아홉 곳). 근본 해법은 **한 상태 모델 두 구동**이고, 이것은 그 실현
478
+ * 어긋난다(2026-08-01 하루에 아홉 곳). 근본 해법은 **한 상태 모델 두 구동**이고, 이것은 그 실현
479
479
  * 가능성을 재는 스파이크다(design/plans/kernel-unification-live-observe.md P0).
480
480
  *
481
481
  * 여기서는 **이미 검증된 조각을 조립**한다: 투영기가 이벤트를 접고, 그 결과를 씨앗 경로
482
- * (`hydrateObserved`)로 커널 상태에 심는다. 그래서 관측으로 굴린 커널을 그대로 `fork`·`tick` 할 수
482
+ * (`hydrateObserved`)로 커널 상태에 주입한다. 그래서 관측으로 실행한 커널을 그대로 `fork`·`tick` 할 수
483
483
  * 있다 — "미러에서 예측한다" 가 별도 배관 없이 성립하는지가 이 스파이크의 질문이다.
484
484
  *
485
485
  * **비용은 정직하게**: 이벤트마다 전체를 다시 심으므로 O(상태 크기)다. P1 에서 반영 로직을 순수
@@ -512,14 +512,14 @@ export declare abstract class FlowEngine implements TwinKernel {
512
512
  */
513
513
  protected routeKeys(): string[] | undefined;
514
514
  /**
515
- * **이 공장이 하루 몇 대를 낼 수 있는가** — 굴려 보지 않고 답한다.
515
+ * **이 공장이 하루 몇 대를 낼 수 있는가** — 실행해 보지 않고 답한다.
516
516
  *
517
517
  * 커널이 직접 답하는 이유: 필요한 사실이 전부 여기 있다(공정 명세·설비와 신뢰도·인원·물리자산·
518
518
  * 자리·근무 달력·시각 기준). 밖에서 모으면 그 값을 옮겨 적게 되고, 한쪽만 바뀌는 순간 "충분하다"
519
519
  * 가 조용히 거짓이 된다. 계산 자체는 순수 함수(`analyzeCapacity`)에 맡긴다.
520
520
  *
521
521
  * 달력은 **자원이 선언한 것**을 쓴다. 자원마다 다른 달력을 쓰는 현장이면 대표를 고를 수 없으므로
522
- * 그 사실을 결과에 실어 보낸다(`mixedCalendars`) — 조용히 하나를 골라 계산하면 천장이 틀린 채로
522
+ * 그 사실을 결과에 실어 보낸다(`mixedCalendars`) — 조용히 하나를 골라 계산하면 상한이 틀린 채로
523
523
  * 그럴듯해 보인다.
524
524
  */
525
525
  capacity(opts: {
@@ -562,7 +562,7 @@ export declare abstract class FlowEngine implements TwinKernel {
562
562
  /**
563
563
  * task 소요 산출 — 우선순위: **① 추정기(이력 보정) → ② 명세(ISA-95 Duration + 변동) → ③ 도메인 상수.**
564
564
  *
565
- * 이 순서인 이유: 실측에서 배운 값이 선언값을 이기고, 선언값이 우리가 코드에 박아 상수를 이긴다.
565
+ * 이 순서인 이유: 실측에서 배운 값이 선언값을 이기고, 선언값이 우리가 코드에 고정한 상수를 이긴다.
566
566
  * 셋 중 무엇을 썼는지는 `specCoverage()` 로 드러낸다 — 상수를 쓴 것이 조용히 넘어가지 않게.
567
567
  * "얼마"만 소비하고 "경로"는 씬이 소유한다(좌표-free 유지).
568
568
  */
@@ -585,7 +585,7 @@ export declare abstract class FlowEngine implements TwinKernel {
585
585
  private noteSpecUse;
586
586
  private noteParamUse;
587
587
  /**
588
- * 시뮬 명세 자기보고 — **어디까지 데이터로 말했고 어디부터 우리가 박아 상수인가.**
588
+ * 시뮬 명세 자기보고 — **어디까지 데이터로 말했고 어디부터 우리가 코드에 고정한 상수인가.**
589
589
  *
590
590
  * 시뮬레이션 결과를 받는 쪽이 이걸 봐야 한다: 소요시간이 전부 기본값이면 그 예측으로 말할 수 있는 것은
591
591
  * "같은 조건에서의 상대 비교" 뿐이고 "몇 시에 끝난다" 는 근거가 없다. 그 구분을 숫자로 드러낸다.
@@ -653,7 +653,7 @@ export declare abstract class FlowEngine implements TwinKernel {
653
653
  */
654
654
  protected reserve(epcs: string[], bizStep: string): void;
655
655
  /**
656
- * 처분 변화 관측 — **상태와 이벤트를 한 번에.** 둘을 따로 쓰면 반드시 갈라진다.
656
+ * 처분 변화 관측 — **상태와 이벤트를 한 번에.** 둘을 따로 쓰면 반드시 어긋난다.
657
657
  *
658
658
  * 실제로 양쪽으로 갈라져 있었다: 할당은 상태만 바꾸고 이벤트를 안 냈고(미러가 모름), 야드 도크
659
659
  * 도착은 이벤트만 내고 상태를 안 바꿨다(이벤트와 상태가 다른 말). 적합성 하네스가 둘 다 잡았다.
@@ -758,7 +758,7 @@ export declare abstract class FlowEngine implements TwinKernel {
758
758
  * 지금은 자리를 따지지 않는다 — 따지려면 자산 이송 작업이 먼저 있어야 한다).
759
759
  */
760
760
  private claimAssets;
761
- /** 확보한 자산을 작업에 묶는다 — 싣는 물류단위(SSCC)가 있으면 연결한다(GRAI ↔ SSCC). */
761
+ /** 확보한 자산을 작업에 배정한다 — 싣는 물류단위(SSCC)가 있으면 연결한다(GRAI ↔ SSCC). */
762
762
  private assignAssets;
763
763
  /**
764
764
  * 작업이 끝나면 자산을 놓아 준다 — **사람과 다른 점: 자산은 도착 자리에 남는다**(물건이므로).
@@ -805,7 +805,7 @@ export declare abstract class FlowEngine implements TwinKernel {
805
805
  * 이 작업이 **딛고 선 것**이 아직 있나 — 없으면 무엇이 없는지 답한다.
806
806
  *
807
807
  * 작업은 혼자 서지 못한다: 옮길 **물품**과, (있다면) 그것을 시킨 **오더** 위에 선다. 진행 중에
808
- * 둘 중 하나가 사라질 수 있다 — 물품은 포장·출하·소비로, 오더는 이미 이행돼 씨앗이 심지 않아서.
808
+ * 둘 중 하나가 사라질 수 있다 — 물품은 포장·출하·소비로, 오더는 이미 이행돼 씨앗이 주입하지 않아서.
809
809
  *
810
810
  * 그때 도메인 훅은 없는 것을 딛으려다 던진다(`order.gtin` · `item.location`). 그 예외 하나가
811
811
  * **예측 전체를 죽였다** — 사용자에게는 기능이 통째로 사라진 것으로 보였다. 그래서 완료 **전에**
@@ -859,7 +859,7 @@ export declare abstract class FlowEngine implements TwinKernel {
859
859
  * 자원 하나의 **가용 능력** — 계약의 판정에 이 커널의 시각·시간대·필수 시험을 채워 넘긴다.
860
860
  *
861
861
  * 배정도 스냅샷도 이 자리를 지난다. 예전에는 배정이 조건을 늘어놓고 화면이 플래그를 보고 짐작해서,
862
- * **자격이 만료된 사람이 화면에서는 `대기` 로 보였다**(놀고 있는 것은 맞지만 쓸 수 있는 것은 아니다).
862
+ * **자격이 만료된 사람이 화면에서는 `대기` 로 보였다**(대기 중인 것은 맞지만 쓸 수 있는 것은 아니다).
863
863
  * 판정에 필요한 둘(시각·등급 상속)을 아는 것은 커널뿐이므로, 답도 커널이 낸다.
864
864
  */
865
865
  private capabilityOfResource;
@@ -870,7 +870,7 @@ export declare abstract class FlowEngine implements TwinKernel {
870
870
  * 여기서는 고르기만 한다 — 확정은 호출부가 다른 자원까지 확보한 뒤에 한다.
871
871
  */
872
872
  private claimEquipment;
873
- /** 확보한 사람을 작업에 묶는다(설비까지 확정된 뒤). */
873
+ /** 확보한 사람을 작업에 배정한다(설비까지 확정된 뒤). */
874
874
  private assignCrew;
875
875
  /** 작업이 끝나면 사람을 놓아 준다 — 설비 해제와 별개 경로. */
876
876
  private releaseCrew;
@@ -897,7 +897,7 @@ export declare abstract class FlowEngine implements TwinKernel {
897
897
  * 유효 기간 밖인가 — **네 번째 이유**(§Effectivity). 설비·사람·자산이 같은 규칙을 쓴다.
898
898
  *
899
899
  * 시뮬 시각을 ISO 로 풀어 계약의 `effectivityAt` 에 넘긴다. **판정은 커널에 두지 않는다** —
900
- * 호스트(라이브 관측)도 같은 판정을 해야 하고, 규칙이 두 벌이면 갈라진다.
900
+ * 호스트(라이브 관측)도 같은 판정을 해야 하고, 규칙이 두 벌이면 어긋난다.
901
901
  */
902
902
  protected effectivityOf(r: EffectivePeriod): Effectivity | undefined;
903
903
  /**
@@ -912,7 +912,7 @@ export declare abstract class FlowEngine implements TwinKernel {
912
912
  /**
913
913
  * 스냅샷·델타에 실을 조각 — **선언한 기간과 판정을 함께** 낸다.
914
914
  *
915
- * 판정만 내면 화면이 "왜" 를 말할 수 없고(언제 폐기됐나), 기간만 내면 소비처마다 다시 판정해 갈라진다.
915
+ * 판정만 내면 화면이 "왜" 를 말할 수 없고(언제 폐기됐나), 기간만 내면 소비처마다 다시 판정해 어긋난다.
916
916
  * 선언이 없으면 아무것도 붙이지 않는다(대부분의 자원이 그렇다 — 필드를 늘리지 않는다).
917
917
  */
918
918
  protected effectivePart(r: EffectivePeriod): Partial<EffectivePeriod> & {
@@ -922,7 +922,7 @@ export declare abstract class FlowEngine implements TwinKernel {
922
922
  * 지금 근무 시간 밖인가 — **캘린더가 있으면 그것으로, 없으면 옛 `window` 로** 판정한다.
923
923
  *
924
924
  * `window`(시 단위 하나)는 캘린더의 특수한 경우다. 둘을 한 함수로 모아 두면 인원·설비가 같은 규칙을
925
- * 쓴다(예전에는 같은 식이 두 곳에 복사돼 있었다 — 한쪽만 고치면 조용히 갈라진다).
925
+ * 쓴다(예전에는 같은 식이 두 곳에 복사돼 있었다 — 한쪽만 고치면 조용히 어긋난다).
926
926
  */
927
927
  protected offCalendar(r: {
928
928
  window?: {
@@ -88,7 +88,7 @@ nowIso) {
88
88
  *
89
89
  * 재는 방법은 스냅샷만으로 답할 수 있어야 한다(추세를 쌓아 두지 않는다). 그래서 같은 공정에서
90
90
  * **기다리는 일과 하고 있는 일의 비**를 본다 — 처리 능력이 충분하면 대기가 길게 늘지 않는다.
91
- * 공정이 아예 돌면(하는 일 0, 기다리는 일 여럿) 그것이 가장 강한 신호다.
91
+ * 공정이 전혀 진행되지 않으면(하는 일 0, 기다리는 일 여럿) 그것이 가장 강한 신호다.
92
92
  *
93
93
  * 공정 이름은 작업이 스스로 말하는 것(`kind`)을 쓴다. 설비 종류로 환산하지 않는다 — 그 대응은
94
94
  * 현장마다 다르고, 커널이 그 방언을 알면 보편 계약이 깨진다.
@@ -100,7 +100,7 @@ nowIso) {
100
100
  * 막혔을 때의 경고까지 같이 무시하게 된다.
101
101
  *
102
102
  * 쉬는 중인지는 **설비가 이미 말하고 있다**(`offShift`, 달력에서 파생). 여기서 달력을 다시 읽지
103
- * 않는다 — 두 벌이 되면 갈라진다. 설비를 아예 모르면 판단하지 않는다(줄만 보고 단정하지 않는다).
103
+ * 않는다 — 두 벌이 되면 어긋난다. 설비를 아예 모르면 판단하지 않는다(줄만 보고 단정하지 않는다).
104
104
  */
105
105
  const shutdown = (view.equipment?.length ?? 0) > 0 && view.equipment.every(m => m.offShift);
106
106
  if (view.tasks?.length && !shutdown) {
@@ -157,7 +157,7 @@ nowIso) {
157
157
  }
158
158
  /* 납기 초과 — **표준 `EndTime` 이 있어야 판정할 수 있다.** 없으면 신호를 만들지 않는다(없는 납기를
159
159
  지연으로도 정시로도 말하지 않는다). 이미 끝난 오더는 대상이 아니다.
160
- 심각도는 얼마나 늦었는지로 가른다 — 방금 넘긴 것과 하루 넘긴 것을 같게 부르면 신호가 무의미해진다. */
160
+ 심각도는 얼마나 늦었는지로 구분한다 — 방금 넘긴 것과 하루 넘긴 것을 같게 부르면 신호가 무의미해진다. */
161
161
  for (const o of view.orders) {
162
162
  if (o.status === 'completed' || o.status === 'fulfilled')
163
163
  continue;
@@ -288,7 +288,7 @@ export class FlowEngine {
288
288
  /*
289
289
  * ── 선언 하나를 상태 하나로 짓는 자리 ─────────────────────────────────────
290
290
  * 최초 적재(`loadTwinModel`)와 가동 중 교체(`adoptStructure`)가 **같은 구성을 쓴다.** 두 곳에서
291
- * 따로 지으면 곧 갈라지고, 갈라진 쪽으로 들어온 자원만 필드 하나가 비는 식으로 조용히 다르다.
291
+ * 따로 지으면 곧 갈라지고, 어긋난 쪽으로 들어온 자원만 필드 하나가 비는 식으로 조용히 다르다.
292
292
  */
293
293
  buildLocation(n) {
294
294
  return { id: n.id, type: n.type, capacity: n.capacity, parallelism: n.parallelism, occupancy: 0, status: 'idle', parentId: n.parentId };
@@ -312,7 +312,7 @@ export class FlowEngine {
312
312
  this.observeMode = true;
313
313
  }
314
314
  /**
315
- * 돌면서 공장을 갈아탄다 — **현실이 안 멈추므로 미러도 멈출 수 없다.**
315
+ * 돌면서 공장을 전환한다 — **현실이 안 멈추므로 미러도 멈출 수 없다.**
316
316
  *
317
317
  * ── 왜 관측 구동에만 여는가 ────────────────────────────────────────────────
318
318
  * 미러는 이미 있는 현실을 따라갈 뿐이라, 설비가 한 대 늘었다고 멈췄다 서는 것은 그 사이의
@@ -325,7 +325,7 @@ export class FlowEngine {
325
325
  * 자원은 버리되 **몇 개를 버렸는지 돌려준다.** 여기서는 그 판단을 커널 자신의 선언 표에도
326
326
  * 그대로 옮긴다. 관측층만 갈면 사라진 설비가 커널 표에 남아 **화면이 없는 설비를 그린다.**
327
327
  *
328
- * 관측으로 알게 된 자리는 선언에 없어도 지운 뒤 다시 심는다 — 다음 스냅샷에서 관측층이
328
+ * 관측으로 알게 된 자리는 선언에 없어도 지운 뒤 다시 주입한다 — 다음 스냅샷에서 관측층이
329
329
  * 되살리므로(`settleObserved` → `hydrateObserved`), 여기서 지켜야 할 것은 **선언분**뿐이다.
330
330
  */
331
331
  adoptStructure(def) {
@@ -337,7 +337,7 @@ export class FlowEngine {
337
337
  this.classDefs = { personnel: def.personnelClasses, equipment: def.equipmentClasses, asset: def.assetClasses, material: def.materialClasses };
338
338
  this.materialDefs = new Map((def.materialDefinitions ?? []).filter(d => d?.id).map(d => [d.id, d]));
339
339
  /* **살아남은 것은 그대로 둔다** — 다시 지으면 가동시간·양품수가 0 으로 돌아간다(OEE 가 리셋된다).
340
- 선언이 바뀐 트윈에서 성과 지표가 조용히 끊기는 것이 이 갈아타기의 가장 비싼 오류다. */
340
+ 선언이 바뀐 트윈에서 성과 지표가 조용히 끊기는 것이 이 구조 전환의 가장 비싼 오류다. */
341
341
  const sync = (map, declared, build) => {
342
342
  const ids = new Set(declared.map(d => d.id));
343
343
  let dropped = 0;
@@ -429,22 +429,22 @@ export class FlowEngine {
429
429
  this.revision = from;
430
430
  }
431
431
  hydrateObserved(snap, orders = []) {
432
- /* 확인 처리를 먼저 이어받는다 — 아래에서 상태를 심으면 곧바로 주목 신호가 계산되므로, 늦게
432
+ /* 확인 처리를 먼저 이어받는다 — 아래에서 상태를 주입하면 곧바로 주목 신호가 계산되므로, 늦게
433
433
  * 이어받으면 그 한 번은 확인 안 된 것으로 계산된다(화면이 잠깐 빨개진다). */
434
434
  for (const id of snap.acked ?? [])
435
435
  this._acked.add(id);
436
436
  /* **관측된 것을 버리지 않는다.** 예전에는 자리·설비 상태를 'idle' 로, OEE 누적을 0 으로 덮고
437
437
  * 물품의 로트·단위·소속·마스터데이터를 떨어뜨렸다. 씨앗이 잃은 것은 **예측도 모른다** —
438
- * 고장 난 설비를 정상으로, 진행 중인 일을 없는 것으로 놓고 미래를 굴리면 답이 낙관 쪽으로 치우친다. */
438
+ * 고장 난 설비를 정상으로, 진행 중인 일을 없는 것으로 놓고 미래를 실행하면 답이 낙관 쪽으로 치우친다. */
439
439
  for (const n of snap.locations) {
440
440
  this.locations.set(n.id, { id: n.id, type: n.type, capacity: n.capacity ?? 0, parallelism: n.parallelism, occupancy: n.occupancy ?? 0, status: n.status ?? 'idle', parentId: n.parentId });
441
441
  }
442
442
  this.items.clear();
443
443
  for (const it of snap.items) {
444
- /* **부분마다 한 줄로 심는다** — `epc` 로 키를 잡으면 같은 로트의 두 부분이 하나로 접혀
444
+ /* **부분마다 한 줄로 주입한다** — `epc` 로 키를 잡으면 같은 로트의 두 부분이 하나로 접혀
445
445
  씨앗에서 재고가 줄어든다(§MaterialSubLot 에서 겪은 것과 같은 오류의 세 번째 자리). */
446
446
  this.items.set(itemKeyOf(it), {
447
- /* 품번 키·로트는 식별자에서 파생되므로 심지 않는다(스냅샷이 다시 낸다 — 두 벌을 두면 어긋난다). */
447
+ /* 품번 키·로트는 식별자에서 파생되므로 주입하지 않는다(스냅샷이 다시 낸다 — 두 벌을 두면 어긋난다). */
448
448
  epc: it.epc, location: it.location, disposition: it.disposition ?? DISP.sellable,
449
449
  ...(it.subLotId ? { subLotId: it.subLotId } : {}),
450
450
  ...(it.definitionId ? { definitionId: it.definitionId } : {}),
@@ -480,7 +480,7 @@ export class FlowEngine {
480
480
  * **잰 값을 이어받으면 잰 구간도 이어받아야 한다.**
481
481
  *
482
482
  * 카운터만 되살리고 창을 지금으로 두면 둘이 어긋난다: 이어받은 가동 8시간이 방금 시작한 창
483
- * 안에 앉아, 놀고 있는 설비가 **가동률 100%·유휴 0초**로 보인다(화면에서 관측됨).
483
+ * 안에 앉아, 대기 중인 설비가 **가동률 100%·유휴 0초**로 보인다(화면에서 관측됨).
484
484
  *
485
485
  * 창은 지표 자신이 말해 준다 — 계획 시간은 가동·준비·고장·유휴의 합이다. 그래서 그 합만큼
486
486
  * 뒤로 물려 창을 다시 세운다. 지표가 없으면 창도 없다(그때는 지금부터 새로 잰다).
@@ -488,7 +488,7 @@ export class FlowEngine {
488
488
  ...(oee
489
489
  ? { metricsSinceMs: this.clockMs - ((oee.runMs ?? 0) + (oee.setupMs ?? 0) + (oee.downMs ?? 0) + (oee.idleMs ?? 0)) }
490
490
  : {}),
491
- /* 계획 정지를 이어받는다 — 잃으면 씨앗이 **정비 중인 설비를 가용으로 놓고** 미래를 굴린다
491
+ /* 계획 정지를 이어받는다 — 잃으면 씨앗이 **정비 중인 설비를 가용으로 놓고** 미래를 시뮬레이션한다
492
492
  (예측이 낙관 쪽으로 치우친다). 씨앗 왕복 대조가 이것을 잡았다. */
493
493
  ...(m.held ? { held: true } : {}),
494
494
  /* 관측 스냅샷이 들고 있는 것은 관측을 따른다(미러가 보드에서 읽어 실어 온다). */
@@ -530,7 +530,7 @@ export class FlowEngine {
530
530
  }
531
531
  /* 진행 중이던 작업을 이어 붙인다 — 없으면 예측이 "일이 하나도 없는 현장" 에서 출발한다.
532
532
  * 남은 시간을 모르면 **진척을 꾸미지 않고** 미착수(created)로 되돌린다: 그 일이 남아 있다는 사실은
533
- * 지키면서, 얼마나 진행됐는지는 모른다고 말하는 쪽이 정직하다(커널이 다시 배정해 굴린다). */
533
+ * 지키면서, 얼마나 진행됐는지는 모른다고 말하는 쪽이 정직하다(커널이 다시 배정해 실행한다). */
534
534
  /*
535
535
  * **주체가 없는 작업은 되살리지 않는다.**
536
536
  *
@@ -597,8 +597,8 @@ export class FlowEngine {
597
597
  orphaned++;
598
598
  continue;
599
599
  }
600
- /* 오더도 같다 — 이미 이행된 오더는 심지 않으므로(위 `remaining <= 0`), 그 오더에 딸린 작업만
601
- 남으면 완료 시점에 없는 오더를 딛는다. 심는 단계에서 함께 뺀다. */
600
+ /* 오더도 같다 — 이미 이행된 오더는 주입하지 않으므로(위 `remaining <= 0`), 그 오더에 딸린 작업만
601
+ 남으면 완료 시점에 없는 오더를 딛는다. 주입 단계에서 함께 뺀다. */
602
602
  if (t.orderId && !seededOrderIds.has(t.orderId)) {
603
603
  orphaned++;
604
604
  continue;
@@ -626,7 +626,7 @@ export class FlowEngine {
626
626
  mv.taskId = t.id;
627
627
  }
628
628
  }
629
- /* 사람도 같다 — 진행 중이던 작업에 투입돼 있던 사람은 여전히 묶여 있어야 한다. */
629
+ /* 사람도 같다 — 진행 중이던 작업에 투입돼 있던 사람은 여전히 점유되어 있어야 한다. */
630
630
  if (known && t.status === 'in-progress') {
631
631
  const restored = this.tasks.get(t.id);
632
632
  if (restored)
@@ -716,7 +716,7 @@ export class FlowEngine {
716
716
  this._acked.add(id);
717
717
  /*
718
718
  * **사실로 남긴다.** 확인 처리는 사람이 한 행위라 상태에서 다시 계산될 수 없다. 저널에
719
- * 남기지 않으면 재기동하면 확인해 둔 신호가 다시 빨개지고, 과거를 되짚어도 그때 무엇을
719
+ * 남기지 않으면 재기동하면 확인해 둔 신호가 다시 빨개지고, 과거를 다시 계산해도 그때 무엇을
720
720
  * 확인했는지 알 수 없다. 다른 커맨드(보류·재개)가 델타를 내는 것과 같은 길이다.
721
721
  */
722
722
  this.emitOp(OP_EVENT.attentionAck, { id, at: this.now() });
@@ -837,7 +837,7 @@ export class FlowEngine {
837
837
  * 시뮬이 아무 표시도 안 하면 소비처가 두 스냅샷을 같은 규칙으로 읽지 못한다. */
838
838
  locations: [...this.locations.values()].map(n => {
839
839
  const { status, ...rest } = n;
840
- /* 상태는 저장값이 아니라 포화도 파생 — 미러와 **같은 함수**를 쓴다(규칙이 둘이면 갈라진다). */
840
+ /* 상태는 저장값이 아니라 포화도 파생 — 미러와 **같은 함수**를 쓴다(규칙이 둘이면 어긋난다). */
841
841
  const derived = locationStatusOf(n);
842
842
  return { ...rest, ...(derived ? { status: derived } : {}), origin: 'master' };
843
843
  }),
@@ -887,7 +887,7 @@ export class FlowEngine {
887
887
  return st;
888
888
  }),
889
889
  /* 스냅샷이 **델타보다 가난하면 안 된다** — 예전에는 소요·남은 시간을 빼고 내보내서, 이 스냅샷으로
890
- * 다른 커널을 심으면(hydrateObserved) 진행 중이던 작업을 이어 굴릴 수 없었다(미러 스냅샷은
890
+ * 다른 커널을 주입하면(hydrateObserved) 진행 중이던 작업을 이어 굴릴 수 없었다(미러 스냅샷은
891
891
  * 델타에서 왔으므로 갖고 있었다 — 같은 계약을 두 구동이 다르게 채우던 자리). */
892
892
  tasks: [...this.tasks.values()].map(t => ({
893
893
  id: t.id, kind: t.kind, status: t.status, itemRefs: [t.itemEpc],
@@ -971,7 +971,7 @@ export class FlowEngine {
971
971
  }
972
972
  /**
973
973
  * fork — 현재 상태를 정확히 복제한 새 엔진 (디지털트윈 본연: "현재로부터 예측").
974
- * 원본(live/sim)은 계속 진행, fork 는 what-if 를 앞으로 굴려 forecast·발산(predicted vs actual) 검사에 쓴다.
974
+ * 원본(live/sim)은 계속 진행, fork 는 what-if 를 앞으로 시뮬레이션해 forecast·발산(predicted vs actual) 검사에 쓴다.
975
975
  * fork 는 자기 구독자·시나리오를 갖고 원본과 격리(handlers·gens 비움, generating=false).
976
976
  * rng 는 fork 의 시나리오 load 시 재시드(드레인 예측은 생성 없어 rng 무관·결정적).
977
977
  */
@@ -1004,7 +1004,7 @@ export class FlowEngine {
1004
1004
  * 이 커널의 **지금**(ms) — 시각으로 바뀌는 모든 판정의 단일 기준.
1005
1005
  *
1006
1006
  * ── 관측 모드에서 시계가 멈춰 있었다 ────────────────────────────────────
1007
- * `apply()` 는 `clockMs` 를 밀지 않는다(관측은 시간을 굴리지 않는다). 그래서 라이브 트윈의 "지금" 이
1007
+ * `apply()` 는 `clockMs` 를 밀지 않는다(관측은 시간을 진행시키지 않는다). 그래서 라이브 트윈의 "지금" 이
1008
1008
  * **BASE_EPOCH(2026-01-01)에 얼어 있었다.** 실 이벤트는 실제 시각을 달고 오므로 결과가 이렇게 된다:
1009
1009
  * · **납기 초과를 영원히 보고하지 않는다** — 실 납기가 항상 미래로 보인다
1010
1010
  * · 폐기·도입예정 판정이 틀린다(§EffectivePeriod)
@@ -1039,14 +1039,14 @@ export class FlowEngine {
1039
1039
  }
1040
1040
  randInt(min, max) { return max <= min ? min : min + Math.floor(this.rng() * (max - min + 1)); }
1041
1041
  /**
1042
- * 관측 구동(P0 스파이크) — **이벤트로 커널을 굴린다.**
1042
+ * 관측 구동(P0 스파이크) — **이벤트로 커널을 실행한다.**
1043
1043
  *
1044
1044
  * 상태를 만드는 구동이 둘인데(시뮬 `tick` / 미러 `apply`) 지금은 **모델도 둘**이라 한쪽만 고치면
1045
- * 갈라진다(2026-08-01 하루에 아홉 곳). 근본 해법은 **한 상태 모델 두 구동**이고, 이것은 그 실현
1045
+ * 어긋난다(2026-08-01 하루에 아홉 곳). 근본 해법은 **한 상태 모델 두 구동**이고, 이것은 그 실현
1046
1046
  * 가능성을 재는 스파이크다(design/plans/kernel-unification-live-observe.md P0).
1047
1047
  *
1048
1048
  * 여기서는 **이미 검증된 조각을 조립**한다: 투영기가 이벤트를 접고, 그 결과를 씨앗 경로
1049
- * (`hydrateObserved`)로 커널 상태에 심는다. 그래서 관측으로 굴린 커널을 그대로 `fork`·`tick` 할 수
1049
+ * (`hydrateObserved`)로 커널 상태에 주입한다. 그래서 관측으로 실행한 커널을 그대로 `fork`·`tick` 할 수
1050
1050
  * 있다 — "미러에서 예측한다" 가 별도 배관 없이 성립하는지가 이 스파이크의 질문이다.
1051
1051
  *
1052
1052
  * **비용은 정직하게**: 이벤트마다 전체를 다시 심으므로 O(상태 크기)다. P1 에서 반영 로직을 순수
@@ -1112,14 +1112,14 @@ export class FlowEngine {
1112
1112
  return undefined;
1113
1113
  }
1114
1114
  /**
1115
- * **이 공장이 하루 몇 대를 낼 수 있는가** — 굴려 보지 않고 답한다.
1115
+ * **이 공장이 하루 몇 대를 낼 수 있는가** — 실행해 보지 않고 답한다.
1116
1116
  *
1117
1117
  * 커널이 직접 답하는 이유: 필요한 사실이 전부 여기 있다(공정 명세·설비와 신뢰도·인원·물리자산·
1118
1118
  * 자리·근무 달력·시각 기준). 밖에서 모으면 그 값을 옮겨 적게 되고, 한쪽만 바뀌는 순간 "충분하다"
1119
1119
  * 가 조용히 거짓이 된다. 계산 자체는 순수 함수(`analyzeCapacity`)에 맡긴다.
1120
1120
  *
1121
1121
  * 달력은 **자원이 선언한 것**을 쓴다. 자원마다 다른 달력을 쓰는 현장이면 대표를 고를 수 없으므로
1122
- * 그 사실을 결과에 실어 보낸다(`mixedCalendars`) — 조용히 하나를 골라 계산하면 천장이 틀린 채로
1122
+ * 그 사실을 결과에 실어 보낸다(`mixedCalendars`) — 조용히 하나를 골라 계산하면 상한이 틀린 채로
1123
1123
  * 그럴듯해 보인다.
1124
1124
  */
1125
1125
  capacity(opts) {
@@ -1209,14 +1209,14 @@ export class FlowEngine {
1209
1209
  /**
1210
1210
  * task 소요 산출 — 우선순위: **① 추정기(이력 보정) → ② 명세(ISA-95 Duration + 변동) → ③ 도메인 상수.**
1211
1211
  *
1212
- * 이 순서인 이유: 실측에서 배운 값이 선언값을 이기고, 선언값이 우리가 코드에 박아 상수를 이긴다.
1212
+ * 이 순서인 이유: 실측에서 배운 값이 선언값을 이기고, 선언값이 우리가 코드에 고정한 상수를 이긴다.
1213
1213
  * 셋 중 무엇을 썼는지는 `specCoverage()` 로 드러낸다 — 상수를 쓴 것이 조용히 넘어가지 않게.
1214
1214
  * "얼마"만 소비하고 "경로"는 씬이 소유한다(좌표-free 유지).
1215
1215
  */
1216
1216
  durationOf(ctx, fallbackMs) {
1217
1217
  const estimated = this.durationEstimator?.estimate(ctx);
1218
1218
  /* 추정기가 답한 것은 **실측·계산에서 온 값**이므로 선언값과 구별해 기록한다 — "현장이 선언했다" 와
1219
- * "이력에서 배웠다" 는 예측의 자격이 다르다(후자가 더 강하다). 뭉개면 그 차이가 사라진다. */
1219
+ * "이력에서 배웠다" 는 예측의 자격이 다르다(후자가 더 강하다). 합치면 그 차이가 사라진다. */
1220
1220
  if (typeof estimated === 'number') {
1221
1221
  this.noteSpecUse(ctx.kind, 'measured');
1222
1222
  return estimated;
@@ -1309,7 +1309,7 @@ export class FlowEngine {
1309
1309
  this.specUse.set(kind, { duration: 'default', params: new Set([id]) });
1310
1310
  }
1311
1311
  /**
1312
- * 시뮬 명세 자기보고 — **어디까지 데이터로 말했고 어디부터 우리가 박아 상수인가.**
1312
+ * 시뮬 명세 자기보고 — **어디까지 데이터로 말했고 어디부터 우리가 코드에 고정한 상수인가.**
1313
1313
  *
1314
1314
  * 시뮬레이션 결과를 받는 쪽이 이걸 봐야 한다: 소요시간이 전부 기본값이면 그 예측으로 말할 수 있는 것은
1315
1315
  * "같은 조건에서의 상대 비교" 뿐이고 "몇 시에 끝난다" 는 근거가 없다. 그 구분을 숫자로 드러낸다.
@@ -1317,7 +1317,7 @@ export class FlowEngine {
1317
1317
  specCoverage() {
1318
1318
  const operations = [...this.specUse.entries()].map(([kind, u]) => {
1319
1319
  const spec = this.operationSpecs.get(kind);
1320
- /* 실측 분포를 쓴 경우가 우선 — 선언 명세의 변동보다 실제로 굴린 것이 사실이다. */
1320
+ /* 실측 분포를 쓴 경우가 우선 — 선언 명세의 변동보다 실제로 실행한 것이 사실이다. */
1321
1321
  const variability = u.variability ?? spec?.variability?.distribution;
1322
1322
  return {
1323
1323
  kind,
@@ -1409,7 +1409,7 @@ export class FlowEngine {
1409
1409
  this.observeDisposition(epcs, DISP.reserved, bizStep);
1410
1410
  }
1411
1411
  /**
1412
- * 처분 변화 관측 — **상태와 이벤트를 한 번에.** 둘을 따로 쓰면 반드시 갈라진다.
1412
+ * 처분 변화 관측 — **상태와 이벤트를 한 번에.** 둘을 따로 쓰면 반드시 어긋난다.
1413
1413
  *
1414
1414
  * 실제로 양쪽으로 갈라져 있었다: 할당은 상태만 바꾸고 이벤트를 안 냈고(미러가 모름), 야드 도크
1415
1415
  * 도착은 이벤트만 내고 상태를 안 바꿨다(이벤트와 상태가 다른 말). 적합성 하네스가 둘 다 잡았다.
@@ -1682,7 +1682,7 @@ export class FlowEngine {
1682
1682
  }
1683
1683
  return picked;
1684
1684
  }
1685
- /** 확보한 자산을 작업에 묶는다 — 싣는 물류단위(SSCC)가 있으면 연결한다(GRAI ↔ SSCC). */
1685
+ /** 확보한 자산을 작업에 배정한다 — 싣는 물류단위(SSCC)가 있으면 연결한다(GRAI ↔ SSCC). */
1686
1686
  assignAssets(t, gear) {
1687
1687
  if (!gear.length)
1688
1688
  return;
@@ -1848,7 +1848,7 @@ export class FlowEngine {
1848
1848
  * 이 작업이 **딛고 선 것**이 아직 있나 — 없으면 무엇이 없는지 답한다.
1849
1849
  *
1850
1850
  * 작업은 혼자 서지 못한다: 옮길 **물품**과, (있다면) 그것을 시킨 **오더** 위에 선다. 진행 중에
1851
- * 둘 중 하나가 사라질 수 있다 — 물품은 포장·출하·소비로, 오더는 이미 이행돼 씨앗이 심지 않아서.
1851
+ * 둘 중 하나가 사라질 수 있다 — 물품은 포장·출하·소비로, 오더는 이미 이행돼 씨앗이 주입하지 않아서.
1852
1852
  *
1853
1853
  * 그때 도메인 훅은 없는 것을 딛으려다 던진다(`order.gtin` · `item.location`). 그 예외 하나가
1854
1854
  * **예측 전체를 죽였다** — 사용자에게는 기능이 통째로 사라진 것으로 보였다. 그래서 완료 **전에**
@@ -1966,7 +1966,7 @@ export class FlowEngine {
1966
1966
  * 자원 하나의 **가용 능력** — 계약의 판정에 이 커널의 시각·시간대·필수 시험을 채워 넘긴다.
1967
1967
  *
1968
1968
  * 배정도 스냅샷도 이 자리를 지난다. 예전에는 배정이 조건을 늘어놓고 화면이 플래그를 보고 짐작해서,
1969
- * **자격이 만료된 사람이 화면에서는 `대기` 로 보였다**(놀고 있는 것은 맞지만 쓸 수 있는 것은 아니다).
1969
+ * **자격이 만료된 사람이 화면에서는 `대기` 로 보였다**(대기 중인 것은 맞지만 쓸 수 있는 것은 아니다).
1970
1970
  * 판정에 필요한 둘(시각·등급 상속)을 아는 것은 커널뿐이므로, 답도 커널이 낸다.
1971
1971
  */
1972
1972
  capabilityOfResource(r, directClassIds, defs,
@@ -2041,7 +2041,7 @@ export class FlowEngine {
2041
2041
  }
2042
2042
  return picked.length ? picked : null;
2043
2043
  }
2044
- /** 확보한 사람을 작업에 묶는다(설비까지 확정된 뒤). */
2044
+ /** 확보한 사람을 작업에 배정한다(설비까지 확정된 뒤). */
2045
2045
  assignCrew(t, crew) {
2046
2046
  if (!crew.length)
2047
2047
  return;
@@ -2099,7 +2099,7 @@ export class FlowEngine {
2099
2099
  * 유효 기간 밖인가 — **네 번째 이유**(§Effectivity). 설비·사람·자산이 같은 규칙을 쓴다.
2100
2100
  *
2101
2101
  * 시뮬 시각을 ISO 로 풀어 계약의 `effectivityAt` 에 넘긴다. **판정은 커널에 두지 않는다** —
2102
- * 호스트(라이브 관측)도 같은 판정을 해야 하고, 규칙이 두 벌이면 갈라진다.
2102
+ * 호스트(라이브 관측)도 같은 판정을 해야 하고, 규칙이 두 벌이면 어긋난다.
2103
2103
  */
2104
2104
  effectivityOf(r) {
2105
2105
  if (!r.effectiveStart && !r.effectiveEnd)
@@ -2123,7 +2123,7 @@ export class FlowEngine {
2123
2123
  /**
2124
2124
  * 스냅샷·델타에 실을 조각 — **선언한 기간과 판정을 함께** 낸다.
2125
2125
  *
2126
- * 판정만 내면 화면이 "왜" 를 말할 수 없고(언제 폐기됐나), 기간만 내면 소비처마다 다시 판정해 갈라진다.
2126
+ * 판정만 내면 화면이 "왜" 를 말할 수 없고(언제 폐기됐나), 기간만 내면 소비처마다 다시 판정해 어긋난다.
2127
2127
  * 선언이 없으면 아무것도 붙이지 않는다(대부분의 자원이 그렇다 — 필드를 늘리지 않는다).
2128
2128
  */
2129
2129
  effectivePart(r) {
@@ -2140,7 +2140,7 @@ export class FlowEngine {
2140
2140
  * 지금 근무 시간 밖인가 — **캘린더가 있으면 그것으로, 없으면 옛 `window` 로** 판정한다.
2141
2141
  *
2142
2142
  * `window`(시 단위 하나)는 캘린더의 특수한 경우다. 둘을 한 함수로 모아 두면 인원·설비가 같은 규칙을
2143
- * 쓴다(예전에는 같은 식이 두 곳에 복사돼 있었다 — 한쪽만 고치면 조용히 갈라진다).
2143
+ * 쓴다(예전에는 같은 식이 두 곳에 복사돼 있었다 — 한쪽만 고치면 조용히 어긋난다).
2144
2144
  */
2145
2145
  offCalendar(r) {
2146
2146
  return offCalendarAt(r, this.nowMs(), this.boardDef?.utcOffsetMinutes);
@@ -2434,7 +2434,7 @@ export class FlowEngine {
2434
2434
  eq.taskId = null;
2435
2435
  if (t.intent !== 'process')
2436
2436
  eq.location = t.toNode; // 운반만 위치 이동; process 는 제자리
2437
- /* 함께 잡힌 설비도 같은 규칙으로 놓아 준다 — 대표만 풀면 나머지가 영원히 묶인다. */
2437
+ /* 함께 잡힌 설비도 같은 규칙으로 놓아 준다 — 대표만 풀면 나머지가 영원히 점유된다. */
2438
2438
  for (const id of t.resources ?? []) {
2439
2439
  if (id === eq.id)
2440
2440
  continue;
package/dist/forecast.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * Probabilistic Forecast — 몬테카를로 예측 (단일 결정값 → 분포).
3
3
  *
4
- * 예측은 본질적으로 불확실하다. 현재 상태를 fork 하고 seed 를 달리해(확률적 미래) N회 굴려,
4
+ * 예측은 본질적으로 불확실하다. 현재 상태를 fork 하고 seed 를 달리해(확률적 미래) N회 실행해,
5
5
  * 관심 지표를 분포로 준다: "언제 끝나?"가 아니라 "P50/P90 완료시각·재고소진 확률".
6
6
  * fork(현재예측) + 시나리오 재시드로 미래만 변주 — 현재 상태(재고·오더·태스크)는 보존.
7
7
  */
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export * from './event-journal.ts';
8
8
  export * from './divergence.ts';
9
9
  export * from './epcis.ts';
10
10
  export * from './wms-profile.ts';
11
+ export * from './ems-profile.ts';
11
12
  export * from './capability.ts';
12
13
  export * from './domain-catalog.ts';
13
14
  export * from './allocation-policy.ts';
@@ -27,4 +28,6 @@ export * from './operations-capability.ts';
27
28
  export { WmsKernel } from './kernel.ts';
28
29
  export { YmsKernel } from './yms-kernel.ts';
29
30
  export { MesKernel, MES_PART_GTINS, MES_PRODUCT_GTINS, MES_PRODUCTS } from './mes-kernel.ts';
31
+ export { EmsKernel, DEMAND_WINDOW_MS, demandWindowStart } from './ems-kernel.ts';
32
+ export type { EnergyState, DemandWindowState, MeterPointState } from './ems-kernel.ts';
30
33
  export * from './vocabulary.ts';
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ export * from "./event-journal.js";
8
8
  export * from "./divergence.js";
9
9
  export * from "./epcis.js";
10
10
  export * from "./wms-profile.js";
11
+ export * from "./ems-profile.js";
11
12
  export * from "./capability.js";
12
13
  export * from "./domain-catalog.js";
13
14
  export * from "./allocation-policy.js";
@@ -27,4 +28,5 @@ export * from "./operations-capability.js";
27
28
  export { WmsKernel } from "./kernel.js";
28
29
  export { YmsKernel } from "./yms-kernel.js";
29
30
  export { MesKernel, MES_PART_GTINS, MES_PRODUCT_GTINS, MES_PRODUCTS } from "./mes-kernel.js";
31
+ export { EmsKernel, DEMAND_WINDOW_MS, demandWindowStart } from "./ems-kernel.js";
30
32
  export * from "./vocabulary.js";
package/dist/kernel.d.ts CHANGED
@@ -35,11 +35,11 @@ export declare class WmsKernel extends FlowEngine {
35
35
  * 자재는 **작업이 일어나는 자리에** 있어야 소비되고, 산출물은 `in_progress` 로 태어나 **팔 수 있는
36
36
  * 재고가 아니다.** 그래서 부품을 작업대로 옮기고, 가공하고, 나온 것을 보관 자리로 되돌린다.
37
37
  *
38
- * **한 오더에 사슬 하나만** 굴린다. 매 tick 마다 다시 발행하면 같은 부품을 두 번 끌어오는 작업이
38
+ * **한 오더에 사슬 하나만** 실행한다. 매 tick 마다 다시 발행하면 같은 부품을 두 번 끌어오는 작업이
39
39
  * 쌓이고, 그중 하나만 성공한 뒤 나머지는 영원히 재료를 기다린다.
40
40
  */
41
41
  private makeShortLines;
42
- /** 이 오더가 굴리고 있는 생산 사슬이 있나 — 이송·가공·되돌리기 중 하나라도 열려 있으면 그렇다. */
42
+ /** 이 오더가 실행 중인 생산 사슬이 있나 — 이송·가공·되돌리기 중 하나라도 열려 있으면 그렇다. */
43
43
  private hasOpenMakeChain;
44
44
  /** 지금 재고 — 판단 함수가 보는 형태로. */
45
45
  private stockLines;
@@ -53,7 +53,7 @@ export declare class WmsKernel extends FlowEngine {
53
53
  * 들여놓는 것**이다: 산출물은 `in_progress` 로 태어나 팔 수 있는 재고가 아니다.
54
54
  *
55
55
  * ── 왜 팔레트로 묶나 ───────────────────────────────────────────────────────
56
- * 코어의 산출은 **클래스+수량 줄**이고 그 키에는 자리가 박혀 있다(`품목@자리`). 창고의 출고 경로는
56
+ * 코어의 산출은 **클래스+수량 줄**이고 그 키에는 자리가 고정되어 있다(`품목@자리`). 창고의 출고 경로는
57
57
  * 직렬 물류단위(팔레트 SSCC)를 다루므로, 그 줄을 그대로 출고에 태우면 키를 EPC 로 착각해 조회가
58
58
  * 깨진다(실제로 그렇게 터졌다). 억지로 태우면 EPCIS 에도 **EPC 가 아닌 문자열**이 `epcList` 로
59
59
  * 나가는데, 그것은 코어가 산출에서 경계한 바로 그 일이다.
package/dist/kernel.js CHANGED
@@ -164,7 +164,7 @@ export class WmsKernel extends FlowEngine {
164
164
  /** 태스크 완료 — 이동 반영 후 putaway=storing, pick=picking(+전량 시 pack→stage→ship). */
165
165
  onTaskComplete(t) {
166
166
  /*
167
- * **생산 사슬의 걸음들을 먼저 가른다.** 아래 이동 처리는 "주체가 팔레트 하나" 를 전제하는데,
167
+ * **생산 사슬의 걸음들을 먼저 구분한다.** 아래 이동 처리는 "주체가 팔레트 하나" 를 전제하는데,
168
168
  * 가공은 클래스+수량 소비/산출이라 주체가 없다 — 그대로 두면 물품을 찾지 못해 터진다(실제로
169
169
  * 그랬다). 걸음마다 무엇이 끝난 것인지 다르므로 각각 답한다.
170
170
  */
@@ -211,7 +211,7 @@ export class WmsKernel extends FlowEngine {
211
211
  * 자재는 **작업이 일어나는 자리에** 있어야 소비되고, 산출물은 `in_progress` 로 태어나 **팔 수 있는
212
212
  * 재고가 아니다.** 그래서 부품을 작업대로 옮기고, 가공하고, 나온 것을 보관 자리로 되돌린다.
213
213
  *
214
- * **한 오더에 사슬 하나만** 굴린다. 매 tick 마다 다시 발행하면 같은 부품을 두 번 끌어오는 작업이
214
+ * **한 오더에 사슬 하나만** 실행한다. 매 tick 마다 다시 발행하면 같은 부품을 두 번 끌어오는 작업이
215
215
  * 쌓이고, 그중 하나만 성공한 뒤 나머지는 영원히 재료를 기다린다.
216
216
  */
217
217
  makeShortLines(o) {
@@ -239,7 +239,7 @@ export class WmsKernel extends FlowEngine {
239
239
  return; // 한 번에 한 라인씩 — 부품을 여러 라인이 동시에 다투지 않게
240
240
  }
241
241
  }
242
- /** 이 오더가 굴리고 있는 생산 사슬이 있나 — 이송·가공·되돌리기 중 하나라도 열려 있으면 그렇다. */
242
+ /** 이 오더가 실행 중인 생산 사슬이 있나 — 이송·가공·되돌리기 중 하나라도 열려 있으면 그렇다. */
243
243
  hasOpenMakeChain(orderId) {
244
244
  for (const t of this.tasks.values()) {
245
245
  if (t.orderId !== orderId || t.status === 'completed')
@@ -274,7 +274,7 @@ export class WmsKernel extends FlowEngine {
274
274
  /* 물품을 지목하지 않는다 — 클래스+수량 소비/산출이므로 주체가 팔레트 하나가 아니다.
275
275
  코어의 작업 게이트는 빈 `itemEpc` 를 견딘다(`t.itemEpc && …` 조건부 확인). */
276
276
  /* **의도를 process 로 밝힌다** — 코어가 이 표시로 이동(모션)과 변환을 가르고, 이 커널의 완료 훅도
277
- 그것으로 판단한다. "선언된 오퍼레이션인가" 로 가르면 산출을 선언한 이동 작업까지 삼킨다. */
277
+ 그것으로 판단한다. "선언된 오퍼레이션인가" 로 구분하면 산출을 선언한 이동 작업까지 삼킨다. */
278
278
  this.pushTask(o, operation, '', at, at, 'process');
279
279
  }
280
280
  pushTask(o, kind, itemEpc, from, to, intent) {
@@ -293,7 +293,7 @@ export class WmsKernel extends FlowEngine {
293
293
  * 들여놓는 것**이다: 산출물은 `in_progress` 로 태어나 팔 수 있는 재고가 아니다.
294
294
  *
295
295
  * ── 왜 팔레트로 묶나 ───────────────────────────────────────────────────────
296
- * 코어의 산출은 **클래스+수량 줄**이고 그 키에는 자리가 박혀 있다(`품목@자리`). 창고의 출고 경로는
296
+ * 코어의 산출은 **클래스+수량 줄**이고 그 키에는 자리가 고정되어 있다(`품목@자리`). 창고의 출고 경로는
297
297
  * 직렬 물류단위(팔레트 SSCC)를 다루므로, 그 줄을 그대로 출고에 태우면 키를 EPC 로 착각해 조회가
298
298
  * 깨진다(실제로 그렇게 터졌다). 억지로 태우면 EPCIS 에도 **EPC 가 아닌 문자열**이 `epcList` 로
299
299
  * 나가는데, 그것은 코어가 산출에서 경계한 바로 그 일이다.
@@ -27,7 +27,7 @@ export declare class MesKernel extends FlowEngine {
27
27
  * **산출을 두 곳에서 만들지 않는다** — 기동 때 막는다.
28
28
  *
29
29
  * MES 는 레시피로 산출을 만든다: WIP 사슬(스텝마다 앞의 WIP 을 먹고 다음 WIP 을 낸다) · **직렬번호**
30
- * (회사 프리픽스 + 일련번호) · **수율**(양품/불량이 처분을 가른다) · 생산오더 연결. 코어의 일반
30
+ * (회사 프리픽스 + 일련번호) · **수율**(양품/불량이 처분을 구분한다) · 생산오더 연결. 코어의 일반
31
31
  * 자재 명세(`use: 'produced'`)는 **비직렬 클래스+수량**이라 그 넷을 표현하지 못한다.
32
32
  *
33
33
  * 그래서 MES 를 일반 선언으로 옮기지 않는다 — 옮기면 정체성·수율·오더 연결을 **잃는다**(§4-20).
@@ -56,7 +56,7 @@ export declare class MesKernel extends FlowEngine {
56
56
  /**
57
57
  * MES 는 완료 시점에 **오더와 제품 정의**를 딛고 선다 — 둘 중 하나만 없어도 끝맺을 수 없다.
58
58
  *
59
- * 씨앗이 이미 이행된 오더를 심지 않으므로(남은 수량 0) 그 오더에 딸린 작업만 남을 수 있고,
59
+ * 씨앗이 이미 이행된 오더를 주입하지 않으므로(남은 수량 0) 그 오더에 딸린 작업만 남을 수 있고,
60
60
  * 관측이 오더 연결을 담지 못한 작업도 있다. 그때 예전에는 `order.gtin` 에서 던졌고 **그 예외
61
61
  * 하나가 정확도 추세 전체를 죽였다.** 이제 코어가 이 답을 보고 그 작업만 접는다.
62
62
  */