@operato/twin-kernel 0.11.12 → 0.11.15

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.
@@ -1,4 +1,4 @@
1
- import type { MaterialActual, TestResult, ISOTime, MaterialQuantity, WorkCalendarEntry, EffectivePeriod, Effectivity, OffCalendarReason, ResourceProperty, ResourceClassDef, MaterialDefinition, Attention, TwinModelDef, CanonicalEnvelope, Command, CommandAck, EventHandler, EquipmentMotion, AssetState, GeneratorSpec, InterventionOutcome, OrderState, PersonState, ScenarioControl, ScenarioOverride, StateSnapshot, TwinKernel, Unsubscribe, LocationState, ItemState, EquipmentState, OrderStatusDelta, TaskState, TaskStatus, StructureShift, IdentityGroundingView, IdentityDeclaration, TestSpecificationCriterion, LocationObservation } from '@operato/ops-contract';
1
+ import type { MaterialActual, TestResult, ISOTime, MaterialQuantity, WorkCalendarEntry, EffectivePeriod, Effectivity, OffCalendarReason, ResourceProperty, ResourceClassDef, MaterialDefinition, Attention, TwinModelDef, CanonicalEnvelope, Command, CommandAck, EventHandler, EquipmentMotion, AssetState, GeneratorSpec, InterventionOutcome, OrderState, PersonState, ScenarioControl, ScenarioOverride, StateSnapshot, TwinKernel, Unsubscribe, LocationState, ItemState, EquipmentState, OrderStatusDelta, TaskState, TaskStatus, StructureShift, IdentityGroundingView, IdentityDeclaration, TestSpecificationCriterion, LocationObservation, DispositionFact } from '@operato/ops-contract';
2
2
  import { type RatedUsage, type UsedUsage } from '@operato/ops-contract';
3
3
  import type { VocabularyElement } from '@operato/ops-contract';
4
4
  import type { ReducerCheckpoint } from './observed-reducer.ts';
@@ -46,6 +46,13 @@ export interface FlowItem {
46
46
  ilmd?: Record<string, unknown>;
47
47
  /** 선언된 모든 수량 — 표준 `MaterialLot.Quantity`(복수). 관측에서 온 물품이 여러 단위를 들 수 있다. */
48
48
  quantities?: MaterialQuantity[];
49
+ /**
50
+ * 로트의 두 표준 칸 — ISA-95 `Disposition`(`nonconformance`) · `Status`(`status`). 관측 모드에서 리듀서가
51
+ * 앉힌 것을 `settleObserved` 가 옮기고 `itemState` 가 스냅샷에 낸다. 둘 중 하나가 빠지면 리듀서에는 있는데
52
+ * 화면에는 없는 칸이 된다(2026-09-16 에 둘 다 그랬다).
53
+ */
54
+ nonconformance?: DispositionFact;
55
+ status?: string;
49
56
  }
50
57
  /** 물리 자산 — 반복사용(팔레트·랙·용기). 설비도 물품도 아니다(GRAI vs SSCC 구분은 계약 주석 참조). */
51
58
  export interface FlowAsset extends EffectivePeriod {
@@ -1034,7 +1034,14 @@ export class FlowEngine {
1034
1034
  ...(it.subLotId ? { subLotId: it.subLotId } : {}),
1035
1035
  ...(it.definitionId ? { definitionId: it.definitionId } : {}),
1036
1036
  gtin: it.gtin, qty: it.qty ?? 1, uom: it.uom, ...(it.quantities?.length ? { quantities: it.quantities } : {}),
1037
- parent: it.parent, carriedBy: it.carriedBy, expiry: it.expiry, ilmd: it.ilmd
1037
+ parent: it.parent, carriedBy: it.carriedBy, expiry: it.expiry, ilmd: it.ilmd,
1038
+ /*
1039
+ * 로트의 두 표준 칸 — ISA-95 `Disposition`(`nonconformance`)과 `Status`(`status`). 이 복사가 칸을 하나씩
1040
+ * 옮기는 자리라, 여기 없는 칸은 리듀서에 앉았어도 **스냅샷에 나오지 않는다**(2026-09-16 에 `status` 를
1041
+ * 넣다가 `nonconformance` 도 여기서 빠져 있던 것을 찾았다 — 자리는 있고 길이 없는 부류). 있을 때만 옮긴다.
1042
+ */
1043
+ ...(it.nonconformance ? { nonconformance: it.nonconformance } : {}),
1044
+ ...(it.status ? { status: it.status } : {})
1038
1045
  });
