@operato/ops-contract 0.9.20 → 0.9.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/contract.js CHANGED
@@ -1112,7 +1112,7 @@ export const OP_EVENT = {
1112
1112
  * 승인은 자재를 움직이지 않는다. 자재가 트윈 밖으로 나가는 것은 EPCIS `shipping` 사건이다. 둘을 한 사건으로
1113
1113
  * 접지 않는다 — 승인됐는데 안 나간 배치가 트윈이 보여야 할 상태다.
1114
1114
  */
1115
- materialLot: 'material-lot.status',
1115
+ materialLot: 'material-lot.status', // 레코드: { lotId(표준 MaterialLotID = ItemState.epc 의 값), status, decidedBy?, reason?, decidedAt? }
1116
1116
  /**
1117
1117
  * **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 그것을 알린다.
1118
1118
  *
@@ -4,12 +4,6 @@ export declare const MES_BIZSTEP: {
4
4
  readonly receiving: "urn:epcglobal:cbv:bizstep:receiving";
5
5
  readonly producing: "urn:epcglobal:cbv:bizstep:commissioning";
6
6
  readonly storing: "urn:epcglobal:cbv:bizstep:storing";
7
- /**
8
- * **출하** — CBV `shipping`: 물품이 시설을 떠난다. 자재를 트윈 밖으로 내는 것은 이 사건이다(disposition
9
- * `in_transit`, readPoint = 출하 dock). 출하 **승인**은 이 사건이 아니라 로트 상태(`OP_EVENT.materialLot`)다 —
10
- * 승인됐는데 안 나간 배치가 보여야 한다(2026-09-16, ADR-0046 곁).
11
- */
12
- readonly shipping: "urn:epcglobal:cbv:bizstep:shipping";
13
7
  };
14
8
  /** 작업지시(Work Order) = 생산 오더 거래 유형. */
15
9
  export declare const BTT_PRODORDER = "urn:epcglobal:cbv:btt:prodorder";
@@ -2,13 +2,7 @@
2
2
  export const MES_BIZSTEP = {
3
3
  receiving: 'urn:epcglobal:cbv:bizstep:receiving', // 원자재 수령
4
4
  producing: 'urn:epcglobal:cbv:bizstep:commissioning', // 생산(제품 최초 생성)
5
- storing: 'urn:epcglobal:cbv:bizstep:storing', // 완제품 저장
6
- /**
7
- * **출하** — CBV `shipping`: 물품이 시설을 떠난다. 자재를 트윈 밖으로 내는 것은 이 사건이다(disposition
8
- * `in_transit`, readPoint = 출하 dock). 출하 **승인**은 이 사건이 아니라 로트 상태(`OP_EVENT.materialLot`)다 —
9
- * 승인됐는데 안 나간 배치가 보여야 한다(2026-09-16, ADR-0046 곁).
10
- */
11
- shipping: 'urn:epcglobal:cbv:bizstep:shipping'
5
+ storing: 'urn:epcglobal:cbv:bizstep:storing' // 완제품 저장
12
6
  };
13
7
  /** 작업지시(Work Order) = 생산 오더 거래 유형. */
14
8
  export const BTT_PRODORDER = 'urn:epcglobal:cbv:btt:prodorder';
