@operato/twin-kernel 0.7.45 → 0.7.46

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.
@@ -107,7 +107,24 @@ export declare class MesKernel extends FlowEngine {
107
107
  */
108
108
  private recipeDef;
109
109
  /** 자재 키 → 구체 gtin 클래스(idpat). 구체 식별은 바인딩+prefix 로 인스턴스가 주입. */
110
+ /**
111
+ * 자재 키 → **품목 클래스 식별자.** 선언된 스킴이 모양을 정한다.
112
+ *
113
+ * ── 두 길 (2026-08-21) ─────────────────────────────────────────────────────
114
+ * **프리픽스가 있으면** `binding[key]` 를 품번(itemRef)으로 보고 SGTIN 을 조립한다(예전 거동).
115
+ * **없으면** `binding[key]` 가 **이미 완성된 클래스 식별자**다 — 커넥터가 선언된 도메인 아래에서
116
+ * 만든 CBV 형식(`https://<도메인>/class/<id>`)이고, 조립하면 그 값을 망친다.
117
+ *
118
+ * 실 연동에서 이것이 드러났다: 커넥터가 `binding` 에 완성된 URI 를 실었는데 커널이 그것을 품번으로
119
+ * 보고 프리픽스와 이어 붙이려 했다. 선언이 권위이므로 선언된 모양을 그대로 쓴다.
120
+ */
110
121
  private classOf;
122
+ /**
123
+ * 자재 키 + 일련번호 → **개체 식별자.**
124
+ *
125
+ * 프리픽스가 있으면 SGTIN(직렬), 없으면 선언된 이름공간 아래 CBV §8.2.4 `.../obj/<Objid>`.
126
+ * `Objid` 는 경로 성분 하나여야 하므로 구분자를 넣지 않는다 — 자재 키와 일련번호를 `-` 로 잇는다.
127
+ */
111
128
  private serialOf;
112
129
  /**
113
130
  * 이 공정이 **만든다고 선언한 품목** — 표준 `OpMaterialSpecification` `MaterialUse='produced'`.
@@ -49,9 +49,27 @@ export class MesKernel extends FlowEngine {
49
49
  * 계약에서 `binding`·`companyPrefix` 는 선택이다 — 유통가공(비직렬 클래스+수량)은 자재 명세에
50
50
  * gtin 을 직접 적으므로 필요 없다. 그래서 **필요한 경로에서만** 요구하고, 없으면 기동을 막는다.
51
51
  */
52
- if (productionSpec?.definition?.recipes?.length && (!productionSpec.binding || !productionSpec.companyPrefix)) {
53
- throw new Error('recipe-driven production needs `binding` and `companyPrefix` — the definition does not know GTINs, ' +
54
- 'so material keys cannot be resolved to GS1 identifiers');
52
+ if (productionSpec?.definition?.recipes?.length) {
53
+ /*
54
+ * ── 프리픽스가 없는 현장도 세운다 (2026-08-21) ──────────────────────────
55
+ * 예전에는 `companyPrefix` 가 없으면 무조건 거절했다. 그래서 **GS1 회사 프리픽스가 없는 실
56
+ * 시스템이 기동조차 못 했다** — 첫 실 연동(F&B MES)이 정확히 그 자리에서 막혔다.
57
+ *
58
+ * 표준은 GS1 키만 인정하지 않는다. 선언된 도메인 아래의 클래스 식별자
59
+ * (CBV 2.0 §8.3.4 `.../class/<id>`)도 정합이고 소유 권한자는 그 도메인의 주인이다
60
+ * (EPCIS 2.0 §6.4). 그러면 프리픽스를 요구할 근거가 없다.
61
+ *
62
+ * 두 길을 섞지 않는다: 프리픽스가 있으면 SGTIN, 없고 이름공간이 있으면 도메인 기반.
63
+ * 둘 다 없으면 자재 키를 식별자로 옮길 방법이 없으므로 그때만 거절한다.
64
+ */
65
+ const hasPrefix = !!productionSpec.companyPrefix;
66
+ const hasNamespace = !!productionSpec.identity?.namespaces?.length;
67
+ if (!productionSpec.binding || !(hasPrefix || hasNamespace)) {
68
+ throw new Error('recipe-driven production needs `binding`, and either `companyPrefix` (GS1 keys) or ' +
69
+ '`identity.namespaces` (domain-based identifiers — CBV 2.0 §8.3.4 `.../class/<id>`, assigned by the ' +
70
+ 'owner of that internet domain). The definition does not know identifiers, so material keys cannot ' +
71
+ 'be resolved without one of the two. The kernel does not invent a company prefix.');
72
+ }
55
73
  }
56
74
  /* 겹침 검사는 **바인딩 검증 뒤**다 — 검사 자체가 자재 키를 식별자로 옮겨 견주기 때문이다. */
57
75
  if (productionSpec?.definition?.operations) {
@@ -106,7 +124,27 @@ export class MesKernel extends FlowEngine {
106
124
  * 아니라 **하나를 고르는** 것이므로 규칙이 갈리지 않는다.
107
125
  */
108
126
  identityDeclaration() {
109
- return this.productionSpec?.identity ?? super.identityDeclaration();
127
+ /*
128
+ * ── 가리지 않고 **합친다** (2026-08-21) ────────────────────────────────────
129
+ * 예전에는 생산 선언이 있으면 모델 선언을 **가렸다**(`?? super`). 그래서 생산 선언이 프리픽스만
130
+ * 말하고 이름공간은 모델에만 있는 현장에서, 커널이 모델의 이름공간을 보지 못해 작업지시 식별자를
131
+ * 만들 수 없었다 — 시험이 그것을 잡았다.
132
+ *
133
+ * 두 선언은 경쟁이 아니라 같은 사실의 두 자리다(생산 선언은 먼저 생긴 자리다). 항목별로 있는 것을
134
+ * 쓴다: 생산 선언이 말한 것이 이기고, 말하지 않은 것은 모델에서 온다.
135
+ */
136
+ const own = this.productionSpec?.identity;
137
+ const model = super.identityDeclaration();
138
+ if (!own)
139
+ return model;
140
+ if (!model)
141
+ return own;
142
+ return {
143
+ namespaces: own.namespaces?.length ? own.namespaces : model.namespaces,
144
+ documentTypes: { ...model.documentTypes, ...own.documentTypes },
145
+ ...(own.companyPrefix ?? model.companyPrefix ? { companyPrefix: own.companyPrefix ?? model.companyPrefix } : {}),
146
+ ...(own.issuedClaim ?? model.issuedClaim ? { issuedClaim: own.issuedClaim ?? model.issuedClaim } : {})
147
+ };
110
148
  }
111
149
  identityGroundingView() {
112
150
  return identityGroundingOf(this.productionSpec);
@@ -264,11 +302,44 @@ export class MesKernel extends FlowEngine {
264
302
  return d.recipes[0];
265
303
  }
266
304
  /** 자재 키 → 구체 gtin 클래스(idpat). 구체 식별은 바인딩+prefix 로 인스턴스가 주입. */
305
+ /**
306
+ * 자재 키 → **품목 클래스 식별자.** 선언된 스킴이 모양을 정한다.
307
+ *
308
+ * ── 두 길 (2026-08-21) ─────────────────────────────────────────────────────
309
+ * **프리픽스가 있으면** `binding[key]` 를 품번(itemRef)으로 보고 SGTIN 을 조립한다(예전 거동).
310
+ * **없으면** `binding[key]` 가 **이미 완성된 클래스 식별자**다 — 커넥터가 선언된 도메인 아래에서
311
+ * 만든 CBV 형식(`https://<도메인>/class/<id>`)이고, 조립하면 그 값을 망친다.
312
+ *
313
+ * 실 연동에서 이것이 드러났다: 커넥터가 `binding` 에 완성된 URI 를 실었는데 커널이 그것을 품번으로
314
+ * 보고 프리픽스와 이어 붙이려 했다. 선언이 권위이므로 선언된 모양을 그대로 쓴다.
315
+ */
267
316
  classOf(materialKey) {
268
- return sgtinClass(this.productionSpec.companyPrefix, this.productionSpec.binding[materialKey]);
317
+ const spec = this.productionSpec;
318
+ const bound = spec.binding[materialKey];
319
+ if (spec.companyPrefix)
320
+ return sgtinClass(spec.companyPrefix, bound);
321
+ if (!bound) {
322
+ throw new Error(`material '${materialKey}' has no binding — the model must say which item this key names. ` +
323
+ 'The kernel does not invent an identifier for it.');
324
+ }
325
+ /* 이미 완성된 식별자다. 모양이 표준에 맞는지는 판정기가 답한다(§classIdentifierViolation). */
326
+ return bound;
269
327
  }
328
+ /**
329
+ * 자재 키 + 일련번호 → **개체 식별자.**
330
+ *
331
+ * 프리픽스가 있으면 SGTIN(직렬), 없으면 선언된 이름공간 아래 CBV §8.2.4 `.../obj/<Objid>`.
332
+ * `Objid` 는 경로 성분 하나여야 하므로 구분자를 넣지 않는다 — 자재 키와 일련번호를 `-` 로 잇는다.
333
+ */
270
334
  serialOf(materialKey, serial) {
271
- return sgtinUri(this.productionSpec.companyPrefix, this.productionSpec.binding[materialKey], serial);
335
+ const spec = this.productionSpec;
336
+ if (spec.companyPrefix)
337
+ return sgtinUri(spec.companyPrefix, spec.binding[materialKey], serial);
338
+ const uri = this.declaredObjectId(`${materialKey}-${serial}`);
339
+ if (uri)
340
+ return uri;
341
+ throw new Error(`cannot name an instance of '${materialKey}': the model declares neither \`companyPrefix\` (GS1 keys) nor a ` +
342
+ 'usable `identity.namespaces` entry (CBV 2.0 §8.2.4 `.../obj/<id>`). The kernel does not invent one.');
272
343
  }
273
344
  /**
274
345
  * 이 공정이 **만든다고 선언한 품목** — 표준 `OpMaterialSpecification` `MaterialUse='produced'`.
@@ -424,7 +495,8 @@ export class MesKernel extends FlowEngine {
424
495
  * 번호라고 말해 주지 않는다.
425
496
  */
426
497
  workOrderId(serial) {
427
- const identity = this.productionSpec.identity;
498
+ /* 선언을 읽는 곳은 하나다(§`FlowEngine.identityDeclaration`) — 직접 읽으면 모델 자리를 놓친다. */
499
+ const identity = this.identityDeclaration();
428
500
  for (const ns of identity?.namespaces ?? []) {
429
501
  const uri = bizTransactionUri(ns, serial);
430
502
  if (uri)
@@ -6643,10 +6643,14 @@ var MesKernel = class extends FlowEngine {
6643
6643
  if (productionSpec?.definition?.operations) {
6644
6644
  this.loadOperations(productionSpec.definition.operations);
6645
6645
  }
6646
- if (productionSpec?.definition?.recipes?.length && (!productionSpec.binding || !productionSpec.companyPrefix)) {
6647
- throw new Error(
6648
- "recipe-driven production needs `binding` and `companyPrefix` \u2014 the definition does not know GTINs, so material keys cannot be resolved to GS1 identifiers"
6649
- );
6646
+ if (productionSpec?.definition?.recipes?.length) {
6647
+ const hasPrefix = !!productionSpec.companyPrefix;
6648
+ const hasNamespace = !!productionSpec.identity?.namespaces?.length;
6649
+ if (!productionSpec.binding || !(hasPrefix || hasNamespace)) {
6650
+ throw new Error(
6651
+ "recipe-driven production needs `binding`, and either `companyPrefix` (GS1 keys) or `identity.namespaces` (domain-based identifiers \u2014 CBV 2.0 \xA78.3.4 `.../class/<id>`, assigned by the owner of that internet domain). The definition does not know identifiers, so material keys cannot be resolved without one of the two. The kernel does not invent a company prefix."
6652
+ );
6653
+ }
6650
6654
  }
6651
6655
  if (productionSpec?.definition?.operations) {
6652
6656
  this.assertNoDoubleProduction(productionSpec.definition.operations);
@@ -6697,7 +6701,16 @@ var MesKernel = class extends FlowEngine {
6697
6701
  * 아니라 **하나를 고르는** 것이므로 규칙이 갈리지 않는다.
6698
6702
  */
6699
6703
  identityDeclaration() {
6700
- return this.productionSpec?.identity ?? super.identityDeclaration();
6704
+ const own = this.productionSpec?.identity;
6705
+ const model = super.identityDeclaration();
6706
+ if (!own) return model;
6707
+ if (!model) return own;
6708
+ return {
6709
+ namespaces: own.namespaces?.length ? own.namespaces : model.namespaces,
6710
+ documentTypes: { ...model.documentTypes, ...own.documentTypes },
6711
+ ...own.companyPrefix ?? model.companyPrefix ? { companyPrefix: own.companyPrefix ?? model.companyPrefix } : {},
6712
+ ...own.issuedClaim ?? model.issuedClaim ? { issuedClaim: own.issuedClaim ?? model.issuedClaim } : {}
6713
+ };
6701
6714
  }
6702
6715
  identityGroundingView() {
6703
6716
  return identityGroundingOf(this.productionSpec);
@@ -6846,11 +6859,42 @@ var MesKernel = class extends FlowEngine {
6846
6859
  return d.recipes[0];
6847
6860
  }
6848
6861
  /** 자재 키 → 구체 gtin 클래스(idpat). 구체 식별은 바인딩+prefix 로 인스턴스가 주입. */
6862
+ /**
6863
+ * 자재 키 → **품목 클래스 식별자.** 선언된 스킴이 모양을 정한다.
6864
+ *
6865
+ * ── 두 길 (2026-08-21) ─────────────────────────────────────────────────────
6866
+ * **프리픽스가 있으면** `binding[key]` 를 품번(itemRef)으로 보고 SGTIN 을 조립한다(예전 거동).
6867
+ * **없으면** `binding[key]` 가 **이미 완성된 클래스 식별자**다 — 커넥터가 선언된 도메인 아래에서
6868
+ * 만든 CBV 형식(`https://<도메인>/class/<id>`)이고, 조립하면 그 값을 망친다.
6869
+ *
6870
+ * 실 연동에서 이것이 드러났다: 커넥터가 `binding` 에 완성된 URI 를 실었는데 커널이 그것을 품번으로
6871
+ * 보고 프리픽스와 이어 붙이려 했다. 선언이 권위이므로 선언된 모양을 그대로 쓴다.
6872
+ */
6849
6873
  classOf(materialKey) {
6850
- return sgtinClass(this.productionSpec.companyPrefix, this.productionSpec.binding[materialKey]);
6874
+ const spec = this.productionSpec;
6875
+ const bound = spec.binding[materialKey];
6876
+ if (spec.companyPrefix) return sgtinClass(spec.companyPrefix, bound);
6877
+ if (!bound) {
6878
+ throw new Error(
6879
+ `material '${materialKey}' has no binding \u2014 the model must say which item this key names. The kernel does not invent an identifier for it.`
6880
+ );
6881
+ }
6882
+ return bound;
6851
6883
  }
6884
+ /**
6885
+ * 자재 키 + 일련번호 → **개체 식별자.**
6886
+ *
6887
+ * 프리픽스가 있으면 SGTIN(직렬), 없으면 선언된 이름공간 아래 CBV §8.2.4 `.../obj/<Objid>`.
6888
+ * `Objid` 는 경로 성분 하나여야 하므로 구분자를 넣지 않는다 — 자재 키와 일련번호를 `-` 로 잇는다.
6889
+ */
6852
6890
  serialOf(materialKey, serial) {
6853
- return sgtinUri(this.productionSpec.companyPrefix, this.productionSpec.binding[materialKey], serial);
6891
+ const spec = this.productionSpec;
6892
+ if (spec.companyPrefix) return sgtinUri(spec.companyPrefix, spec.binding[materialKey], serial);
6893
+ const uri = this.declaredObjectId(`${materialKey}-${serial}`);
6894
+ if (uri) return uri;
6895
+ throw new Error(
6896
+ `cannot name an instance of '${materialKey}': the model declares neither \`companyPrefix\` (GS1 keys) nor a usable \`identity.namespaces\` entry (CBV 2.0 \xA78.2.4 \`.../obj/<id>\`). The kernel does not invent one.`
6897
+ );
6854
6898
  }
6855
6899
  /**
6856
6900
  * 이 공정이 **만든다고 선언한 품목** — 표준 `OpMaterialSpecification` `MaterialUse='produced'`.
@@ -6991,7 +7035,7 @@ var MesKernel = class extends FlowEngine {
6991
7035
  * 번호라고 말해 주지 않는다.
6992
7036
  */
6993
7037
  workOrderId(serial) {
6994
- const identity = this.productionSpec.identity;
7038
+ const identity = this.identityDeclaration();
6995
7039
  for (const ns of identity?.namespaces ?? []) {
6996
7040
  const uri = bizTransactionUri(ns, serial);
6997
7041
  if (uri) return uri;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/twin-kernel",
3
- "version": "0.7.45",
3
+ "version": "0.7.46",
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": {