@operato/twin-kernel 0.11.20 → 0.11.22
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.
- package/dist/flow-engine.d.ts +2 -1
- package/dist/flow-engine.js +4 -0
- package/dist/mes-kernel.js +10 -2
- package/dist/observed-reducer.js +1 -0
- package/dist-cjs/index.cjs +65 -3
- package/package.json +2 -2
package/dist/flow-engine.d.ts
CHANGED
|
@@ -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, DispositionFact, AllocatedQuantity, MaterialLotUse } from '@operato/ops-contract';
|
|
1
|
+
import type { MaterialActual, RecipeExecutionBasis, 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, AllocatedQuantity, MaterialLotUse } 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';
|
|
@@ -271,6 +271,7 @@ export declare function allocatedQty(rows: readonly AllocatedQuantity[] | undefi
|
|
|
271
271
|
/** 잡아 둔 줄들의 식별자 — EPCIS 이벤트가 싣는 것은 열쇠뿐이다. */
|
|
272
272
|
export declare function allocatedEpcs(rows: readonly AllocatedQuantity[] | undefined): string[];
|
|
273
273
|
export interface FlowOrder {
|
|
274
|
+
recipeBasis?: RecipeExecutionBasis;
|
|
274
275
|
id: string;
|
|
275
276
|
kind: string;
|
|
276
277
|
status: string;
|
package/dist/flow-engine.js
CHANGED
|
@@ -1241,6 +1241,7 @@ export class FlowEngine {
|
|
|
1241
1241
|
...(o.allocated?.length ? { allocated: o.allocated } : {}),
|
|
1242
1242
|
...(o.gtin ? { gtin: o.gtin } : {}),
|
|
1243
1243
|
...(o.recipeKey ? { recipeKey: o.recipeKey } : {}),
|
|
1244
|
+
...(o.recipeBasis ? { recipeBasis: structuredClone(o.recipeBasis) } : {}),
|
|
1244
1245
|
...(o.bizTransaction ? { bizTransaction: o.bizTransaction } : {}),
|
|
1245
1246
|
...(o.dockDoor ? { dockDoor: o.dockDoor } : {}),
|
|
1246
1247
|
...(o.windowStartMs !== undefined ? { windowStartMs: o.windowStartMs } : {})
|
|
@@ -1303,6 +1304,7 @@ export class FlowEngine {
|
|
|
1303
1304
|
/* 레시피도 함께 — 품목만 이어받으면 되살아난 오더가 「무엇으로」를 모르고, 그 오더의
|
|
1304
1305
|
남은 공정·소요가 다른 레시피 기준으로 계산된다(같은 품목에 대체 레시피가 있다). */
|
|
1305
1306
|
...(o.recipeKey ? { recipeKey: o.recipeKey } : {}),
|
|
1307
|
+
...(o.recipeBasis ? { recipeBasis: structuredClone(o.recipeBasis) } : {}),
|
|
1306
1308
|
bizTransaction: o.bizTransaction ?? '', allocated: resolved.kept, picked: [], shipmentEpc: null,
|
|
1307
1309
|
/* 라인도 같다 — 씨앗은 남은 양만 남기고, 미러는 원본이 말한 요청·이행을 그대로 든다. */
|
|
1308
1310
|
lines: observing ? (o.lines ?? []).map(l => ({ ...l })) : lines,
|
|
@@ -1820,6 +1822,7 @@ export class FlowEngine {
|
|
|
1820
1822
|
...(o.gtin ? { gtin: o.gtin } : {}),
|
|
1821
1823
|
/* **어느 레시피로 만드나** — 품목만으로는 「무엇으로」가 안 남는다(같은 품목의 대체 레시피가 있다). */
|
|
1822
1824
|
...(o.recipeKey ? { recipeKey: o.recipeKey } : {}),
|
|
1825
|
+
...(o.recipeBasis ? { recipeBasis: structuredClone(o.recipeBasis) } : {}),
|
|
1823
1826
|
/* 씨앗이 다 심지 못했다는 사실 — 조용히 자르지 않는다(그 오더의 답은 부족한 씨앗 위에 있다). */
|
|
1824
1827
|
...(o.seedIncomplete ? { seedIncomplete: true } : {}),
|
|
1825
1828
|
/* 왜 대기하는지 — 없으면 화면은 「running」만 보여 주고 사람은 원인을 찾을 자리가 없다. */
|
|
@@ -2989,6 +2992,7 @@ export class FlowEngine {
|
|
|
2989
2992
|
/* 어느 레시피로 만드는가 — 품목만 보내면 미러는 「무엇으로」를 알 수 없다(같은 품목에 대체
|
|
2990
2993
|
레시피가 있다). 시뮬만 아는 사실이 남으면 두 스냅샷을 같은 규칙으로 읽을 수 없다. */
|
|
2991
2994
|
...(o.recipeKey ? { recipeKey: o.recipeKey } : {}),
|
|
2995
|
+
...(o.recipeBasis ? { recipeBasis: structuredClone(o.recipeBasis) } : {}),
|
|
2992
2996
|
...(o.allocated?.length ? { allocated: o.allocated.slice() } : {}),
|
|
2993
2997
|
...(o.bizTransaction ? { bizTransaction: o.bizTransaction } : {}),
|
|
2994
2998
|
/* 약속해 둔 자리·시각창 — 이것이 빠져서 야드 트윈이 재기동마다 첫 틱에 죽었다(§dockDoor). */
|
package/dist/mes-kernel.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* ③ 셋업/체인지오버 = work-center 가 제품 전환 시 셋업(changeoverKey=제품 gtin) → OEE Availability.
|
|
9
9
|
* ④ OEE = base 가 설비(설비)별 계측(가동/셋업/기아/품질). cut=cutter·weld=welder 이종 자원.
|
|
10
10
|
*/
|
|
11
|
-
import { identityGroundingOf, procedureViolations } from '@operato/ops-contract';
|
|
11
|
+
import { identityGroundingOf, procedureViolations, isRecipeExecutionBasis } from '@operato/ops-contract';
|
|
12
12
|
import { firstFitPolicy } from "./allocation-policy.js";
|
|
13
13
|
import { FlowEngine, allocatedEpcs, allocatedQty } from "./flow-engine.js";
|
|
14
14
|
import { DISP, objectEvent, transactionEvent, gdtiUri, sgtinClass, bizTransactionUri } from '@operato/ops-contract';
|
|
@@ -472,6 +472,11 @@ export class MesKernel extends FlowEngine {
|
|
|
472
472
|
* 그 뒤 계보·재고·수율이 전부 엉뚱한 품목에 붙는다.
|
|
473
473
|
*/
|
|
474
474
|
recipeDef(order) {
|
|
475
|
+
if (order?.recipeBasis !== undefined) {
|
|
476
|
+
if (!isRecipeExecutionBasis(order.recipeBasis) || (order.recipeKey && order.recipeKey !== order.recipeBasis.recipe.key))
|
|
477
|
+
throw new Error('Invalid pinned recipe evidence; refusing to substitute the current master.');
|
|
478
|
+
return order.recipeBasis.recipe;
|
|
479
|
+
}
|
|
475
480
|
const d = this.productionSpec.definition;
|
|
476
481
|
const key = order?.recipeKey ?? this.productionSpec.recipeKey;
|
|
477
482
|
if (key) {
|
|
@@ -658,7 +663,10 @@ export class MesKernel extends FlowEngine {
|
|
|
658
663
|
/** recipe.route → 오퍼레이션 시퀀스 해소. 오더가 자기 레시피를 들면 그 라우트다. */
|
|
659
664
|
routeOps(order) {
|
|
660
665
|
const d = this.productionSpec.definition;
|
|
661
|
-
const
|
|
666
|
+
const recipe = this.recipeDef(order);
|
|
667
|
+
const route = order?.recipeBasis ? order.recipeBasis.route : d.routes?.find(r => r.key === recipe.route);
|
|
668
|
+
if (order?.recipeBasis && route?.steps.some(key => !d.operations?.some(operation => operation.key === key)))
|
|
669
|
+
throw new Error('A captured recipe route references an unavailable operation; refusing a shortened route.');
|
|
662
670
|
return (route?.steps ?? []).map(sk => d.operations?.find(o => o.key === sk)).filter((o) => !!o);
|
|
663
671
|
}
|
|
664
672
|
/** 정의 모드 수령 — skuMix gtin 이 레시피 입력 자재면 **그 자재가 선언한 자리**에 생성. */
|
package/dist/observed-reducer.js
CHANGED
|
@@ -722,6 +722,7 @@ export class ObservedReducer {
|
|
|
722
722
|
...(d.gtin ? { gtin: d.gtin } : {}),
|
|
723
723
|
/* 어느 레시피로 만드는가 — 품목만으로는 「무엇으로」가 남지 않는다(같은 품목에 대체 레시피). */
|
|
724
724
|
...(d.recipeKey ? { recipeKey: d.recipeKey } : {}),
|
|
725
|
+
...(d.recipeBasis ? { recipeBasis: structuredClone(d.recipeBasis) } : {}),
|
|
725
726
|
...(d.allocated?.length ? { allocated: d.allocated.slice() } : {}),
|
|
726
727
|
...(d.bizTransaction ? { bizTransaction: d.bizTransaction } : {}),
|
|
727
728
|
/* 이행하는 계획 — 「이 계획이 얼마나 됐나」를 이 값으로 묶어 답한다(§`OrderState.operationsRequestId`). */
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -328,8 +328,36 @@ function outsideLimit(criterion, observed) {
|
|
|
328
328
|
return true;
|
|
329
329
|
return false;
|
|
330
330
|
}
|
|
331
|
+
function isTestSpecificationBasis(value) {
|
|
332
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
333
|
+
return false;
|
|
334
|
+
const basis = value;
|
|
335
|
+
if (!["id", "revisionId", "planId", "contentHash", "businessTime"].every((key) => typeof basis[key] === "string" && basis[key].trim()))
|
|
336
|
+
return false;
|
|
337
|
+
if (!Number.isFinite(Date.parse(basis.businessTime)))
|
|
338
|
+
return false;
|
|
339
|
+
return Array.isArray(basis.criteria) && basis.criteria.every((c) => {
|
|
340
|
+
if (!c || typeof c !== "object" || Array.isArray(c) || typeof c.id !== "string" || !c.id.trim())
|
|
341
|
+
return false;
|
|
342
|
+
if (["description", "expression", "evaluatedPropertyId"].some((key) => c[key] !== void 0 && typeof c[key] !== "string"))
|
|
343
|
+
return false;
|
|
344
|
+
if (c.limit === void 0)
|
|
345
|
+
return true;
|
|
346
|
+
const limit = c.limit;
|
|
347
|
+
if (!limit || typeof limit !== "object" || Array.isArray(limit))
|
|
348
|
+
return false;
|
|
349
|
+
if (["minimum", "maximum"].some((key) => limit[key] !== void 0 && (typeof limit[key] !== "number" || !Number.isFinite(limit[key]))))
|
|
350
|
+
return false;
|
|
351
|
+
if (limit.uom !== void 0 && typeof limit.uom !== "string")
|
|
352
|
+
return false;
|
|
353
|
+
return !(limit.minimum !== void 0 && limit.maximum !== void 0 && limit.minimum > limit.maximum);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
331
356
|
function judgeAgainstSpec(spec, result) {
|
|
332
|
-
const
|
|
357
|
+
const basis = result?.specificationBasis;
|
|
358
|
+
if (basis !== void 0 && !isTestSpecificationBasis(basis))
|
|
359
|
+
return void 0;
|
|
360
|
+
const criteria = (basis ? basis.criteria : spec?.criteria ?? []).filter((c) => !criterionSaysNothing(c));
|
|
333
361
|
if (!criteria.length)
|
|
334
362
|
return void 0;
|
|
335
363
|
const measurements = result?.propertyMeasurements ?? [];
|
|
@@ -607,6 +635,14 @@ function capabilityOf(r, ctx) {
|
|
|
607
635
|
return { available: false, reason: "working" };
|
|
608
636
|
return { available: true, reason: "available" };
|
|
609
637
|
}
|
|
638
|
+
function isRecipeExecutionBasis(value) {
|
|
639
|
+
if (!value || typeof value !== "object")
|
|
640
|
+
return false;
|
|
641
|
+
const v = value;
|
|
642
|
+
const text = (x) => typeof x === "string" && x.trim().length > 0;
|
|
643
|
+
const lines = (x) => Array.isArray(x) && x.length > 0 && x.every((row) => row && text(row.material) && typeof row.qty === "number" && Number.isFinite(row.qty) && row.qty > 0 && (row.operation === void 0 || text(row.operation)));
|
|
644
|
+
return [v.basisId, v.recipeId, v.revisionId, v.planId, v.contentHash, v.businessTime].every(text) && /(?:Z|[+-]\d{2}:\d{2})$/.test(v.businessTime) && Number.isFinite(Date.parse(v.businessTime)) && !!v.recipe && text(v.recipe.key) && text(v.recipe.label) && lines(v.recipe.inputs) && lines(v.recipe.outputs) && (v.recipe.route === void 0 ? v.route === void 0 && v.recipe.inputs.every((line) => !line.operation) : text(v.recipe.route) && !!v.route && v.route.key === v.recipe.route && Array.isArray(v.route.steps) && v.route.steps.every(text) && v.recipe.inputs.every((line) => !line.operation || v.route.steps.includes(line.operation)));
|
|
645
|
+
}
|
|
610
646
|
var OP_EVENT = {
|
|
611
647
|
task: "task.status",
|
|
612
648
|
equipment: "equipment.status",
|
|
@@ -1986,13 +2022,19 @@ var SPECS = {
|
|
|
1986
2022
|
*
|
|
1987
2023
|
* 새 어휘가 아니다 — 계약이 `recipeKey` 를 네 자리에서 이미 쓴다. 오더 관측에만 자리가 없었다.
|
|
1988
2024
|
*/
|
|
1989
|
-
recipeKey: "string"
|
|
2025
|
+
recipeKey: "string",
|
|
2026
|
+
recipeBasis: "object"
|
|
1990
2027
|
},
|
|
1991
2028
|
/*
|
|
1992
2029
|
* 확보분의 안쪽을 본다 — 열쇠와 수 둘이다(§`AllocatedQuantity`). 안 보면 틀린 이름이 조용히
|
|
1993
2030
|
* 지나가고(`epcs`·`quantity` 따위), 미러는 잡힌 수를 0 으로 읽는다.
|
|
1994
2031
|
*/
|
|
1995
2032
|
shapes: {
|
|
2033
|
+
recipeBasis: {
|
|
2034
|
+
required: ["basisId", "recipeId", "revisionId", "planId", "contentHash", "businessTime", "recipe"],
|
|
2035
|
+
fields: { basisId: "string", recipeId: "string", revisionId: "string", planId: "string", contentHash: "string", businessTime: "string" }
|
|
2036
|
+
// Owned composition and captured route are additionally checked by isRecipeExecutionBasis below.
|
|
2037
|
+
},
|
|
1996
2038
|
allocated: {
|
|
1997
2039
|
required: ["epc", "qty"],
|
|
1998
2040
|
fields: { epc: "string", qty: "number" },
|
|
@@ -2156,8 +2198,15 @@ var SPECS = {
|
|
|
2156
2198
|
expiresAt: "string",
|
|
2157
2199
|
derived: "boolean",
|
|
2158
2200
|
propertyMeasurements: "object[]",
|
|
2201
|
+
specificationBasis: "object",
|
|
2159
2202
|
recordTime: "string"
|
|
2160
2203
|
},
|
|
2204
|
+
shapes: {
|
|
2205
|
+
specificationBasis: {
|
|
2206
|
+
required: ["id", "revisionId", "planId", "contentHash", "businessTime", "criteria"],
|
|
2207
|
+
fields: { id: "string", revisionId: "string", planId: "string", contentHash: "string", businessTime: "string" }
|
|
2208
|
+
}
|
|
2209
|
+
},
|
|
2161
2210
|
enums: { result: ["pass", "fail"] }
|
|
2162
2211
|
},
|
|
2163
2212
|
/*
|
|
@@ -3062,6 +3111,7 @@ var ObservedReducer = class {
|
|
|
3062
3111
|
...d.gtin ? { gtin: d.gtin } : {},
|
|
3063
3112
|
/* 어느 레시피로 만드는가 — 품목만으로는 「무엇으로」가 남지 않는다(같은 품목에 대체 레시피). */
|
|
3064
3113
|
...d.recipeKey ? { recipeKey: d.recipeKey } : {},
|
|
3114
|
+
...d.recipeBasis ? { recipeBasis: structuredClone(d.recipeBasis) } : {},
|
|
3065
3115
|
...d.allocated?.length ? { allocated: d.allocated.slice() } : {},
|
|
3066
3116
|
...d.bizTransaction ? { bizTransaction: d.bizTransaction } : {},
|
|
3067
3117
|
/* 이행하는 계획 — 「이 계획이 얼마나 됐나」를 이 값으로 묶어 답한다(§`OrderState.operationsRequestId`). */
|
|
@@ -4637,6 +4687,7 @@ var FlowEngine = class {
|
|
|
4637
4687
|
...o.allocated?.length ? { allocated: o.allocated } : {},
|
|
4638
4688
|
...o.gtin ? { gtin: o.gtin } : {},
|
|
4639
4689
|
...o.recipeKey ? { recipeKey: o.recipeKey } : {},
|
|
4690
|
+
...o.recipeBasis ? { recipeBasis: structuredClone(o.recipeBasis) } : {},
|
|
4640
4691
|
...o.bizTransaction ? { bizTransaction: o.bizTransaction } : {},
|
|
4641
4692
|
...o.dockDoor ? { dockDoor: o.dockDoor } : {},
|
|
4642
4693
|
...o.windowStartMs !== void 0 ? { windowStartMs: o.windowStartMs } : {}
|
|
@@ -4661,6 +4712,7 @@ var FlowEngine = class {
|
|
|
4661
4712
|
/* 레시피도 함께 — 품목만 이어받으면 되살아난 오더가 「무엇으로」를 모르고, 그 오더의
|
|
4662
4713
|
남은 공정·소요가 다른 레시피 기준으로 계산된다(같은 품목에 대체 레시피가 있다). */
|
|
4663
4714
|
...o.recipeKey ? { recipeKey: o.recipeKey } : {},
|
|
4715
|
+
...o.recipeBasis ? { recipeBasis: structuredClone(o.recipeBasis) } : {},
|
|
4664
4716
|
bizTransaction: o.bizTransaction ?? "",
|
|
4665
4717
|
allocated: resolved.kept,
|
|
4666
4718
|
picked: [],
|
|
@@ -5094,6 +5146,7 @@ var FlowEngine = class {
|
|
|
5094
5146
|
...o.gtin ? { gtin: o.gtin } : {},
|
|
5095
5147
|
/* **어느 레시피로 만드나** — 품목만으로는 「무엇으로」가 안 남는다(같은 품목의 대체 레시피가 있다). */
|
|
5096
5148
|
...o.recipeKey ? { recipeKey: o.recipeKey } : {},
|
|
5149
|
+
...o.recipeBasis ? { recipeBasis: structuredClone(o.recipeBasis) } : {},
|
|
5097
5150
|
/* 씨앗이 다 심지 못했다는 사실 — 조용히 자르지 않는다(그 오더의 답은 부족한 씨앗 위에 있다). */
|
|
5098
5151
|
...o.seedIncomplete ? { seedIncomplete: true } : {},
|
|
5099
5152
|
/* 왜 대기하는지 — 없으면 화면은 「running」만 보여 주고 사람은 원인을 찾을 자리가 없다. */
|
|
@@ -6128,6 +6181,7 @@ var FlowEngine = class {
|
|
|
6128
6181
|
/* 어느 레시피로 만드는가 — 품목만 보내면 미러는 「무엇으로」를 알 수 없다(같은 품목에 대체
|
|
6129
6182
|
레시피가 있다). 시뮬만 아는 사실이 남으면 두 스냅샷을 같은 규칙으로 읽을 수 없다. */
|
|
6130
6183
|
...o.recipeKey ? { recipeKey: o.recipeKey } : {},
|
|
6184
|
+
...o.recipeBasis ? { recipeBasis: structuredClone(o.recipeBasis) } : {},
|
|
6131
6185
|
...o.allocated?.length ? { allocated: o.allocated.slice() } : {},
|
|
6132
6186
|
...o.bizTransaction ? { bizTransaction: o.bizTransaction } : {},
|
|
6133
6187
|
/* 약속해 둔 자리·시각창 — 이것이 빠져서 야드 트윈이 재기동마다 첫 틱에 죽었다(§dockDoor). */
|
|
@@ -8039,6 +8093,11 @@ The steps are the source of truth for execution order; the hierarchy only names
|
|
|
8039
8093
|
* 그 뒤 계보·재고·수율이 전부 엉뚱한 품목에 붙는다.
|
|
8040
8094
|
*/
|
|
8041
8095
|
recipeDef(order) {
|
|
8096
|
+
if (order?.recipeBasis !== void 0) {
|
|
8097
|
+
if (!isRecipeExecutionBasis(order.recipeBasis) || order.recipeKey && order.recipeKey !== order.recipeBasis.recipe.key)
|
|
8098
|
+
throw new Error("Invalid pinned recipe evidence; refusing to substitute the current master.");
|
|
8099
|
+
return order.recipeBasis.recipe;
|
|
8100
|
+
}
|
|
8042
8101
|
const d = this.productionSpec.definition;
|
|
8043
8102
|
const key = order?.recipeKey ?? this.productionSpec.recipeKey;
|
|
8044
8103
|
if (key) {
|
|
@@ -8201,7 +8260,10 @@ The steps are the source of truth for execution order; the hierarchy only names
|
|
|
8201
8260
|
/** recipe.route → 오퍼레이션 시퀀스 해소. 오더가 자기 레시피를 들면 그 라우트다. */
|
|
8202
8261
|
routeOps(order) {
|
|
8203
8262
|
const d = this.productionSpec.definition;
|
|
8204
|
-
const
|
|
8263
|
+
const recipe = this.recipeDef(order);
|
|
8264
|
+
const route = order?.recipeBasis ? order.recipeBasis.route : d.routes?.find((r) => r.key === recipe.route);
|
|
8265
|
+
if (order?.recipeBasis && route?.steps.some((key) => !d.operations?.some((operation) => operation.key === key)))
|
|
8266
|
+
throw new Error("A captured recipe route references an unavailable operation; refusing a shortened route.");
|
|
8205
8267
|
return (route?.steps ?? []).map((sk) => d.operations?.find((o) => o.key === sk)).filter((o) => !!o);
|
|
8206
8268
|
}
|
|
8207
8269
|
/** 정의 모드 수령 — skuMix gtin 이 레시피 입력 자재면 **그 자재가 선언한 자리**에 생성. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@operato/twin-kernel",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.22",
|
|
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.
|
|
31
|
+
"@operato/ops-contract": "^0.9.32"
|
|
32
32
|
}
|
|
33
33
|
}
|