@operato/ops-contract 0.9.8 → 0.9.9

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.
@@ -34,7 +34,7 @@ export interface OperationalIngestOptions {
34
34
  * 거절도 경고도 없었다. **같은 계약의 ERP 경로는 이것을 잡는다**(§`erp.ts` 의 자재 줄 검사).
35
35
  * 한 계약 안에 검사가 두 벌이고 한쪽만 봤다.
36
36
  *
37
- * 이름을 맞춰 달라고 원본에 청하는 것으로는 다음에 또 조용해진다 다음 원본이 `use: 'out'` 같은
37
+ * 이름을 맞춰 달라고 원본에 요청하는 것으로는 다음에 또 오류보고없이 넘어간다. 다음 원본이 `use: 'out'` 같은
38
38
  * 것을 보내면 같은 일이 난다(실제로 그런 시드가 있었다: 계약에 `out` 이라는 낱말이 없다).
39
39
  */
40
40
  export interface ObjectShape {
package/dist/webhook.d.ts CHANGED
@@ -79,7 +79,7 @@ export interface WebhookEnvelope {
79
79
  /**
80
80
  * 번호 판정 — **네 가지이고, 합치지 않는다.**
81
81
  *
82
- * first 이 단위의 첫 봉투다. 받는 쪽에 커서가 없다
82
+ * first 이 단위의 첫 봉투다. 받는 쪽에 커서가 없다 — 그 앞을 몇 개 못 봤는지 함께 낸다
83
83
  * next 바로 다음 번호다
84
84
  * behind 이미 본 번호다. 재전송이다
85
85
  * gap 건너뛰었다. `expectedSeq` 부터 다시 받아야 한다
@@ -94,6 +94,24 @@ export interface WebhookEnvelope {
94
94
  export type SequenceVerdict = {
95
95
  kind: 'first';
96
96
  lastSeq: number;
97
+ /**
98
+ * 이 번호 아래로 몇 개가 있나 — **받은 적이 없는 개수**다(`seq - 1`).
99
+ *
100
+ * ── 왜 필요한가 (2026-09-06 실측, 인티그레이션 레인이 찾음) ──────────────
101
+ * plant 아웃박스의 1~20 이 400 으로 거절돼 그 연결에 커서가 없었다. 그래서 다음에 온 21번이
102
+ * **첫 봉투로 앉으며 커서가 21 로 뛰었다.** 커서가 0 이었다면 같은 21이 `gap` 이었다.
103
+ *
104
+ * 스무 건이 없는데 빈 구간으로도 안 세어지고, 경고도 없고, backfill 대상도 아니었다. backfill 은
105
+ * 커서(48)부터 묻기 때문에 영영 메워지지 않는다.
106
+ *
107
+ * ── 「잃었다」가 아니다 ────────────────────────────────────────────────
108
+ * 이 수가 곧 손실은 아니다. 원본이 오래 돌고 있는데 트윈을 나중에 붙이면 seq 4,712 부터 받는
109
+ * 것이 정상이고, 그 아래 4,711 개는 **애초에 안 받기로 한 것**이다.
110
+ *
111
+ * 계약은 둘을 구별할 수 없다. 그래서 「못 봤다」까지만 말하고 손실인지는 판정하지 않는다.
112
+ * 판정에 필요한 것(원본이 언제부터 보냈나)은 원본만 안다.
113
+ */
114
+ unseenBefore: number;
97
115
  } | {
98
116
  kind: 'next';
99
117
  lastSeq: number;
package/dist/webhook.js CHANGED
@@ -90,7 +90,7 @@ export function checkSequence(lastSeq, seq) {
90
90
  throw new Error(`checkSequence: 번호는 1 이상의 정수여야 한다 — 받은 값 ${JSON.stringify(seq)}`);
91
91
  }
92
92
  if (lastSeq === undefined || lastSeq === null)
93
- return { kind: 'first', lastSeq: seq };
93
+ return { kind: 'first', lastSeq: seq, unseenBefore: seq - 1 };
94
94
  if (seq <= lastSeq)
95
95
  return { kind: 'behind', lastSeq };
96
96
  if (seq === lastSeq + 1)
@@ -4016,7 +4016,7 @@ function checkSequence(lastSeq, seq) {
4016
4016
  if (!Number.isInteger(seq) || seq < 1) {
4017
4017
  throw new Error(`checkSequence: \uBC88\uD638\uB294 1 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD55C\uB2E4 \u2014 \uBC1B\uC740 \uAC12 ${JSON.stringify(seq)}`);
4018
4018
  }
4019
- if (lastSeq === void 0 || lastSeq === null) return { kind: "first", lastSeq: seq };
4019
+ if (lastSeq === void 0 || lastSeq === null) return { kind: "first", lastSeq: seq, unseenBefore: seq - 1 };
4020
4020
  if (seq <= lastSeq) return { kind: "behind", lastSeq };
4021
4021
  if (seq === lastSeq + 1) return { kind: "next", lastSeq: seq };
4022
4022
  return { kind: "gap", lastSeq, expectedSeq: lastSeq + 1 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/ops-contract",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
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",