@operato/twin-kernel 0.7.44 → 0.7.45
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/contract.d.ts +13 -0
- package/dist/domain-catalog.d.ts +1 -1
- package/dist/ems-kernel.d.ts +1 -1
- package/dist/ems-kernel.js +1 -1
- package/dist/epcis.js +13 -0
- package/dist/flow-engine.d.ts +12 -0
- package/dist/flow-engine.js +35 -2
- package/dist/kernel.js +6 -18
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +1 -1
- package/dist/yms-kernel.js +5 -11
- package/dist-cjs/index.cjs +38 -12
- package/package.json +1 -1
package/dist/contract.d.ts
CHANGED
|
@@ -2242,6 +2242,19 @@ export interface IdentityDeclaration {
|
|
|
2242
2242
|
* 키는 문서의 종류(예: `workorder`), 값은 그 현장이 배정한 문서 타입이다.
|
|
2243
2243
|
*/
|
|
2244
2244
|
documentTypes?: Record<string, string>;
|
|
2245
|
+
/**
|
|
2246
|
+
* **이 현장이 배정받은 GS1 회사 프리픽스** — GS1 키(SSCC·GRAI·GDTI)를 쓰는 현장이 선언한다.
|
|
2247
|
+
*
|
|
2248
|
+
* 이 자리가 없으면 GS1 키를 만들 수 없다. 그것이 막힘이 아니라 **정직**이다: 프리픽스는 GS1 이
|
|
2249
|
+
* 회사에 배정하는 것이고, 커널이 값을 고르면 저널에 남의 번호가 영구히 남는다.
|
|
2250
|
+
*
|
|
2251
|
+
* 프리픽스가 없는 현장은 `namespaces` 로 간다 — 도메인만 있으면 표준 정합이다(CBV §8.2.4·§8.5.5).
|
|
2252
|
+
* 그쪽이 진입장벽이 낮다.
|
|
2253
|
+
*
|
|
2254
|
+
* 여기 있다는 것이 **발급받았다는 증명은 아니다** — 그 주장은 `issuedClaim` 이 따로 말하고, 우리는
|
|
2255
|
+
* 확인하지 않는다(§`IdentityGroundingView`).
|
|
2256
|
+
*/
|
|
2257
|
+
companyPrefix?: string;
|
|
2245
2258
|
}
|
|
2246
2259
|
/**
|
|
2247
2260
|
* 정체성의 **근거** — 값이 어디서 왔나. 「우리가 구현했나」와 **다른 축**이다.
|
package/dist/domain-catalog.d.ts
CHANGED
|
@@ -100,7 +100,7 @@ export interface TwinAxisInfo {
|
|
|
100
100
|
*
|
|
101
101
|
* **`source` 가 가리키는 자료 안의 경로다** — 예전에는 「`document` 일 때만 있다」고 못 박았는데,
|
|
102
102
|
* 상태 축도 중첩될 수 있다는 것이 에너지에서 드러났다(수요 구간은 `state.energy.closed` 에 산다).
|
|
103
|
-
* 그때 축 이름을 상태의 최상위 키로 맞추려면 같은 배열을 두 자리에 실어야 했다(
|
|
103
|
+
* 그때 축 이름을 상태의 최상위 키로 맞추려면 같은 배열을 두 자리에 실어야 했다(브로드캐스팅이 그만큼 커진다).
|
|
104
104
|
*
|
|
105
105
|
* 규칙은 하나다: **경로가 있으면 그 경로로 읽고, 없으면 축 이름으로 읽는다.** 빈 문자열로 두지
|
|
106
106
|
* 않는다 — 소비처가 자료의 뿌리를 읽고 통째로 잘못된 답을 만든다.
|
package/dist/ems-kernel.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare class EmsKernel extends FlowEngine {
|
|
|
11
11
|
private closed;
|
|
12
12
|
private closedTotal;
|
|
13
13
|
private peak?;
|
|
14
|
-
/** 이 구간에서 이미 제안을 냈나 — 같은 사실을 되풀어
|
|
14
|
+
/** 이 구간에서 이미 제안을 냈나 — 같은 사실을 되풀어 브로드캐스팅하지 않는다(라이브 브리지의 교훈). */
|
|
15
15
|
private suggestedFor?;
|
|
16
16
|
private windowMs;
|
|
17
17
|
/**
|
package/dist/ems-kernel.js
CHANGED
package/dist/epcis.js
CHANGED
|
@@ -77,6 +77,19 @@ export function lgtinClass(companyPrefix, itemRefAndIndicator, lot) {
|
|
|
77
77
|
*/
|
|
78
78
|
export function parseEpc(uri) {
|
|
79
79
|
const raw = String(uri ?? '');
|
|
80
|
+
/*
|
|
81
|
+
* ── CBV 형식을 먼저 읽는다 (2026-08-21) ──────────────────────────────────
|
|
82
|
+
* GS1 프리픽스가 없는 현장은 선언된 이름공간 아래에서 식별자를 만든다(CBV 2.0 §8.2.3·§8.2.4 개체
|
|
83
|
+
* `obj` · §8.3.3·§8.3.4 클래스 `class`). 그 형식을 모르면 `scheme: 'unknown'` · `instance: false` 가
|
|
84
|
+
* 되고, 소비처가 **개체를 클래스로 읽는다** — 미러가 트레일러의 개체 URI 를 그 물품의 품번으로 적었다
|
|
85
|
+
* (적합성 대조가 「미러만 아는 것=[gtin]」으로 잡았다).
|
|
86
|
+
*
|
|
87
|
+
* 표지가 종류를 말한다. 그것이 이 형식의 설계이고(그래서 표지가 필수다) 여기서 그대로 읽는다.
|
|
88
|
+
*/
|
|
89
|
+
if (/(?:\/obj\/|:obj:)[^/:\s]+$/.test(raw))
|
|
90
|
+
return { scheme: 'unknown', instance: true, uri: raw };
|
|
91
|
+
if (/(?:\/class\/|:class:)[^/:\s]+$/.test(raw))
|
|
92
|
+
return { scheme: 'unknown', instance: false, uri: raw };
|
|
80
93
|
const cls = raw.match(/^urn:epc:class:lgtin:(.+)$/);
|
|
81
94
|
if (cls) {
|
|
82
95
|
const seg = cls[1].split('.');
|
package/dist/flow-engine.d.ts
CHANGED
|
@@ -1271,6 +1271,18 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
1271
1271
|
* 호출부가 무엇을 할지 정한다(레거시 경로는 아직 상수를 쓴다).
|
|
1272
1272
|
*/
|
|
1273
1273
|
protected declaredObjectId(id: string | number): string | undefined;
|
|
1274
|
+
/**
|
|
1275
|
+
* 개체 식별자 — **선언이 없으면 오류를 낸다.**
|
|
1276
|
+
*
|
|
1277
|
+
* 예전에는 선언이 없으면 커널 상수 프리픽스로 만들었다(`0614141` — GS1 이 규격 예제로 쓰다 버린
|
|
1278
|
+
* 값이고 `06…` 은 실 배정 대역이다). 저널에 남는 것은 가짜 데이터가 아니라 **남의 번호일 수 있는
|
|
1279
|
+
* 데이터**이고, 저널은 지우지 않는다. 그래서 지어내지 않고 멈춘다 — 조용히 도는 것보다 낫다.
|
|
1280
|
+
*/
|
|
1281
|
+
protected requireObjectId(id: string | number): string;
|
|
1282
|
+
/** 거래 문서 식별자 — 선언된 이름공간 또는 선언된 GDTI 문서 타입. 둘 다 없으면 오류를 낸다. */
|
|
1283
|
+
protected requireBizTransactionId(id: string | number, docKind: string): string;
|
|
1284
|
+
/** 같은 문장을 두 곳에 적지 않는다 — 고치는 자리가 하나여야 한다. */
|
|
1285
|
+
private identityMissing;
|
|
1274
1286
|
/** 선언된 이름공간 아래의 **거래 문서 식별자**(발주·주문·어포인트먼트) — 없으면 답하지 않는다. */
|
|
1275
1287
|
protected declaredBizTransactionId(id: string | number): string | undefined;
|
|
1276
1288
|
private produceMaterials;
|
package/dist/flow-engine.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { OP_EVENT, CMD, locationStatusOf, readBoardEquipment, readBoardLocations, readBoardAssets, classClosure, capabilityOf, requiredTestsFor, priorityRank, dueStatusOf, effectivityAt, offCalendarAt, offCalendarReasonAt, minuteOfDayAt, activeShiftAt, subLotIdOf, itemKeyOf, identityGroundingOf } from "./contract.js";
|
|
13
13
|
import { ObservedReducer } from "./observed-reducer.js";
|
|
14
|
-
import { transformationEvent, aggregationEvent, objectEvent, parseEpc, DISP, ILMD_ATTR, CBV_BIZSTEP, objectUri, bizTransactionUri } from "./epcis.js";
|
|
14
|
+
import { transformationEvent, aggregationEvent, objectEvent, parseEpc, DISP, ILMD_ATTR, CBV_BIZSTEP, objectUri, bizTransactionUri, gdtiUri } from "./epcis.js";
|
|
15
15
|
import { OP_PARAM } from "./domain-definition.js";
|
|
16
16
|
import { parseIsoDuration } from "./iso-duration.js";
|
|
17
17
|
import { analyzeCapacity } from "./capacity.js";
|
|
@@ -1622,7 +1622,7 @@ export class FlowEngine {
|
|
|
1622
1622
|
}
|
|
1623
1623
|
this.observer.apply(envelope);
|
|
1624
1624
|
/* **구독자에게 그대로 흘린다** — 호스트가 시뮬·관측 두 모드에서 같은 배선을 쓰게 하기 위해서다
|
|
1625
|
-
* (`onEvent` 하나로
|
|
1625
|
+
* (`onEvent` 하나로 저널·브로드캐스팅이 붙는다). 관측 모드에서 이것은 **재방출**이지 새 사실이 아니다:
|
|
1626
1626
|
* 원천이 이미 그 이벤트를 갖고 있으므로, 호스트가 인입과 재방출을 **둘 다 저널에 적으면 중복**이
|
|
1627
1627
|
* 된다. 저널은 인입에서 한 번만 적는다. */
|
|
1628
1628
|
for (const h of this.observedHandlers())
|
|
@@ -2605,6 +2605,39 @@ export class FlowEngine {
|
|
|
2605
2605
|
}
|
|
2606
2606
|
return undefined;
|
|
2607
2607
|
}
|
|
2608
|
+
/**
|
|
2609
|
+
* 개체 식별자 — **선언이 없으면 오류를 낸다.**
|
|
2610
|
+
*
|
|
2611
|
+
* 예전에는 선언이 없으면 커널 상수 프리픽스로 만들었다(`0614141` — GS1 이 규격 예제로 쓰다 버린
|
|
2612
|
+
* 값이고 `06…` 은 실 배정 대역이다). 저널에 남는 것은 가짜 데이터가 아니라 **남의 번호일 수 있는
|
|
2613
|
+
* 데이터**이고, 저널은 지우지 않는다. 그래서 지어내지 않고 멈춘다 — 조용히 도는 것보다 낫다.
|
|
2614
|
+
*/
|
|
2615
|
+
requireObjectId(id) {
|
|
2616
|
+
const uri = this.declaredObjectId(id);
|
|
2617
|
+
if (uri)
|
|
2618
|
+
return uri;
|
|
2619
|
+
throw new Error(this.identityMissing('object'));
|
|
2620
|
+
}
|
|
2621
|
+
/** 거래 문서 식별자 — 선언된 이름공간 또는 선언된 GDTI 문서 타입. 둘 다 없으면 오류를 낸다. */
|
|
2622
|
+
requireBizTransactionId(id, docKind) {
|
|
2623
|
+
const uri = this.declaredBizTransactionId(id);
|
|
2624
|
+
if (uri)
|
|
2625
|
+
return uri;
|
|
2626
|
+
const decl = this.identityDeclaration();
|
|
2627
|
+
const docType = decl?.documentTypes?.[docKind];
|
|
2628
|
+
const prefix = decl?.companyPrefix;
|
|
2629
|
+
if (docType && prefix)
|
|
2630
|
+
return gdtiUri(prefix, docType, Number(id) || 0);
|
|
2631
|
+
throw new Error(this.identityMissing(`business transaction '${docKind}'`));
|
|
2632
|
+
}
|
|
2633
|
+
/** 같은 문장을 두 곳에 적지 않는다 — 고치는 자리가 하나여야 한다. */
|
|
2634
|
+
identityMissing(what) {
|
|
2635
|
+
return (`this twin has no identity declaration, so the kernel cannot name a ${what}. ` +
|
|
2636
|
+
'Declare `identity.namespaces` on the model (CBV 2.0 §8.2.4 `.../obj/<id>` · §8.5.5 `.../bt/<id>` — ' +
|
|
2637
|
+
'assigned by the owner of that internet domain), or `identity.documentTypes` with `identity.companyPrefix` ' +
|
|
2638
|
+
'for GS1 keys. The kernel does not invent a company prefix: GS1 assigns it to a company, and a value the ' +
|
|
2639
|
+
'kernel picked would sit in the journal forever with nothing saying it was invented.');
|
|
2640
|
+
}
|
|
2608
2641
|
/** 선언된 이름공간 아래의 **거래 문서 식별자**(발주·주문·어포인트먼트) — 없으면 답하지 않는다. */
|
|
2609
2642
|
declaredBizTransactionId(id) {
|
|
2610
2643
|
for (const ns of this.identityDeclaration()?.namespaces ?? []) {
|
package/dist/kernel.js
CHANGED
|
@@ -10,21 +10,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
12
|
import { BIZSTEP, BTT } from "./wms-profile.js";
|
|
13
|
-
import { DISP, ILMD_ATTR, aggregationEvent,
|
|
13
|
+
import { DISP, ILMD_ATTR, aggregationEvent, objectEvent, transactionEvent } from "./epcis.js";
|
|
14
14
|
import { subLotIdOf, itemKeyOf } from "./contract.js";
|
|
15
15
|
const TRAVEL_MS = 30_000;
|
|
16
|
-
/**
|
|
17
|
-
* 이 내장 시나리오가 쓰는 **날조된 회사 프리픽스** — 선언이 없는 트윈만 이 길로 온다.
|
|
18
|
-
*
|
|
19
|
-
* 값이 실 배정 대역(`06…`)이라 저널에 남는 것은 **남의 번호일 수 있는 데이터**다. GS1 은 2022 년에
|
|
20
|
-
* 모든 예제를 `952` 로 옮겼으므로(CBV 2.0 변경 이력), 이 값은 「GS1 이 버린 옛 예제」이기도 하다.
|
|
21
|
-
*
|
|
22
|
-
* 지우는 길은 이 상수를 다른 값으로 바꾸는 것이 **아니다** — 이 경로는 부품·라우트까지 상수여서
|
|
23
|
-
* 정체성만 고치면 「지원하는 척」이 된다. 선언(`productionSpec`)으로 도는 트윈은 이 상수를 지나지
|
|
24
|
-
* 않으며, 지나는 트윈은 `identityGroundingOf` 가 `fabricated` 로 판정한다 — 릴리즈 게이트가 그 값으로
|
|
25
|
-
* 막는다. 이름에 `LEGACY_` 를 둔 이유는 **새 코드가 무심코 집어 들지 못하게** 하는 것이다.
|
|
26
|
-
*/
|
|
27
|
-
const LEGACY_COMPANY_PREFIX = '0614141';
|
|
28
16
|
const SHELF_MS = 30 * 24 * 3_600_000; // 기본 유통기한(30일)
|
|
29
17
|
const SHELF_JITTER_MS = 5 * 24 * 3_600_000; // 로트별 만료 편차(FEFO 가 FIFO 와 갈리게)
|
|
30
18
|
export class WmsKernel extends FlowEngine {
|
|
@@ -59,7 +47,7 @@ export class WmsKernel extends FlowEngine {
|
|
|
59
47
|
const dock = this.builtInLocation('dock', 'inbound pallets land here');
|
|
60
48
|
/* 선언이 있으면 선언에서(CBV §8.2.4) — 없으면 레거시 상수. 가드가 그 상수를 계속 센다. */
|
|
61
49
|
const seq = ++this.epcSeq;
|
|
62
|
-
const epc = this.
|
|
50
|
+
const epc = this.requireObjectId(`PAL-${seq}`); // 팔레트
|
|
63
51
|
const gtin = this.pickGtin(spec.content.skuMix); // SGTIN idpat = epcClass
|
|
64
52
|
/* 품목 구성이 비어 있으면 **도착을 만들지 않는다** — 무엇이 왔는지 말할 수 없는 입고는 사실이 아니다. */
|
|
65
53
|
if (!gtin)
|
|
@@ -67,7 +55,7 @@ export class WmsKernel extends FlowEngine {
|
|
|
67
55
|
const qty = this.randInt(spec.content.qtyPerLine.min, spec.content.qtyPerLine.max); // 케이스 수(비직렬)
|
|
68
56
|
const poSeq = ++this.poSeq;
|
|
69
57
|
/* 발주 문서 식별자 — 선언된 이름공간 아래(CBV §8.5.5) 또는 선언된 GDTI 문서 타입. */
|
|
70
|
-
const po = this.
|
|
58
|
+
const po = this.requireBizTransactionId(`PO-${poSeq}`, 'purchaseorder');
|
|
71
59
|
const eventTime = this.now();
|
|
72
60
|
const qtyList = [{ epcClass: gtin, quantity: qty }];
|
|
73
61
|
const poTxn = [{ type: BTT.po, bizTransaction: po }];
|
|
@@ -142,7 +130,7 @@ export class WmsKernel extends FlowEngine {
|
|
|
142
130
|
createSalesOrder(lines, spec) {
|
|
143
131
|
const id = `order-${++this.orderSeq}`;
|
|
144
132
|
const soSeq = ++this.soSeq;
|
|
145
|
-
const so = this.
|
|
133
|
+
const so = this.requireBizTransactionId(`SO-${soSeq}`, 'salesorder');
|
|
146
134
|
const requested = lines.reduce((s, l) => s + l.qty, 0);
|
|
147
135
|
const order = {
|
|
148
136
|
id, kind: 'outbound', status: 'created', requested, fulfilled: 0, bizTransaction: so,
|
|
@@ -355,7 +343,7 @@ export class WmsKernel extends FlowEngine {
|
|
|
355
343
|
/* 클래스 줄을 팔레트로 바꾼다 — 자리 점유는 줄 하나에서 줄 하나로(늘지 않는다). */
|
|
356
344
|
this.items.delete(key);
|
|
357
345
|
const palSeq = ++this.epcSeq;
|
|
358
|
-
const pallet = this.
|
|
346
|
+
const pallet = this.requireObjectId(`PAL-${palSeq}`);
|
|
359
347
|
const qtyList = [{ epcClass: m.definitionId, quantity: qty }];
|
|
360
348
|
this.items.set(pallet, { epc: pallet, gtin: m.definitionId, qty, location: at, disposition: DISP.in_progress });
|
|
361
349
|
const eventTime = this.now();
|
|
@@ -373,7 +361,7 @@ export class WmsKernel extends FlowEngine {
|
|
|
373
361
|
const shipDock = this.locationByType('dock-ship') ?? staging;
|
|
374
362
|
const eventTime = this.now();
|
|
375
363
|
const shpSeq = ++this.epcSeq;
|
|
376
|
-
const shipment = this.
|
|
364
|
+
const shipment = this.requireObjectId(`SHP-${shpSeq}`);
|
|
377
365
|
order.shipmentEpc = shipment;
|
|
378
366
|
const soTxn = [{ type: BTT.so, bizTransaction: order.bizTransaction }];
|
|
379
367
|
// 패킹: 화물(shipment) ← 팔레트 조립(merge). 팔레트는 출하 DELETE 까지 독립 유지 → consume 없음.
|
package/dist/runtime.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export declare class TwinRuntime {
|
|
|
28
28
|
revision: number;
|
|
29
29
|
state: StateSnapshot;
|
|
30
30
|
};
|
|
31
|
-
/** 클록-싱크 하트비트 메시지 — host 가 자기 send-rate 로
|
|
31
|
+
/** 클록-싱크 하트비트 메시지 — host 가 자기 send-rate 로 브로드캐스팅(⊥ tick). 클라 보간의 nowSim. */
|
|
32
32
|
nowClock(): Extract<SubscriptionMessage, {
|
|
33
33
|
kind: 'clock';
|
|
34
34
|
}>;
|
package/dist/runtime.js
CHANGED
|
@@ -37,7 +37,7 @@ export class TwinRuntime {
|
|
|
37
37
|
resync() {
|
|
38
38
|
return { revision: this.currentRevision, state: this.kernel.getSnapshot() };
|
|
39
39
|
}
|
|
40
|
-
/** 클록-싱크 하트비트 메시지 — host 가 자기 send-rate 로
|
|
40
|
+
/** 클록-싱크 하트비트 메시지 — host 가 자기 send-rate 로 브로드캐스팅(⊥ tick). 클라 보간의 nowSim. */
|
|
41
41
|
nowClock() {
|
|
42
42
|
return { kind: 'clock', revision: this.currentRevision, simClockMs: this.kernel.getSnapshot().simClockMs };
|
|
43
43
|
}
|
package/dist/yms-kernel.js
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
import { itemKeyOf } from "./contract.js";
|
|
19
19
|
import { firstFitPolicy } from "./allocation-policy.js";
|
|
20
20
|
import { FlowEngine } from "./flow-engine.js";
|
|
21
|
-
import { DISP, objectEvent, transactionEvent
|
|
22
|
-
import { YARD_BIZSTEP, BTT_DELIVERY
|
|
21
|
+
import { DISP, objectEvent, transactionEvent } from "./epcis.js";
|
|
22
|
+
import { YARD_BIZSTEP, BTT_DELIVERY } from "./yms-profile.js";
|
|
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 → 대기)
|
|
@@ -34,12 +34,6 @@ const WINDOW_DELAY_MS = 40_000; // 도착 후 어포인트먼트 창까지(early
|
|
|
34
34
|
*/
|
|
35
35
|
const WINDOW_LENGTH_MS = 20 * 60_000;
|
|
36
36
|
const CARGO_PER_TRAILER = 2; // 트레일러 적재/하역 화물(SSCC) 수
|
|
37
|
-
/**
|
|
38
|
-
* 이 내장 시나리오가 쓰는 **날조된 회사 프리픽스** — 선언이 없는 트윈만 이 길로 온다.
|
|
39
|
-
* 근거·처분은 WMS 커널의 같은 상수 주석에 있다(`kernel.ts`). 이름의 `LEGACY_` 는 새 코드가 무심코
|
|
40
|
-
* 집어 들지 못하게 하는 표시다 — 이 상수를 지나는 트윈은 `fabricated` 로 판정된다.
|
|
41
|
-
*/
|
|
42
|
-
const LEGACY_CP = '0614141';
|
|
43
37
|
export class YmsKernel extends FlowEngine {
|
|
44
38
|
doorRR = 0;
|
|
45
39
|
cargoSeq = 0;
|
|
@@ -66,7 +60,7 @@ export class YmsKernel extends FlowEngine {
|
|
|
66
60
|
const inbound = kind === 'appointment';
|
|
67
61
|
const trSeq = ++this.epcSeq;
|
|
68
62
|
/* 선언이 있으면 선언에서(CBV §8.2.4) — 없으면 레거시 상수. */
|
|
69
|
-
const epc = this.
|
|
63
|
+
const epc = this.requireObjectId(`TRL-${trSeq}`);
|
|
70
64
|
this.items.set(epc, { epc, location: gate.id, disposition: DISP.in_progress });
|
|
71
65
|
gate.occupancy++;
|
|
72
66
|
this.emit(objectEvent({ eventTime: this.now(), action: 'ADD', bizStep: YARD_BIZSTEP.arriving, disposition: DISP.in_progress, epcList: [epc], readPoint: gate.id, bizLocation: gate.id }));
|
|
@@ -74,7 +68,7 @@ export class YmsKernel extends FlowEngine {
|
|
|
74
68
|
if (inbound) {
|
|
75
69
|
const cargo = Array.from({ length: CARGO_PER_TRAILER }, () => {
|
|
76
70
|
const c = ++this.cargoSeq;
|
|
77
|
-
return this.
|
|
71
|
+
return this.requireObjectId(`CGO-${c}`);
|
|
78
72
|
});
|
|
79
73
|
this.trailerCargo.set(epc, cargo);
|
|
80
74
|
this.aggregate(epc, cargo, { bizStep: YARD_BIZSTEP.arriving, readPoint: gate.id }); // 적재된 채 도착(자식 opaque, 미materialize)
|
|
@@ -82,7 +76,7 @@ export class YmsKernel extends FlowEngine {
|
|
|
82
76
|
const door = doors[this.doorRR++ % doors.length];
|
|
83
77
|
const id = `order-${++this.orderSeq}`;
|
|
84
78
|
const apSeq = ++this.soSeq;
|
|
85
|
-
const appt = this.
|
|
79
|
+
const appt = this.requireBizTransactionId(`APPT-${apSeq}`, 'appointment');
|
|
86
80
|
const order = { id, kind, status: 'created', requested: 1, fulfilled: 0, bizTransaction: appt, allocated: [epc], picked: [], dockDoor: door.id,
|
|
87
81
|
/* 내부 스케줄 게이트(시뮬 ms) — 표준 `EarliestStartTime` 과 같은 개념의 우리 단위. */
|
|
88
82
|
windowStartMs: this.clockMs + WINDOW_DELAY_MS,
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -914,6 +914,8 @@ function lgtinClass(companyPrefix, itemRefAndIndicator, lot) {
|
|
|
914
914
|
}
|
|
915
915
|
function parseEpc(uri) {
|
|
916
916
|
const raw = String(uri ?? "");
|
|
917
|
+
if (/(?:\/obj\/|:obj:)[^/:\s]+$/.test(raw)) return { scheme: "unknown", instance: true, uri: raw };
|
|
918
|
+
if (/(?:\/class\/|:class:)[^/:\s]+$/.test(raw)) return { scheme: "unknown", instance: false, uri: raw };
|
|
917
919
|
const cls = raw.match(/^urn:epc:class:lgtin:(.+)$/);
|
|
918
920
|
if (cls) {
|
|
919
921
|
const seg = cls[1].split(".");
|
|
@@ -3020,7 +3022,7 @@ var TwinRuntime = class {
|
|
|
3020
3022
|
resync() {
|
|
3021
3023
|
return { revision: this.currentRevision, state: this.kernel.getSnapshot() };
|
|
3022
3024
|
}
|
|
3023
|
-
/** 클록-싱크 하트비트 메시지 — host 가 자기 send-rate 로
|
|
3025
|
+
/** 클록-싱크 하트비트 메시지 — host 가 자기 send-rate 로 브로드캐스팅(⊥ tick). 클라 보간의 nowSim. */
|
|
3024
3026
|
nowClock() {
|
|
3025
3027
|
return { kind: "clock", revision: this.currentRevision, simClockMs: this.kernel.getSnapshot().simClockMs };
|
|
3026
3028
|
}
|
|
@@ -5474,6 +5476,32 @@ var FlowEngine = class {
|
|
|
5474
5476
|
}
|
|
5475
5477
|
return void 0;
|
|
5476
5478
|
}
|
|
5479
|
+
/**
|
|
5480
|
+
* 개체 식별자 — **선언이 없으면 오류를 낸다.**
|
|
5481
|
+
*
|
|
5482
|
+
* 예전에는 선언이 없으면 커널 상수 프리픽스로 만들었다(`0614141` — GS1 이 규격 예제로 쓰다 버린
|
|
5483
|
+
* 값이고 `06…` 은 실 배정 대역이다). 저널에 남는 것은 가짜 데이터가 아니라 **남의 번호일 수 있는
|
|
5484
|
+
* 데이터**이고, 저널은 지우지 않는다. 그래서 지어내지 않고 멈춘다 — 조용히 도는 것보다 낫다.
|
|
5485
|
+
*/
|
|
5486
|
+
requireObjectId(id) {
|
|
5487
|
+
const uri = this.declaredObjectId(id);
|
|
5488
|
+
if (uri) return uri;
|
|
5489
|
+
throw new Error(this.identityMissing("object"));
|
|
5490
|
+
}
|
|
5491
|
+
/** 거래 문서 식별자 — 선언된 이름공간 또는 선언된 GDTI 문서 타입. 둘 다 없으면 오류를 낸다. */
|
|
5492
|
+
requireBizTransactionId(id, docKind) {
|
|
5493
|
+
const uri = this.declaredBizTransactionId(id);
|
|
5494
|
+
if (uri) return uri;
|
|
5495
|
+
const decl = this.identityDeclaration();
|
|
5496
|
+
const docType = decl?.documentTypes?.[docKind];
|
|
5497
|
+
const prefix = decl?.companyPrefix;
|
|
5498
|
+
if (docType && prefix) return gdtiUri(prefix, docType, Number(id) || 0);
|
|
5499
|
+
throw new Error(this.identityMissing(`business transaction '${docKind}'`));
|
|
5500
|
+
}
|
|
5501
|
+
/** 같은 문장을 두 곳에 적지 않는다 — 고치는 자리가 하나여야 한다. */
|
|
5502
|
+
identityMissing(what) {
|
|
5503
|
+
return `this twin has no identity declaration, so the kernel cannot name a ${what}. Declare \`identity.namespaces\` on the model (CBV 2.0 \xA78.2.4 \`.../obj/<id>\` \xB7 \xA78.5.5 \`.../bt/<id>\` \u2014 assigned by the owner of that internet domain), or \`identity.documentTypes\` with \`identity.companyPrefix\` for GS1 keys. The kernel does not invent a company prefix: GS1 assigns it to a company, and a value the kernel picked would sit in the journal forever with nothing saying it was invented.`;
|
|
5504
|
+
}
|
|
5477
5505
|
/** 선언된 이름공간 아래의 **거래 문서 식별자**(발주·주문·어포인트먼트) — 없으면 답하지 않는다. */
|
|
5478
5506
|
declaredBizTransactionId(id) {
|
|
5479
5507
|
for (const ns of this.identityDeclaration()?.namespaces ?? []) {
|
|
@@ -6091,7 +6119,6 @@ function matches(s, req) {
|
|
|
6091
6119
|
|
|
6092
6120
|
// src/kernel.ts
|
|
6093
6121
|
var TRAVEL_MS = 3e4;
|
|
6094
|
-
var LEGACY_COMPANY_PREFIX = "0614141";
|
|
6095
6122
|
var SHELF_MS = 30 * 24 * 36e5;
|
|
6096
6123
|
var SHELF_JITTER_MS = 5 * 24 * 36e5;
|
|
6097
6124
|
var WmsKernel = class extends FlowEngine {
|
|
@@ -6126,12 +6153,12 @@ var WmsKernel = class extends FlowEngine {
|
|
|
6126
6153
|
onArrival(spec) {
|
|
6127
6154
|
const dock = this.builtInLocation("dock", "inbound pallets land here");
|
|
6128
6155
|
const seq = ++this.epcSeq;
|
|
6129
|
-
const epc = this.
|
|
6156
|
+
const epc = this.requireObjectId(`PAL-${seq}`);
|
|
6130
6157
|
const gtin = this.pickGtin(spec.content.skuMix);
|
|
6131
6158
|
if (!gtin) return;
|
|
6132
6159
|
const qty = this.randInt(spec.content.qtyPerLine.min, spec.content.qtyPerLine.max);
|
|
6133
6160
|
const poSeq = ++this.poSeq;
|
|
6134
|
-
const po = this.
|
|
6161
|
+
const po = this.requireBizTransactionId(`PO-${poSeq}`, "purchaseorder");
|
|
6135
6162
|
const eventTime = this.now();
|
|
6136
6163
|
const qtyList = [{ epcClass: gtin, quantity: qty }];
|
|
6137
6164
|
const poTxn = [{ type: BTT.po, bizTransaction: po }];
|
|
@@ -6203,7 +6230,7 @@ var WmsKernel = class extends FlowEngine {
|
|
|
6203
6230
|
createSalesOrder(lines, spec) {
|
|
6204
6231
|
const id = `order-${++this.orderSeq}`;
|
|
6205
6232
|
const soSeq = ++this.soSeq;
|
|
6206
|
-
const so = this.
|
|
6233
|
+
const so = this.requireBizTransactionId(`SO-${soSeq}`, "salesorder");
|
|
6207
6234
|
const requested = lines.reduce((s, l) => s + l.qty, 0);
|
|
6208
6235
|
const order = {
|
|
6209
6236
|
id,
|
|
@@ -6377,7 +6404,7 @@ var WmsKernel = class extends FlowEngine {
|
|
|
6377
6404
|
if (qty <= 0) continue;
|
|
6378
6405
|
this.items.delete(key);
|
|
6379
6406
|
const palSeq = ++this.epcSeq;
|
|
6380
|
-
const pallet = this.
|
|
6407
|
+
const pallet = this.requireObjectId(`PAL-${palSeq}`);
|
|
6381
6408
|
const qtyList = [{ epcClass: m.definitionId, quantity: qty }];
|
|
6382
6409
|
this.items.set(pallet, { epc: pallet, gtin: m.definitionId, qty, location: at, disposition: DISP.in_progress });
|
|
6383
6410
|
const eventTime = this.now();
|
|
@@ -6392,7 +6419,7 @@ var WmsKernel = class extends FlowEngine {
|
|
|
6392
6419
|
const shipDock = this.locationByType("dock-ship") ?? staging;
|
|
6393
6420
|
const eventTime = this.now();
|
|
6394
6421
|
const shpSeq = ++this.epcSeq;
|
|
6395
|
-
const shipment = this.
|
|
6422
|
+
const shipment = this.requireObjectId(`SHP-${shpSeq}`);
|
|
6396
6423
|
order.shipmentEpc = shipment;
|
|
6397
6424
|
const soTxn = [{ type: BTT.so, bizTransaction: order.bizTransaction }];
|
|
6398
6425
|
this.aggregate(shipment, order.picked.slice(), { bizStep: BIZSTEP.packing, readPoint: staging.id, bizLocation: staging.id });
|
|
@@ -6425,7 +6452,6 @@ var DWELL_MS = 3e4;
|
|
|
6425
6452
|
var WINDOW_DELAY_MS = 4e4;
|
|
6426
6453
|
var WINDOW_LENGTH_MS = 20 * 6e4;
|
|
6427
6454
|
var CARGO_PER_TRAILER = 2;
|
|
6428
|
-
var LEGACY_CP = "0614141";
|
|
6429
6455
|
var YmsKernel = class extends FlowEngine {
|
|
6430
6456
|
doorRR = 0;
|
|
6431
6457
|
cargoSeq = 0;
|
|
@@ -6456,14 +6482,14 @@ var YmsKernel = class extends FlowEngine {
|
|
|
6456
6482
|
if (!gate || doors.length === 0) return;
|
|
6457
6483
|
const inbound = kind === "appointment";
|
|
6458
6484
|
const trSeq = ++this.epcSeq;
|
|
6459
|
-
const epc = this.
|
|
6485
|
+
const epc = this.requireObjectId(`TRL-${trSeq}`);
|
|
6460
6486
|
this.items.set(epc, { epc, location: gate.id, disposition: DISP.in_progress });
|
|
6461
6487
|
gate.occupancy++;
|
|
6462
6488
|
this.emit(objectEvent({ eventTime: this.now(), action: "ADD", bizStep: YARD_BIZSTEP.arriving, disposition: DISP.in_progress, epcList: [epc], readPoint: gate.id, bizLocation: gate.id }));
|
|
6463
6489
|
if (inbound) {
|
|
6464
6490
|
const cargo = Array.from({ length: CARGO_PER_TRAILER }, () => {
|
|
6465
6491
|
const c = ++this.cargoSeq;
|
|
6466
|
-
return this.
|
|
6492
|
+
return this.requireObjectId(`CGO-${c}`);
|
|
6467
6493
|
});
|
|
6468
6494
|
this.trailerCargo.set(epc, cargo);
|
|
6469
6495
|
this.aggregate(epc, cargo, { bizStep: YARD_BIZSTEP.arriving, readPoint: gate.id });
|
|
@@ -6471,7 +6497,7 @@ var YmsKernel = class extends FlowEngine {
|
|
|
6471
6497
|
const door = doors[this.doorRR++ % doors.length];
|
|
6472
6498
|
const id = `order-${++this.orderSeq}`;
|
|
6473
6499
|
const apSeq = ++this.soSeq;
|
|
6474
|
-
const appt = this.
|
|
6500
|
+
const appt = this.requireBizTransactionId(`APPT-${apSeq}`, "appointment");
|
|
6475
6501
|
const order = {
|
|
6476
6502
|
id,
|
|
6477
6503
|
kind,
|
|
@@ -7113,7 +7139,7 @@ var EmsKernel = class extends FlowEngine {
|
|
|
7113
7139
|
closed = [];
|
|
7114
7140
|
closedTotal = 0;
|
|
7115
7141
|
peak;
|
|
7116
|
-
/** 이 구간에서 이미 제안을 냈나 — 같은 사실을 되풀어
|
|
7142
|
+
/** 이 구간에서 이미 제안을 냈나 — 같은 사실을 되풀어 브로드캐스팅하지 않는다(라이브 브리지의 교훈). */
|
|
7117
7143
|
suggestedFor;
|
|
7118
7144
|
windowMs;
|
|
7119
7145
|
/**
|
package/package.json
CHANGED