@operato/twin-kernel 0.7.11 → 0.7.12

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.
@@ -1125,6 +1125,20 @@ export interface OrderState {
1125
1125
  * 이것도 상태에 없어서 되살아난 오더는 빈 문자열을 갖고, 이후 모든 이벤트의 거래번호가 빈다.
1126
1126
  */
1127
1127
  bizTransaction?: string;
1128
+ /**
1129
+ * 도메인이 **약속해 둔 자리와 시각창** — 어디로/언제 들이기로 했는가.
1130
+ *
1131
+ * ── 왜 사실에 실어야 하나 (2026-08-17) ─────────────────────────────────────
1132
+ * 야드 트윈이 재기동 뒤 **첫 틱에서 죽었다.** 어포인트먼트를 받을 때 도크 도어를 정해 두는데
1133
+ * 그것이 어느 사실에도 실리지 않아, 웜스타트가 오더는 되살리고 배정은 잃었다. 그리고 다음 틱에
1134
+ * 「그 도어의 점유」를 읽다 예외가 났다(`Cannot read properties of undefined`). 안전망이 그 트윈만
1135
+ * 세워 다른 트윈은 살았지만, 야드 트윈 셋이 부팅마다 죽는 상태였다.
1136
+ *
1137
+ * `allocated` 가 두 자리에만 있어 계보를 잃었던 것과 **같은 부류**다: 상태가 필요로 하는 것을
1138
+ * 사실이 갖고 있지 않으면 재기동을 넘지 못한다.
1139
+ */
1140
+ dockDoor?: string;
1141
+ windowStartMs?: number;
1128
1142
  /** 우선순위 — 표준 `OperationsRequest.Priority`. 작은 값이 급하다. 오더 할당 순서를 정한다. */
1129
1143
  priority?: number;
1130
1144
  /** 예정 착수 — 표준 `OperationsRequest.StartTime`. */
@@ -1670,6 +1684,20 @@ export interface OrderStatusDelta {
1670
1684
  */
1671
1685
  allocated?: string[];
1672
1686
  bizTransaction?: string;
1687
+ /**
1688
+ * 도메인이 **약속해 둔 자리와 시각창** — 어디로/언제 들이기로 했는가.
1689
+ *
1690
+ * ── 왜 사실에 실어야 하나 (2026-08-17) ─────────────────────────────────────
1691
+ * 야드 트윈이 재기동 뒤 **첫 틱에서 죽었다.** 어포인트먼트를 받을 때 도크 도어를 정해 두는데
1692
+ * 그것이 어느 사실에도 실리지 않아, 웜스타트가 오더는 되살리고 배정은 잃었다. 그리고 다음 틱에
1693
+ * 「그 도어의 점유」를 읽다 예외가 났다(`Cannot read properties of undefined`). 안전망이 그 트윈만
1694
+ * 세워 다른 트윈은 살았지만, 야드 트윈 셋이 부팅마다 죽는 상태였다.
1695
+ *
1696
+ * `allocated` 가 두 자리에만 있어 계보를 잃었던 것과 **같은 부류**다: 상태가 필요로 하는 것을
1697
+ * 사실이 갖고 있지 않으면 재기동을 넘지 못한다.
1698
+ */
1699
+ dockDoor?: string;
1700
+ windowStartMs?: number;
1673
1701
  }
1674
1702
  export declare const CMD: {
1675
1703
  readonly orderHold: "order.hold";
@@ -589,7 +589,9 @@ export class FlowEngine {
589
589
  사라진다(같은 사실이 델타·스냅샷·이 변환 **세 길**을 지난다). */
590
590
  ...(o.allocated?.length ? { allocated: o.allocated } : {}),
591
591
  ...(o.gtin ? { gtin: o.gtin } : {}),
592
- ...(o.bizTransaction ? { bizTransaction: o.bizTransaction } : {})
592
+ ...(o.bizTransaction ? { bizTransaction: o.bizTransaction } : {}),
593
+ ...(o.dockDoor ? { dockDoor: o.dockDoor } : {}),
594
+ ...(o.windowStartMs !== undefined ? { windowStartMs: o.windowStartMs } : {})
593
595
  }));