@@ -65,6 +65,12 @@ export interface ObjectShape {
65
65
  * 거부했다. 자원(설비·사람·자산)을 먼저 보고, 작업을 오더보다 먼저 본다.
66
66
  */
67
67
  export declare function operationalKindOf(record: unknown): OperationalKind | undefined;
68
+ /**
69
+ * 어느 갈래도 아닐 때의 거절 문장 — **표에서 세어 만든다.** 손으로 적은 목록은 다섯에서 멈춰 있었다(kind 는 열둘).
70
+ * 그 문장은 보내는 쪽이 실제로 읽는 것이라, 빠진 갈래는 「애초에 못 보내는 것」으로 읽힌다(2026-09-16, 인티그레이션
71
+ * 레인 측정). 여기서 만들면 kind 가 늘 때 문장도 함께 는다.
72
+ */
73
+ export declare function unknownKindReason(): string;
68
74
  /** 이 레코드가 운영 사실인가 — 호스트의 라우팅이 묻는 자리. */
69
75
  /**
70
76
  * 그 종류의 사실을 **무엇이 가리키나** — 선언된 identity 칸의 이름.
@@ -323,17 +323,19 @@ const SPECS = {
323
323
  /*
324
324
  * **로트 상태** — ISA-95 `MaterialLot.Status`. 출하 승인(batch release)이 들어오는 문(§`OP_EVENT.materialLot`).
325
325
  *
326
- * `disposition`(subjectId + decision) 과 필드가 겹치지 않는다 — 여기는 `epc` + `status`. 상태 낱말은 열려
327
- * 있어 enum 없다(오더 상태와 같은 규율). 로트 전체의 사실이므로 `subLotId` 받지 않는다 부분마다 다른
328
- * 상태가 필요해지면 그것은 다른 사실이다.
326
+ * `disposition`(subjectId + decision) 과 필드가 겹치지 않는다 — 여기는 `lotId` + `status`. 정체 이름이 표준
327
+ * `MaterialLotID` 이유가 하나 있다: 문은 `epc` 레코드를 EPCIS 어휘로 보고 받지 않는다
328
+ * (§`operationalKindOf`). 값은 로트의 식별자(`ItemState.epc` 와 같은 값)다. 상태 낱말은 열려 있어 enum 이
329
+ * 없다(오더 상태와 같은 규율). 로트 전체의 사실이므로 부분(`subLotId`)은 받지 않는다 — 부분마다 다른 상태가
330
+ * 필요해지면 그것은 다른 사실이다.
329
331
  */
330
332
  'material-lot': {
331
333
  eventType: OP_EVENT.materialLot,
332
- match: ['epc', 'status'],
334
+ match: ['lotId', 'status'],
333
335
  matchOrder: 85,
334
- identity: 'epc',
335
- required: ['epc', 'status'],
336
- fields: { epc: 'string', status: 'string', decidedBy: 'string', reason: 'string', decidedAt: 'string', recordTime: 'string' }
336
+ identity: 'lotId',
337
+ required: ['lotId', 'status'],
338
+ fields: { lotId: 'string', status: 'string', decidedBy: 'string', reason: 'string', decidedAt: 'string', recordTime: 'string' }
337
339
  },
338
340
  test: {
339
341
  eventType: OP_EVENT.test,
@@ -425,6 +427,20 @@ export function operationalKindOf(record) {
425
427
  }
426
428
  return undefined;
427
429
  }
430
+ /**
431
+ * 어느 갈래도 아닐 때의 거절 문장 — **표에서 세어 만든다.** 손으로 적은 목록은 다섯에서 멈춰 있었다(kind 는 열둘).
432
+ * 그 문장은 보내는 쪽이 실제로 읽는 것이라, 빠진 갈래는 「애초에 못 보내는 것」으로 읽힌다(2026-09-16, 인티그레이션
433
+ * 레인 측정). 여기서 만들면 kind 가 늘 때 문장도 함께 는다.
434
+ */
435
+ export function unknownKindReason() {
436
+ const shapes = MATCH_ORDER.map(kind => {
437
+ const spec = SPECS[kind];
438
+ const fields = spec.match ?? [spec.identity];
439
+ return `${kind}: ${fields.join('+')}`;
440
+ });
441
+ // vocabulary-guard: allow 거부 이유가 계약 필드 이름을 말한다
442
+ return `어느 운영 사실인지 모른다 — 갈래를 정하는 필드가 필요하다(${shapes.join(' · ')}). epc 를 든 레코드는 EPCIS 문으로 간다`;
443
+ }
428
444
  /**
429
445
  * 판정 순서 — 표의 `matchOrder` 에서 한 번만 만든다.
430
446
  *
@@ -471,10 +487,7 @@ export function ingestOperationalRecords(records, opts) {
471
487
  for (const record of arr) {
472
488
  const kind = operationalKindOf(record);
473
489
  if (!kind) {
474
- rejected.push({
475
- record,
476
- errors: ['어느 운영 사실인지 모른다 — 정체 필드가 필요하다(taskId · moverId(+good=품질) · personId · assetId · orderId)'] // vocabulary-guard: allow 거부 이유가 계약 필드 이름을 말한다
477
- });
490
+ rejected.push({ record, errors: [unknownKindReason()] });
478
491
  continue;
479
492
  }
480
493
  const spec = SPECS[kind];
@@ -230,6 +230,7 @@ __export(index_exports, {
230
230
  transactionEvent: () => transactionEvent,
231
231
  transformationEvent: () => transformationEvent,
232
232
  unavailableVerdict: () => unavailableVerdict,
233
+ unknownKindReason: () => unknownKindReason,
233
234
  validateDomainDefinition: () => validateDomainDefinition,
234
235
  validateEpcisEvent: () => validateEpcisEvent,
235
236
  validatePerformance: () => validatePerformance,
@@ -925,6 +926,7 @@ var OP_EVENT = {
925
926
  * 접지 않는다 — 승인됐는데 안 나간 배치가 트윈이 보여야 할 상태다.
926
927
  */
927
928
  materialLot: "material-lot.status",
929
+ // 레코드: { lotId(표준 MaterialLotID = ItemState.epc 의 값), status, decidedBy?, reason?, decidedAt? }
928
930
  /**
929
931
  * **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 그것을 알린다.
930
932
  *
@@ -1316,14 +1318,8 @@ var MES_BIZSTEP = {
1316
1318
  // 원자재 수령
1317
1319
  producing: "urn:epcglobal:cbv:bizstep:commissioning",
1318
1320
  // 생산(제품 최초 생성)
1319
- storing: "urn:epcglobal:cbv:bizstep:storing",
1321
+ storing: "urn:epcglobal:cbv:bizstep:storing"
1320
1322
  // 완제품 저장
1321
- /**
1322
- * **출하** — CBV `shipping`: 물품이 시설을 떠난다. 자재를 트윈 밖으로 내는 것은 이 사건이다(disposition
1323
- * `in_transit`, readPoint = 출하 dock). 출하 **승인**은 이 사건이 아니라 로트 상태(`OP_EVENT.materialLot`)다 —
1324
- * 승인됐는데 안 나간 배치가 보여야 한다(2026-09-16, ADR-0046 곁).
1325
- */
1326
- shipping: "urn:epcglobal:cbv:bizstep:shipping"
1327
1323
  };
