@operato/twin-kernel 0.7.59 → 0.7.61

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.
@@ -1633,6 +1633,25 @@ export interface OrderState {
1633
1633
  * 이것도 상태에 없어서 되살아난 오더는 빈 문자열을 갖고, 이후 모든 이벤트의 거래번호가 빈다.
1634
1634
  */
1635
1635
  bizTransaction?: string;
1636
+ /**
1637
+ * 이 오더가 **이행하는 계획** — 표준 `OperationsRequestID`.
1638
+ *
1639
+ * ── 왜 부모-자식이 아닌가 (원문 대조 2026-08-24) ───────────────────────────
1640
+ * 「생산 계획 하나에 작업지시 여럿」을 담아야 했고, `parentOrderId` 같은 위계를 만들려다 원문을
1641
+ * 읽었다. **`OperationsRequestType` 에는 부모 축이 없다** — 계획은 `SegmentRequirement` 로 분해되고
1642
+ * 자식 계획을 갖지 않는다.
1643
+ *
1644
+ * 표준이 쓰는 방향은 반대다: **이행하는 쪽이 계획을 가리킨다.** `OperationsResponseType` 과
1645
+ * `OpSegmentResponseType` **둘 다** `OperationsRequestID` 를 든다(B2MML-OperationsPerformance ·
1646
+ * OperationsPerformanceTypes). 그래서 위계를 발명하지 않고 그 참조를 그대로 쓴다.
1647
+ *
1648
+ * 그러면 「이 계획이 얼마나 이행됐나」는 이 값으로 묶어 답한다 — **새 축을 만들지 않는다.** 계획
1649
+ * 자체도 오더로 들어오면 그 오더의 `requested` 가 계획량이고, 묶인 것들의 합과 **다를 수 있다.**
1650
+ * 그 차이가 곧 진단이므로 둘을 같은 값으로 만들지 않는다.
1651
+ *
1652
+ * `bizTransaction` 과 다르다 — 그것은 외부 거래(PO/SO)이고 이것은 **우리 안의 상위 계획**이다.
1653
+ */
1654
+ operationsRequestId?: string;
1636
1655
  /**
1637
1656
  * 도메인이 **약속해 둔 자리와 시각창** — 어디로/언제 들이기로 했는가.
1638
1657
  *
@@ -2439,6 +2458,8 @@ export interface OrderStatusDelta {
2439
2458
  */
2440
2459
  allocated?: string[];
2441
2460
  bizTransaction?: string;
2461
+ /** 이행하는 계획 — 표준 `OperationsRequestID`(§`OrderState.operationsRequestId`). */
2462
+ operationsRequestId?: string;
2442
2463
  /**
2443
2464
  * 도메인이 **약속해 둔 자리와 시각창** — 어디로/언제 들이기로 했는가.
2444
2465
  *
package/dist/epcis.d.ts CHANGED
@@ -81,6 +81,16 @@ export declare const CBV_BIZSTEP: {
81
81
  * 공정 중 검사가 그 모양이다.
82
82
  */
83
83
  readonly inspecting: "urn:epcglobal:cbv:bizstep:inspecting";
84
+ /**
85
+ * **어느 단계로도 이름 붙지 않는 활동** — 원문: 「A business step not identified by any other」.
86
+ *
87
+ * 이것은 근사가 아니라 **표준이 준 낱말**이다. 그 구별이 중요하다: 재고 조정처럼 한 낱말이 두 일을
88
+ * 하는 원천(세어 보고 맞춘 것 · 사람이 정정한 것)을 `cycle_counting` 으로 옮기면 **일어나지 않은
89
+ * 계수를 기록**하게 된다. 그때 쓰는 것이 이 값이고, 무슨 일이었는지는 값으로 함께 나른다.
90
+ *
91
+ * 원문 확인: CBV Standard Release 2.0(Ratified Jun 2022) §7.1.
92
+ */
93
+ readonly other: "urn:epcglobal:cbv:bizstep:other";
84
94
  };
85
95
  export type EpcisEventType = 'ObjectEvent' | 'AggregationEvent' | 'TransactionEvent' | 'TransformationEvent';
86
96
  export type EpcisAction = 'ADD' | 'OBSERVE' | 'DELETE';