594
596
  for (const o of observedOrders) {
595
597
  const lines = (o.lines ?? []).map(l => ({ gtin: l.gtin, requested: l.requested - (l.fulfilled ?? 0) })).filter(l => l.requested > 0);
@@ -614,7 +616,11 @@ export class FlowEngine {
614
616
  ...(o.held ? { held: true } : {}),
615
617
  ...(o.endTime ? { endTime: o.endTime } : {}),
616
618
  ...(o.startTime ? { startTime: o.startTime } : {}),
617
- ...(o.priority !== undefined ? { priority: o.priority } : {})
619
+ ...(o.priority !== undefined ? { priority: o.priority } : {}),
620
+ /* 약속해 둔 자리·시각창을 이어받는다 — 없으면 도메인이 「정해 뒀다」고 믿는 것을 잃는다.
621
+ 사실이 말해 주지 않으면(옛 저널) 비는 것이 사실이다 — 지어내지 않는다. */
622
+ ...(o.dockDoor ? { dockDoor: o.dockDoor } : {}),
623
+ ...(o.windowStartMs !== undefined ? { windowStartMs: o.windowStartMs } : {})
618
624
  });
619
625
  }
620
626
  /* 작업이 딛고 설 오더를 먼저 세운다 — 순서가 뒤바뀌면 아래 확인이 언제나 "없다" 로 답한다. */
@@ -955,6 +961,9 @@ export class FlowEngine {
955
961
  /* 진행 중 확보분과 거래번호 — **스냅샷에도** 실어야 한다. 델타에만 실으면 스냅샷으로
956
962
  재기동하는 경로(체크포인트)에서 그대로 잃는다(같은 사실을 두 길로 나르는 값이다). */
957
963
  ...(o.allocated?.length ? { allocated: o.allocated.slice() } : {}),
964
+ /* 스냅샷도 함께 나른다 — 델타·스냅샷·복원 **세 길** 중 하나만 빠져도 재기동이 잃는다. */
965
+ ...(o.dockDoor ? { dockDoor: o.dockDoor } : {}),
966
+ ...(o.windowStartMs !== undefined ? { windowStartMs: o.windowStartMs } : {}),
958
967
  ...(o.bizTransaction ? { bizTransaction: o.bizTransaction } : {}),
959
968
  held: o.held
960
969
  })),
@@ -1660,7 +1669,10 @@ export class FlowEngine {
1660
1669
  계보가 입력 없이 나간다(무엇이 무엇으로 바뀌었나의 절반이 사라진다). */
1661
1670
  ...(o.gtin ? { gtin: o.gtin } : {}),
1662
1671
  ...(o.allocated?.length ? { allocated: o.allocated.slice() } : {}),
1663
- ...(o.bizTransaction ? { bizTransaction: o.bizTransaction } : {})
1672
+ ...(o.bizTransaction ? { bizTransaction: o.bizTransaction } : {}),
1673
+ /* 약속해 둔 자리·시각창 — 이것이 빠져서 야드 트윈이 재기동마다 첫 틱에 죽었다(§dockDoor). */
1674
+ ...(o.dockDoor ? { dockDoor: o.dockDoor } : {}),
1675
+ ...(o.windowStartMs !== undefined ? { windowStartMs: o.windowStartMs } : {})
1664
1676
  });
1665
1677
  }
1666
1678
  // ── 내부 mechanics ─────────────────────────────────────────────────────────