1328
1324
  var BTT_PRODORDER = "urn:epcglobal:cbv:btt:prodorder";
1329
1325
  function sgtinUri(companyPrefix, itemRef, serial) {
@@ -3713,17 +3709,19 @@ var SPECS = {
3713
3709
  /*
3714
3710
  * **로트 상태** — ISA-95 `MaterialLot.Status`. 출하 승인(batch release)이 들어오는 문(§`OP_EVENT.materialLot`).
3715
3711
  *
3716
- * `disposition`(subjectId + decision) 과 필드가 겹치지 않는다 — 여기는 `epc` + `status`. 상태 낱말은 열려
3717
- * 있어 enum 없다(오더 상태와 같은 규율). 로트 전체의 사실이므로 `subLotId` 받지 않는다 부분마다 다른
3718
- * 상태가 필요해지면 그것은 다른 사실이다.
3712
+ * `disposition`(subjectId + decision) 과 필드가 겹치지 않는다 — 여기는 `lotId` + `status`. 정체 이름이 표준
3713
+ * `MaterialLotID` 이유가 하나 있다: 문은 `epc` 레코드를 EPCIS 어휘로 보고 받지 않는다
3714
+ * (§`operationalKindOf`). 값은 로트의 식별자(`ItemState.epc` 와 같은 값)다. 상태 낱말은 열려 있어 enum 이
3715
+ * 없다(오더 상태와 같은 규율). 로트 전체의 사실이므로 부분(`subLotId`)은 받지 않는다 — 부분마다 다른 상태가
3716
+ * 필요해지면 그것은 다른 사실이다.
3719
3717
  */
3720
3718
  "material-lot": {
3721
3719
  eventType: OP_EVENT.materialLot,
3722
- match: ["epc", "status"],
3720
+ match: ["lotId", "status"],
3723
3721
  matchOrder: 85,
3724
- identity: "epc",
3725
- required: ["epc", "status"],
3726
- fields: { epc: "string", status: "string", decidedBy: "string", reason: "string", decidedAt: "string", recordTime: "string" }
3722
+ identity: "lotId",
3723
+ required: ["lotId", "status"],
3724
+ fields: { lotId: "string", status: "string", decidedBy: "string", reason: "string", decidedAt: "string", recordTime: "string" }
3727
3725
  },
3728
3726
  test: {
3729
3727
  eventType: OP_EVENT.test,
@@ -3805,6 +3803,14 @@ function operationalKindOf(record) {
3805
3803
  }
3806
3804
  return void 0;
3807
3805
  }
3806
+ function unknownKindReason() {
3807
+ const shapes = MATCH_ORDER.map((kind) => {
3808
+ const spec = SPECS[kind];
3809
+ const fields = spec.match ?? [spec.identity];
3810
+ return `${kind}: ${fields.join("+")}`;
3811
+ });
3812
+ return `\uC5B4\uB290 \uC6B4\uC601 \uC0AC\uC2E4\uC778\uC9C0 \uBAA8\uB978\uB2E4 \u2014 \uAC08\uB798\uB97C \uC815\uD558\uB294 \uD544\uB4DC\uAC00 \uD544\uC694\uD558\uB2E4(${shapes.join(" \xB7 ")}). epc \uB97C \uB4E0 \uB808\uCF54\uB4DC\uB294 EPCIS \uBB38\uC73C\uB85C \uAC04\uB2E4`;
3813
+ }
3808
3814
  var MATCH_ORDER = Object.keys(SPECS).sort(
3809
3815
  (a, b) => (SPECS[a].matchOrder ?? 1e3) - (SPECS[b].matchOrder ?? 1e3)
3810
3816
  );
@@ -3822,11 +3828,7 @@ function ingestOperationalRecords(records, opts) {
3822
3828
  for (const record of arr) {
3823
3829
  const kind = operationalKindOf(record);
3824
3830
  if (!kind) {
3825
- rejected.push({
3826
- record,
3827
- errors: ["\uC5B4\uB290 \uC6B4\uC601 \uC0AC\uC2E4\uC778\uC9C0 \uBAA8\uB978\uB2E4 \u2014 \uC815\uCCB4 \uD544\uB4DC\uAC00 \uD544\uC694\uD558\uB2E4(taskId \xB7 moverId(+good=\uD488\uC9C8) \xB7 personId \xB7 assetId \xB7 orderId)"]
3828
- // vocabulary-guard: allow 거부 이유가 계약 필드 이름을 말한다
3829
- });
3831
+ rejected.push({ record, errors: [unknownKindReason()] });
3830
3832
  continue;
3831
3833
  }
3832
3834
  const spec = SPECS[kind];
@@ -4790,6 +4792,7 @@ function nonEmpty(value) {
4790
4792
  transactionEvent,
4791
4793
  transformationEvent,
4792
4794
  unavailableVerdict,
4795
+ unknownKindReason,
4793
4796
  validateDomainDefinition,
4794
4797
  validateEpcisEvent,
4795
4798
  validatePerformance,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/ops-contract",
3
- "version": "0.9.20",
3
+ "version": "0.9.22",
4
4
  "description": "Operations domain contract — the standard vocabulary that producers and readers agree on (EPCIS 2.0/GS1, ISA-95, IEC 61850/ISO 50001). Types, guards, validation. No state, no engine.",
5
5
  "type": "module",
6
6
  "main": "./dist-cjs/index.cjs",