package/dist/epcis.js CHANGED
@@ -89,7 +89,17 @@ export const CBV_BIZSTEP = {
89
89
  * 이 단계에 `DISP.conformant`/`DISP.non_conformant` 가 붙어 판정이 처분으로 남는다 — 입고검수·
90
90
  * 공정 중 검사가 그 모양이다.
91
91
  */
92
- inspecting: 'urn:epcglobal:cbv:bizstep:inspecting'
92
+ inspecting: 'urn:epcglobal:cbv:bizstep:inspecting',
93
+ /**
94
+ * **어느 단계로도 이름 붙지 않는 활동** — 원문: 「A business step not identified by any other」.
95
+ *
96
+ * 이것은 근사가 아니라 **표준이 준 낱말**이다. 그 구별이 중요하다: 재고 조정처럼 한 낱말이 두 일을
97
+ * 하는 원천(세어 보고 맞춘 것 · 사람이 정정한 것)을 `cycle_counting` 으로 옮기면 **일어나지 않은
98
+ * 계수를 기록**하게 된다. 그때 쓰는 것이 이 값이고, 무슨 일이었는지는 값으로 함께 나른다.
99
+ *
100
+ * 원문 확인: CBV Standard Release 2.0(Ratified Jun 2022) §7.1.
101
+ */
102
+ other: 'urn:epcglobal:cbv:bizstep:other'
93
103
  };
94
104
  // ── GS1 EPC URI 헬퍼 (표준) ────────────────────────────────────────────────
95
105
  /** SSCC (물류단위: 팔레트/화물/트레일러) — 결정적 카운터 기반. */
@@ -433,6 +433,8 @@ export class ObservedReducer {
433
433
  ...(d.recipeKey ? { recipeKey: d.recipeKey } : {}),
434
434
  ...(d.allocated?.length ? { allocated: d.allocated.slice() } : {}),
435
435
  ...(d.bizTransaction ? { bizTransaction: d.bizTransaction } : {}),
436
+ /* 이행하는 계획 — 「이 계획이 얼마나 됐나」를 이 값으로 묶어 답한다(§`OrderState.operationsRequestId`). */
437
+ ...(d.operationsRequestId ? { operationsRequestId: d.operationsRequestId } : {}),
436
438
  /* 약속해 둔 자리·시각창도 채운다 — 시뮬만 알면 미러 위 예측이 「어디로 들일지」를 모른다.
437
439
  (이 필드가 어느 길에서 빠지면 무슨 일이 나는지는 §dockDoor 에 적혀 있다.) */
438
440
  ...(d.dockDoor ? { dockDoor: d.dockDoor } : {}),
@@ -1,6 +1,6 @@
1
1
  import type { IngestResult } from './face2-adapter.ts';
2
2
  /** 이 문이 받는 여섯 가지 — 리듀서가 접는 것과 같은 목록(주목 확인은 우리 안의 행위라 제외). */
3
- export type OperationalKind = 'task' | 'equipment' | 'person' | 'asset' | 'order' | 'quality';
3
+ export type OperationalKind = 'task' | 'equipment' | 'person' | 'asset' | 'order' | 'quality' | 'test';
4
4
  /**
5
5
  * 정규 운영 레코드 — **델타의 필드 이름 + 시각(`at`)**.
6
6
  *
@@ -109,7 +109,7 @@ const SPECS = {
109
109
  fields: {
110
110
  orderId: 'string', kind: 'string', status: 'string', requested: 'number', fulfilled: 'number',
111
111
  gtin: 'string', held: 'boolean', lines: 'object[]', priority: 'number', startTime: 'string', endTime: 'string',
112
- allocated: 'string[]', bizTransaction: 'string', dockDoor: 'string', windowStartMs: 'number', recordTime: 'string'
112
+ allocated: 'string[]', bizTransaction: 'string', operationsRequestId: 'string', dockDoor: 'string', windowStartMs: 'number', recordTime: 'string'
113
113
  }
114
114
  /* 상태·종류는 도메인이 소유한다 — 닫지 않는다. */
115
115
  },