@@ -357,6 +357,10 @@ export class ObservedReducer {
357
357
  ...(d.gtin ? { gtin: d.gtin } : {}),
358
358
  ...(d.allocated?.length ? { allocated: d.allocated.slice() } : {}),
359
359
  ...(d.bizTransaction ? { bizTransaction: d.bizTransaction } : {}),
360
+ /* 약속해 둔 자리·시각창도 채운다 — 시뮬만 알면 미러 위 예측이 「어디로 들일지」를 모른다.
361
+ (이 필드가 어느 길에서 빠지면 무슨 일이 나는지는 §dockDoor 에 적혀 있다.) */
362
+ ...(d.dockDoor ? { dockDoor: d.dockDoor } : {}),
363
+ ...(d.windowStartMs !== undefined ? { windowStartMs: d.windowStartMs } : {}),
360
364
  held: d.held
361
365
  });
362
366
  break;
@@ -8,6 +8,8 @@ export declare class YmsKernel extends FlowEngine {
8
8
  private cargoSeq;
9
9
  private trailerCargo;
10
10
  private apptMode;
11
+ /** 도어를 못 찾아 대기시킨 오더 — 같은 말을 매 틱 반복하지 않기 위한 표시(§allocate). */
12
+ private doorWarned;
11
13
  /** 야드 운영 정책(상하차 방식). 기본 drop(야드 버퍼). live 는 게이트/도크 직행. */
12
14
  mode: YardMode;
13
15
  constructor(tenantId: string, policy?: AllocationPolicy, mode?: YardMode);
@@ -39,6 +39,8 @@ export class YmsKernel extends FlowEngine {
39
39
  cargoSeq = 0;
40
40
  trailerCargo = new Map(); // 트레일러 epc → 화물 SSCC(하차 전 opaque / 상차 예약분)
41
41
  apptMode = new Map(); // orderId → 상하차 모드(생성 시 캡처)
42
+ /** 도어를 못 찾아 대기시킨 오더 — 같은 말을 매 틱 반복하지 않기 위한 표시(§allocate). */
43
+ doorWarned = new Set();
42
44
  /** 야드 운영 정책(상하차 방식). 기본 drop(야드 버퍼). live 는 게이트/도크 직행. */
43
45
  mode;
44
46
  constructor(tenantId, policy = firstFitPolicy, mode = 'drop') {
@@ -102,7 +104,25 @@ export class YmsKernel extends FlowEngine {
102
104
  return;
103
105
  if (this.clockMs < (o.windowStartMs ?? 0))
104
106
  return; // 창 도래 전
105
- const door = this.locations.get(o.dockDoor);
107
+ /*
108
+ * ── 배정된 도어가 없으면 **기다린다** (2026-08-17) ────────────────────────
109
+ *
110
+ * 예전에는 `this.locations.get(o.dockDoor!)!` 였다. 그래서 배정을 모르는 오더가 들어오면 바로
111
+ * 아래에서 `undefined.occupancy` 를 읽고 **틱 전체가 예외로 죽었다** — 야드 트윈 셋이 재기동마다
112
+ * 그렇게 멈췄다(안전망이 세운 것이지 스스로 멈춘 것이 아니다).
113
+ *
114
+ * 이제 그 오더만 대기로 남기고 **왜인지 한 번 말한다.** 매 틱 말하면 로그가 그것만 남으므로 한 번만
115
+ * 말하고, 조용히 넘기지도 않는다 — 조용하면 「왜 이 오더는 영원히 안 움직이나」를 아무도 답할 수 없다.
116
+ */
117
+ const door = o.dockDoor ? this.locations.get(o.dockDoor) : undefined;
118
+ if (!door) {
119
+ if (!this.doorWarned.has(o.id)) {
120
+ this.doorWarned.add(o.id);
121
+ console.warn(`[yms-kernel] order "${o.id}" waits: ${o.dockDoor ? `its dock door "${o.dockDoor}" is not in this model` : 'no dock door was assigned to it'}` +
122
+ ' — the appointment cannot be scheduled until the declaration says where.');
123
+ }
124
+ return;
125
+ }
106
126
  const inflight = [...this.tasks.values()].some(t => t.status !== 'completed' && t.toNode === door.id);
107
127
  if (door.occupancy > 0 || inflight)
108
128
  return; // 도어 사용중/예약됨 → 대기(직렬화)
@@ -1354,6 +1354,10 @@ var ObservedReducer = class {
1354
1354
  ...d.gtin ? { gtin: d.gtin } : {},
1355
1355
  ...d.allocated?.length ? { allocated: d.allocated.slice() } : {},
1356
1356
  ...d.bizTransaction ? { bizTransaction: d.bizTransaction } : {},
1357
+ /* 약속해 둔 자리·시각창도 채운다 — 시뮬만 알면 미러 위 예측이 「어디로 들일지」를 모른다.
1358
+ (이 필드가 어느 길에서 빠지면 무슨 일이 나는지는 §dockDoor 에 적혀 있다.) */
1359
+ ...d.dockDoor ? { dockDoor: d.dockDoor } : {},
1360
+ ...d.windowStartMs !== void 0 ? { windowStartMs: d.windowStartMs } : {},
1357
1361
  held: d.held
1358
1362
  });
1359
1363
  break;
@@ -3219,7 +3223,9 @@ var FlowEngine = class {
3219
3223
  사라진다(같은 사실이 델타·스냅샷·이 변환 **세 길**을 지난다). */
3220
3224
  ...o.allocated?.length ? { allocated: o.allocated } : {},
3221
3225
  ...o.gtin ? { gtin: o.gtin } : {},
3222
- ...o.bizTransaction ? { bizTransaction: o.bizTransaction } : {}
3226
+ ...o.bizTransaction ? { bizTransaction: o.bizTransaction } : {},
3227
+ ...o.dockDoor ? { dockDoor: o.dockDoor } : {},
3228
+ ...o.windowStartMs !== void 0 ? { windowStartMs: o.windowStartMs } : {}
3223
3229
  }));
3224
3230
  for (const o of observedOrders) {
3225
3231
  const lines = (o.lines ?? []).map((l) => ({ gtin: l.gtin, requested: l.requested - (l.fulfilled ?? 0) })).filter((l) => l.requested > 0);
@@ -3246,7 +3252,11 @@ var FlowEngine = class {
3246
3252
  ...o.held ? { held: true } : {},
3247
3253
  ...o.endTime ? { endTime: o.endTime } : {},
3248
3254
  ...o.startTime ? { startTime: o.startTime } : {},
3249
- ...o.priority !== void 0 ? { priority: o.priority } : {}
3255
+ ...o.priority !== void 0 ? { priority: o.priority } : {},
3256
+ /* 약속해 둔 자리·시각창을 이어받는다 — 없으면 도메인이 「정해 뒀다」고 믿는 것을 잃는다.
3257
+ 사실이 말해 주지 않으면(옛 저널) 비는 것이 사실이다 — 지어내지 않는다. */
3258
+ ...o.dockDoor ? { dockDoor: o.dockDoor } : {},
3259
+ ...o.windowStartMs !== void 0 ? { windowStartMs: o.windowStartMs } : {}
3250
3260
  });
3251
3261
  }
