@operato/ops-contract 0.9.17 → 0.9.20
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/README.md +84 -0
- package/dist/command-verdict.d.ts +47 -0
- package/dist/command-verdict.js +65 -0
- package/dist/contract.d.ts +37 -4
- package/dist/contract.js +16 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mes-profile.d.ts +6 -0
- package/dist/mes-profile.js +7 -1
- package/dist/operational-attention.d.ts +43 -0
- package/dist/operational-attention.js +35 -0
- package/dist/operational-ingest.d.ts +6 -2
- package/dist/operational-ingest.js +15 -0
- package/dist/webhook-delivery.d.ts +42 -0
- package/dist/webhook-delivery.js +40 -0
- package/dist/webhook-secret.d.ts +0 -12
- package/dist/webhook-secret.js +5 -4
- package/dist/webhook.d.ts +17 -0
- package/dist/webhook.js +17 -0
- package/dist-cjs/index.cjs +142 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,90 @@ IEC 61850 · ISO 50001 전기 계통과 성과지표
|
|
|
14
14
|
|
|
15
15
|
여기 없는 것은 **정책과 엔진**입니다. 그 사실로 무엇을 할지는 `@operato/twin-kernel` 이 정합니다.
|
|
16
16
|
|
|
17
|
+
## 제품 간 AI 분석 입력 — `OperationalAttentionV1`
|
|
18
|
+
|
|
19
|
+
`OperationalAttentionV1`은 Plant·WMS·YMS·EMS가 자신이 관측한 운영 주의 사실을 Twin 또는 AI 분석기에
|
|
20
|
+
보낼 때 쓰는 공통 봉투입니다. 국제 표준의 알람 포맷을 새로 만드는 것이 아닙니다. EPCIS/ISA-95 등의
|
|
21
|
+
원본 사실을 **어떤 제품이, 어떤 근거로, 무엇을 분석 대상으로 열었거나 닫았는지** 안전하게 전달하는
|
|
22
|
+
Operato의 제품 간 계약입니다.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import {
|
|
26
|
+
OPERATIONAL_ATTENTION_CONTRACT,
|
|
27
|
+
assertOperationalAttentionV1,
|
|
28
|
+
type OperationalAttentionV1
|
|
29
|
+
} from '@operato/ops-contract'
|
|
30
|
+
|
|
31
|
+
const attention: OperationalAttentionV1 = {
|
|
32
|
+
contract: OPERATIONAL_ATTENTION_CONTRACT,
|
|
33
|
+
domainId: 'factory-a',
|
|
34
|
+
attentionId: 'schedule-overdue:line-a:101',
|
|
35
|
+
dedupeKey: 'schedule-overdue:line-a:101',
|
|
36
|
+
kind: 'plant.schedule-overdue',
|
|
37
|
+
subjectLabel: 'Line A schedule 101',
|
|
38
|
+
transition: 'opened',
|
|
39
|
+
impact: 'high',
|
|
40
|
+
observedAt: '2026-09-12T01:00:00.000Z',
|
|
41
|
+
evidenceFingerprint: 'sha256:...',
|
|
42
|
+
facts: [{ system: 'operato-plant', type: 'schedule-entry', id: '101' }]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
assertOperationalAttentionV1(attention)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 공통으로 고정한 것
|
|
49
|
+
|
|
50
|
+
- 식별·재전송: `attentionId`, `dedupeKey`
|
|
51
|
+
- 분석 수명주기: `opened`, `changed`, `resolved`, `reopened`
|
|
52
|
+
- 분석 우선순위: `medium`, `high`
|
|
53
|
+
- 근거: 최소 하나 이상의 안정된 `{ system, type, id }` 원본 사실 참조와 `evidenceFingerprint`
|
|
54
|
+
|
|
55
|
+
사람이 읽는 제목만 있고 원본 사실 ID가 없는 입력은 거부합니다. AI가 그럴듯한 설명을 만들더라도
|
|
56
|
+
근거를 다시 열어 볼 수 없으면 운영 분석의 입력이 될 수 없기 때문입니다.
|
|
57
|
+
|
|
58
|
+
### 제품별로 남기는 것
|
|
59
|
+
|
|
60
|
+
`kind`와 `facts[].type`은 생산자가 소유합니다. 예를 들어 Plant는 `plant.schedule-overdue`, WMS는
|
|
61
|
+
`wms.short-pick`을 쓰며 서로의 업무 모델을 흉내 내지 않습니다. 또한 `acknowledged`, `suppressed`,
|
|
62
|
+
승인, 실제 조치 결과는 생산 제품의 업무 흐름에 남습니다. 이 계약은 분석에 필요한 **사실 변화**만
|
|
63
|
+
전달하며, 수신자가 현장을 조치할 권한을 주지 않습니다.
|
|
64
|
+
|
|
65
|
+
새 제품이 붙을 때는 공통 봉투를 넓히기 전에 그 제품의 adapter에서 자기 `kind`·사실 유형·판정 기준을
|
|
66
|
+
검증합니다. 모든 제품이 실제로 공유하는 필드만 이 계약에 추가합니다.
|
|
67
|
+
|
|
68
|
+
### 기존 webhook을 통한 전송
|
|
69
|
+
|
|
70
|
+
새 URL은 만들지 않는다. attention도 기존 `POST /domain/{domain}/twin/hook/{source}/{instanceId}`로 보내며,
|
|
71
|
+
봉투의 `lane: 'ATTENTIONS'`가 Twin의 AI inbox 경로를 고른다. `scope`는 계속 원천의 순번 흐름 이름이며
|
|
72
|
+
`lane`과 합치지 않는다. `lane`을 생략한 기존 커넥터는 `FACTS`로 읽혀 기존 canonical ingest 경로를 그대로
|
|
73
|
+
쓴다. 따라서 주소·서명·재시도·순번 규약은 하나이고, 사실과 attention의 저장·처리만 Twin 내부에서 갈린다.
|
|
74
|
+
|
|
75
|
+
#### Webhook 봉투에서의 구분
|
|
76
|
+
|
|
77
|
+
`lane`은 **전달 목적**, `scope`는 **생산자의 순번 스트림 이름**이다. 같은 값으로 쓰거나 `scope`를
|
|
78
|
+
`ATTENTIONS`로 바꾸지 않는다.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const batch = {
|
|
82
|
+
lane: 'ATTENTIONS', // 없으면 FACTS: 기존 커넥터와 호환
|
|
83
|
+
scope: 'mes-line-a', // 이 생산자가 관리하는 순번 스트림
|
|
84
|
+
items: [{
|
|
85
|
+
eventId: 'outbox-1002',
|
|
86
|
+
seq: 1002,
|
|
87
|
+
record: attention
|
|
88
|
+
}]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| lane | Twin 내부 목적지 | 정본 저널 반영 |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| `FACTS` 또는 생략 | 기존 canonical ingest | 예. EPCIS/운영 사실로 유입한다. |
|
|
95
|
+
| `ATTENTIONS` | 제품이 등록한 AI attention inbox | 아니오. 분석을 요청하는 근거 묶음으로 분리한다. |
|
|
96
|
+
|
|
97
|
+
두 lane 모두 같은 webhook 인증·서명·순번·재시도 응답 계약을 쓴다. 수신자는 `OperationalAttentionV1`의
|
|
98
|
+
도메인 일치와 원본 사실 참조를 검증하고, 같은 `domain/source/instance/attention/evidenceFingerprint`는
|
|
99
|
+
중복으로 처리한다. 이 계약은 attention을 현장 명령으로 바꾸지 않는다.
|
|
100
|
+
|
|
17
101
|
## 왜 나뉘어 있나
|
|
18
102
|
|
|
19
103
|
사실을 **만드는 쪽**(MES · 커넥터)은 계약만 필요하고 엔진은 필요 없습니다. 한 패키지에 두면 레코드
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { CommandAck, RefusalCode, RefusalParams } from './contract.ts';
|
|
2
|
+
export type CommandVerdictKind = 'accepted' | 'refused' | 'unavailable';
|
|
3
|
+
/** Why a command was refused, or why no preview could be made. Language-neutral (ADR-0037 ④). */
|
|
4
|
+
export interface CommandVerdictReason {
|
|
5
|
+
/** Stable code — `CommandAck.errorCode` verbatim for a refusal; a host code for `unavailable`. */
|
|
6
|
+
code: RefusalCode;
|
|
7
|
+
/** Raw values the code refers to — `CommandAck.errorParams` verbatim. */
|
|
8
|
+
params?: RefusalParams;
|
|
9
|
+
/** Developer fallback in English (`CommandAck.error`). Not for viewers; hosts translate `code`. */
|
|
10
|
+
message?: string;
|
|
11
|
+
}
|
|
12
|
+
export type CommandVerdict = {
|
|
13
|
+
verdict: 'accepted';
|
|
14
|
+
commandId: string;
|
|
15
|
+
} | {
|
|
16
|
+
verdict: 'refused';
|
|
17
|
+
commandId: string;
|
|
18
|
+
reason: CommandVerdictReason;
|
|
19
|
+
} | {
|
|
20
|
+
verdict: 'unavailable';
|
|
21
|
+
reason: CommandVerdictReason;
|
|
22
|
+
};
|
|
23
|
+
/** The code a refusal carries when the kernel gave none — the same word the intervention log uses. */
|
|
24
|
+
export declare const REFUSED_WITHOUT_CODE = "refused";
|
|
25
|
+
/**
|
|
26
|
+
* Fold a kernel ack into a verdict. Pure: reads the ack, invents nothing.
|
|
27
|
+
*
|
|
28
|
+
* An ack that was not accepted and names no code still becomes `refused`, with the code the
|
|
29
|
+
* intervention log already uses for that case, so the two never disagree about what "no code" is.
|
|
30
|
+
*/
|
|
31
|
+
export declare function verdictOfAck(ack: CommandAck): CommandVerdict;
|
|
32
|
+
/**
|
|
33
|
+
* The verdict when no preview can be made. The host decides this **before** dispatching, from what
|
|
34
|
+
* it can see: whether the runtime has `fork`, whether a checkpoint exists at that time.
|
|
35
|
+
*/
|
|
36
|
+
export declare function unavailableVerdict(code: RefusalCode, params?: RefusalParams, message?: string): CommandVerdict;
|
|
37
|
+
/**
|
|
38
|
+
* Can this runtime preview at all? Read as a capability, not by trying and catching (ADR-0042 ②).
|
|
39
|
+
*
|
|
40
|
+
* This answers "can we look", not "can we act". A mirror twin refuses to *apply* an actuation
|
|
41
|
+
* (`actuation-not-wired` — the real system would not know), yet previewing it on a fork is fine and
|
|
42
|
+
* is exactly where a preview is worth most. Keep the two answers apart (ADR-0042 ⑤).
|
|
43
|
+
*/
|
|
44
|
+
export declare function canPreview(runtime: unknown): runtime is {
|
|
45
|
+
fork(): unknown;
|
|
46
|
+
dispatch(cmd: unknown): CommandAck;
|
|
47
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Preview verdict — the seam between a kernel `CommandAck` and a screen (ADR-0042).
|
|
3
|
+
*
|
|
4
|
+
* A proposal is previewed by dispatching the same command on a fork of the live kernel. Three
|
|
5
|
+
* things can come back, and a viewer has to act differently on each:
|
|
6
|
+
*
|
|
7
|
+
* accepted the fork took the command — the preview shows what follows
|
|
8
|
+
* refused the fork did not take it — **this is a successful preview**: we learned, before
|
|
9
|
+
* anyone pressed apply, that the kernel will not do this here. Drawing it as an
|
|
10
|
+
* error tells the viewer the preview broke, which is the opposite of what happened
|
|
11
|
+
* unavailable no preview could be made at all — the runtime has no `fork`, there is no
|
|
12
|
+
* checkpoint at that time, the twin has no runtime. Decided from capability first,
|
|
13
|
+
* never from an exception that happened to be thrown (ADR-0036 ③ · ADR-0042 ②)
|
|
14
|
+
*
|
|
15
|
+
* The reason keeps the shape ADR-0037 ④ fixed for every violation in this repo: `{ code, params,
|
|
16
|
+
* message }`. `code` and `params` are `CommandAck.errorCode` and `errorParams` **verbatim** — there
|
|
17
|
+
* is no mapping table, because a table goes stale the day the kernel emits a new code, and that
|
|
18
|
+
* day nobody notices. `message` is the developer fallback (`ack.error`), never the viewer line.
|
|
19
|
+
*
|
|
20
|
+
* Why this lives in the contract rather than as a convention: a convention holds only while the
|
|
21
|
+
* next author remembers it. A type does not need remembering.
|
|
22
|
+
*/
|
|
23
|
+
/** The code a refusal carries when the kernel gave none — the same word the intervention log uses. */
|
|
24
|
+
export const REFUSED_WITHOUT_CODE = 'refused';
|
|
25
|
+
/**
|
|
26
|
+
* Fold a kernel ack into a verdict. Pure: reads the ack, invents nothing.
|
|
27
|
+
*
|
|
28
|
+
* An ack that was not accepted and names no code still becomes `refused`, with the code the
|
|
29
|
+
* intervention log already uses for that case, so the two never disagree about what "no code" is.
|
|
30
|
+
*/
|
|
31
|
+
export function verdictOfAck(ack) {
|
|
32
|
+
if (ack.accepted)
|
|
33
|
+
return { verdict: 'accepted', commandId: ack.commandId };
|
|
34
|
+
const reason = { code: ack.errorCode ?? REFUSED_WITHOUT_CODE };
|
|
35
|
+
if (ack.errorParams !== undefined)
|
|
36
|
+
reason.params = ack.errorParams;
|
|
37
|
+
if (ack.error !== undefined)
|
|
38
|
+
reason.message = ack.error;
|
|
39
|
+
return { verdict: 'refused', commandId: ack.commandId, reason };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The verdict when no preview can be made. The host decides this **before** dispatching, from what
|
|
43
|
+
* it can see: whether the runtime has `fork`, whether a checkpoint exists at that time.
|
|
44
|
+
*/
|
|
45
|
+
export function unavailableVerdict(code, params, message) {
|
|
46
|
+
const reason = { code };
|
|
47
|
+
if (params !== undefined)
|
|
48
|
+
reason.params = params;
|
|
49
|
+
if (message !== undefined)
|
|
50
|
+
reason.message = message;
|
|
51
|
+
return { verdict: 'unavailable', reason };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Can this runtime preview at all? Read as a capability, not by trying and catching (ADR-0042 ②).
|
|
55
|
+
*
|
|
56
|
+
* This answers "can we look", not "can we act". A mirror twin refuses to *apply* an actuation
|
|
57
|
+
* (`actuation-not-wired` — the real system would not know), yet previewing it on a fork is fine and
|
|
58
|
+
* is exactly where a preview is worth most. Keep the two answers apart (ADR-0042 ⑤).
|
|
59
|
+
*/
|
|
60
|
+
export function canPreview(runtime) {
|
|
61
|
+
if (runtime === null || typeof runtime !== 'object')
|
|
62
|
+
return false;
|
|
63
|
+
const r = runtime;
|
|
64
|
+
return typeof r.fork === 'function' && typeof r.dispatch === 'function';
|
|
65
|
+
}
|
package/dist/contract.d.ts
CHANGED
|
@@ -1463,6 +1463,12 @@ export interface ItemState {
|
|
|
1463
1463
|
* 「이 로트가 지금 어떤 것이냐」다. 시험 결과를 명세당 하나만 든 것과 같은 규율이다.
|
|
1464
1464
|
*/
|
|
1465
1465
|
nonconformance?: DispositionFact;
|
|
1466
|
+
/**
|
|
1467
|
+
* **로트 상태** — 표준 `MaterialLot.Status`. `nonconformance`(표준 `Disposition`)와 다른 칸이다: 처분은
|
|
1468
|
+
* 부적합을 어떻게 하나, 상태는 이 로트가 지금 어떤 것이냐(released · quarantine · on-hold …). 낱말은 도메인의
|
|
1469
|
+
* 것(열림). 마지막 하나만 든다 — 이력은 저널이 답한다(§`OP_EVENT.materialLot`).
|
|
1470
|
+
*/
|
|
1471
|
+
status?: string;
|
|
1466
1472
|
/** 소속 물류단위(팔레트 SSCC 등) — AggregationEvent 로 맺어진다. 3D 적재 표현의 재료. */
|
|
1467
1473
|
parent?: string;
|
|
1468
1474
|
/**
|
|
@@ -2559,6 +2565,17 @@ export interface Command<T = unknown> {
|
|
|
2559
2565
|
correlationId?: string;
|
|
2560
2566
|
args: T;
|
|
2561
2567
|
}
|
|
2568
|
+
/**
|
|
2569
|
+
* 거절 사유의 **낱말 하나** — 커맨드가 낸 언어 중립 코드(`resource-not-found` 꼴, kebab-case).
|
|
2570
|
+
*
|
|
2571
|
+
* 같은 값이 세 자리에 실린다: `CommandAck.errorCode`(조치 ack) · `InterventionOutcome.refusedCode`(시나리오
|
|
2572
|
+
* 개입 로그) · 구동 검증의 실패 코드(호스트가 저장). 칸 이름은 자리마다 다르지만 **값은 하나**이고, 그 사실을
|
|
2573
|
+
* 이 타입이 말한다 — 이름이 셋이면 어딘가에 매핑 표가 생기고, 그것이 ADR-0042 ③ 이 금지한 것이다.
|
|
2574
|
+
* 칸 이름을 하나로 줄이는 개명은 저장되는 개입 로그에 닿아 따로 결정한다(ADR-0042 「하지 않는 결정」).
|
|
2575
|
+
*/
|
|
2576
|
+
export type RefusalCode = string;
|
|
2577
|
+
/** `RefusalCode` 가 가리키는 원시값들 — 사람 언어가 아니라 값이다. 표현층이 문장에 끼운다. */
|
|
2578
|
+
export type RefusalParams = Record<string, string | number>;
|
|
2562
2579
|
export interface CommandAck {
|
|
2563
2580
|
commandId: string;
|
|
2564
2581
|
accepted: boolean;
|
|
@@ -2566,8 +2583,8 @@ export interface CommandAck {
|
|
|
2566
2583
|
* 거절 사유 — 언어 중립. errorCode(안정 코드) + errorParams(원시값)로 방출하고 사람 언어는
|
|
2567
2584
|
* 표현계층(클라 i18next)이 렌더한다(무방언·다국어, attention 과 동형). error 는 개발자/로그용 영어 폴백.
|
|
2568
2585
|
*/
|
|
2569
|
-
errorCode?:
|
|
2570
|
-
errorParams?:
|
|
2586
|
+
errorCode?: RefusalCode;
|
|
2587
|
+
errorParams?: RefusalParams;
|
|
2571
2588
|
error?: string;
|
|
2572
2589
|
}
|
|
2573
2590
|
/**
|
|
@@ -2699,8 +2716,8 @@ export interface InterventionOutcome {
|
|
|
2699
2716
|
kind: string;
|
|
2700
2717
|
args?: Record<string, unknown>;
|
|
2701
2718
|
applied: boolean;
|
|
2702
|
-
/** 거절 이유
|
|
2703
|
-
refusedCode?:
|
|
2719
|
+
/** 거절 이유 — `CommandAck.errorCode` 와 **같은 값**(`RefusalCode`). 걸렸으면 없다. */
|
|
2720
|
+
refusedCode?: RefusalCode;
|
|
2704
2721
|
}
|
|
2705
2722
|
export interface ScenarioControl {
|
|
2706
2723
|
load(def: ScenarioDef): void;
|
|
@@ -2758,6 +2775,22 @@ export declare const OP_EVENT: {
|
|
|
2758
2775
|
* 이것은 **결정**이다. 누가·언제·왜 그렇게 정했는지의 자리는 그쪽에 없다.
|
|
2759
2776
|
*/
|
|
2760
2777
|
readonly disposition: "nonconformance.disposition";
|
|
2778
|
+
/**
|
|
2779
|
+
* **로트 상태** — ISA-95 `MaterialLot.Status`(B2MML-Material.xsd). 「이 로트가 지금 어떤 것이냐」의 결정.
|
|
2780
|
+
*
|
|
2781
|
+
* ── `nonconformance.disposition` 과 다른 사실이다 (2026-09-16) ──────────────
|
|
2782
|
+
* 그것은 표준 `Disposition` — **부적합을 어떻게 처리하나**(재작업·특채·폐기). 이것은 표준 `Status` — 적합한
|
|
2783
|
+
* 로트에도 붙는 상태(released · quarantine · on-hold …). 출하 승인(batch release)이 이 축이다. 처분에 실으면
|
|
2784
|
+
* 정상 출하가 전부 부적합 이력이 된다 — 진짜 term 을 틀린 뜻으로 쓰는 것이 자리가 없는 것보다 나쁘다.
|
|
2785
|
+
*
|
|
2786
|
+
* ── 낱말은 열려 있다 ──────────────────────────────────────────────────────
|
|
2787
|
+
* 오더 상태와 같은 규율 — 커널이 이 낱말로 무엇을 계산하지 않으므로 닫을 근거가 없다. 도메인이 소유한다.
|
|
2788
|
+
*
|
|
2789
|
+
* ── 출발과 다른 사실이다 ─────────────────────────────────────────────────
|
|
2790
|
+
* 승인은 자재를 움직이지 않는다. 자재가 트윈 밖으로 나가는 것은 EPCIS `shipping` 사건이다. 둘을 한 사건으로
|
|
2791
|
+
* 접지 않는다 — 승인됐는데 안 나간 배치가 트윈이 보여야 할 상태다.
|
|
2792
|
+
*/
|
|
2793
|
+
readonly materialLot: "material-lot.status";
|
|
2761
2794
|
/**
|
|
2762
2795
|
* **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 그것을 알린다.
|
|
2763
2796
|
*
|
package/dist/contract.js
CHANGED
|
@@ -1097,6 +1097,22 @@ export const OP_EVENT = {
|
|
|
1097
1097
|
* 이것은 **결정**이다. 누가·언제·왜 그렇게 정했는지의 자리는 그쪽에 없다.
|
|
1098
1098
|
*/
|
|
1099
1099
|
disposition: 'nonconformance.disposition',
|
|
1100
|
+
/**
|
|
1101
|
+
* **로트 상태** — ISA-95 `MaterialLot.Status`(B2MML-Material.xsd). 「이 로트가 지금 어떤 것이냐」의 결정.
|
|
1102
|
+
*
|
|
1103
|
+
* ── `nonconformance.disposition` 과 다른 사실이다 (2026-09-16) ──────────────
|
|
1104
|
+
* 그것은 표준 `Disposition` — **부적합을 어떻게 처리하나**(재작업·특채·폐기). 이것은 표준 `Status` — 적합한
|
|
1105
|
+
* 로트에도 붙는 상태(released · quarantine · on-hold …). 출하 승인(batch release)이 이 축이다. 처분에 실으면
|
|
1106
|
+
* 정상 출하가 전부 부적합 이력이 된다 — 진짜 term 을 틀린 뜻으로 쓰는 것이 자리가 없는 것보다 나쁘다.
|
|
1107
|
+
*
|
|
1108
|
+
* ── 낱말은 열려 있다 ──────────────────────────────────────────────────────
|
|
1109
|
+
* 오더 상태와 같은 규율 — 커널이 이 낱말로 무엇을 계산하지 않으므로 닫을 근거가 없다. 도메인이 소유한다.
|
|
1110
|
+
*
|
|
1111
|
+
* ── 출발과 다른 사실이다 ─────────────────────────────────────────────────
|
|
1112
|
+
* 승인은 자재를 움직이지 않는다. 자재가 트윈 밖으로 나가는 것은 EPCIS `shipping` 사건이다. 둘을 한 사건으로
|
|
1113
|
+
* 접지 않는다 — 승인됐는데 안 나간 배치가 트윈이 보여야 할 상태다.
|
|
1114
|
+
*/
|
|
1115
|
+
materialLot: 'material-lot.status',
|
|
1100
1116
|
/**
|
|
1101
1117
|
* **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 그것을 알린다.
|
|
1102
1118
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export * from './vocabulary.ts';
|
|
|
17
17
|
export * from './wms-profile.ts';
|
|
18
18
|
export * from './yms-profile.ts';
|
|
19
19
|
export * from './canonical-record.ts';
|
|
20
|
+
export * from './command-verdict.ts';
|
|
21
|
+
export * from './webhook-delivery.ts';
|
|
20
22
|
export * from './webhook.ts';
|
|
21
23
|
export * from './webhook-secret.ts';
|
|
22
24
|
export * from './oee.ts';
|
|
@@ -25,3 +27,4 @@ export * from './yield.ts';
|
|
|
25
27
|
export * from './reliability.ts';
|
|
26
28
|
export * from './erp.ts';
|
|
27
29
|
export * from './actuation.ts';
|
|
30
|
+
export * from './operational-attention.ts';
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,8 @@ export * from "./vocabulary.js";
|
|
|
38
38
|
export * from "./wms-profile.js";
|
|
39
39
|
export * from "./yms-profile.js";
|
|
40
40
|
export * from "./canonical-record.js";
|
|
41
|
+
export * from "./command-verdict.js";
|
|
42
|
+
export * from "./webhook-delivery.js";
|
|
41
43
|
/* `webhook-signature.ts` 는 여기 없다 — `node:crypto` 를 쓰므로 `@operato/ops-contract/webhook` 으로만 나간다. */
|
|
42
44
|
export * from "./webhook.js";
|
|
43
45
|
/* 비밀값 **이름** 규약 — 값을 읽지 않으므로 여기서 나간다(§`webhook-secret`). */
|
|
@@ -48,3 +50,4 @@ export * from "./yield.js";
|
|
|
48
50
|
export * from "./reliability.js";
|
|
49
51
|
export * from "./erp.js";
|
|
50
52
|
export * from "./actuation.js";
|
|
53
|
+
export * from "./operational-attention.js";
|
package/dist/mes-profile.d.ts
CHANGED
|
@@ -4,6 +4,12 @@ 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";
|
|
7
13
|
};
|
|
8
14
|
/** 작업지시(Work Order) = 생산 오더 거래 유형. */
|
|
9
15
|
export declare const BTT_PRODORDER = "urn:epcglobal:cbv:btt:prodorder";
|
package/dist/mes-profile.js
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
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' // 완제품 저장
|
|
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'
|
|
6
12
|
};
|
|
7
13
|
/** 작업지시(Work Order) = 생산 오더 거래 유형. */
|
|
8
14
|
export const BTT_PRODORDER = 'urn:epcglobal:cbv:btt:prodorder';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ISOTime } from './contract.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Product-neutral evidence for an operational concern that another product's
|
|
4
|
+
* Twin/AI may analyse. It is not an alarm protocol and never authorises work.
|
|
5
|
+
*
|
|
6
|
+
* A producer owns how it detects and manages an attention. Consumers receive
|
|
7
|
+
* only its evidence-backed lifecycle change; acknowledgement, suppression,
|
|
8
|
+
* approval, and outcome remain in the producer's workflow.
|
|
9
|
+
*/
|
|
10
|
+
export declare const OPERATIONAL_ATTENTION_CONTRACT: "operational.attention.v1";
|
|
11
|
+
export declare const OPERATIONAL_ATTENTION_TRANSITIONS: readonly ["opened", "changed", "resolved", "reopened"];
|
|
12
|
+
export declare const OPERATIONAL_ATTENTION_IMPACTS: readonly ["medium", "high"];
|
|
13
|
+
export type OperationalAttentionTransition = (typeof OPERATIONAL_ATTENTION_TRANSITIONS)[number];
|
|
14
|
+
export type OperationalAttentionImpact = (typeof OPERATIONAL_ATTENTION_IMPACTS)[number];
|
|
15
|
+
/** Stable reference to a fact held by the producing product. */
|
|
16
|
+
export interface OperationalFactReference {
|
|
17
|
+
/** Product/system that owns the source fact, e.g. operato-plant or operato-wms. */
|
|
18
|
+
system: string;
|
|
19
|
+
/** Producer-owned entity vocabulary, e.g. schedule-entry or inventory. */
|
|
20
|
+
type: string;
|
|
21
|
+
id: string;
|
|
22
|
+
observedAt?: ISOTime;
|
|
23
|
+
label?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface OperationalAttentionV1 {
|
|
26
|
+
contract: typeof OPERATIONAL_ATTENTION_CONTRACT;
|
|
27
|
+
domainId: string;
|
|
28
|
+
/** Stable producer identifier; unchanged across lifecycle updates. */
|
|
29
|
+
attentionId: string;
|
|
30
|
+
dedupeKey: string;
|
|
31
|
+
/** Producer-owned, namespaced reason code (for example plant.schedule-overdue). */
|
|
32
|
+
kind: string;
|
|
33
|
+
subjectLabel: string;
|
|
34
|
+
transition: OperationalAttentionTransition;
|
|
35
|
+
impact: OperationalAttentionImpact;
|
|
36
|
+
observedAt: ISOTime;
|
|
37
|
+
/** Deterministic hash of the evidence used for this particular observation. */
|
|
38
|
+
evidenceFingerprint: string;
|
|
39
|
+
/** At least one stable producer fact is mandatory; labels alone are not evidence. */
|
|
40
|
+
facts: OperationalFactReference[];
|
|
41
|
+
}
|
|
42
|
+
export declare function isOperationalAttentionV1(value: unknown): value is OperationalAttentionV1;
|
|
43
|
+
export declare function assertOperationalAttentionV1(value: unknown): asserts value is OperationalAttentionV1;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product-neutral evidence for an operational concern that another product's
|
|
3
|
+
* Twin/AI may analyse. It is not an alarm protocol and never authorises work.
|
|
4
|
+
*
|
|
5
|
+
* A producer owns how it detects and manages an attention. Consumers receive
|
|
6
|
+
* only its evidence-backed lifecycle change; acknowledgement, suppression,
|
|
7
|
+
* approval, and outcome remain in the producer's workflow.
|
|
8
|
+
*/
|
|
9
|
+
export const OPERATIONAL_ATTENTION_CONTRACT = 'operational.attention.v1';
|
|
10
|
+
export const OPERATIONAL_ATTENTION_TRANSITIONS = ['opened', 'changed', 'resolved', 'reopened'];
|
|
11
|
+
export const OPERATIONAL_ATTENTION_IMPACTS = ['medium', 'high'];
|
|
12
|
+
export function isOperationalAttentionV1(value) {
|
|
13
|
+
const one = value;
|
|
14
|
+
return !!one && typeof one === 'object' &&
|
|
15
|
+
one.contract === OPERATIONAL_ATTENTION_CONTRACT &&
|
|
16
|
+
nonEmpty(one.domainId) && nonEmpty(one.attentionId) && nonEmpty(one.dedupeKey) &&
|
|
17
|
+
nonEmpty(one.kind) && nonEmpty(one.subjectLabel) && nonEmpty(one.observedAt) && nonEmpty(one.evidenceFingerprint) &&
|
|
18
|
+
OPERATIONAL_ATTENTION_TRANSITIONS.includes(one.transition) &&
|
|
19
|
+
OPERATIONAL_ATTENTION_IMPACTS.includes(one.impact) &&
|
|
20
|
+
Array.isArray(one.facts) && one.facts.length > 0 && one.facts.every(isOperationalFactReference);
|
|
21
|
+
}
|
|
22
|
+
export function assertOperationalAttentionV1(value) {
|
|
23
|
+
if (!isOperationalAttentionV1(value)) {
|
|
24
|
+
throw new Error('invalid operational.attention.v1: lifecycle fields and stable source-fact references are required');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function isOperationalFactReference(value) {
|
|
28
|
+
const one = value;
|
|
29
|
+
return !!one && typeof one === 'object' && nonEmpty(one.system) && nonEmpty(one.type) && nonEmpty(one.id) &&
|
|
30
|
+
(one.observedAt === undefined || nonEmpty(one.observedAt)) &&
|
|
31
|
+
(one.label === undefined || nonEmpty(one.label));
|
|
32
|
+
}
|
|
33
|
+
function nonEmpty(value) {
|
|
34
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
35
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { IngestResult } from './face2-adapter.ts';
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* 이 문이 받는 종류 — **아래 union 이 정본이고 수를 여기 적지 않는다**(2026-08-19 에 여섯이었고 이제 열하나다.
|
|
4
|
+
* 주석의 수가 union 보다 오래 살아 읽는 사람이 여섯에서 멈췄다). 리듀서가 다루는 것과 같은 목록이다(주목 확인은 우리 안의
|
|
5
|
+
* 행위라 제외).
|
|
6
|
+
*/
|
|
7
|
+
export type OperationalKind = 'task' | 'equipment' | 'equipment-period' | 'person' | 'asset' | 'order' | 'quality' | 'test' | 'disposition' | 'material-lot' | 'observation' | 'complete';
|
|
4
8
|
/**
|
|
5
9
|
* 정규 운영 레코드 — **델타의 필드 이름 + 시각(`at`)**.
|
|
6
10
|
*
|
|
@@ -320,6 +320,21 @@ const SPECS = {
|
|
|
320
320
|
},
|
|
321
321
|
enums: { decision: DISPOSITION_DECISION }
|
|
322
322
|
},
|
|
323
|
+
/*
|
|
324
|
+
* **로트 상태** — ISA-95 `MaterialLot.Status`. 출하 승인(batch release)이 들어오는 문(§`OP_EVENT.materialLot`).
|
|
325
|
+
*
|
|
326
|
+
* `disposition`(subjectId + decision) 과 필드가 겹치지 않는다 — 여기는 `epc` + `status`. 상태 낱말은 열려
|
|
327
|
+
* 있어 enum 이 없다(오더 상태와 같은 규율). 로트 전체의 사실이므로 `subLotId` 는 받지 않는다 — 부분마다 다른
|
|
328
|
+
* 상태가 필요해지면 그것은 다른 사실이다.
|
|
329
|
+
*/
|
|
330
|
+
'material-lot': {
|
|
331
|
+
eventType: OP_EVENT.materialLot,
|
|
332
|
+
match: ['epc', 'status'],
|
|
333
|
+
matchOrder: 85,
|
|
334
|
+
identity: 'epc',
|
|
335
|
+
required: ['epc', 'status'],
|
|
336
|
+
fields: { epc: 'string', status: 'string', decidedBy: 'string', reason: 'string', decidedAt: 'string', recordTime: 'string' }
|
|
337
|
+
},
|
|
323
338
|
test: {
|
|
324
339
|
eventType: OP_EVENT.test,
|
|
325
340
|
match: ['testableObjectId'],
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { RefusalParams } from './contract.ts';
|
|
2
|
+
export declare const WEBHOOK_DELIVERY_CODES: {
|
|
3
|
+
/** The peer received and verified the envelope, then declined the job order itself (200, accepted=false). */
|
|
4
|
+
readonly jobOrderRefused: "job-order-refused";
|
|
5
|
+
/** The envelope does not match the specification — retry never helps until the sender changes (400). */
|
|
6
|
+
readonly envelopeInvalid: "envelope-invalid";
|
|
7
|
+
/** The signature did not verify (401). `params.reason` is `mismatch` or `expired`. */
|
|
8
|
+
readonly signatureRejected: "signature-rejected";
|
|
9
|
+
/** The peer has no such site (404). */
|
|
10
|
+
readonly siteUnknown: "site-unknown";
|
|
11
|
+
/** The peer has no verify key under the name the sender used (503). `params.peer` names that peer — never the env var. */
|
|
12
|
+
readonly verifySecretMissing: "verify-secret-missing";
|
|
13
|
+
/** No HTTP answer at all — DNS, connection refused, timeout. */
|
|
14
|
+
readonly peerUnreachable: "peer-unreachable";
|
|
15
|
+
/** The peer verified the envelope and then failed on its own side — storing, for instance (500). Retrying may help. */
|
|
16
|
+
readonly peerInternalFailure: "peer-internal-failure";
|
|
17
|
+
/** An answer came back that fits none of the above — the code is the honest "unclassified", not a guess. */
|
|
18
|
+
readonly responseUnrecognised: "response-unrecognised";
|
|
19
|
+
};
|
|
20
|
+
export type WebhookDeliveryCode = (typeof WEBHOOK_DELIVERY_CODES)[keyof typeof WEBHOOK_DELIVERY_CODES];
|
|
21
|
+
/** Reasons a signature can be rejected — the only two the verifier can tell apart. */
|
|
22
|
+
export declare const SIGNATURE_REJECTED_REASONS: readonly ["mismatch", "expired"];
|
|
23
|
+
export type SignatureRejectedReason = (typeof SIGNATURE_REJECTED_REASONS)[number];
|
|
24
|
+
/** Is this string one of the delivery codes? For the host that stores what a connector hands it. */
|
|
25
|
+
export declare function isWebhookDeliveryCode(code: string): code is WebhookDeliveryCode;
|
|
26
|
+
/**
|
|
27
|
+
* What a receiving door answers when it does not take the envelope (any non-2xx).
|
|
28
|
+
*
|
|
29
|
+
* The **receiver** names the fact as a code; the sender's connector passes it on verbatim and never
|
|
30
|
+
* parses the sentence. Two connectors reading one door then cannot invent two spellings, and a door
|
|
31
|
+
* that rewords its sentence cannot silently blank the code. `error` stays for people and logs.
|
|
32
|
+
*
|
|
33
|
+
* `params` is scalar only (`RefusalParams`): a list such as the specification checks a 400 failed is
|
|
34
|
+
* not a parameter of the code, it is detail — it rides in its own field of the body and is not stored
|
|
35
|
+
* by the host.
|
|
36
|
+
*/
|
|
37
|
+
export interface WebhookIntakeRefusal {
|
|
38
|
+
code: WebhookDeliveryCode;
|
|
39
|
+
params?: RefusalParams;
|
|
40
|
+
/** Developer sentence in English. Never the thing a screen translates from. */
|
|
41
|
+
error: string;
|
|
42
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Webhook delivery outcome codes — what a sender records when an envelope did not land.
|
|
3
|
+
*
|
|
4
|
+
* The connector that delivers a job order to a peer (twin → plant intake) sees an HTTP status and a
|
|
5
|
+
* body. Neither is what gets stored: a status number is the transport's word, and the body sentence
|
|
6
|
+
* is a person's language (the intake writes Korean today). What the host records on the connection
|
|
7
|
+
* (ADR: actuation verification axis — `actuationFailure.code`) is a `RefusalCode`, the same kind of
|
|
8
|
+
* word a kernel refusal carries, so one vocabulary reaches the screen through one door.
|
|
9
|
+
*
|
|
10
|
+
* Spelling follows the kernel's refusal codes: kebab-case, a noun phrase naming the fact, no HTTP
|
|
11
|
+
* number in the word. The number is how the connector *learned* the fact; the code is the fact.
|
|
12
|
+
*
|
|
13
|
+
* The mapping from status → code is the connector's, not the contract's: a 401 can mean two
|
|
14
|
+
* different facts (signature mismatch, replay expired) and only the connector reading the body knows
|
|
15
|
+
* which. The contract fixes the words so two connectors never invent two spellings for one fact.
|
|
16
|
+
*/
|
|
17
|
+
export const WEBHOOK_DELIVERY_CODES = {
|
|
18
|
+
/** The peer received and verified the envelope, then declined the job order itself (200, accepted=false). */
|
|
19
|
+
jobOrderRefused: 'job-order-refused',
|
|
20
|
+
/** The envelope does not match the specification — retry never helps until the sender changes (400). */
|
|
21
|
+
envelopeInvalid: 'envelope-invalid',
|
|
22
|
+
/** The signature did not verify (401). `params.reason` is `mismatch` or `expired`. */
|
|
23
|
+
signatureRejected: 'signature-rejected',
|
|
24
|
+
/** The peer has no such site (404). */
|
|
25
|
+
siteUnknown: 'site-unknown',
|
|
26
|
+
/** The peer has no verify key under the name the sender used (503). `params.peer` names that peer — never the env var. */
|
|
27
|
+
verifySecretMissing: 'verify-secret-missing',
|
|
28
|
+
/** No HTTP answer at all — DNS, connection refused, timeout. */
|
|
29
|
+
peerUnreachable: 'peer-unreachable',
|
|
30
|
+
/** The peer verified the envelope and then failed on its own side — storing, for instance (500). Retrying may help. */
|
|
31
|
+
peerInternalFailure: 'peer-internal-failure',
|
|
32
|
+
/** An answer came back that fits none of the above — the code is the honest "unclassified", not a guess. */
|
|
33
|
+
responseUnrecognised: 'response-unrecognised'
|
|
34
|
+
};
|
|
35
|
+
/** Reasons a signature can be rejected — the only two the verifier can tell apart. */
|
|
36
|
+
export const SIGNATURE_REJECTED_REASONS = ['mismatch', 'expired'];
|
|
37
|
+
/** Is this string one of the delivery codes? For the host that stores what a connector hands it. */
|
|
38
|
+
export function isWebhookDeliveryCode(code) {
|
|
39
|
+
return Object.values(WEBHOOK_DELIVERY_CODES).includes(code);
|
|
40
|
+
}
|
package/dist/webhook-secret.d.ts
CHANGED
|
@@ -81,18 +81,6 @@ export interface WebhookSecretLookup {
|
|
|
81
81
|
* 받되, 열리면 부르는 쪽이 한 번 알린다 — 조용히 되면 아무도 새 이름으로 옮기지 않는다.
|
|
82
82
|
*/
|
|
83
83
|
legacyBases?: readonly string[];
|
|
84
|
-
/**
|
|
85
|
-
* **검증 쪽에서 넓은 칸까지 내려가는 것을 허락한다.** 기본은 거짓이고, 그것이 ③ 규칙이다.
|
|
86
|
-
*
|
|
87
|
-
* 이 인자를 참으로 주려면 부르는 쪽이 **그 도메인에 상대가 하나뿐임을 확인**해야 한다. 둘이면
|
|
88
|
-
* 둘이 같은 키로 풀리고, 그 순간 주소가 「이 사실이 누구 것인가」를 정하는데 아무도 그것을
|
|
89
|
-
* 검사하지 않는다 — 상대 A 가 제대로 서명한 봉투를 상대 B 의 주소에 앉힐 수 있다
|
|
90
|
-
* (§`webhook-signature` 의 불변식: 주소는 키가 이미 고정한 것만 정할 수 있다).
|
|
91
|
-
*
|
|
92
|
-
* 그리고 그 상태는 **두 번째 상대가 붙는 날 아무 경고 없이 끝난다.** 이름을 명시적으로 주게 한
|
|
93
|
-
* 이유가 그것이다 — 코드에서 찾을 수 있어야 한다.
|
|
94
|
-
*/
|
|
95
|
-
allowSinglePeerFallback?: boolean;
|
|
96
84
|
}
|
|
97
85
|
/**
|
|
98
86
|
* ③ 사다리 — 찾아볼 이름을 순서대로.
|
package/dist/webhook-secret.js
CHANGED
|
@@ -104,15 +104,16 @@ export function webhookSecretEnvName(base, domain, peer) {
|
|
|
104
104
|
* 상태이고, 뒤엣것은 사람이 볼 수 없다.
|
|
105
105
|
*/
|
|
106
106
|
export function webhookSecretCandidates(lookup) {
|
|
107
|
-
const { purpose, domain, peer, legacyBases = []
|
|
107
|
+
const { purpose, domain, peer, legacyBases = [] } = lookup;
|
|
108
108
|
const base = WEBHOOK_SECRET_BASE[purpose];
|
|
109
109
|
const out = [
|
|
110
110
|
{ name: webhookSecretEnvName(base, domain, peer), pins: 'peer', compatibility: false }
|
|
111
111
|
];
|
|
112
|
-
/* 검증은 여기서 멈춘다 —
|
|
113
|
-
if (purpose === 'verify'
|
|
112
|
+
/* 검증은 여기서 멈춘다 — 여는 인자가 없다. 넓어지면 아무 상대나 통과한다. */
|
|
113
|
+
if (purpose === 'verify')
|
|
114
114
|
return out;
|
|
115
|
-
|
|
115
|
+
/* 여기부터는 서명 쪽만이다 — 도메인 칸은 서명이 원래 내려가는 칸이라 「호환」이 아니다. */
|
|
116
|
+
out.push({ name: webhookSecretEnvName(base, domain), pins: 'domain', compatibility: false });
|
|
116
117
|
for (const legacy of legacyBases) {
|
|
117
118
|
out.push({ name: webhookSecretEnvName(legacy, domain), pins: 'domain', compatibility: true });
|
|
118
119
|
out.push({ name: legacy, pins: 'installation', compatibility: true });
|
package/dist/webhook.d.ts
CHANGED
|
@@ -30,6 +30,18 @@ export declare const WEBHOOK_STATUS: {
|
|
|
30
30
|
readonly notLive: 503;
|
|
31
31
|
};
|
|
32
32
|
export type WebhookStatus = (typeof WEBHOOK_STATUS)[keyof typeof WEBHOOK_STATUS];
|
|
33
|
+
/**
|
|
34
|
+
* A webhook lane says what the envelope is for; it is deliberately separate
|
|
35
|
+
* from `scope`, which names the producer's sequence stream.
|
|
36
|
+
*
|
|
37
|
+
* Omitting it means FACTS so every existing connector remains compatible.
|
|
38
|
+
*/
|
|
39
|
+
export declare const WEBHOOK_LANE: {
|
|
40
|
+
readonly facts: "FACTS";
|
|
41
|
+
readonly attentions: "ATTENTIONS";
|
|
42
|
+
};
|
|
43
|
+
export type WebhookLane = (typeof WEBHOOK_LANE)[keyof typeof WEBHOOK_LANE];
|
|
44
|
+
export declare function webhookLaneOf(value: unknown): WebhookLane | undefined;
|
|
33
45
|
/**
|
|
34
46
|
* 그 응답을 받은 뒤 보내는 쪽이 할 일.
|
|
35
47
|
*
|
|
@@ -217,6 +229,11 @@ export interface WebhookEnvelope {
|
|
|
217
229
|
seq: number;
|
|
218
230
|
/** 번호를 매기는 단위의 이름. 보내는 쪽이 정하고 받는 쪽은 열쇠로만 다룬다. */
|
|
219
231
|
scope: string;
|
|
232
|
+
/**
|
|
233
|
+
* Delivery purpose. `scope` remains the source-owned numbering stream;
|
|
234
|
+
* `lane` selects the receiver path. Missing means `FACTS` for compatibility.
|
|
235
|
+
*/
|
|
236
|
+
lane?: WebhookLane;
|
|
220
237
|
/** 멱등 키. 같은 값이 두 번 와도 결과가 같아야 한다. */
|
|
221
238
|
eventId?: string;
|
|
222
239
|
/** 사실이 일어난 시각. */
|
package/dist/webhook.js
CHANGED
|
@@ -45,6 +45,23 @@ export const WEBHOOK_STATUS = {
|
|
|
45
45
|
unsupported: 501,
|
|
46
46
|
notLive: 503
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* A webhook lane says what the envelope is for; it is deliberately separate
|
|
50
|
+
* from `scope`, which names the producer's sequence stream.
|
|
51
|
+
*
|
|
52
|
+
* Omitting it means FACTS so every existing connector remains compatible.
|
|
53
|
+
*/
|
|
54
|
+
export const WEBHOOK_LANE = {
|
|
55
|
+
facts: 'FACTS',
|
|
56
|
+
attentions: 'ATTENTIONS'
|
|
57
|
+
};
|
|
58
|
+
export function webhookLaneOf(value) {
|
|
59
|
+
return value === undefined || value === WEBHOOK_LANE.facts
|
|
60
|
+
? WEBHOOK_LANE.facts
|
|
61
|
+
: value === WEBHOOK_LANE.attentions
|
|
62
|
+
? WEBHOOK_LANE.attentions
|
|
63
|
+
: undefined;
|
|
64
|
+
}
|
|
48
65
|
/**
|
|
49
66
|
* 응답 코드 → 보내는 쪽의 행동. **모르는 코드도 답을 낸다.**
|
|
50
67
|
*
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -58,13 +58,18 @@ __export(index_exports, {
|
|
|
58
58
|
MES_TYPES: () => MES_TYPES,
|
|
59
59
|
METER_DIRECTION: () => METER_DIRECTION,
|
|
60
60
|
OBSERVATION_BASIS: () => OBSERVATION_BASIS,
|
|
61
|
+
OPERATIONAL_ATTENTION_CONTRACT: () => OPERATIONAL_ATTENTION_CONTRACT,
|
|
62
|
+
OPERATIONAL_ATTENTION_IMPACTS: () => OPERATIONAL_ATTENTION_IMPACTS,
|
|
63
|
+
OPERATIONAL_ATTENTION_TRANSITIONS: () => OPERATIONAL_ATTENTION_TRANSITIONS,
|
|
61
64
|
OP_EVENT: () => OP_EVENT,
|
|
62
65
|
OP_PARAM: () => OP_PARAM,
|
|
63
66
|
ORDER_TERMINAL_STATUS: () => ORDER_TERMINAL_STATUS,
|
|
64
67
|
PRIORITY_UNSET: () => PRIORITY_UNSET,
|
|
68
|
+
REFUSED_WITHOUT_CODE: () => REFUSED_WITHOUT_CODE,
|
|
65
69
|
RESOURCE_KIND: () => RESOURCE_KIND,
|
|
66
70
|
RETIRED_VOCABULARY: () => RETIRED_VOCABULARY,
|
|
67
71
|
SCHEDULE_STATUS: () => SCHEDULE_STATUS,
|
|
72
|
+
SIGNATURE_REJECTED_REASONS: () => SIGNATURE_REJECTED_REASONS,
|
|
68
73
|
TWIN_AXES: () => TWIN_AXES,
|
|
69
74
|
TWIN_PROPERTIES: () => TWIN_PROPERTIES,
|
|
70
75
|
TWIN_RELATIONS: () => TWIN_RELATIONS,
|
|
@@ -72,6 +77,8 @@ __export(index_exports, {
|
|
|
72
77
|
UTC_OFFSET: () => UTC_OFFSET,
|
|
73
78
|
VOCABULARY_EXCEPTIONS: () => VOCABULARY_EXCEPTIONS,
|
|
74
79
|
VOCABULARY_TYPE: () => VOCABULARY_TYPE,
|
|
80
|
+
WEBHOOK_DELIVERY_CODES: () => WEBHOOK_DELIVERY_CODES,
|
|
81
|
+
WEBHOOK_LANE: () => WEBHOOK_LANE,
|
|
75
82
|
WEBHOOK_SECRET_BASE: () => WEBHOOK_SECRET_BASE,
|
|
76
83
|
WEBHOOK_SECRET_NAME_SHAPE: () => WEBHOOK_SECRET_NAME_SHAPE,
|
|
77
84
|
WEBHOOK_STATUS: () => WEBHOOK_STATUS,
|
|
@@ -88,11 +95,13 @@ __export(index_exports, {
|
|
|
88
95
|
analyzeCapacity: () => analyzeCapacity,
|
|
89
96
|
approveCommand: () => approveCommand,
|
|
90
97
|
asApproved: () => asApproved,
|
|
98
|
+
assertOperationalAttentionV1: () => assertOperationalAttentionV1,
|
|
91
99
|
axesOfSystem: () => axesOfSystem,
|
|
92
100
|
axisAppliesTo: () => axisAppliesTo,
|
|
93
101
|
axisInfo: () => axisInfo,
|
|
94
102
|
axisSource: () => axisSource,
|
|
95
103
|
bizTransactionUri: () => bizTransactionUri,
|
|
104
|
+
canPreview: () => canPreview,
|
|
96
105
|
capabilitiesForType: () => capabilitiesForType,
|
|
97
106
|
capabilityOf: () => capabilityOf,
|
|
98
107
|
checkSequence: () => checkSequence,
|
|
@@ -154,11 +163,13 @@ __export(index_exports, {
|
|
|
154
163
|
isEquipmentStatus: () => isEquipmentStatus,
|
|
155
164
|
isFailureStatus: () => isFailureStatus,
|
|
156
165
|
isMasterDataRecord: () => isMasterDataRecord,
|
|
166
|
+
isOperationalAttentionV1: () => isOperationalAttentionV1,
|
|
157
167
|
isOperationalRecord: () => isOperationalRecord,
|
|
158
168
|
isOrderTerminal: () => isOrderTerminal,
|
|
159
169
|
isPlannedStopStatus: () => isPlannedStopStatus,
|
|
160
170
|
isResourceKind: () => isResourceKind,
|
|
161
171
|
isTransformationRecord: () => isTransformationRecord,
|
|
172
|
+
isWebhookDeliveryCode: () => isWebhookDeliveryCode,
|
|
162
173
|
isWebhookSecretSegment: () => isWebhookSecretSegment,
|
|
163
174
|
isoDurationHours: () => isoDurationHours,
|
|
164
175
|
itemKeyOf: () => itemKeyOf,
|
|
@@ -218,10 +229,13 @@ __export(index_exports, {
|
|
|
218
229
|
testPassedAt: () => testPassedAt,
|
|
219
230
|
transactionEvent: () => transactionEvent,
|
|
220
231
|
transformationEvent: () => transformationEvent,
|
|
232
|
+
unavailableVerdict: () => unavailableVerdict,
|
|
221
233
|
validateDomainDefinition: () => validateDomainDefinition,
|
|
222
234
|
validateEpcisEvent: () => validateEpcisEvent,
|
|
223
235
|
validatePerformance: () => validatePerformance,
|
|
224
236
|
validateScenario: () => validateScenario,
|
|
237
|
+
verdictOfAck: () => verdictOfAck,
|
|
238
|
+
webhookLaneOf: () => webhookLaneOf,
|
|
225
239
|
webhookSecretCandidates: () => webhookSecretCandidates,
|
|
226
240
|
webhookSecretEnvName: () => webhookSecretEnvName,
|
|
227
241
|
webhookSecretFrom: () => webhookSecretFrom,
|
|
@@ -895,6 +909,22 @@ var OP_EVENT = {
|
|
|
895
909
|
* 이것은 **결정**이다. 누가·언제·왜 그렇게 정했는지의 자리는 그쪽에 없다.
|
|
896
910
|
*/
|
|
897
911
|
disposition: "nonconformance.disposition",
|
|
912
|
+
/**
|
|
913
|
+
* **로트 상태** — ISA-95 `MaterialLot.Status`(B2MML-Material.xsd). 「이 로트가 지금 어떤 것이냐」의 결정.
|
|
914
|
+
*
|
|
915
|
+
* ── `nonconformance.disposition` 과 다른 사실이다 (2026-09-16) ──────────────
|
|
916
|
+
* 그것은 표준 `Disposition` — **부적합을 어떻게 처리하나**(재작업·특채·폐기). 이것은 표준 `Status` — 적합한
|
|
917
|
+
* 로트에도 붙는 상태(released · quarantine · on-hold …). 출하 승인(batch release)이 이 축이다. 처분에 실으면
|
|
918
|
+
* 정상 출하가 전부 부적합 이력이 된다 — 진짜 term 을 틀린 뜻으로 쓰는 것이 자리가 없는 것보다 나쁘다.
|
|
919
|
+
*
|
|
920
|
+
* ── 낱말은 열려 있다 ──────────────────────────────────────────────────────
|
|
921
|
+
* 오더 상태와 같은 규율 — 커널이 이 낱말로 무엇을 계산하지 않으므로 닫을 근거가 없다. 도메인이 소유한다.
|
|
922
|
+
*
|
|
923
|
+
* ── 출발과 다른 사실이다 ─────────────────────────────────────────────────
|
|
924
|
+
* 승인은 자재를 움직이지 않는다. 자재가 트윈 밖으로 나가는 것은 EPCIS `shipping` 사건이다. 둘을 한 사건으로
|
|
925
|
+
* 접지 않는다 — 승인됐는데 안 나간 배치가 트윈이 보여야 할 상태다.
|
|
926
|
+
*/
|
|
927
|
+
materialLot: "material-lot.status",
|
|
898
928
|
/**
|
|
899
929
|
* **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 그것을 알린다.
|
|
900
930
|
*
|
|
@@ -1286,8 +1316,14 @@ var MES_BIZSTEP = {
|
|
|
1286
1316
|
// 원자재 수령
|
|
1287
1317
|
producing: "urn:epcglobal:cbv:bizstep:commissioning",
|
|
1288
1318
|
// 생산(제품 최초 생성)
|
|
1289
|
-
storing: "urn:epcglobal:cbv:bizstep:storing"
|
|
1319
|
+
storing: "urn:epcglobal:cbv:bizstep:storing",
|
|
1290
1320
|
// 완제품 저장
|
|
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"
|
|
1291
1327
|
};
|
|
1292
1328
|
var BTT_PRODORDER = "urn:epcglobal:cbv:btt:prodorder";
|
|
1293
1329
|
function sgtinUri(companyPrefix, itemRef, serial) {
|
|
@@ -3674,6 +3710,21 @@ var SPECS = {
|
|
|
3674
3710
|
},
|
|
3675
3711
|
enums: { decision: DISPOSITION_DECISION }
|
|
3676
3712
|
},
|
|
3713
|
+
/*
|
|
3714
|
+
* **로트 상태** — ISA-95 `MaterialLot.Status`. 출하 승인(batch release)이 들어오는 문(§`OP_EVENT.materialLot`).
|
|
3715
|
+
*
|
|
3716
|
+
* `disposition`(subjectId + decision) 과 필드가 겹치지 않는다 — 여기는 `epc` + `status`. 상태 낱말은 열려
|
|
3717
|
+
* 있어 enum 이 없다(오더 상태와 같은 규율). 로트 전체의 사실이므로 `subLotId` 는 받지 않는다 — 부분마다 다른
|
|
3718
|
+
* 상태가 필요해지면 그것은 다른 사실이다.
|
|
3719
|
+
*/
|
|
3720
|
+
"material-lot": {
|
|
3721
|
+
eventType: OP_EVENT.materialLot,
|
|
3722
|
+
match: ["epc", "status"],
|
|
3723
|
+
matchOrder: 85,
|
|
3724
|
+
identity: "epc",
|
|
3725
|
+
required: ["epc", "status"],
|
|
3726
|
+
fields: { epc: "string", status: "string", decidedBy: "string", reason: "string", decidedAt: "string", recordTime: "string" }
|
|
3727
|
+
},
|
|
3677
3728
|
test: {
|
|
3678
3729
|
eventType: OP_EVENT.test,
|
|
3679
3730
|
match: ["testableObjectId"],
|
|
@@ -4038,6 +4089,51 @@ function retiredVocabularyIn(line) {
|
|
|
4038
4089
|
return hits;
|
|
4039
4090
|
}
|
|
4040
4091
|
|
|
4092
|
+
// src/command-verdict.ts
|
|
4093
|
+
var REFUSED_WITHOUT_CODE = "refused";
|
|
4094
|
+
function verdictOfAck(ack) {
|
|
4095
|
+
if (ack.accepted) return { verdict: "accepted", commandId: ack.commandId };
|
|
4096
|
+
const reason = { code: ack.errorCode ?? REFUSED_WITHOUT_CODE };
|
|
4097
|
+
if (ack.errorParams !== void 0) reason.params = ack.errorParams;
|
|
4098
|
+
if (ack.error !== void 0) reason.message = ack.error;
|
|
4099
|
+
return { verdict: "refused", commandId: ack.commandId, reason };
|
|
4100
|
+
}
|
|
4101
|
+
function unavailableVerdict(code, params, message) {
|
|
4102
|
+
const reason = { code };
|
|
4103
|
+
if (params !== void 0) reason.params = params;
|
|
4104
|
+
if (message !== void 0) reason.message = message;
|
|
4105
|
+
return { verdict: "unavailable", reason };
|
|
4106
|
+
}
|
|
4107
|
+
function canPreview(runtime) {
|
|
4108
|
+
if (runtime === null || typeof runtime !== "object") return false;
|
|
4109
|
+
const r = runtime;
|
|
4110
|
+
return typeof r.fork === "function" && typeof r.dispatch === "function";
|
|
4111
|
+
}
|
|
4112
|
+
|
|
4113
|
+
// src/webhook-delivery.ts
|
|
4114
|
+
var WEBHOOK_DELIVERY_CODES = {
|
|
4115
|
+
/** The peer received and verified the envelope, then declined the job order itself (200, accepted=false). */
|
|
4116
|
+
jobOrderRefused: "job-order-refused",
|
|
4117
|
+
/** The envelope does not match the specification — retry never helps until the sender changes (400). */
|
|
4118
|
+
envelopeInvalid: "envelope-invalid",
|
|
4119
|
+
/** The signature did not verify (401). `params.reason` is `mismatch` or `expired`. */
|
|
4120
|
+
signatureRejected: "signature-rejected",
|
|
4121
|
+
/** The peer has no such site (404). */
|
|
4122
|
+
siteUnknown: "site-unknown",
|
|
4123
|
+
/** The peer has no verify key under the name the sender used (503). `params.peer` names that peer — never the env var. */
|
|
4124
|
+
verifySecretMissing: "verify-secret-missing",
|
|
4125
|
+
/** No HTTP answer at all — DNS, connection refused, timeout. */
|
|
4126
|
+
peerUnreachable: "peer-unreachable",
|
|
4127
|
+
/** The peer verified the envelope and then failed on its own side — storing, for instance (500). Retrying may help. */
|
|
4128
|
+
peerInternalFailure: "peer-internal-failure",
|
|
4129
|
+
/** An answer came back that fits none of the above — the code is the honest "unclassified", not a guess. */
|
|
4130
|
+
responseUnrecognised: "response-unrecognised"
|
|
4131
|
+
};
|
|
4132
|
+
var SIGNATURE_REJECTED_REASONS = ["mismatch", "expired"];
|
|
4133
|
+
function isWebhookDeliveryCode(code) {
|
|
4134
|
+
return Object.values(WEBHOOK_DELIVERY_CODES).includes(code);
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4041
4137
|
// src/webhook.ts
|
|
4042
4138
|
var WEBHOOK_STATUS = {
|
|
4043
4139
|
ok: 200,
|
|
@@ -4050,6 +4146,13 @@ var WEBHOOK_STATUS = {
|
|
|
4050
4146
|
unsupported: 501,
|
|
4051
4147
|
notLive: 503
|
|
4052
4148
|
};
|
|
4149
|
+
var WEBHOOK_LANE = {
|
|
4150
|
+
facts: "FACTS",
|
|
4151
|
+
attentions: "ATTENTIONS"
|
|
4152
|
+
};
|
|
4153
|
+
function webhookLaneOf(value) {
|
|
4154
|
+
return value === void 0 || value === WEBHOOK_LANE.facts ? WEBHOOK_LANE.facts : value === WEBHOOK_LANE.attentions ? WEBHOOK_LANE.attentions : void 0;
|
|
4155
|
+
}
|
|
4053
4156
|
function webhookSenderAction(status) {
|
|
4054
4157
|
switch (status) {
|
|
4055
4158
|
case WEBHOOK_STATUS.ok:
|
|
@@ -4125,13 +4228,13 @@ function webhookSecretEnvName(base, domain, peer) {
|
|
|
4125
4228
|
return [base, ...segments.map((s) => s.toUpperCase().replace(/-/g, "_"))].join("__");
|
|
4126
4229
|
}
|
|
4127
4230
|
function webhookSecretCandidates(lookup) {
|
|
4128
|
-
const { purpose, domain, peer, legacyBases = []
|
|
4231
|
+
const { purpose, domain, peer, legacyBases = [] } = lookup;
|
|
4129
4232
|
const base = WEBHOOK_SECRET_BASE[purpose];
|
|
4130
4233
|
const out = [
|
|
4131
4234
|
{ name: webhookSecretEnvName(base, domain, peer), pins: "peer", compatibility: false }
|
|
4132
4235
|
];
|
|
4133
|
-
if (purpose === "verify"
|
|
4134
|
-
out.push({ name: webhookSecretEnvName(base, domain), pins: "domain", compatibility:
|
|
4236
|
+
if (purpose === "verify") return out;
|
|
4237
|
+
out.push({ name: webhookSecretEnvName(base, domain), pins: "domain", compatibility: false });
|
|
4135
4238
|
for (const legacy of legacyBases) {
|
|
4136
4239
|
out.push({ name: webhookSecretEnvName(legacy, domain), pins: "domain", compatibility: true });
|
|
4137
4240
|
out.push({ name: legacy, pins: "installation", compatibility: true });
|
|
@@ -4453,6 +4556,27 @@ function commandSpecGaps(specs, command) {
|
|
|
4453
4556
|
}
|
|
4454
4557
|
return gaps;
|
|
4455
4558
|
}
|
|
4559
|
+
|
|
4560
|
+
// src/operational-attention.ts
|
|
4561
|
+
var OPERATIONAL_ATTENTION_CONTRACT = "operational.attention.v1";
|
|
4562
|
+
var OPERATIONAL_ATTENTION_TRANSITIONS = ["opened", "changed", "resolved", "reopened"];
|
|
4563
|
+
var OPERATIONAL_ATTENTION_IMPACTS = ["medium", "high"];
|
|
4564
|
+
function isOperationalAttentionV1(value) {
|
|
4565
|
+
const one = value;
|
|
4566
|
+
return !!one && typeof one === "object" && one.contract === OPERATIONAL_ATTENTION_CONTRACT && nonEmpty(one.domainId) && nonEmpty(one.attentionId) && nonEmpty(one.dedupeKey) && nonEmpty(one.kind) && nonEmpty(one.subjectLabel) && nonEmpty(one.observedAt) && nonEmpty(one.evidenceFingerprint) && OPERATIONAL_ATTENTION_TRANSITIONS.includes(one.transition) && OPERATIONAL_ATTENTION_IMPACTS.includes(one.impact) && Array.isArray(one.facts) && one.facts.length > 0 && one.facts.every(isOperationalFactReference);
|
|
4567
|
+
}
|
|
4568
|
+
function assertOperationalAttentionV1(value) {
|
|
4569
|
+
if (!isOperationalAttentionV1(value)) {
|
|
4570
|
+
throw new Error("invalid operational.attention.v1: lifecycle fields and stable source-fact references are required");
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
function isOperationalFactReference(value) {
|
|
4574
|
+
const one = value;
|
|
4575
|
+
return !!one && typeof one === "object" && nonEmpty(one.system) && nonEmpty(one.type) && nonEmpty(one.id) && (one.observedAt === void 0 || nonEmpty(one.observedAt)) && (one.label === void 0 || nonEmpty(one.label));
|
|
4576
|
+
}
|
|
4577
|
+
function nonEmpty(value) {
|
|
4578
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
4579
|
+
}
|
|
4456
4580
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4457
4581
|
0 && (module.exports = {
|
|
4458
4582
|
BIZSTEP,
|
|
@@ -4494,13 +4618,18 @@ function commandSpecGaps(specs, command) {
|
|
|
4494
4618
|
MES_TYPES,
|
|
4495
4619
|
METER_DIRECTION,
|
|
4496
4620
|
OBSERVATION_BASIS,
|
|
4621
|
+
OPERATIONAL_ATTENTION_CONTRACT,
|
|
4622
|
+
OPERATIONAL_ATTENTION_IMPACTS,
|
|
4623
|
+
OPERATIONAL_ATTENTION_TRANSITIONS,
|
|
4497
4624
|
OP_EVENT,
|
|
4498
4625
|
OP_PARAM,
|
|
4499
4626
|
ORDER_TERMINAL_STATUS,
|
|
4500
4627
|
PRIORITY_UNSET,
|
|
4628
|
+
REFUSED_WITHOUT_CODE,
|
|
4501
4629
|
RESOURCE_KIND,
|
|
4502
4630
|
RETIRED_VOCABULARY,
|
|
4503
4631
|
SCHEDULE_STATUS,
|
|
4632
|
+
SIGNATURE_REJECTED_REASONS,
|
|
4504
4633
|
TWIN_AXES,
|
|
4505
4634
|
TWIN_PROPERTIES,
|
|
4506
4635
|
TWIN_RELATIONS,
|
|
@@ -4508,6 +4637,8 @@ function commandSpecGaps(specs, command) {
|
|
|
4508
4637
|
UTC_OFFSET,
|
|
4509
4638
|
VOCABULARY_EXCEPTIONS,
|
|
4510
4639
|
VOCABULARY_TYPE,
|
|
4640
|
+
WEBHOOK_DELIVERY_CODES,
|
|
4641
|
+
WEBHOOK_LANE,
|
|
4511
4642
|
WEBHOOK_SECRET_BASE,
|
|
4512
4643
|
WEBHOOK_SECRET_NAME_SHAPE,
|
|
4513
4644
|
WEBHOOK_STATUS,
|
|
@@ -4524,11 +4655,13 @@ function commandSpecGaps(specs, command) {
|
|
|
4524
4655
|
analyzeCapacity,
|
|
4525
4656
|
approveCommand,
|
|
4526
4657
|
asApproved,
|
|
4658
|
+
assertOperationalAttentionV1,
|
|
4527
4659
|
axesOfSystem,
|
|
4528
4660
|
axisAppliesTo,
|
|
4529
4661
|
axisInfo,
|
|
4530
4662
|
axisSource,
|
|
4531
4663
|
bizTransactionUri,
|
|
4664
|
+
canPreview,
|
|
4532
4665
|
capabilitiesForType,
|
|
4533
4666
|
capabilityOf,
|
|
4534
4667
|
checkSequence,
|
|
@@ -4590,11 +4723,13 @@ function commandSpecGaps(specs, command) {
|
|
|
4590
4723
|
isEquipmentStatus,
|
|
4591
4724
|
isFailureStatus,
|
|
4592
4725
|
isMasterDataRecord,
|
|
4726
|
+
isOperationalAttentionV1,
|
|
4593
4727
|
isOperationalRecord,
|
|
4594
4728
|
isOrderTerminal,
|
|
4595
4729
|
isPlannedStopStatus,
|
|
4596
4730
|
isResourceKind,
|
|
4597
4731
|
isTransformationRecord,
|
|
4732
|
+
isWebhookDeliveryCode,
|
|
4598
4733
|
isWebhookSecretSegment,
|
|
4599
4734
|
isoDurationHours,
|
|
4600
4735
|
itemKeyOf,
|
|
@@ -4654,10 +4789,13 @@ function commandSpecGaps(specs, command) {
|
|
|
4654
4789
|
testPassedAt,
|
|
4655
4790
|
transactionEvent,
|
|
4656
4791
|
transformationEvent,
|
|
4792
|
+
unavailableVerdict,
|
|
4657
4793
|
validateDomainDefinition,
|
|
4658
4794
|
validateEpcisEvent,
|
|
4659
4795
|
validatePerformance,
|
|
4660
4796
|
validateScenario,
|
|
4797
|
+
verdictOfAck,
|
|
4798
|
+
webhookLaneOf,
|
|
4661
4799
|
webhookSecretCandidates,
|
|
4662
4800
|
webhookSecretEnvName,
|
|
4663
4801
|
webhookSecretFrom,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@operato/ops-contract",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.20",
|
|
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",
|