@@ -119,6 +119,27 @@ const SPECS = {
119
119
  /* 누적 카운터가 없으면 OEE 가 양품률을 못 센다 — 판정 하나만으로는 비율이 나오지 않는다. */
120
120
  required: ['moverId', 'good', 'goodCount', 'scrapCount'], // vocabulary-guard: allow
121
121
  fields: { moverId: 'string', good: 'boolean', goodCount: 'number', scrapCount: 'number', recordTime: 'string' } // vocabulary-guard: allow
122
+ },
123
+ /*
124
+ * **시험 결과** — 대상을 가리켜 들어온다(표준 `TestResult.TestableObjectID`).
125
+ *
126
+ * `result` 를 **요구하지 않는다**: 재기만 하고 판정하지 않는 원천이 정상이고, 요구하면 그 원천의
127
+ * 사실을 아예 담을 수 없다(§`TestResult.result`). 비어 있으면 커널이 선언된 기준으로 판정하고
128
+ * `derived` 를 세운다 — 판정하지 못하면 비워 둔다.
129
+ *
130
+ * `propertyMeasurements` 안쪽은 재검사하지 않는다 — 그 모양은 `PropertyMeasurement` 계약이고,
131
+ * 여기서 두 번 지키면 두 벌이 된다(설비 `motion` 과 같은 규율).
132
+ */
133
+ test: {
134
+ eventType: OP_EVENT.test,
135
+ identity: 'testableObjectId',
136
+ /* 무엇을 어느 기준으로 시험했나 — 둘 중 하나가 없으면 그 결과는 아무 데도 붙지 못한다. */
137
+ required: ['testableObjectId', 'specId'],
138
+ fields: {
139
+ testableObjectId: 'string', specId: 'string', result: 'string', at: 'string', expiresAt: 'string',
140
+ derived: 'boolean', propertyMeasurements: 'object[]', recordTime: 'string'
141
+ },
142
+ enums: { result: ['pass', 'fail'] }
122
143
  }
123
144
  };
124
145
  /**
@@ -152,6 +173,20 @@ export function operationalKindOf(record) {
152
173
  return 'task'; // 작업이 든 `orderId` 는 소속(참조)이다
153
174
  if (has('orderId'))
154
175
  return 'order';
176
+ /*
177
+ * ── ★ **채널을 열고 들어오는 길을 내지 않았다** (2026-08-24) ─────────────────
178
+ * `OP_EVENT.test` 를 계약에 냈는데 이 라우팅이 `testableObjectId` 를 보지 않았다. 그래서 커넥터의
179
+ * 시험 결과가 **어느 통도 아니어서 조용히 버려졌다** — 거부 목록에도 남지 않았다(운영 경로를 아예
180
+ * 지나지 않으므로).
181
+ *
182
+ * 같은 부류가 하루에 세 번 났다: `ilmd`(매핑에 자리 없음) · 사건 시각(이름 어긋남) · 그리고 이것.
183
+ * **계약에 자리를 만드는 것과 그 자리로 가는 길을 내는 것은 다른 일이다.** 앞의 것만 하면 보내는
184
+ * 쪽에는 「실었다」로 보이고 화면에는 「없다」로 보인다.
185
+ *
186
+ * 순서상 뒤에 둔다 — 시험 결과가 작업·오더를 함께 가리킬 수 있고, 그때 그것은 **그 작업의 사실**이다.
187
+ */
188
+ if (has('testableObjectId'))
189
+ return 'test';
155
190
  return undefined;
156
191
  }
157
192
  /** 이 레코드가 운영 사실인가 — 호스트의 라우팅이 묻는 자리. */
@@ -1078,7 +1078,17 @@ var CBV_BIZSTEP = {
1078
1078
  * 이 단계에 `DISP.conformant`/`DISP.non_conformant` 가 붙어 판정이 처분으로 남는다 — 입고검수·
1079
1079
  * 공정 중 검사가 그 모양이다.
1080
1080
  */
1081
- inspecting: "urn:epcglobal:cbv:bizstep:inspecting"
1081
+ inspecting: "urn:epcglobal:cbv:bizstep:inspecting",
1082
+ /**
1083
+ * **어느 단계로도 이름 붙지 않는 활동** — 원문: 「A business step not identified by any other」.
1084
+ *
1085
+ * 이것은 근사가 아니라 **표준이 준 낱말**이다. 그 구별이 중요하다: 재고 조정처럼 한 낱말이 두 일을
1086
+ * 하는 원천(세어 보고 맞춘 것 · 사람이 정정한 것)을 `cycle_counting` 으로 옮기면 **일어나지 않은
1087
+ * 계수를 기록**하게 된다. 그때 쓰는 것이 이 값이고, 무슨 일이었는지는 값으로 함께 나른다.
1088
+ *
1089
+ * 원문 확인: CBV Standard Release 2.0(Ratified Jun 2022) §7.1.
1090
+ */
1091
+ other: "urn:epcglobal:cbv:bizstep:other"
1082
1092
  };