3252
3262
  const seededOrderIds = new Set(this.orders.keys());
@@ -3562,6 +3572,9 @@ var FlowEngine = class {
3562
3572
  /* 진행 중 확보분과 거래번호 — **스냅샷에도** 실어야 한다. 델타에만 실으면 스냅샷으로
3563
3573
  재기동하는 경로(체크포인트)에서 그대로 잃는다(같은 사실을 두 길로 나르는 값이다). */
3564
3574
  ...o.allocated?.length ? { allocated: o.allocated.slice() } : {},
3575
+ /* 스냅샷도 함께 나른다 — 델타·스냅샷·복원 **세 길** 중 하나만 빠져도 재기동이 잃는다. */
3576
+ ...o.dockDoor ? { dockDoor: o.dockDoor } : {},
3577
+ ...o.windowStartMs !== void 0 ? { windowStartMs: o.windowStartMs } : {},
3565
3578
  ...o.bizTransaction ? { bizTransaction: o.bizTransaction } : {},
3566
3579
  held: o.held
3567
3580
  })),
@@ -4208,7 +4221,10 @@ var FlowEngine = class {
4208
4221
  계보가 입력 없이 나간다(무엇이 무엇으로 바뀌었나의 절반이 사라진다). */
4209
4222
  ...o.gtin ? { gtin: o.gtin } : {},
4210
4223
  ...o.allocated?.length ? { allocated: o.allocated.slice() } : {},
4211
- ...o.bizTransaction ? { bizTransaction: o.bizTransaction } : {}
4224
+ ...o.bizTransaction ? { bizTransaction: o.bizTransaction } : {},
4225
+ /* 약속해 둔 자리·시각창 — 이것이 빠져서 야드 트윈이 재기동마다 첫 틱에 죽었다(§dockDoor). */
4226
+ ...o.dockDoor ? { dockDoor: o.dockDoor } : {},
4227
+ ...o.windowStartMs !== void 0 ? { windowStartMs: o.windowStartMs } : {}
4212
4228
  });
