@teamscala/service-boot 0.23.361 → 0.23.362
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/package.json
CHANGED
|
@@ -147,6 +147,27 @@ export function formatSpanMinutes(minutes: number): string {
|
|
|
147
147
|
return `${hours >= 10 ? Math.round(hours) : Math.round(hours * 10) / 10} h`;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* The WhatsApp message id from a GOWA /send/message result. The canonical
|
|
152
|
+
* shape (from the spec the api-gowa adapter is generated from,
|
|
153
|
+
* SendResponse = { code, message, results: { message_id, status } }) is
|
|
154
|
+
* `results.message_id`; the legacy shape returned the bare id string as
|
|
155
|
+
* `results`. Delivery is only VERIFIED on a real id — `"true"`, a status
|
|
156
|
+
* string, or an empty body is not a message id and must not be recorded as
|
|
157
|
+
* delivered.
|
|
158
|
+
*/
|
|
159
|
+
export function extractGowaMessageId(sendResult: unknown): string | null {
|
|
160
|
+
const results = (sendResult as { results?: unknown } | undefined)?.results;
|
|
161
|
+
if (typeof results === "string") {
|
|
162
|
+
return /^[A-Z0-9]{8,}$/i.test(results) ? results : null;
|
|
163
|
+
}
|
|
164
|
+
if (results && typeof results === "object") {
|
|
165
|
+
const id = (results as { message_id?: unknown }).message_id;
|
|
166
|
+
if (typeof id === "string" && id.length > 0) return id;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
150
171
|
/**
|
|
151
172
|
* The escalation message — escalation-routing's three-field contract adapted
|
|
152
173
|
* to an automated detector: what was OBSERVED (evidence), what it MEANS
|
|
@@ -12,6 +12,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
12
12
|
import {
|
|
13
13
|
composeEscalationMessage,
|
|
14
14
|
decideSweep,
|
|
15
|
+
extractGowaMessageId,
|
|
15
16
|
type EscalationEpisode,
|
|
16
17
|
type FaultAggregate,
|
|
17
18
|
type FaultEscalationPolicy,
|
|
@@ -166,6 +167,26 @@ describe("decideSweep — resolution + re-arm", () => {
|
|
|
166
167
|
});
|
|
167
168
|
});
|
|
168
169
|
|
|
170
|
+
describe("extractGowaMessageId — delivery verification", () => {
|
|
171
|
+
test("the canonical GOWA SendResponse shape yields results.message_id", () => {
|
|
172
|
+
expect(extractGowaMessageId({ code: "SUCCESS", results: { message_id: "3EB089B9D6ADD58153C561", status: "sent" } })).toBe(
|
|
173
|
+
"3EB089B9D6ADD58153C561",
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("the legacy bare-string results shape yields the id", () => {
|
|
178
|
+
expect(extractGowaMessageId({ results: "3EB089B9D6ADD58153C561" })).toBe("3EB089B9D6ADD58153C561");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("a 'true' ack is NOT a message id — delivery stays unverified", () => {
|
|
182
|
+
// A boolean-ish ack string must never be recorded as a delivered id:
|
|
183
|
+
// that would mark the episode delivered with an unfollowable handle.
|
|
184
|
+
expect(extractGowaMessageId({ results: "true" })).toBeNull();
|
|
185
|
+
expect(extractGowaMessageId({ code: "SUCCESS", message: "sent", results: { status: "sent" } })).toBeNull();
|
|
186
|
+
expect(extractGowaMessageId({ code: "SUCCESS" })).toBeNull();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
169
190
|
describe("composeEscalationMessage", () => {
|
|
170
191
|
test("carries the three escalation-routing fields and stays phone-sized", () => {
|
|
171
192
|
const message = composeEscalationMessage({
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
composeEscalationDescription,
|
|
56
56
|
composeEscalationMessage,
|
|
57
57
|
decideSweep,
|
|
58
|
+
extractGowaMessageId,
|
|
58
59
|
type EscalationEpisode,
|
|
59
60
|
type FaultAggregate,
|
|
60
61
|
type KindThresholdOverride,
|
|
@@ -178,17 +179,6 @@ async function resolvePaDeviceId(paPhoneDigits: string): Promise<string | undefi
|
|
|
178
179
|
return match?.id;
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
function extractMessageId(sendResult: unknown): string | null {
|
|
182
|
-
const results = (sendResult as { results?: unknown } | undefined)?.results;
|
|
183
|
-
const id =
|
|
184
|
-
typeof results === "string"
|
|
185
|
-
? results
|
|
186
|
-
: Array.isArray(results)
|
|
187
|
-
? ((results[0] as { id?: unknown } | undefined)?.id as string | undefined)
|
|
188
|
-
: ((results as { id?: unknown } | undefined)?.id as string | undefined);
|
|
189
|
-
return typeof id === "string" && id.length > 0 ? id : null;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
182
|
async function deliverEscalation(
|
|
193
183
|
kind: string,
|
|
194
184
|
aggregate: FaultAggregate,
|
|
@@ -246,7 +236,7 @@ async function deliverEscalation(
|
|
|
246
236
|
|
|
247
237
|
// 3. VERIFY delivery before recording it: the send must return a WhatsApp
|
|
248
238
|
// message id. No id => throw, the claim stays undelivered, next sweep retries.
|
|
249
|
-
const messageId =
|
|
239
|
+
const messageId = extractGowaMessageId(sendResult);
|
|
250
240
|
if (!messageId) {
|
|
251
241
|
throw new Error(
|
|
252
242
|
`platform-fault-escalation: WhatsApp send for '${kind}' returned no message id — not recording delivery`,
|