1083
1093
  function ssccUri(companyPrefix, serial) {
1084
1094
  return `urn:epc:id:sscc:${companyPrefix}.${String(serial).padStart(10, "0")}`;
@@ -1681,6 +1691,8 @@ var ObservedReducer = class {
1681
1691
  ...d.recipeKey ? { recipeKey: d.recipeKey } : {},
1682
1692
  ...d.allocated?.length ? { allocated: d.allocated.slice() } : {},
1683
1693
  ...d.bizTransaction ? { bizTransaction: d.bizTransaction } : {},
1694
+ /* 이행하는 계획 — 「이 계획이 얼마나 됐나」를 이 값으로 묶어 답한다(§`OrderState.operationsRequestId`). */
1695
+ ...d.operationsRequestId ? { operationsRequestId: d.operationsRequestId } : {},
1684
1696
  /* 약속해 둔 자리·시각창도 채운다 — 시뮬만 알면 미러 위 예측이 「어디로 들일지」를 모른다.
1685
1697
  (이 필드가 어느 길에서 빠지면 무슨 일이 나는지는 §dockDoor 에 적혀 있다.) */
1686
1698
  ...d.dockDoor ? { dockDoor: d.dockDoor } : {},
@@ -8975,6 +8987,7 @@ var SPECS = {
8975
8987
  endTime: "string",
8976
8988
  allocated: "string[]",
8977
8989
  bizTransaction: "string",
8990
+ operationsRequestId: "string",
8978
8991
  dockDoor: "string",
8979
8992
  windowStartMs: "number",
8980
8993
  recordTime: "string"
@@ -8990,6 +9003,33 @@ var SPECS = {
8990
9003
  // vocabulary-guard: allow
8991
9004
  fields: { moverId: "string", good: "boolean", goodCount: "number", scrapCount: "number", recordTime: "string" }
8992
9005
  // vocabulary-guard: allow
9006
+ },
9007
+ /*
9008
+ * **시험 결과** — 대상을 가리켜 들어온다(표준 `TestResult.TestableObjectID`).
9009
+ *
9010
+ * `result` 를 **요구하지 않는다**: 재기만 하고 판정하지 않는 원천이 정상이고, 요구하면 그 원천의
9011
+ * 사실을 아예 담을 수 없다(§`TestResult.result`). 비어 있으면 커널이 선언된 기준으로 판정하고
9012
+ * `derived` 를 세운다 — 판정하지 못하면 비워 둔다.
9013
+ *
9014
+ * `propertyMeasurements` 안쪽은 재검사하지 않는다 — 그 모양은 `PropertyMeasurement` 계약이고,
9015
+ * 여기서 두 번 지키면 두 벌이 된다(설비 `motion` 과 같은 규율).
9016
+ */
9017
+ test: {
9018
+ eventType: OP_EVENT.test,
9019
+ identity: "testableObjectId",
9020
+ /* 무엇을 어느 기준으로 시험했나 — 둘 중 하나가 없으면 그 결과는 아무 데도 붙지 못한다. */
9021
+ required: ["testableObjectId", "specId"],
9022
+ fields: {
9023
+ testableObjectId: "string",
9024
+ specId: "string",
9025
+ result: "string",
9026
+ at: "string",
9027
+ expiresAt: "string",
9028
+ derived: "boolean",
9029
+ propertyMeasurements: "object[]",
9030
+ recordTime: "string"
9031
+ },
9032
+ enums: { result: ["pass", "fail"] }
8993
9033
  }
8994
9034
  };
8995
9035
  function operationalKindOf(record) {
@@ -9002,6 +9042,7 @@ function operationalKindOf(record) {
9002
9042
  if (has("assetId")) return "asset";
9003
9043
  if (has("taskId")) return "task";
9004
9044
  if (has("orderId")) return "order";
9045
+ if (has("testableObjectId")) return "test";
9005
9046
  return void 0;
9006
9047
  }
9007
9048
  function isOperationalRecord(record) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/twin-kernel",
3
- "version": "0.7.59",
3
+ "version": "0.7.61",
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": {