4213
4229
  }
4214
4230
  // ── 내부 mechanics ─────────────────────────────────────────────────────────
@@ -5300,6 +5316,8 @@ var YmsKernel = class extends FlowEngine {
5300
5316
  // 트레일러 epc → 화물 SSCC(하차 전 opaque / 상차 예약분)
5301
5317
  apptMode = /* @__PURE__ */ new Map();
5302
5318
  // orderId → 상하차 모드(생성 시 캡처)
5319
+ /** 도어를 못 찾아 대기시킨 오더 — 같은 말을 매 틱 반복하지 않기 위한 표시(§allocate). */
5320
+ doorWarned = /* @__PURE__ */ new Set();
5303
5321
  /** 야드 운영 정책(상하차 방식). 기본 drop(야드 버퍼). live 는 게이트/도크 직행. */
5304
5322
  mode;
5305
5323
  constructor(tenantId, policy = firstFitPolicy, mode = "drop") {
@@ -5371,7 +5389,16 @@ var YmsKernel = class extends FlowEngine {
5371
5389
  const trailer = this.items.get(o.allocated[0]);
5372
5390
  if (!trailer) return;
5373
5391
  if (this.clockMs < (o.windowStartMs ?? 0)) return;
5374
- const door = this.locations.get(o.dockDoor);
5392
+ const door = o.dockDoor ? this.locations.get(o.dockDoor) : void 0;
5393
+ if (!door) {
5394
+ if (!this.doorWarned.has(o.id)) {
5395
+ this.doorWarned.add(o.id);
5396
+ console.warn(
5397
+ `[yms-kernel] order "${o.id}" waits: ${o.dockDoor ? `its dock door "${o.dockDoor}" is not in this model` : "no dock door was assigned to it"} \u2014 the appointment cannot be scheduled until the declaration says where.`
5398
+ );
5399
+ }
5400
+ return;
5401
+ }
5375
5402
  const inflight = [...this.tasks.values()].some((t) => t.status !== "completed" && t.toNode === door.id);
5376
5403
  if (door.occupancy > 0 || inflight) return;
5377
5404
  const mode = this.apptMode.get(o.id) ?? "drop";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/twin-kernel",
3
- "version": "0.7.11",
3
+ "version": "0.7.12",
4
4
  "type": "module",
5
5
  "description": "Twin Domain Kernel — framework-agnostic, zero-dep (domain + sim + 3-channel contract). WMS/YMS/MES, EPCIS 2.0 · ISA-95.",
6
6
  "publishConfig": {