1039
1046
  }
1040
1047
  for (const m of snap.equipment) {
@@ -3869,7 +3876,10 @@ export class FlowEngine {
3869
3876
  ...(i.uom ? { uom: i.uom } : {}),
3870
3877
  ...(i.quantities?.length ? { quantities: i.quantities } : {}),
3871
3878
  ...(i.expiry !== undefined ? { expiry: i.expiry } : {}),
3872
- ...(i.ilmd ? { ilmd: i.ilmd } : {})
3879
+ ...(i.ilmd ? { ilmd: i.ilmd } : {}),
3880
+ /* 로트의 두 표준 칸(§FlowItem) — 여기서 빠지면 리듀서·엔진에는 있고 스냅샷에는 없다. */
3881
+ ...(i.nonconformance ? { nonconformance: i.nonconformance } : {}),
3882
+ ...(i.status ? { status: i.status } : {})
3873
3883
  };
3874
3884
  }
3875
3885
  /**
package/dist/kernel.js CHANGED
@@ -9,7 +9,7 @@ import { CMD } from '@operato/ops-contract';
9
9
  import { firstFitPolicy } from "./allocation-policy.js";
10
10
  import { FlowEngine } from "./flow-engine.js";
11
11
  import { planMakeToOrder } from "./make-to-order.js";
12
- import { BIZSTEP, BTT } from '@operato/ops-contract';
12
+ import { BIZSTEP, BTT, WMS_ORDER_KIND } from '@operato/ops-contract';
13
13
  import { DISP, ILMD_ATTR, aggregationEvent, objectEvent, transactionEvent } from '@operato/ops-contract';
14
14
  import { subLotIdOf, itemKeyOf } from '@operato/ops-contract';
15
15
  const TRAVEL_MS = 30_000;
@@ -133,7 +133,7 @@ export class WmsKernel extends FlowEngine {
133
133
  const so = this.requireBizTransactionId(`SO-${soSeq}`, 'salesorder');
134
134
  const requested = lines.reduce((s, l) => s + l.qty, 0);
135
135
  const order = {
136
- id, kind: 'outbound', status: 'created', requested, fulfilled: 0, bizTransaction: so,
136
+ id, kind: WMS_ORDER_KIND.outbound, status: 'created', requested, fulfilled: 0, bizTransaction: so,
137
137
  allocated: [], picked: [], shipmentEpc: null, lines: lines.map(l => ({ gtin: l.gtin, requested: l.qty })),
138
138
  ...(spec ? this.promiseOf(spec) : {})
139
139
  };
@@ -175,7 +175,7 @@ export class WmsKernel extends FlowEngine {
175
175
  /* 할당은 아직 집은 것이 아니다 — 물건은 보관 자리에 있고 처분만 '예약' 으로 바뀐다.
176
176
  * 그래서 단계는 storing 이다(피킹 관측은 실제로 집을 때 따로 난다). */
177
177
  this.reserve(chosenAll, BIZSTEP.storing);
