@operato/twin-kernel 0.11.25 → 0.11.27

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.
@@ -29,7 +29,11 @@ export interface FlowLocation {
29
29
  export interface FlowItem {
30
30
  epc: string;
31
31
  location: string;
32
- disposition: string;
32
+ /**
33
+ * CBV disposition. Optional, as in EPCIS: a yard trailer carries none — CBV 2.0 has no disposition for an
34
+ * asset waiting in a yard, and `bizStep: staging` already says it waits (ADR-0050 ruling, 2026-09-24).
35
+ */
36
+ disposition?: string;
33
37
  /** 무슨 품목인가 — 표준 `MaterialLot.MaterialDefinitionID`. 보통은 `gtin` 이 곧 정의 id 라 비어 있다. */
34
38
  definitionId?: string;
35
39
  /** 로트의 부분(표준 `MaterialSubLot.ID`) — 비직렬 로트가 자리마다 갈릴 때. */
@@ -15,9 +15,9 @@ import { ObservedReducer } from "./observed-reducer.js";
15
15
  /* 주체를 정하는 규칙은 유입 문과 한 벌이다 — 두 곳에 적으면 한쪽만 고쳐진다. */
16
16
  import { resolveSubject } from '@operato/ops-contract';
17
17
  import { WMS_ORDER_KIND } from '@operato/ops-contract';
18
- import { transformationEvent, aggregationEvent, objectEvent, parseEpc, DISP, ILMD_ATTR, CBV_BIZSTEP, objectUri, bizTransactionUri, gdtiUri } from '@operato/ops-contract';
18
+ import { transformationEvent, aggregationEvent, objectEvent, parseEpc, DISP, ILMD_ATTR, CBV_BIZSTEP, PRIVATE_BIZSTEP, objectUri, bizTransactionUri, gdtiUri } from '@operato/ops-contract';
19
19
  import { allocationPolicyOf } from "./allocation-policy.js";
20
- import { ORDER_FOLD_DEFAULT, emptyFoldedOrders, foldOrdersInto, foldedOrderCount, orderFoldPolicyOf, planOrderFold } from '@operato/ops-contract';
20
+ import { ORDER_FOLD_DEFAULT, emptyFoldedOrders, foldOrdersInto, foldedOrderCount, pruneFoldedHours, orderFoldPolicyOf, planOrderFold } from '@operato/ops-contract';
21
21
  import { OP_PARAM } from '@operato/ops-contract';
22
22
  import { parseIsoDuration } from '@operato/ops-contract';
23
23
  import { analyzeCapacity } from '@operato/ops-contract';
@@ -721,7 +721,9 @@ export class FlowEngine {
721
721
  const ids = planOrderFold([...this.orders.values()], nowMs ?? this.nowMs(), this.orderFoldPolicy);
722
722
  if (!ids.length)
723
723
  return 0;
724
- this.foldedOrders = foldOrdersInto(this.foldedOrders, ids.map(id => this.orders.get(id)).filter(Boolean));
724
+ /* 창 밖 시간 칸은 접을 때 버린다 — 칸 수가 T/1h 를 넘지 않게(ADR-0092 결정 2 보탬). */
725
+ const now = nowMs ?? this.nowMs();
726
+ this.foldedOrders = pruneFoldedHours(foldOrdersInto(this.foldedOrders, ids.map(id => this.orders.get(id)).filter(Boolean)), now, this.orderFoldPolicy);
725
727
  for (const id of ids)
726
728
  this.orders.delete(id);
727
729
  this.revision++;
@@ -3609,7 +3611,7 @@ export class FlowEngine {
3609
3611
  this.recordMaterialActual(t, it.definitionId ?? it.gtin, 'consumed', take, it.uom, it.epc);
3610
3612
  /* 남은 수량을 그대로 알린다 — 관측 경로가 그 자리의 사실을 갱신한다(§quantityList). */
3611
3613
  this.emit(objectEvent({
3612
- eventTime: this.now(), action: 'OBSERVE', bizStep: CBV_BIZSTEP.consuming, disposition: DISP.in_progress,
3614
+ eventTime: this.now(), action: 'OBSERVE', bizStep: PRIVATE_BIZSTEP.consuming, disposition: DISP.in_progress,
3613
3615
  epcList: [], quantityList: [{ epcClass: it.gtin ?? it.epc, quantity: left, uom: it.uom }],
3614
3616
  readPoint: it.location, bizLocation: it.location
3615
3617
  }));
@@ -3637,7 +3639,7 @@ export class FlowEngine {
3637
3639
  * 다른 작업이 같은 물품을 또 잡지 못하게 한다 — 소비 시점은 시작인데 변환은 완료 시점이라 그
3638
3640
  * 사이가 열려 있다. **변환 중**으로 표시한다(상태와 사건을 한 번에 — §`observeDisposition`).
3639
3641
  */
3640
- this.observeDisposition(whole, DISP.in_progress, CBV_BIZSTEP.consuming);
3642
+ this.observeDisposition(whole, DISP.in_progress, PRIVATE_BIZSTEP.consuming);
3641
3643
  return;
3642
3644
  }
3643
3645
  const at = this.items.get(whole[0])?.location ?? t.toNode;
@@ -3652,7 +3654,7 @@ export class FlowEngine {
3652
3654
  this.items.delete(epc);
3653
3655
  }
3654
3656
  this.emit(objectEvent({
3655
- eventTime: this.now(), action: 'DELETE', bizStep: CBV_BIZSTEP.consuming, disposition: DISP.in_progress,
3657
+ eventTime: this.now(), action: 'DELETE', bizStep: PRIVATE_BIZSTEP.consuming, disposition: DISP.in_progress,
3656
3658
  epcList: whole.slice(), readPoint: at, bizLocation: at
3657
3659
  }));
3658
3660
  }
@@ -22,7 +22,7 @@
22
22
  import { computeOee, isWorkShiftExecutionBasis } from '@operato/ops-contract';
23
23
  import { OP_EVENT, capabilityOf, itemKeyOf, judgeAgainstSpec, requiredTestsFor, locationStatusOf, readBoardEquipment, readBoardLocations, readBoardAssets, effectivityAt, offCalendarAt, offCalendarReasonAt, activeShiftAt } from '@operato/ops-contract';
24
24
  import { ILMD_ATTR, parseEpc } from '@operato/ops-contract';
25
- import { emptyFoldedOrders, foldOrdersInto, foldedOrderCount, isOrderTerminal, orderFoldPolicyOf, planOrderFold, standsNewOrderRow } from '@operato/ops-contract';
25
+ import { emptyFoldedOrders, foldOrdersInto, foldedOrderCount, pruneFoldedHours, isOrderTerminal, orderFoldPolicyOf, planOrderFold, standsNewOrderRow } from '@operato/ops-contract';
26
26
  import { expiryFromAttributes, lotFromAttributes } from '@operato/ops-contract';
27
27
  /* 클래스 마스터 표는 상태를 들므로 커널에 있다(§`class-master-table`). */
28
28
  import { ClassMasterTable } from "./class-master-table.js";
@@ -1423,7 +1423,8 @@ export class ObservedReducer {
1423
1423
  if (!ids.length)
1424
1424
  return 0;
1425
1425
  const rows = ids.map(id => this.orders.get(id)).filter(Boolean);
1426
- this.foldedOrders = foldOrdersInto(this.foldedOrders, rows);
1426
+ /* 창 밖 시간 칸은 접을 때 버린다 — 칸 수가 T/1h 를 넘지 않게(ADR-0092 결정 2 보탬). */
1427
+ this.foldedOrders = pruneFoldedHours(foldOrdersInto(this.foldedOrders, rows), nowMs, this.orderFoldPolicy);
1427
1428
  for (const id of ids) {
1428
1429
  this.orders.delete(id);
1429
1430
  this.lastAt.delete(`order:${id}`);
@@ -19,7 +19,7 @@ import { itemKeyOf } from '@operato/ops-contract';
19
19
  import { firstFitPolicy } from "./allocation-policy.js";
20
20
  import { FlowEngine } from "./flow-engine.js";
21
21
  import { DISP, objectEvent, transactionEvent } from '@operato/ops-contract';
22
- import { YARD_BIZSTEP } from '@operato/ops-contract';
22
+ import { YARD_BIZSTEP, YARD_PRIVATE_BIZSTEP } from '@operato/ops-contract';
23
23
  const TRAVEL_MS = 20_000; // 야드 이동
24
24
  const DWELL_MS = 30_000; // 도크 체류(상·하차) — depart 이동에 folded
25
25
  const WINDOW_DELAY_MS = 40_000; // 도착 후 어포인트먼트 창까지(early arrival → 대기)
@@ -182,8 +182,13 @@ export class YmsKernel extends FlowEngine {
182
182
  }
183
183
  if (to.type === 'yard-slot') {
184
184
  // drop 주차 — 야드 대기(창 도래까지). 도어는 depart 완료까지 미점유.
185
- trailer.disposition = DISP.sellable;
186
- this.emit(objectEvent({ eventTime: this.now(), action: 'OBSERVE', bizStep: YARD_BIZSTEP.staging, disposition: DISP.sellable, epcList: [trailer.epc], readPoint: to.id, bizLocation: to.id }));
185
+ /*
186
+ * No disposition (ADR-0050 ruling, 2026-09-24). It was `sellable` — "product can be sold as is" — and a
187
+ * trailer is not product; CBV 2.0 has nothing for an asset waiting in a yard. `staging` says it waits.
188
+ * The step is ours, not CBV's (YARD_PRIVATE_BIZSTEP, contract 836c308).
189
+ */
190
+ delete trailer.disposition;
191
+ this.emit(objectEvent({ eventTime: this.now(), action: 'OBSERVE', bizStep: YARD_PRIVATE_BIZSTEP.staging, epcList: [trailer.epc], readPoint: to.id, bizLocation: to.id }));
187
192
  return;
188
193
  }
189
194
  if (to.type === 'dock-door') {
@@ -201,7 +206,7 @@ export class YmsKernel extends FlowEngine {
201
206
  this.aggregate(trailer.epc, cargo, { bizStep: YARD_BIZSTEP.loading, readPoint: to.id, consume: { readPoint: staging.id, disposition: DISP.in_transit } });
202
207
  }
203
208
  this.trailerCargo.delete(trailer.epc); // 적재 완료 → 예약분 소진(트레일러가 화물 보유)
204
- trailer.disposition = DISP.sellable; // 적재 완료(loaded)
209
+ delete trailer.disposition; // 적재 완료(loaded) — no disposition, same ruling as the yard drop
205
210
  }
206
211
  else if (cargo.length && staging) {
207
212
  // 하차: 트레일러←화물 분해(split) → 화물이 staging 에 materialize(WMS receiving hand-off).