@operato/twin-kernel 0.7.55 → 0.7.57

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.
@@ -132,6 +132,7 @@ __export(index_exports, {
132
132
  isTransformationRecord: () => isTransformationRecord,
133
133
  isoDurationHours: () => isoDurationHours,
134
134
  itemKeyOf: () => itemKeyOf,
135
+ judgeAgainstSpec: () => judgeAgainstSpec,
135
136
  levelOfLocationType: () => levelOfLocationType,
136
137
  lgtinClass: () => lgtinClass,
137
138
  locationStatusOf: () => locationStatusOf,
@@ -325,6 +326,19 @@ function testEvidenceGaps(spec, result) {
325
326
  unmatched: measurements.map((m) => m.testableObjectPropertyId).filter((k) => !!k && !wanted.has(k))
326
327
  };
327
328
  }
329
+ function judgeAgainstSpec(spec, result) {
330
+ const criteria = (spec?.criteria ?? []).filter((c) => !criterionSaysNothing(c));
331
+ if (!criteria.length) return void 0;
332
+ const measurements = result?.propertyMeasurements ?? [];
333
+ let allJudged = true;
334
+ for (const c of criteria) {
335
+ const m = c.evaluatedPropertyId ? measurements.find((x) => x.testableObjectPropertyId === c.evaluatedPropertyId) : void 0;
336
+ const out = outsideLimit(c, m);
337
+ if (out === true) return "fail";
338
+ if (out === void 0) allJudged = false;
339
+ }
340
+ return allJudged ? "pass" : void 0;
341
+ }
328
342
  function effectivityAt(p, at) {
329
343
  if (!p || !at) return void 0;
330
344
  const atMs = parsedMs(at);
@@ -587,6 +601,19 @@ var OP_EVENT = {
587
601
  order: "order.status",
588
602
  quality: "quality.output",
589
603
  // 품질 산출(양품/불량) — OEE quality 입력. live 누적기가 이걸로 good/scrap 정확 추적.
604
+ /**
605
+ * **시험 결과** — 어느 대상을 어느 기준으로 재고 판정했나.
606
+ *
607
+ * `quality.output` 과 **다른 사실이다.** 그것은 생산의 양품·불량 수(가동률 입력)이고, 이것은 선언된
608
+ * 기준에 대한 검사 판정이다. 한 낱말이 두 일을 하면 어느 쪽 어휘도 옳지 않게 된다.
609
+ *
610
+ * **대상을 가리킨다**(`testableObjectId`) — 표준 `TestResult.TestableObjectID` 그대로. 로트·설비·
611
+ * 사람·자리에 두루 쓰이므로 주체를 이름에 넣지 않았다.
612
+ *
613
+ * 이 채널이 없으면 시험 결과는 **상태에만 있는 축**이 된다 — 재기동에서 사라지고, 폴드가 되살릴 수
614
+ * 없고, 미러가 이어받지 못한다(§상태 ⊆ 이벤트).
615
+ */
616
+ test: "test.result",
590
617
  /**
591
618
  * 주목 신호 확인(ack) — **사람이 한 행위**라 파생될 수 없다.
592
619
  *
@@ -599,7 +626,19 @@ var OP_EVENT = {
599
626
  * "확인해라"(요청)이고 이벤트는 "확인했다"(사실)다. 같은 문자열을 쓰면 저널에서 요청과 사실이
600
627
  * 구별되지 않는다.
601
628
  */
602
- attentionAck: "attention.acked"
629
+ attentionAck: "attention.acked",
630
+ /**
631
+ * **자리에서 관측된 물리량** — 냉장실 온도·습도, 세척수 유량 같은 것(§`LocationObservation`).
632
+ *
633
+ * ── 왜 채널이 필요한가 ────────────────────────────────────────────────────
634
+ * `LocationState.observations` 를 상태에 두었는데 그것을 낳는 사건이 없었다. 그러면 **상태 ⊆ 이벤트**
635
+ * 가 깨진다: 채워도 재기동에서 사라지고, 폴드가 되살릴 수 없고, 미러가 이어받을 수도 없다. 상태에만
636
+ * 있는 축은 조용히 사라지는 축이다.
637
+ *
638
+ * 이름을 `energy.measured` 와 같은 결로 둔다 — 그쪽이 에너지 계량의 도착이고 이쪽이 그 일반형이다.
639
+ * 두 채널을 합치지 않는 이유는 에너지 쪽이 **구간에 누적되는 표본**이라 처리가 다르기 때문이다.
640
+ */
641
+ observation: "location.measured"
603
642
  };
604
643
  var ENERGY_EVENT = {
605
644
  /** 계량 도착 — 그 시점의 유효전력·누적량. 15분 수요 구간에 누적된다. */
@@ -971,14 +1010,75 @@ var DISP = {
971
1010
  sellable: "urn:epcglobal:cbv:disp:sellable_accessible",
972
1011
  reserved: "urn:epcglobal:cbv:disp:reserved",
973
1012
  in_transit: "urn:epcglobal:cbv:disp:in_transit",
974
- non_sellable: "urn:epcglobal:cbv:disp:non_sellable_other"
1013
+ non_sellable: "urn:epcglobal:cbv:disp:non_sellable_other",
975
1014
  // 불량/scrap
1015
+ /**
1016
+ * **기한이 지났다** — CBV `expired`.
1017
+ *
1018
+ * ── 왜 `non_sellable` 로 접지 않나 (2026-08-24) ─────────────────────────────
1019
+ * 커널의 `non_sellable` 은 CBV 의 `non_sellable_other`, 즉 **「그 밖의 이유」**다. 기한 지남을 거기
1020
+ * 넣으면 「기한이 지나 못 판다」와 「깨져서 못 판다」가 같은 값이 되고, 화면은 회수·폐기의 사유를
1021
+ * 구별할 수 없다. 식품에서 그 둘은 다른 조치다.
1022
+ *
1023
+ * 그리고 표준에 **정확한 낱말이 있다** — 접는 것은 있는 낱말을 버리는 것이다.
1024
+ *
1025
+ * ── 기한 날짜와 다른 축이다 ────────────────────────────────────────────────
1026
+ * `ItemState.expiry` 는 **날짜**이고 이것은 **상태**다. 날짜가 있으면 「지났나」는 파생이지만, 원본이
1027
+ * 「기한 지남」을 상태로 선언하는 시스템이 있다 — 그때 이 값은 관측이다.
1028
+ *
1029
+ * 둘이 어긋나면(날짜는 남았는데 상태가 지남, 또는 그 반대) **어느 쪽이 맞다고 정하지 않는다** —
1030
+ * 아직 그 판정을 세울 근거가 없다. 어긋남의 구분을 없애지 않는 것이 지금의 규율이다.
1031
+ *
1032
+ * ── 원문으로 확인했다 (2026-08-24) ────────────────────────────────────────
1033
+ * 1차 출처: **CBV Standard Release 2.0, Ratified Jun 2022** §7.2.3 처분 값 표(38개). 이 객체의
1034
+ * 다른 값들(`in_progress`·`sellable_accessible`·`reserved`·`in_transit`·`non_sellable_other`)도
1035
+ * 그 표에 있다.
1036
+ *
1037
+ * **`non_sellable_expired` 를 쓰지 않는 이유**: 그 값은 CBV 1.0 의 것이고 표준이 **폐기**했다 —
1038
+ * 「deprecated in favour of new disposition values expired, damaged, disposed, … introduced in
1039
+ * CBV 1.1」. 폐기된 값을 쓰면 새 소비처가 읽지 못한다.
1040
+ *
1041
+ * 참고: GS1 어휘 등록처(`ref.gs1.org/cbv/…`)로는 확인할 수 없었다 — **없는 값에도 같은 응답**을
1042
+ * 준다(지어낸 값의 JSON-LD 가 실재 값과 바이트까지 같았다). 그 경로를 근거로 삼지 말 것.
1043
+ */
1044
+ expired: "urn:epcglobal:cbv:disp:expired",
1045
+ /**
1046
+ * **검사에 합격했다 / 불합격했다** — CBV `conformant` / `non_conformant`.
1047
+ *
1048
+ * 1차 출처(CBV 2.0 §7.2.3) 정의 그대로다.
1049
+ *
1050
+ * conformant Outcome of a successful/passed inspection in an inspecting or repairing step
1051
+ * non_conformant Outcome of an unsuccessful/failed inspection in an inspecting or repairing step
1052
+ *
1053
+ * ── 왜 시험 결과 축을 자원에 더하지 않고 이것을 쓰나 (2026-08-24) ────────────
1054
+ * 로트의 검사 판정을 담을 자리를 찾다가 `ItemState.testResults` 를 더하려 했다. 그런데 표준은 그
1055
+ * 사실을 **이미 처분으로 말한다**: `bizStep: inspecting` 사건에 이 처분이 붙는다.
1056
+ *
1057
+ * 처분을 쓰면 두 가지가 공짜로 성립한다.
1058
+ * ① **상태 ⊆ 이벤트** — 처분은 이미 사건에서 온다. 상태에만 있는 축을 만들지 않는다
1059
+ * ② **운영에 곧 닿는다** — 「이 자재를 쓸 수 있나」가 처분으로 답해진다(판정을 따로 읽지 않는다)
1060
+ *
1061
+ * 시험의 **자세한 내용**(어느 명세로, 무엇을 재어)은 다른 물음이고, 표준은 그것을 `TestResult` 로
1062
+ * 두며 결과가 대상을 가리킨다(`TestableObjectID`) — 대상이 결과를 들지 않는다. 그 축이 필요해지면
1063
+ * 그때 열되, **판정 자체는 여기서 끝난다.**
1064
+ */
1065
+ conformant: "urn:epcglobal:cbv:disp:conformant",
1066
+ non_conformant: "urn:epcglobal:cbv:disp:non_conformant"
976
1067
  };
977
1068
  var CBV_BIZSTEP = {
978
1069
  /** 공정에 자재가 들어갔다 — ISA-95 `MaterialUse: Consumed`. */
979
1070
  consuming: "urn:epcglobal:cbv:bizstep:consuming",
980
1071
  /** 새 물품이 생겨 계보가 시작된다 — ISA-95 `MaterialUse: Produced`. */
981
- commissioning: "urn:epcglobal:cbv:bizstep:commissioning"
1072
+ commissioning: "urn:epcglobal:cbv:bizstep:commissioning",
1073
+ /**
1074
+ * **검사** — CBV `inspecting`. 1차 출처(CBV 2.0) 정의: 「Process of reviewing objects to address
1075
+ * potential physical or documentation defects」이고, 「표본과 달리 검사된 대상은 그대로 남는다」고
1076
+ * 이어진다(즉 검사는 물건을 소비하지 않는다).
1077
+ *
1078
+ * 이 단계에 `DISP.conformant`/`DISP.non_conformant` 가 붙어 판정이 처분으로 남는다 — 입고검수·
1079
+ * 공정 중 검사가 그 모양이다.
1080
+ */
1081
+ inspecting: "urn:epcglobal:cbv:bizstep:inspecting"
982
1082
  };
983
1083
  function ssccUri(companyPrefix, serial) {
984
1084
  return `urn:epc:id:sscc:${companyPrefix}.${String(serial).padStart(10, "0")}`;
@@ -1256,6 +1356,8 @@ function effectiveOf(r) {
1256
1356
  var ObservedReducer = class {
1257
1357
  /** 로케이션 마스터 — 출처를 함께 들고 있다(마스터가 말한 자리 vs 관측으로 알게 된 자리). */
1258
1358
  master = /* @__PURE__ */ new Map();
1359
+ /** 자리별 · 속성별 **마지막 관측** — 이력이 아니다(§`OP_EVENT.observation`). */
1360
+ observations = /* @__PURE__ */ new Map();
1259
1361
  items = /* @__PURE__ */ new Map();
1260
1362
  aggregation = /* @__PURE__ */ new Map();
1261
1363
  /** 아직 관측되지 않은 자식의 담김 — 물품을 지어내지 않고 보류했다가 등장할 때 붙인다. */
@@ -1282,6 +1384,8 @@ var ObservedReducer = class {
1282
1384
  corrections = [];
1283
1385
  /** 반영하지 못한 사건의 종류별 집계 — 원문은 쌓지 않는다(저널에 이미 있다). */
1284
1386
  unhandled = /* @__PURE__ */ new Map();
1387
+ /** 선언된 판정 기준(보드에서 한 번 읽는다) — 판정은 선언한 것에만 걸린다. */
1388
+ testSpecs = /* @__PURE__ */ new Map();
1285
1389
  /**
1286
1390
  * 자원별 **교대 선언** — 미러가 "지금 근무 중인가" 를 스스로 판정하기 위한 재료.
1287
1391
  *
@@ -1301,6 +1405,7 @@ var ObservedReducer = class {
1301
1405
  constructor(model) {
1302
1406
  this.utcOffsetMinutes = model.utcOffsetMinutes;
1303
1407
  this.classDefs = { personnel: model.personnelClasses, equipment: model.equipmentClasses, asset: model.assetClasses };
1408
+ for (const sp of model.testSpecifications ?? []) this.testSpecs.set(sp.id, sp);
1304
1409
  for (const n of readBoardLocations(model)) this.master.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, parallelism: n.parallelism, parentId: n.parentId, origin: "master" });
1305
1410
  for (const m of readBoardEquipment(model)) this.equipment.set(m.id, { id: m.id, kind: m.kind, status: "idle", location: m.homeLocation, homeLocation: m.homeLocation, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, ...m.testResults ? { testResults: m.testResults } : {}, ...effectiveOf(m), origin: "master" });
1306
1411
  for (const p of model.persons ?? []) this.persons.set(p.id, { id: p.id, personnelClassIds: p.personnelClassIds, status: "idle", ...p.homeLocation ? { location: p.homeLocation } : {}, ...p.properties ? { properties: p.properties } : {}, ...p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}, ...p.testResults ? { testResults: p.testResults } : {}, ...effectiveOf(p) });
@@ -1524,6 +1629,32 @@ var ObservedReducer = class {
1524
1629
  this.touchLocation(d.location);
1525
1630
  break;
1526
1631
  }
1632
+ case OP_EVENT.observation: {
1633
+ const d = e.data;
1634
+ if (!d?.locationId || !d?.propertyId) break;
1635
+ if (this.stale(`observation:${d.locationId}:${d.propertyId}`, e)) return;
1636
+ this.touchLocation(d.locationId);
1637
+ const bin = this.observations.get(d.locationId) ?? /* @__PURE__ */ new Map();
1638
+ bin.set(d.propertyId, { ...d });
1639
+ this.observations.set(d.locationId, bin);
1640
+ break;
1641
+ }
1642
+ case OP_EVENT.test: {
1643
+ const d = e.data;
1644
+ if (!d?.testableObjectId || !d?.specId) break;
1645
+ if (this.stale(`test:${d.testableObjectId}:${d.specId}`, e)) return;
1646
+ const item = this.items.get(d.testableObjectId);
1647
+ if (!item) break;
1648
+ const { testableObjectId: _target, ...rest } = d;
1649
+ const judged = rest.result === void 0 ? judgeAgainstSpec(this.testSpecs.get(d.specId), rest) : void 0;
1650
+ const next = {
1651
+ ...rest,
1652
+ ...judged ? { result: judged, derived: true } : {}
1653
+ };
1654
+ const kept = (item.testResults ?? []).filter((r) => r.specId !== d.specId);
1655
+ item.testResults = [...kept, next];
1656
+ break;
1657
+ }
1527
1658
  case OP_EVENT.attentionAck: {
1528
1659
  const d = e.data;
1529
1660
  if (d?.id) this.acked.add(d.id);
@@ -1846,6 +1977,8 @@ var ObservedReducer = class {
1846
1977
  */
1847
1978
  serialize() {
1848
1979
  return {
1980
+ /* 관측도 재개점에 든다 — 없으면 이어 접기가 그 축을 0부터 다시 만든다. */
1981
+ observations: [...this.observations.entries()].map(([id, bin]) => ({ id, values: [...bin.values()] })),
1849
1982
  revision: this.revision,
1850
1983
  master: [...this.master.values()].map((n) => ({ ...n })),
1851
1984
  items: [...this.items.values()].map((i) => ({ ...i })),
@@ -1870,6 +2003,9 @@ var ObservedReducer = class {
1870
2003
  * 모델에서 오는 판정 입력(교대·등급·시간대)은 생성자가 이미 세웠으므로 건드리지 않는다.
1871
2004
  */
1872
2005
  restore(cp) {
2006
+ this.observations = new Map(
2007
+ (cp?.observations ?? []).map((o) => [o.id, new Map((o.values ?? []).map((v) => [v.propertyId, { ...v }]))])
2008
+ );
1873
2009
  this.revision = cp?.revision ?? 0;
1874
2010
  this.master = new Map((cp?.master ?? []).map((n) => [n.id, { ...n }]));
1875
2011
  this.items = new Map((cp?.items ?? []).map((i) => [i.epc, { ...i }]));
@@ -1905,6 +2041,11 @@ var ObservedReducer = class {
1905
2041
  ...n.capacity === void 0 ? {} : { capacity: n.capacity },
1906
2042
  ...n.parallelism === void 0 ? {} : { parallelism: n.parallelism },
1907
2043
  ...n.parentId ? { parentId: n.parentId } : {},
2044
+ /* 관측된 물리량 — 없으면 **키를 만들지 않는다**(빈 배열은 「센서가 없다」와 「못 들었다」를 같게 만든다). */
2045
+ ...(() => {
2046
+ const bin = this.observations.get(n.id);
2047
+ return bin?.size ? { observations: [...bin.values()] } : {};
2048
+ })(),
1908
2049
  origin: n.origin
1909
2050
  };
1910
2051
  }),
@@ -3421,6 +3562,34 @@ function deriveAttentions(view, acked, nowIso, thresholds) {
3421
3562
  }
3422
3563
  }
3423
3564
  for (const n of view.locations) {
3565
+ for (const c of n.criteria ?? []) {
3566
+ const propertyId = c.evaluatedPropertyId;
3567
+ if (!propertyId) continue;
3568
+ const observed = (n.observations ?? []).find((o) => o.propertyId === propertyId);
3569
+ if (!observed) continue;
3570
+ if (outsideLimit(c, observed) !== true) continue;
3571
+ out.push({
3572
+ /* 자리·기준마다 하나 — 같은 방의 온도와 습도가 한 신호로 뭉치지 않는다. */
3573
+ id: `observation-out-of-limit:${n.id}:${c.id}`,
3574
+ kind: "observation-out-of-limit",
3575
+ severity: "high",
3576
+ anchor: { locationId: n.id },
3577
+ /* 언어중립 원시값만 — 문장은 표현계층이 kind 로 골라 렌더한다. */
3578
+ params: {
3579
+ locationId: n.id,
3580
+ criterionId: c.id,
3581
+ propertyId,
3582
+ /* 값이 없으면 판정이 서지 않으므로 여기 오지 않는다 — 그래도 타입을 좁혀 둔다. */
3583
+ value: observed.value ?? "",
3584
+ ...observed.uom ? { uom: observed.uom } : {},
3585
+ ...c.limit?.minimum !== void 0 ? { minimum: c.limit.minimum } : {},
3586
+ ...c.limit?.maximum !== void 0 ? { maximum: c.limit.maximum } : {},
3587
+ /* **언제의 값인가** — 4개월 전 값으로 지금을 판정한 것인지 소비처가 알아야 한다. */
3588
+ effectiveTime: observed.effectiveTime,
3589
+ ...observed.derived ? { derived: "true" } : {}
3590
+ }
3591
+ });
3592
+ }
3424
3593
  if ((n.capacity ?? 0) > 0) {
3425
3594
  const r = (n.occupancy ?? 0) / n.capacity;
3426
3595
  const declaredPct = thresholds?.congestionPctOf?.(n.id);
@@ -3801,6 +3970,47 @@ var FlowEngine = class {
3801
3970
  localParams = /* @__PURE__ */ new Map();
3802
3971
  /** 관측 구동(P0) — 이벤트를 접는 투영기와 그 사실. tick 과 섞이지 않게 명시적으로 들고 있다. */
3803
3972
  observer;
3973
+ /**
3974
+ * **관측 리듀서의 재개점을 꺼낸다** — 호스트가 저장해 다음 기동에서 되돌릴 수 있게.
3975
+ *
3976
+ * ── 무엇이 문제였나 (2026-08-24 실측) ──────────────────────────────────────
3977
+ * 미러는 재기동마다 저널을 **0부터** 다시 집계했다. 실측으로 저널이 2,960만 줄이고, 그 때문에 기동
3978
+ * 직후 몇 분간 상태가 비어 있었다. 계측처럼 경계 없이 자라는 흐름이 들어오면 그 몇 분이 몇십 분이
3979
+ * 된다 — 불편이 아니라 벽이다.
3980
+ *
3981
+ * 재개점 자체는 오래전부터 있었다(`serialize`/`restore`). 조회 경로는 그것을 쓰는데
3982
+ * (`replayFrom`) **라이브 경로에는 꺼낼 문이 없었다.** 그래서 호스트가 저장할 수 없었다.
3983
+ *
3984
+ * ── 상태 스냅샷으로는 대신할 수 없다 ──────────────────────────────────────
3985
+ * 리듀서는 소비처가 보는 값 말고도 든다: 부모를 기다리는 담김·집계 중인 수량·담을 줄 몰라 세어 둔
3986
+ * 사건. 상태만 되돌리고 이어 집계하면 **0부터 집계한 결과와 조용히 달라진다**(§`ReducerCheckpoint`).
3987
+ *
3988
+ * 관측 구동이 아니면 `undefined` — 시뮬은 리듀서를 갖지 않는다(저장할 것이 없다).
3989
+ */
3990
+ observedCheckpoint() {
3991
+ return this.observer?.serialize();
3992
+ }
3993
+ /**
3994
+ * **재개점에서 관측 리듀서를 되세운다** — 저널을 0부터 다시 집계하지 않게.
3995
+ *
3996
+ * 리듀서가 아직 없으면 만든다: 미러는 첫 봉투가 올 때 리듀서를 만드는데(§`apply`), 되돌리기는 그보다
3997
+ * 먼저 일어나야 한다(그러지 않으면 첫 봉투가 빈 리듀서를 만들고 되돌린 것을 덮는다).
3998
+ *
3999
+ * 되돌린 뒤 상태로 옮긴다 — 그러지 않으면 첫 스냅샷이 빈 상태를 보인다.
4000
+ *
4001
+ * **구조가 다르면 되돌리지 않는다**: 재개점은 그 모델 위에서 만들어진 것이고, 다른 공장의 재개점을
4002
+ * 얹으면 없는 자리·설비가 생긴다. 판단은 부르는 쪽이 한다(`structureRev` 를 아는 것은 호스트다) —
4003
+ * 여기서는 받은 것을 그대로 세운다.
4004
+ */
4005
+ restoreObserved(cp) {
4006
+ if (!this.observer) {
4007
+ this.observer = new ObservedReducer(this.boardDef ?? { locations: [], equipment: [] });
4008
+ this.observeMode = true;
4009
+ }
4010
+ this.observer.restore(cp);
4011
+ this.observedDirty = true;
4012
+ this.settleObserved();
4013
+ }
3804
4014
  /**
3805
4015
  * 이 커널의 상태가 **관측에서 왔나** — 미러인가.
3806
4016
  *
@@ -4700,10 +4910,23 @@ var FlowEngine = class {
4700
4910
  */
4701
4911
  collectAttentions() {
4702
4912
  const now = this.now();
4913
+ const specById = new Map((this.boardDef?.testSpecifications ?? []).map((sp) => [sp.id, sp]));
4914
+ const criteriaOf = /* @__PURE__ */ new Map();
4915
+ if (specById.size) {
4916
+ for (const n of readBoardLocations(this.boardDef ?? {})) {
4917
+ const ids = n.testSpecificationIds;
4918
+ if (!ids?.length) continue;
4919
+ const cs = ids.flatMap((id) => specById.get(id)?.criteria ?? []);
4920
+ if (cs.length) criteriaOf.set(n.id, cs);
4921
+ }
4922
+ }
4703
4923
  const out = deriveAttentions(
4704
4924
  {
4705
4925
  equipment: [...this.equipment.values()],
4706
- locations: [...this.locations.values()],
4926
+ locations: [...this.locations.values()].map((n) => {
4927
+ const criteria = criteriaOf.get(n.id);
4928
+ return criteria?.length ? { ...n, criteria } : n;
4929
+ }),
4707
4930
  orders: [...this.orders.values()],
4708
4931
  tasks: [...this.tasks.values()]
4709
4932
  },
@@ -9223,6 +9446,7 @@ function retiredVocabularyIn(line) {
9223
9446
  isTransformationRecord,
9224
9447
  isoDurationHours,
9225
9448
  itemKeyOf,
9449
+ judgeAgainstSpec,
9226
9450
  levelOfLocationType,
9227
9451
  lgtinClass,
9228
9452
  locationStatusOf,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/twin-kernel",
3
- "version": "0.7.55",
3
+ "version": "0.7.57",
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": {