178
- this.emit(transactionEvent({ eventTime: this.now(), action: 'ADD', bizStep: BIZSTEP.picking, bizTransactionList: [{ type: BTT.so, bizTransaction: o.bizTransaction }], epcList: chosenAll.slice() }));
178
+ this.emit(transactionEvent({ eventTime: this.now(), action: 'ADD', bizStep: BIZSTEP.picking, bizTransactionList: [{ type: BTT.poc, bizTransaction: o.bizTransaction }], epcList: chosenAll.slice() }));
179
179
  for (const epc of chosenAll) {
180
180
  const it = this.items.get(epc);
181
181
  const id = `task-${++this.taskSeq}`;
@@ -363,7 +363,7 @@ export class WmsKernel extends FlowEngine {
363
363
  const shpSeq = ++this.epcSeq;
364
364
  const shipment = this.requireObjectId(`SHP-${shpSeq}`);
365
365
  order.shipmentEpc = shipment;
366
- const soTxn = [{ type: BTT.so, bizTransaction: order.bizTransaction }];
366
+ const soTxn = [{ type: BTT.poc, bizTransaction: order.bizTransaction }];
367
367
  // 패킹: 화물(shipment) ← 팔레트 조립(merge). 팔레트는 출하 DELETE 까지 독립 유지 → consume 없음.
368
368
  this.aggregate(shipment, order.picked.slice(), { bizStep: BIZSTEP.packing, readPoint: staging.id, bizLocation: staging.id });
369
369
  order.status = 'packed';
@@ -573,11 +573,12 @@ export class ObservedReducer {
573
573
  * 않는다** — 모르는 로트의 상태는 `unhandled` 로 세어서 낸다.
574
574
  */
575
575
  const d = e.data;
576
- if (!d?.epc || !d?.status)
576
+ if (!d?.lotId || !d?.status)
577
577
  break;
578
- if (this.stale(`material-lot:${d.epc}`, e))
578
+ if (this.stale(`material-lot:${d.lotId}`, e))
579
579
  return;
580
- const parts = [...this.items.values()].filter(i => i.epc === d.epc);
580
+ /* `lotId` 는 표준 `MaterialLotID` — 값은 로트의 식별자, 즉 여기서 `epc` 로 든 것과 같다. */
581
+ const parts = [...this.items.values()].filter(i => i.epc === d.lotId);
581
582
  if (!parts.length) {
582
583
  this.noteUnhandled(e, `${OP_EVENT.materialLot}:unknown-lot`);
583
584
  break;
@@ -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, BTT_DELIVERY } from '@operato/ops-contract';
22
+ import { YARD_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 → 대기)
@@ -149,7 +149,7 @@ export class YmsKernel extends FlowEngine {
149
149
  this.trailerCargo.set(trailer.epc, avail.slice(0, CARGO_PER_TRAILER).map(i => i.epc));
150
150
  }
151
151
  this.reserve([trailer.epc], bizStep); // 처분 변화를 이벤트로 — 미러가 "잡혔다" 를 알 수 있게
152
- this.emit(transactionEvent({ eventTime: this.now(), action: 'ADD', bizStep, bizTransactionList: [{ type: BTT_DELIVERY, bizTransaction: o.bizTransaction }], epcList: [trailer.epc], readPoint: door.id }));
152
+ this.emit(transactionEvent({ eventTime: this.now(), action: 'ADD', bizStep, bizTransactionList: [{ bizTransaction: o.bizTransaction }], epcList: [trailer.epc], readPoint: door.id }));
153
153
  // drop: 야드→도크 pull. live: 게이트→도크 직행(spot-live). 둘 다 toNode=도크도어.
154
154
  const dockKind = mode === 'drop' ? 'pull' : 'spot-live';
155
155
  const task = { id: `task-${++this.taskSeq}`, kind: dockKind, status: 'created', itemEpc: trailer.epc, fromNode: trailer.location, toNode: door.id, resource: null, remainingMs: 0, durationMs: this.durationOf({ kind: dockKind, fromNode: trailer.location, toNode: door.id }, TRAVEL_MS), orderId: o.id };
@@ -668,6 +668,7 @@ var OP_EVENT = {
668
668
  * 접지 않는다 — 승인됐는데 안 나간 배치가 트윈이 보여야 할 상태다.
669
669
  */
670
670
  materialLot: "material-lot.status",
671
+ // 레코드: { lotId(표준 MaterialLotID = ItemState.epc 의 값), status, decidedBy?, reason?, decidedAt? }
671
672
  /**
672
673
  * **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 그것을 알린다.
673
674
  *
@@ -992,8 +993,28 @@ var BIZSTEP = {
992
993
  replenishing: "urn:epcglobal:cbv:bizstep:replenishing"
993
994
  };
994
995
  var BTT = {
996
+ /** Purchase Order — the document an inbound receipt is made against. */
995
997
  po: "urn:epcglobal:cbv:btt:po",
996
- so: "urn:epcglobal:cbv:btt:so"
998
+ /** Purchase Order Confirmation — the seller's sales order. */
999
+ poc: "urn:epcglobal:cbv:btt:poc",
1000
+ /** Despatch Advice (ASN) — announces an inbound delivery; also attached when shipping. */
1001
+ desadv: "urn:epcglobal:cbv:btt:desadv",
1002
+ /** Receiving Advice — the reply once an inbound receipt is complete. */
1003
+ recadv: "urn:epcglobal:cbv:btt:recadv",
1004
+ /** Return Merchandise Authorization — what a return is received against. */
1005
+ rma: "urn:epcglobal:cbv:btt:rma",
1006
+ /** Bill of Lading — the carrier's document, attached when shipping. */
1007
+ bol: "urn:epcglobal:cbv:btt:bol"
1008
+ };
1009
+ var WMS_ORDER_KIND = {
1010
+ /** Expected receipt (ASN). The kernel has no inbound order yet — arrival carries a `po` without one. */
1011
+ inbound: "inbound",
1012
+ /** Outbound order — what the kernel's sales order is. */
1013
+ outbound: "outbound",
1014
+ /** Receipt of returned goods. */
1015
+ return: "return",
1016
+ /** Stock transfer between sites. CBV has no document type for it: carry the id without a type. */
1017
+ transfer: "transfer"
997
1018
  };
998
1019
  var WMS_LOCATION_TYPES = ["dock", "storage", "staging", "dock-ship", "vas-station"];
999
1020
  var WMS_LOCATION_LEVEL = {
@@ -1024,7 +1045,6 @@ var YARD_BIZSTEP = {
1024
1045
  departing: "urn:epcglobal:cbv:bizstep:departing"
1025
1046
  // 게이트-아웃
1026
1047
  };
1027
- var BTT_DELIVERY = "urn:epcglobal:cbv:btt:deliv";
1028
1048
  var YMS_LOCATION_TYPES = ["gate", "yard-slot", "dock-door", "staging"];
1029
1049
  var YMS_LOCATION_LEVEL = {
1030
1050
  gate: "Other",
@@ -1043,14 +1063,8 @@ var MES_BIZSTEP = {
1043
1063
  // 원자재 수령
1044
1064
  producing: "urn:epcglobal:cbv:bizstep:commissioning",
1045
1065
  // 생산(제품 최초 생성)
1046
- storing: "urn:epcglobal:cbv:bizstep:storing",
1066
+ storing: "urn:epcglobal:cbv:bizstep:storing"
1047
1067
  // 완제품 저장
1048
- /**
1049
- * **출하** — CBV `shipping`: 물품이 시설을 떠난다. 자재를 트윈 밖으로 내는 것은 이 사건이다(disposition
1050
- * `in_transit`, readPoint = 출하 dock). 출하 **승인**은 이 사건이 아니라 로트 상태(`OP_EVENT.materialLot`)다 —
1051
- * 승인됐는데 안 나간 배치가 보여야 한다(2026-09-16, ADR-0046 곁).
1052
- */
1053
- shipping: "urn:epcglobal:cbv:bizstep:shipping"
1054
1068
  };
1055
1069
  var BTT_PRODORDER = "urn:epcglobal:cbv:btt:prodorder";
1056
1070
  function sgtinUri(companyPrefix, itemRef, serial) {
@@ -2065,17 +2079,19 @@ var SPECS = {
2065
2079
  /*
2066
2080
  * **로트 상태** — ISA-95 `MaterialLot.Status`. 출하 승인(batch release)이 들어오는 문(§`OP_EVENT.materialLot`).
2067
2081
  *
2068
- * `disposition`(subjectId + decision) 과 필드가 겹치지 않는다 — 여기는 `epc` + `status`. 상태 낱말은 열려
2069
- * 있어 enum 이 없다(오더 상태와 같은 규율). 로트 전체의 사실이므로 `subLotId` 는 받지 않는다 — 부분마다 다른
2070
- * 상태가 필요해지면 그것은 다른 사실이다.
2082
+ * `disposition`(subjectId + decision) 과 필드가 겹치지 않는다 — 여기는 `lotId` + `status`. 정체 이름이 표준
2083
+ * `MaterialLotID` 인 이유가 하나 더 있다: 이 문은 `epc` 를 든 레코드를 EPCIS 어휘로 보고 받지 않는다
2084
+ * (§`operationalKindOf`). 값은 로트의 식별자(`ItemState.epc` 와 같은 값)다. 상태 낱말은 열려 있어 enum 이
2085
+ * 없다(오더 상태와 같은 규율). 로트 전체의 사실이므로 부분(`subLotId`)은 받지 않는다 — 부분마다 다른 상태가
2086
+ * 필요해지면 그것은 다른 사실이다.
2071
2087
  */
2072
2088
  "material-lot": {
2073
2089
  eventType: OP_EVENT.materialLot,
2074
- match: ["epc", "status"],
2090
+ match: ["lotId", "status"],
2075
2091
  matchOrder: 85,
2076
- identity: "epc",
2077
- required: ["epc", "status"],
2078
- fields: { epc: "string", status: "string", decidedBy: "string", reason: "string", decidedAt: "string", recordTime: "string" }
2092
+ identity: "lotId",
2093
+ required: ["lotId", "status"],
2094
+ fields: { lotId: "string", status: "string", decidedBy: "string", reason: "string", decidedAt: "string", recordTime: "string" }
2079
2095
  },
2080
2096
  test: {
2081
2097
  eventType: OP_EVENT.test,
@@ -2921,9 +2937,9 @@ var ObservedReducer = class {
2921
2937
  }
2922
2938
  case OP_EVENT.materialLot: {
2923
2939
  const d = e.data;
2924
- if (!d?.epc || !d?.status) break;
2925
- if (this.stale(`material-lot:${d.epc}`, e)) return;
2926
- const parts = [...this.items.values()].filter((i) => i.epc === d.epc);
2940
+ if (!d?.lotId || !d?.status) break;
2941
+ if (this.stale(`material-lot:${d.lotId}`, e)) return;
2942
+ const parts = [...this.items.values()].filter((i) => i.epc === d.lotId);
2927
2943
  if (!parts.length) {
2928
2944
  this.noteUnhandled(e, `${OP_EVENT.materialLot}:unknown-lot`);
2929
2945
  break;
@@ -4367,7 +4383,14 @@ var FlowEngine = class {
4367
4383
  parent: it.parent,
4368
4384
  carriedBy: it.carriedBy,
4369
4385
  expiry: it.expiry,
4370
- ilmd: it.ilmd
4386
+ ilmd: it.ilmd,
4387
+ /*
4388
+ * 로트의 두 표준 칸 — ISA-95 `Disposition`(`nonconformance`)과 `Status`(`status`). 이 복사가 칸을 하나씩
4389
+ * 옮기는 자리라, 여기 없는 칸은 리듀서에 앉았어도 **스냅샷에 나오지 않는다**(2026-09-16 에 `status` 를
4390
+ * 넣다가 `nonconformance` 도 여기서 빠져 있던 것을 찾았다 — 자리는 있고 길이 없는 부류). 있을 때만 옮긴다.
4391
+ */
4392
+ ...it.nonconformance ? { nonconformance: it.nonconformance } : {},
4393
+ ...it.status ? { status: it.status } : {}
4371
4394
  });
4372
4395
  }
4373
4396
  for (const m of snap.equipment) {
@@ -6774,7 +6797,10 @@ var FlowEngine = class {
6774
6797
  ...i.uom ? { uom: i.uom } : {},
6775
6798
  ...i.quantities?.length ? { quantities: i.quantities } : {},
6776
6799
  ...i.expiry !== void 0 ? { expiry: i.expiry } : {},
6777
- ...i.ilmd ? { ilmd: i.ilmd } : {}
6800
+ ...i.ilmd ? { ilmd: i.ilmd } : {},
6801
+ /* 로트의 두 표준 칸(§FlowItem) — 여기서 빠지면 리듀서·엔진에는 있고 스냅샷에는 없다. */
6802
+ ...i.nonconformance ? { nonconformance: i.nonconformance } : {},
6803
+ ...i.status ? { status: i.status } : {}
6778
6804
  };
6779
6805
  }
6780
6806
  /**
@@ -7123,7 +7149,7 @@ var WmsKernel = class extends FlowEngine {
7123
7149
  const requested = lines.reduce((s, l) => s + l.qty, 0);
7124
7150
  const order = {
7125
7151
  id,
7126
- kind: "outbound",
7152
+ kind: WMS_ORDER_KIND.outbound,
7127
7153
  status: "created",
7128
7154
  requested,
7129
7155
  fulfilled: 0,
@@ -7159,7 +7185,7 @@ var WmsKernel = class extends FlowEngine {
7159
7185
  }
7160
7186
  if (chosenAll.length === 0) return this.makeShortLines(o);
7161
7187
  this.reserve(chosenAll, BIZSTEP.storing);
7162
- this.emit(transactionEvent({ eventTime: this.now(), action: "ADD", bizStep: BIZSTEP.picking, bizTransactionList: [{ type: BTT.so, bizTransaction: o.bizTransaction }], epcList: chosenAll.slice() }));
7188
+ this.emit(transactionEvent({ eventTime: this.now(), action: "ADD", bizStep: BIZSTEP.picking, bizTransactionList: [{ type: BTT.poc, bizTransaction: o.bizTransaction }], epcList: chosenAll.slice() }));
7163
7189
  for (const epc of chosenAll) {
7164
7190
  const it = this.items.get(epc);
7165
7191
  const id = `task-${++this.taskSeq}`;
@@ -7310,7 +7336,7 @@ var WmsKernel = class extends FlowEngine {
7310
7336
  const shpSeq = ++this.epcSeq;
7311
7337
  const shipment = this.requireObjectId(`SHP-${shpSeq}`);
7312
7338
  order.shipmentEpc = shipment;
7313
- const soTxn = [{ type: BTT.so, bizTransaction: order.bizTransaction }];
7339
+ const soTxn = [{ type: BTT.poc, bizTransaction: order.bizTransaction }];
7314
7340
  this.aggregate(shipment, order.picked.slice(), { bizStep: BIZSTEP.packing, readPoint: staging.id, bizLocation: staging.id });
7315
7341
  order.status = "packed";
7316
7342
  this.emit(objectEvent({ eventTime, action: "OBSERVE", bizStep: BIZSTEP.staging_outbound, disposition: DISP.reserved, epcList: [shipment], readPoint: staging.id, bizLocation: staging.id, bizTransactionList: soTxn }));
@@ -7450,7 +7476,7 @@ var YmsKernel = class extends FlowEngine {
7450
7476
  this.trailerCargo.set(trailer.epc, avail.slice(0, CARGO_PER_TRAILER).map((i) => i.epc));
7451
7477
  }
7452
7478
  this.reserve([trailer.epc], bizStep);
7453
- this.emit(transactionEvent({ eventTime: this.now(), action: "ADD", bizStep, bizTransactionList: [{ type: BTT_DELIVERY, bizTransaction: o.bizTransaction }], epcList: [trailer.epc], readPoint: door.id }));
7479
+ this.emit(transactionEvent({ eventTime: this.now(), action: "ADD", bizStep, bizTransactionList: [{ bizTransaction: o.bizTransaction }], epcList: [trailer.epc], readPoint: door.id }));
7454
7480
  const dockKind = mode === "drop" ? "pull" : "spot-live";
7455
7481
  const task = { id: `task-${++this.taskSeq}`, kind: dockKind, status: "created", itemEpc: trailer.epc, fromNode: trailer.location, toNode: door.id, resource: null, remainingMs: 0, durationMs: this.durationOf({ kind: dockKind, fromNode: trailer.location, toNode: door.id }, TRAVEL_MS2), orderId: o.id };
7456
7482
  this.tasks.set(task.id, task);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/twin-kernel",
3
- "version": "0.11.12",
3
+ "version": "0.11.15",
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": {
@@ -28,6 +28,6 @@
28
28
  "test": "node --test test/*.test.ts"
29
29
  },
30
30
  "dependencies": {
31
- "@operato/ops-contract": "^0.9.20"
31
+ "@operato/ops-contract": "^0.9.24"
32
32
  }
33
33
  }