@operato/ops-contract 0.9.19 → 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 +26 -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 +2 -0
- package/dist/index.js +2 -0
- package/dist/mes-profile.d.ts +6 -0
- package/dist/mes-profile.js +7 -1
- 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-cjs/index.cjs +100 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,6 +72,32 @@ assertOperationalAttentionV1(attention)
|
|
|
72
72
|
`lane`과 합치지 않는다. `lane`을 생략한 기존 커넥터는 `FACTS`로 읽혀 기존 canonical ingest 경로를 그대로
|
|
73
73
|
쓴다. 따라서 주소·서명·재시도·순번 규약은 하나이고, 사실과 attention의 저장·처리만 Twin 내부에서 갈린다.
|
|
74
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
|
+
|
|
75
101
|
## 왜 나뉘어 있나
|
|
76
102
|
|
|
77
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';
|
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`). */
|
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';
|
|
@@ -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-cjs/index.cjs
CHANGED
|
@@ -65,9 +65,11 @@ __export(index_exports, {
|
|
|
65
65
|
OP_PARAM: () => OP_PARAM,
|
|
66
66
|
ORDER_TERMINAL_STATUS: () => ORDER_TERMINAL_STATUS,
|
|
67
67
|
PRIORITY_UNSET: () => PRIORITY_UNSET,
|
|
68
|
+
REFUSED_WITHOUT_CODE: () => REFUSED_WITHOUT_CODE,
|
|
68
69
|
RESOURCE_KIND: () => RESOURCE_KIND,
|
|
69
70
|
RETIRED_VOCABULARY: () => RETIRED_VOCABULARY,
|
|
70
71
|
SCHEDULE_STATUS: () => SCHEDULE_STATUS,
|
|
72
|
+
SIGNATURE_REJECTED_REASONS: () => SIGNATURE_REJECTED_REASONS,
|
|
71
73
|
TWIN_AXES: () => TWIN_AXES,
|
|
72
74
|
TWIN_PROPERTIES: () => TWIN_PROPERTIES,
|
|
73
75
|
TWIN_RELATIONS: () => TWIN_RELATIONS,
|
|
@@ -75,6 +77,7 @@ __export(index_exports, {
|
|
|
75
77
|
UTC_OFFSET: () => UTC_OFFSET,
|
|
76
78
|
VOCABULARY_EXCEPTIONS: () => VOCABULARY_EXCEPTIONS,
|
|
77
79
|
VOCABULARY_TYPE: () => VOCABULARY_TYPE,
|
|
80
|
+
WEBHOOK_DELIVERY_CODES: () => WEBHOOK_DELIVERY_CODES,
|
|
78
81
|
WEBHOOK_LANE: () => WEBHOOK_LANE,
|
|
79
82
|
WEBHOOK_SECRET_BASE: () => WEBHOOK_SECRET_BASE,
|
|
80
83
|
WEBHOOK_SECRET_NAME_SHAPE: () => WEBHOOK_SECRET_NAME_SHAPE,
|
|
@@ -98,6 +101,7 @@ __export(index_exports, {
|
|
|
98
101
|
axisInfo: () => axisInfo,
|
|
99
102
|
axisSource: () => axisSource,
|
|
100
103
|
bizTransactionUri: () => bizTransactionUri,
|
|
104
|
+
canPreview: () => canPreview,
|
|
101
105
|
capabilitiesForType: () => capabilitiesForType,
|
|
102
106
|
capabilityOf: () => capabilityOf,
|
|
103
107
|
checkSequence: () => checkSequence,
|
|
@@ -165,6 +169,7 @@ __export(index_exports, {
|
|
|
165
169
|
isPlannedStopStatus: () => isPlannedStopStatus,
|
|
166
170
|
isResourceKind: () => isResourceKind,
|
|
167
171
|
isTransformationRecord: () => isTransformationRecord,
|
|
172
|
+
isWebhookDeliveryCode: () => isWebhookDeliveryCode,
|
|
168
173
|
isWebhookSecretSegment: () => isWebhookSecretSegment,
|
|
169
174
|
isoDurationHours: () => isoDurationHours,
|
|
170
175
|
itemKeyOf: () => itemKeyOf,
|
|
@@ -224,10 +229,12 @@ __export(index_exports, {
|
|
|
224
229
|
testPassedAt: () => testPassedAt,
|
|
225
230
|
transactionEvent: () => transactionEvent,
|
|
226
231
|
transformationEvent: () => transformationEvent,
|
|
232
|
+
unavailableVerdict: () => unavailableVerdict,
|
|
227
233
|
validateDomainDefinition: () => validateDomainDefinition,
|
|
228
234
|
validateEpcisEvent: () => validateEpcisEvent,
|
|
229
235
|
validatePerformance: () => validatePerformance,
|
|
230
236
|
validateScenario: () => validateScenario,
|
|
237
|
+
verdictOfAck: () => verdictOfAck,
|
|
231
238
|
webhookLaneOf: () => webhookLaneOf,
|
|
232
239
|
webhookSecretCandidates: () => webhookSecretCandidates,
|
|
233
240
|
webhookSecretEnvName: () => webhookSecretEnvName,
|
|
@@ -902,6 +909,22 @@ var OP_EVENT = {
|
|
|
902
909
|
* 이것은 **결정**이다. 누가·언제·왜 그렇게 정했는지의 자리는 그쪽에 없다.
|
|
903
910
|
*/
|
|
904
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",
|
|
905
928
|
/**
|
|
906
929
|
* **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 그것을 알린다.
|
|
907
930
|
*
|
|
@@ -1293,8 +1316,14 @@ var MES_BIZSTEP = {
|
|
|
1293
1316
|
// 원자재 수령
|
|
1294
1317
|
producing: "urn:epcglobal:cbv:bizstep:commissioning",
|
|
1295
1318
|
// 생산(제품 최초 생성)
|
|
1296
|
-
storing: "urn:epcglobal:cbv:bizstep:storing"
|
|
1319
|
+
storing: "urn:epcglobal:cbv:bizstep:storing",
|
|
1297
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"
|
|
1298
1327
|
};
|
|
1299
1328
|
var BTT_PRODORDER = "urn:epcglobal:cbv:btt:prodorder";
|
|
1300
1329
|
function sgtinUri(companyPrefix, itemRef, serial) {
|
|
@@ -3681,6 +3710,21 @@ var SPECS = {
|
|
|
3681
3710
|
},
|
|
3682
3711
|
enums: { decision: DISPOSITION_DECISION }
|
|
3683
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
|
+
},
|
|
3684
3728
|
test: {
|
|
3685
3729
|
eventType: OP_EVENT.test,
|
|
3686
3730
|
match: ["testableObjectId"],
|
|
@@ -4045,6 +4089,51 @@ function retiredVocabularyIn(line) {
|
|
|
4045
4089
|
return hits;
|
|
4046
4090
|
}
|
|
4047
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
|
+
|
|
4048
4137
|
// src/webhook.ts
|
|
4049
4138
|
var WEBHOOK_STATUS = {
|
|
4050
4139
|
ok: 200,
|
|
@@ -4139,13 +4228,13 @@ function webhookSecretEnvName(base, domain, peer) {
|
|
|
4139
4228
|
return [base, ...segments.map((s) => s.toUpperCase().replace(/-/g, "_"))].join("__");
|
|
4140
4229
|
}
|
|
4141
4230
|
function webhookSecretCandidates(lookup) {
|
|
4142
|
-
const { purpose, domain, peer, legacyBases = []
|
|
4231
|
+
const { purpose, domain, peer, legacyBases = [] } = lookup;
|
|
4143
4232
|
const base = WEBHOOK_SECRET_BASE[purpose];
|
|
4144
4233
|
const out = [
|
|
4145
4234
|
{ name: webhookSecretEnvName(base, domain, peer), pins: "peer", compatibility: false }
|
|
4146
4235
|
];
|
|
4147
|
-
if (purpose === "verify"
|
|
4148
|
-
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 });
|
|
4149
4238
|
for (const legacy of legacyBases) {
|
|
4150
4239
|
out.push({ name: webhookSecretEnvName(legacy, domain), pins: "domain", compatibility: true });
|
|
4151
4240
|
out.push({ name: legacy, pins: "installation", compatibility: true });
|
|
@@ -4536,9 +4625,11 @@ function nonEmpty(value) {
|
|
|
4536
4625
|
OP_PARAM,
|
|
4537
4626
|
ORDER_TERMINAL_STATUS,
|
|
4538
4627
|
PRIORITY_UNSET,
|
|
4628
|
+
REFUSED_WITHOUT_CODE,
|
|
4539
4629
|
RESOURCE_KIND,
|
|
4540
4630
|
RETIRED_VOCABULARY,
|
|
4541
4631
|
SCHEDULE_STATUS,
|
|
4632
|
+
SIGNATURE_REJECTED_REASONS,
|
|
4542
4633
|
TWIN_AXES,
|
|
4543
4634
|
TWIN_PROPERTIES,
|
|
4544
4635
|
TWIN_RELATIONS,
|
|
@@ -4546,6 +4637,7 @@ function nonEmpty(value) {
|
|
|
4546
4637
|
UTC_OFFSET,
|
|
4547
4638
|
VOCABULARY_EXCEPTIONS,
|
|
4548
4639
|
VOCABULARY_TYPE,
|
|
4640
|
+
WEBHOOK_DELIVERY_CODES,
|
|
4549
4641
|
WEBHOOK_LANE,
|
|
4550
4642
|
WEBHOOK_SECRET_BASE,
|
|
4551
4643
|
WEBHOOK_SECRET_NAME_SHAPE,
|
|
@@ -4569,6 +4661,7 @@ function nonEmpty(value) {
|
|
|
4569
4661
|
axisInfo,
|
|
4570
4662
|
axisSource,
|
|
4571
4663
|
bizTransactionUri,
|
|
4664
|
+
canPreview,
|
|
4572
4665
|
capabilitiesForType,
|
|
4573
4666
|
capabilityOf,
|
|
4574
4667
|
checkSequence,
|
|
@@ -4636,6 +4729,7 @@ function nonEmpty(value) {
|
|
|
4636
4729
|
isPlannedStopStatus,
|
|
4637
4730
|
isResourceKind,
|
|
4638
4731
|
isTransformationRecord,
|
|
4732
|
+
isWebhookDeliveryCode,
|
|
4639
4733
|
isWebhookSecretSegment,
|
|
4640
4734
|
isoDurationHours,
|
|
4641
4735
|
itemKeyOf,
|
|
@@ -4695,10 +4789,12 @@ function nonEmpty(value) {
|
|
|
4695
4789
|
testPassedAt,
|
|
4696
4790
|
transactionEvent,
|
|
4697
4791
|
transformationEvent,
|
|
4792
|
+
unavailableVerdict,
|
|
4698
4793
|
validateDomainDefinition,
|
|
4699
4794
|
validateEpcisEvent,
|
|
4700
4795
|
validatePerformance,
|
|
4701
4796
|
validateScenario,
|
|
4797
|
+
verdictOfAck,
|
|
4702
4798
|
webhookLaneOf,
|
|
4703
4799
|
webhookSecretCandidates,
|
|
4704
4800
|
webhookSecretEnvName,
|
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",
|