@modainteract/moda-interact-shared 1.5.0 → 2.1.0

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.
@@ -0,0 +1,264 @@
1
+ // src/incidents/index.ts
2
+ import { createHash } from "crypto";
3
+ import { z } from "zod";
4
+
5
+ // src/incidents/reporter.ts
6
+ function createOperationalIncidentReporter(options) {
7
+ return {
8
+ async report(raw) {
9
+ let occurrence;
10
+ try {
11
+ occurrence = parseOperationalIncidentOccurrenceV1(raw);
12
+ } catch {
13
+ options.logger.error("operational_incident.invalid_occurrence", {});
14
+ return null;
15
+ }
16
+ const incidentKey = createOperationalIncidentKey({
17
+ sourceService: occurrence.sourceService,
18
+ environment: occurrence.environment,
19
+ category: occurrence.category,
20
+ reasonCode: occurrence.reasonCode,
21
+ resourceKey: occurrence.resourceKey,
22
+ shopId: occurrence.shopId
23
+ });
24
+ try {
25
+ return await options.persistence.transactionForKey(incidentKey, async (tx) => {
26
+ const current = await tx.loadOrCreate({ incidentKey, occurrence });
27
+ const recorded = await tx.insertOccurrenceIfNew({ incidentKey, occurrence });
28
+ if (!recorded) {
29
+ return {
30
+ incidentKey,
31
+ recorded: false,
32
+ occurrenceCount: current.occurrenceCount,
33
+ status: current.status
34
+ };
35
+ }
36
+ const updated = await tx.recordOccurrence({
37
+ incidentKey,
38
+ occurrence,
39
+ reopen: current.status === "RESOLVED",
40
+ newlyCreated: current.newlyCreated
41
+ });
42
+ if (options.notifySupport && occurrence.shopId !== null && tx.notifySupportOnce) {
43
+ await tx.notifySupportOnce({ incidentKey, occurrence });
44
+ }
45
+ return {
46
+ incidentKey,
47
+ recorded: true,
48
+ occurrenceCount: updated.occurrenceCount,
49
+ status: "OPEN"
50
+ };
51
+ });
52
+ } catch {
53
+ options.logger.error("operational_incident.report_failed", {
54
+ incidentKey,
55
+ sourceService: occurrence.sourceService,
56
+ environment: occurrence.environment,
57
+ occurrenceId: occurrence.occurrenceId
58
+ });
59
+ return null;
60
+ }
61
+ }
62
+ };
63
+ }
64
+
65
+ // src/incidents/postgres.ts
66
+ import { randomUUID } from "crypto";
67
+ async function rows(tx, statement, ...params) {
68
+ return tx.$queryRawUnsafe(statement, ...params);
69
+ }
70
+ function snapshot(row) {
71
+ return {
72
+ incidentKey: row.incidentKey,
73
+ status: row.status,
74
+ occurrenceCount: row.occurrenceCount,
75
+ lastOccurredAt: row.lastOccurredAt
76
+ };
77
+ }
78
+ var PgIncidentTransaction = class {
79
+ constructor(tx, supportWriter) {
80
+ this.tx = tx;
81
+ this.supportWriter = supportWriter;
82
+ }
83
+ tx;
84
+ supportWriter;
85
+ incidentId = null;
86
+ newIncident = false;
87
+ async loadOrCreate({ incidentKey, occurrence }) {
88
+ const inserted = await rows(
89
+ this.tx,
90
+ `
91
+ INSERT INTO support."OperationalIncident" (
92
+ "id", "incidentKey", "sourceService", "environment", "category", "reasonCode",
93
+ "resourceKey", "shopId", "severity", "status", "occurrenceCount",
94
+ "firstOccurredAt", "lastOccurredAt", "lastCorrelationId")
95
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,'OPEN',1,$10::timestamptz,$10::timestamptz,$11)
96
+ ON CONFLICT ("incidentKey") DO NOTHING RETURNING *`,
97
+ randomUUID(),
98
+ incidentKey,
99
+ occurrence.sourceService,
100
+ occurrence.environment,
101
+ occurrence.category,
102
+ occurrence.reasonCode,
103
+ occurrence.resourceKey,
104
+ occurrence.shopId,
105
+ occurrence.severity,
106
+ occurrence.occurredAt,
107
+ occurrence.correlationId
108
+ );
109
+ this.newIncident = inserted.length > 0;
110
+ const found = await rows(
111
+ this.tx,
112
+ `SELECT * FROM support."OperationalIncident" WHERE "incidentKey" = $1 FOR UPDATE`,
113
+ incidentKey
114
+ );
115
+ if (found.length !== 1) throw new Error("incident row not found after upsert");
116
+ this.incidentId = found[0].id;
117
+ return { ...snapshot(found[0]), newlyCreated: this.newIncident };
118
+ }
119
+ async insertOccurrenceIfNew({ incidentKey, occurrence }) {
120
+ if (!this.incidentId) throw new Error("incident not loaded");
121
+ const created = await rows(
122
+ this.tx,
123
+ `
124
+ INSERT INTO support."OperationalIncidentEvent"
125
+ ("id", "incidentId", "eventType", "correlationId", "occurredAt")
126
+ VALUES ($1::uuid, $2, 'OCCURRED', $3, $4::timestamptz)
127
+ ON CONFLICT ("id") DO NOTHING RETURNING "id"`,
128
+ occurrence.occurrenceId,
129
+ this.incidentId,
130
+ occurrence.correlationId,
131
+ occurrence.occurredAt
132
+ );
133
+ if (!created.length) {
134
+ if (this.newIncident) throw new Error("duplicate occurrence cannot create new incident");
135
+ const existing = await rows(this.tx, `
136
+ SELECT i."incidentKey" FROM support."OperationalIncidentEvent" e
137
+ JOIN support."OperationalIncident" i ON i."id" = e."incidentId"
138
+ WHERE e."id" = $1::uuid`, occurrence.occurrenceId);
139
+ if (existing.length !== 1 || existing[0].incidentKey !== incidentKey) {
140
+ throw new Error("occurrence already belongs to another incident");
141
+ }
142
+ return false;
143
+ }
144
+ return true;
145
+ }
146
+ async recordOccurrence({ incidentKey, occurrence, reopen, newlyCreated }) {
147
+ if (!this.incidentId) throw new Error("incident not loaded");
148
+ if (reopen) {
149
+ await rows(
150
+ this.tx,
151
+ `INSERT INTO support."OperationalIncidentEvent"
152
+ ("id", "incidentId", "eventType", "correlationId", "occurredAt")
153
+ VALUES ($1::uuid,$2,'REOPENED',$3,$4::timestamptz)`,
154
+ randomUUID(),
155
+ this.incidentId,
156
+ occurrence.correlationId,
157
+ occurrence.occurredAt
158
+ );
159
+ }
160
+ const updated = await rows(
161
+ this.tx,
162
+ `
163
+ UPDATE support."OperationalIncident" SET
164
+ "occurrenceCount" = "occurrenceCount" + $2::integer,
165
+ "lastOccurredAt" = GREATEST("lastOccurredAt", $3::timestamptz),
166
+ "lastCorrelationId" = $4,
167
+ "severity" = $5,
168
+ "status" = 'OPEN', "resolvedAt" = NULL,
169
+ "resolvedByPlatformAdminId" = NULL, "resolutionNote" = NULL,
170
+ "revision" = "revision" + 1
171
+ WHERE "incidentKey" = $1 RETURNING *`,
172
+ incidentKey,
173
+ newlyCreated ? 0 : 1,
174
+ occurrence.occurredAt,
175
+ occurrence.correlationId,
176
+ occurrence.severity
177
+ );
178
+ if (updated.length !== 1) throw new Error("incident update failed");
179
+ return snapshot(updated[0]);
180
+ }
181
+ async notifySupportOnce(input) {
182
+ if (this.supportWriter && this.incidentId) await this.supportWriter({
183
+ tx: this.tx,
184
+ incidentId: this.incidentId,
185
+ incidentKey: input.incidentKey,
186
+ occurrence: input.occurrence
187
+ });
188
+ }
189
+ };
190
+ function retryable(error) {
191
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
192
+ return code === "P2034" || code === "40001" || code === "40P01";
193
+ }
194
+ function createPrismaOperationalIncidentPersistence(options) {
195
+ const maxAttempts = options.maxAttempts ?? 3;
196
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 10) {
197
+ throw new Error("maxAttempts must be between 1 and 10");
198
+ }
199
+ return {
200
+ async transactionForKey(key, fn) {
201
+ for (let attempt = 1; ; attempt++) {
202
+ try {
203
+ return await options.prisma.$transaction(async (tx) => {
204
+ await rows(tx, `SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))::text AS acquired`, key);
205
+ return fn(new PgIncidentTransaction(tx, options.supportWriter));
206
+ }, { isolationLevel: "Serializable", timeout: 15e3 });
207
+ } catch (error) {
208
+ if (attempt >= maxAttempts || !retryable(error)) throw error;
209
+ }
210
+ }
211
+ }
212
+ };
213
+ }
214
+
215
+ // src/incidents/index.ts
216
+ var OPERATIONAL_INCIDENT_CONTRACT_VERSION = "operational-incident.v1";
217
+ var identifier = z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
218
+ var smallIdentifier = z.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
219
+ var opaqueId = z.string().uuid();
220
+ var OperationalIncidentOccurrenceV1Schema = z.strictObject({
221
+ contractVersion: z.literal(OPERATIONAL_INCIDENT_CONTRACT_VERSION),
222
+ sourceService: smallIdentifier,
223
+ environment: z.enum(["local", "development", "test", "staging", "production"]),
224
+ category: smallIdentifier,
225
+ reasonCode: identifier,
226
+ resourceKey: identifier,
227
+ shopId: z.string().min(1).max(128).nullable(),
228
+ severity: z.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]),
229
+ occurrenceId: opaqueId,
230
+ correlationId: identifier,
231
+ occurredAt: z.iso.datetime({ offset: true })
232
+ // Deliberately no arbitrary metadata bag, message, headers or exception object.
233
+ });
234
+ function parseOperationalIncidentOccurrenceV1(input) {
235
+ return OperationalIncidentOccurrenceV1Schema.parse(input);
236
+ }
237
+ function createOperationalIncidentKey(input) {
238
+ const parsed = OperationalIncidentOccurrenceV1Schema.pick({
239
+ sourceService: true,
240
+ environment: true,
241
+ category: true,
242
+ reasonCode: true,
243
+ resourceKey: true,
244
+ shopId: true
245
+ }).parse(input);
246
+ return createHash("sha256").update(JSON.stringify([
247
+ "operational-incident-key.v1",
248
+ parsed.sourceService,
249
+ parsed.environment,
250
+ parsed.category,
251
+ parsed.reasonCode,
252
+ parsed.shopId,
253
+ parsed.resourceKey
254
+ ])).digest("hex");
255
+ }
256
+ export {
257
+ OPERATIONAL_INCIDENT_CONTRACT_VERSION,
258
+ OperationalIncidentOccurrenceV1Schema,
259
+ createOperationalIncidentKey,
260
+ createOperationalIncidentReporter,
261
+ createPrismaOperationalIncidentPersistence,
262
+ parseOperationalIncidentOccurrenceV1
263
+ };
264
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/incidents/index.ts","../../src/incidents/reporter.ts","../../src/incidents/postgres.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { z } from \"zod\";\n\n// ARCH-033 v1: safe, internal-only occurrence envelope. Producer classifies actionability.\nexport const OPERATIONAL_INCIDENT_CONTRACT_VERSION = \"operational-incident.v1\" as const;\nconst identifier = z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);\nconst smallIdentifier = z.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);\nconst opaqueId = z.string().uuid();\nexport const OperationalIncidentOccurrenceV1Schema = z.strictObject({\n contractVersion: z.literal(OPERATIONAL_INCIDENT_CONTRACT_VERSION),\n sourceService: smallIdentifier,\n environment: z.enum([\"local\", \"development\", \"test\", \"staging\", \"production\"]),\n category: smallIdentifier,\n reasonCode: identifier,\n resourceKey: identifier,\n shopId: z.string().min(1).max(128).nullable(),\n severity: z.enum([\"LOW\", \"MEDIUM\", \"HIGH\", \"CRITICAL\"]),\n occurrenceId: opaqueId,\n correlationId: identifier,\n occurredAt: z.iso.datetime({ offset: true }),\n // Deliberately no arbitrary metadata bag, message, headers or exception object.\n});\nexport type OperationalIncidentOccurrenceV1 = z.infer<typeof OperationalIncidentOccurrenceV1Schema>;\nexport function parseOperationalIncidentOccurrenceV1(input: unknown): OperationalIncidentOccurrenceV1 {\n return OperationalIncidentOccurrenceV1Schema.parse(input);\n}\nexport function createOperationalIncidentKey(input: Pick<OperationalIncidentOccurrenceV1,\n \"sourceService\" | \"environment\" | \"category\" | \"reasonCode\" | \"resourceKey\" | \"shopId\">): string {\n const parsed = OperationalIncidentOccurrenceV1Schema.pick({\n sourceService: true, environment: true, category: true, reasonCode: true, resourceKey: true, shopId: true,\n }).parse(input);\n return createHash(\"sha256\").update(JSON.stringify([\n \"operational-incident-key.v1\", parsed.sourceService, parsed.environment, parsed.category,\n parsed.reasonCode, parsed.shopId, parsed.resourceKey,\n ])).digest(\"hex\");\n}\n\nexport { createOperationalIncidentReporter } from \"./reporter.js\";\nexport type {\n IncidentPersistence, IncidentTransaction, IncidentSnapshot, IncidentReportResult,\n IncidentReporterLogger,\n} from \"./reporter.js\";\n\nexport { createPrismaOperationalIncidentPersistence } from \"./postgres.js\";\nexport type { OperationalIncidentPrismaClient, OperationalIncidentSqlClient, OperationalIncidentSupportWriter } from \"./postgres.js\";\n","import { createOperationalIncidentKey, parseOperationalIncidentOccurrenceV1 } from \"./index.js\";\nimport type { OperationalIncidentOccurrenceV1 } from \"./index.js\";\n\n/** Durable outcome of one occurrence; duplicate deliveries have no side effects. */\nexport type IncidentReportResult = {\n incidentKey: string;\n recorded: boolean;\n occurrenceCount: number;\n status: \"OPEN\" | \"RESOLVED\";\n};\n\nexport type IncidentSnapshot = {\n incidentKey: string;\n status: \"OPEN\" | \"RESOLVED\";\n occurrenceCount: number;\n lastOccurredAt: Date;\n};\n\n/**\n * Transactional persistence contract, implemented by the consuming service.\n * transactionForKey MUST serialize operations for the same key across processes,\n * INCLUDING first creation; the callback must be retried on a conflicting insert.\n * insertOccurrenceIfNew MUST enforce a GLOBAL unique occurrenceId (not only per\n * incident); an occurrenceId already belonging to another key MUST throw.\n * On creation loadOrCreate MUST initialise occurrenceCount=1; recordOccurrence\n * MUST NOT increment that first occurrence again. If a duplicate is encountered\n * when newlyCreated=true, the transaction MUST fail and roll back creation.\n * Every operation in this port, including optional support notification, MUST\n * participate in the SAME database transaction and roll back together.\n */\nexport interface IncidentTransaction {\n loadOrCreate(input: { incidentKey: string; occurrence: OperationalIncidentOccurrenceV1 }): Promise<IncidentSnapshot & { newlyCreated: boolean }>;\n insertOccurrenceIfNew(input: { incidentKey: string; occurrence: OperationalIncidentOccurrenceV1 }): Promise<boolean>;\n recordOccurrence(input: {\n incidentKey: string;\n occurrence: OperationalIncidentOccurrenceV1;\n reopen: boolean;\n newlyCreated: boolean;\n }): Promise<IncidentSnapshot>;\n /** Optional, safe internal support projection; at most one per incident key. */\n notifySupportOnce?(input: { incidentKey: string; occurrence: OperationalIncidentOccurrenceV1 }): Promise<void>;\n}\nexport interface IncidentPersistence {\n transactionForKey<T>(key: string, fn: (tx: IncidentTransaction) => Promise<T>): Promise<T>;\n}\nexport interface IncidentReporterLogger {\n error(event: string, fields: Record<string, unknown>): void;\n}\n\n/** Reporter never turns an operational-notification failure into the producer's failure. */\nexport function createOperationalIncidentReporter(options: {\n persistence: IncidentPersistence;\n logger: IncidentReporterLogger;\n notifySupport?: boolean;\n}) {\n return {\n async report(raw: unknown): Promise<IncidentReportResult | null> {\n let occurrence: OperationalIncidentOccurrenceV1;\n try {\n occurrence = parseOperationalIncidentOccurrenceV1(raw);\n } catch {\n // Fail closed, and do not log potentially sensitive invalid payloads.\n options.logger.error(\"operational_incident.invalid_occurrence\", {});\n return null;\n }\n const incidentKey = createOperationalIncidentKey({\n sourceService: occurrence.sourceService, environment: occurrence.environment,\n category: occurrence.category, reasonCode: occurrence.reasonCode,\n resourceKey: occurrence.resourceKey, shopId: occurrence.shopId,\n });\n try {\n return await options.persistence.transactionForKey(incidentKey, async (tx) => {\n const current = await tx.loadOrCreate({ incidentKey, occurrence });\n const recorded = await tx.insertOccurrenceIfNew({ incidentKey, occurrence });\n if (!recorded) {\n return { incidentKey, recorded: false, occurrenceCount: current.occurrenceCount,\n status: current.status };\n }\n const updated = await tx.recordOccurrence({ incidentKey, occurrence,\n reopen: current.status === \"RESOLVED\", newlyCreated: current.newlyCreated });\n if (options.notifySupport && occurrence.shopId !== null && tx.notifySupportOnce) {\n await tx.notifySupportOnce({ incidentKey, occurrence });\n }\n return { incidentKey, recorded: true, occurrenceCount: updated.occurrenceCount,\n status: \"OPEN\" as const };\n });\n } catch {\n options.logger.error(\"operational_incident.report_failed\", {\n incidentKey, sourceService: occurrence.sourceService,\n environment: occurrence.environment, occurrenceId: occurrence.occurrenceId,\n });\n return null;\n }\n },\n };\n}\n","import { randomUUID } from \"node:crypto\";\nimport type { OperationalIncidentOccurrenceV1 } from \"./index.js\";\nimport type { IncidentPersistence, IncidentSnapshot, IncidentTransaction } from \"./reporter.js\";\n\n/** A deliberately small interface; the application's Prisma transaction client supplies it. */\nexport interface OperationalIncidentSqlClient {\n $queryRawUnsafe<T = unknown>(query: string, ...parameters: unknown[]): Promise<T>;\n}\nexport interface OperationalIncidentPrismaClient {\n $transaction<T>(fn: (tx: OperationalIncidentSqlClient) => Promise<T>, options?: {\n maxWait?: number; timeout?: number; isolationLevel?: \"Serializable\";\n }): Promise<T>;\n}\n\n/** Optional merchant-support projection. This callback MUST use the supplied transaction. */\nexport type OperationalIncidentSupportWriter = (input: {\n tx: OperationalIncidentSqlClient;\n incidentId: string;\n incidentKey: string;\n occurrence: OperationalIncidentOccurrenceV1;\n}) => Promise<void>;\n\ninterface IncidentRow {\n id: string;\n incidentKey: string;\n status: \"OPEN\" | \"RESOLVED\";\n occurrenceCount: number;\n lastOccurredAt: Date;\n}\n\nasync function rows<T>(tx: OperationalIncidentSqlClient, statement: string, ...params: unknown[]): Promise<T[]> {\n return tx.$queryRawUnsafe<T[]>(statement, ...params);\n}\n\nfunction snapshot(row: IncidentRow): IncidentSnapshot {\n return { incidentKey: row.incidentKey, status: row.status,\n occurrenceCount: row.occurrenceCount, lastOccurredAt: row.lastOccurredAt };\n}\n\nclass PgIncidentTransaction implements IncidentTransaction {\n private incidentId: string | null = null;\n private newIncident = false;\n constructor(private readonly tx: OperationalIncidentSqlClient,\n private readonly supportWriter?: OperationalIncidentSupportWriter) {}\n\n async loadOrCreate({ incidentKey, occurrence }: {\n incidentKey: string; occurrence: OperationalIncidentOccurrenceV1;\n }): Promise<IncidentSnapshot & { newlyCreated: boolean }> {\n const inserted = await rows<IncidentRow>(this.tx, `\n INSERT INTO support.\"OperationalIncident\" (\n \"id\", \"incidentKey\", \"sourceService\", \"environment\", \"category\", \"reasonCode\",\n \"resourceKey\", \"shopId\", \"severity\", \"status\", \"occurrenceCount\",\n \"firstOccurredAt\", \"lastOccurredAt\", \"lastCorrelationId\")\n VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,'OPEN',1,$10::timestamptz,$10::timestamptz,$11)\n ON CONFLICT (\"incidentKey\") DO NOTHING RETURNING *`,\n randomUUID(), incidentKey, occurrence.sourceService, occurrence.environment,\n occurrence.category, occurrence.reasonCode, occurrence.resourceKey,\n occurrence.shopId, occurrence.severity, occurrence.occurredAt, occurrence.correlationId);\n this.newIncident = inserted.length > 0;\n const found = await rows<IncidentRow>(this.tx,\n `SELECT * FROM support.\"OperationalIncident\" WHERE \"incidentKey\" = $1 FOR UPDATE`, incidentKey);\n if (found.length !== 1) throw new Error(\"incident row not found after upsert\");\n this.incidentId = found[0]!.id;\n return { ...snapshot(found[0]!), newlyCreated: this.newIncident };\n }\n\n async insertOccurrenceIfNew({ incidentKey, occurrence }: {\n incidentKey: string; occurrence: OperationalIncidentOccurrenceV1;\n }): Promise<boolean> {\n if (!this.incidentId) throw new Error(\"incident not loaded\");\n const created = await rows<{ id: string }>(this.tx, `\n INSERT INTO support.\"OperationalIncidentEvent\"\n (\"id\", \"incidentId\", \"eventType\", \"correlationId\", \"occurredAt\")\n VALUES ($1::uuid, $2, 'OCCURRED', $3, $4::timestamptz)\n ON CONFLICT (\"id\") DO NOTHING RETURNING \"id\"`,\n occurrence.occurrenceId, this.incidentId, occurrence.correlationId, occurrence.occurredAt);\n if (!created.length) {\n if (this.newIncident) throw new Error(\"duplicate occurrence cannot create new incident\");\n const existing = await rows<{ incidentKey: string }>(this.tx, `\n SELECT i.\"incidentKey\" FROM support.\"OperationalIncidentEvent\" e\n JOIN support.\"OperationalIncident\" i ON i.\"id\" = e.\"incidentId\"\n WHERE e.\"id\" = $1::uuid`, occurrence.occurrenceId);\n if (existing.length !== 1 || existing[0]!.incidentKey !== incidentKey) {\n throw new Error(\"occurrence already belongs to another incident\");\n }\n return false;\n }\n return true;\n }\n\n async recordOccurrence({ incidentKey, occurrence, reopen, newlyCreated }: {\n incidentKey: string; occurrence: OperationalIncidentOccurrenceV1;\n reopen: boolean; newlyCreated: boolean;\n }): Promise<IncidentSnapshot> {\n if (!this.incidentId) throw new Error(\"incident not loaded\");\n if (reopen) {\n await rows(this.tx, `INSERT INTO support.\"OperationalIncidentEvent\"\n (\"id\", \"incidentId\", \"eventType\", \"correlationId\", \"occurredAt\")\n VALUES ($1::uuid,$2,'REOPENED',$3,$4::timestamptz)`,\n randomUUID(), this.incidentId, occurrence.correlationId, occurrence.occurredAt);\n }\n const updated = await rows<IncidentRow>(this.tx, `\n UPDATE support.\"OperationalIncident\" SET\n \"occurrenceCount\" = \"occurrenceCount\" + $2::integer,\n \"lastOccurredAt\" = GREATEST(\"lastOccurredAt\", $3::timestamptz),\n \"lastCorrelationId\" = $4,\n \"severity\" = $5,\n \"status\" = 'OPEN', \"resolvedAt\" = NULL,\n \"resolvedByPlatformAdminId\" = NULL, \"resolutionNote\" = NULL,\n \"revision\" = \"revision\" + 1\n WHERE \"incidentKey\" = $1 RETURNING *`, incidentKey,\n newlyCreated ? 0 : 1, occurrence.occurredAt, occurrence.correlationId, occurrence.severity);\n if (updated.length !== 1) throw new Error(\"incident update failed\");\n return snapshot(updated[0]!);\n }\n\n async notifySupportOnce(input: { incidentKey: string; occurrence: OperationalIncidentOccurrenceV1 }): Promise<void> {\n if (this.supportWriter && this.incidentId) await this.supportWriter({\n tx: this.tx, incidentId: this.incidentId,\n incidentKey: input.incidentKey, occurrence: input.occurrence,\n });\n }\n}\n\nfunction retryable(error: unknown): boolean {\n const code = typeof error === \"object\" && error !== null && \"code\" in error\n ? (error as { code?: unknown }).code : undefined;\n return code === \"P2034\" || code === \"40001\" || code === \"40P01\";\n}\n\n/**\n * PostgreSQL/Prisma adapter. No global Prisma client or credentials are created.\n * All writes and an optional support projection occur within a single transaction.\n * Uses a per-key transaction advisory lock and a row lock for cross-process safety.\n */\nexport function createPrismaOperationalIncidentPersistence(options: {\n prisma: OperationalIncidentPrismaClient;\n supportWriter?: OperationalIncidentSupportWriter;\n maxAttempts?: number;\n}): IncidentPersistence {\n const maxAttempts = options.maxAttempts ?? 3;\n if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 10) {\n throw new Error(\"maxAttempts must be between 1 and 10\");\n }\n return {\n async transactionForKey<T>(key: string, fn: (tx: IncidentTransaction) => Promise<T>): Promise<T> {\n for (let attempt = 1; ; attempt++) {\n try {\n return await options.prisma.$transaction(async (tx) => {\n await rows(tx, `SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))::text AS acquired`, key);\n return fn(new PgIncidentTransaction(tx, options.supportWriter));\n }, { isolationLevel: \"Serializable\", timeout: 15000 });\n } catch (error) {\n if (attempt >= maxAttempts || !retryable(error)) throw error;\n }\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -2,9 +2,9 @@ import { z } from 'zod';
2
2
  import { PrimitiveType } from 'intl-messageformat';
3
3
 
4
4
  declare const LanguageSourceSchema: z.ZodEnum<{
5
+ shopify: "shopify";
5
6
  "customer-explicit": "customer-explicit";
6
7
  detected: "detected";
7
- shopify: "shopify";
8
8
  "merchant-default": "merchant-default";
9
9
  "platform-default": "platform-default";
10
10
  }>;
@@ -45,9 +45,9 @@ declare const TimeZoneIdSchema: z.ZodPipe<z.ZodString, z.ZodTransform<string, st
45
45
  declare const InternationalContextSchema: z.ZodObject<{
46
46
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
47
47
  languageSource: z.ZodNullable<z.ZodEnum<{
48
+ shopify: "shopify";
48
49
  "customer-explicit": "customer-explicit";
49
50
  detected: "detected";
50
- shopify: "shopify";
51
51
  "merchant-default": "merchant-default";
52
52
  "platform-default": "platform-default";
53
53
  }>>;
@@ -388,9 +388,9 @@ declare const ShopifyCheckoutCreatedEventV2Schema: z.ZodObject<{
388
388
  internationalContext: z.ZodOptional<z.ZodObject<{
389
389
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
390
390
  languageSource: z.ZodNullable<z.ZodEnum<{
391
+ shopify: "shopify";
391
392
  "customer-explicit": "customer-explicit";
392
393
  detected: "detected";
393
- shopify: "shopify";
394
394
  "merchant-default": "merchant-default";
395
395
  "platform-default": "platform-default";
396
396
  }>>;
@@ -425,9 +425,9 @@ declare const ShopifyCartActivityEventV2Schema: z.ZodObject<{
425
425
  internationalContext: z.ZodOptional<z.ZodObject<{
426
426
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
427
427
  languageSource: z.ZodNullable<z.ZodEnum<{
428
+ shopify: "shopify";
428
429
  "customer-explicit": "customer-explicit";
429
430
  detected: "detected";
430
- shopify: "shopify";
431
431
  "merchant-default": "merchant-default";
432
432
  "platform-default": "platform-default";
433
433
  }>>;
@@ -460,9 +460,9 @@ declare const ShopifyCheckoutUpdatedEventV2Schema: z.ZodObject<{
460
460
  internationalContext: z.ZodOptional<z.ZodObject<{
461
461
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
462
462
  languageSource: z.ZodNullable<z.ZodEnum<{
463
+ shopify: "shopify";
463
464
  "customer-explicit": "customer-explicit";
464
465
  detected: "detected";
465
- shopify: "shopify";
466
466
  "merchant-default": "merchant-default";
467
467
  "platform-default": "platform-default";
468
468
  }>>;
@@ -494,9 +494,9 @@ declare const ShopifyOrderCompletedEventV2Schema: z.ZodObject<{
494
494
  internationalContext: z.ZodOptional<z.ZodObject<{
495
495
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
496
496
  languageSource: z.ZodNullable<z.ZodEnum<{
497
+ shopify: "shopify";
497
498
  "customer-explicit": "customer-explicit";
498
499
  detected: "detected";
499
- shopify: "shopify";
500
500
  "merchant-default": "merchant-default";
501
501
  "platform-default": "platform-default";
502
502
  }>>;
@@ -531,9 +531,9 @@ declare const ShopifyRecoveryEventV2Schema: z.ZodDiscriminatedUnion<[z.ZodObject
531
531
  internationalContext: z.ZodOptional<z.ZodObject<{
532
532
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
533
533
  languageSource: z.ZodNullable<z.ZodEnum<{
534
+ shopify: "shopify";
534
535
  "customer-explicit": "customer-explicit";
535
536
  detected: "detected";
536
- shopify: "shopify";
537
537
  "merchant-default": "merchant-default";
538
538
  "platform-default": "platform-default";
539
539
  }>>;
@@ -564,9 +564,9 @@ declare const ShopifyRecoveryEventV2Schema: z.ZodDiscriminatedUnion<[z.ZodObject
564
564
  internationalContext: z.ZodOptional<z.ZodObject<{
565
565
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
566
566
  languageSource: z.ZodNullable<z.ZodEnum<{
567
+ shopify: "shopify";
567
568
  "customer-explicit": "customer-explicit";
568
569
  detected: "detected";
569
- shopify: "shopify";
570
570
  "merchant-default": "merchant-default";
571
571
  "platform-default": "platform-default";
572
572
  }>>;
@@ -599,9 +599,9 @@ declare const ShopifyRecoveryEventV2Schema: z.ZodDiscriminatedUnion<[z.ZodObject
599
599
  internationalContext: z.ZodOptional<z.ZodObject<{
600
600
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
601
601
  languageSource: z.ZodNullable<z.ZodEnum<{
602
+ shopify: "shopify";
602
603
  "customer-explicit": "customer-explicit";
603
604
  detected: "detected";
604
- shopify: "shopify";
605
605
  "merchant-default": "merchant-default";
606
606
  "platform-default": "platform-default";
607
607
  }>>;
@@ -631,9 +631,9 @@ declare const ShopifyRecoveryEventV2Schema: z.ZodDiscriminatedUnion<[z.ZodObject
631
631
  internationalContext: z.ZodOptional<z.ZodObject<{
632
632
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
633
633
  languageSource: z.ZodNullable<z.ZodEnum<{
634
+ shopify: "shopify";
634
635
  "customer-explicit": "customer-explicit";
635
636
  detected: "detected";
636
- shopify: "shopify";
637
637
  "merchant-default": "merchant-default";
638
638
  "platform-default": "platform-default";
639
639
  }>>;
@@ -676,7 +676,7 @@ declare function safeParseShopifyRecoveryEventV2(input: unknown): z.ZodSafeParse
676
676
  };
677
677
  internationalContext?: {
678
678
  languageTag: string | null;
679
- languageSource: "customer-explicit" | "detected" | "shopify" | "merchant-default" | "platform-default" | null;
679
+ languageSource: "shopify" | "customer-explicit" | "detected" | "merchant-default" | "platform-default" | null;
680
680
  countryCode: string | null;
681
681
  currencyCode: string | null;
682
682
  timeZone: string | null;
@@ -703,7 +703,7 @@ declare function safeParseShopifyRecoveryEventV2(input: unknown): z.ZodSafeParse
703
703
  };
704
704
  internationalContext?: {
705
705
  languageTag: string | null;
706
- languageSource: "customer-explicit" | "detected" | "shopify" | "merchant-default" | "platform-default" | null;
706
+ languageSource: "shopify" | "customer-explicit" | "detected" | "merchant-default" | "platform-default" | null;
707
707
  countryCode: string | null;
708
708
  currencyCode: string | null;
709
709
  timeZone: string | null;
@@ -729,7 +729,7 @@ declare function safeParseShopifyRecoveryEventV2(input: unknown): z.ZodSafeParse
729
729
  };
730
730
  internationalContext?: {
731
731
  languageTag: string | null;
732
- languageSource: "customer-explicit" | "detected" | "shopify" | "merchant-default" | "platform-default" | null;
732
+ languageSource: "shopify" | "customer-explicit" | "detected" | "merchant-default" | "platform-default" | null;
733
733
  countryCode: string | null;
734
734
  currencyCode: string | null;
735
735
  timeZone: string | null;
@@ -758,7 +758,7 @@ declare function safeParseShopifyRecoveryEventV2(input: unknown): z.ZodSafeParse
758
758
  };
759
759
  internationalContext?: {
760
760
  languageTag: string | null;
761
- languageSource: "customer-explicit" | "detected" | "shopify" | "merchant-default" | "platform-default" | null;
761
+ languageSource: "shopify" | "customer-explicit" | "detected" | "merchant-default" | "platform-default" | null;
762
762
  countryCode: string | null;
763
763
  currencyCode: string | null;
764
764
  timeZone: string | null;
@@ -802,9 +802,9 @@ declare const ShopifyCheckoutUpdatedEventV3Schema: z.ZodObject<{
802
802
  internationalContext: z.ZodOptional<z.ZodObject<{
803
803
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
804
804
  languageSource: z.ZodNullable<z.ZodEnum<{
805
+ shopify: "shopify";
805
806
  "customer-explicit": "customer-explicit";
806
807
  detected: "detected";
807
- shopify: "shopify";
808
808
  "merchant-default": "merchant-default";
809
809
  "platform-default": "platform-default";
810
810
  }>>;
@@ -840,9 +840,9 @@ declare const ShopifyRecoveryEventSchema: z.ZodUnion<readonly [z.ZodDiscriminate
840
840
  internationalContext: z.ZodOptional<z.ZodObject<{
841
841
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
842
842
  languageSource: z.ZodNullable<z.ZodEnum<{
843
+ shopify: "shopify";
843
844
  "customer-explicit": "customer-explicit";
844
845
  detected: "detected";
845
- shopify: "shopify";
846
846
  "merchant-default": "merchant-default";
847
847
  "platform-default": "platform-default";
848
848
  }>>;
@@ -873,9 +873,9 @@ declare const ShopifyRecoveryEventSchema: z.ZodUnion<readonly [z.ZodDiscriminate
873
873
  internationalContext: z.ZodOptional<z.ZodObject<{
874
874
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
875
875
  languageSource: z.ZodNullable<z.ZodEnum<{
876
+ shopify: "shopify";
876
877
  "customer-explicit": "customer-explicit";
877
878
  detected: "detected";
878
- shopify: "shopify";
879
879
  "merchant-default": "merchant-default";
880
880
  "platform-default": "platform-default";
881
881
  }>>;
@@ -908,9 +908,9 @@ declare const ShopifyRecoveryEventSchema: z.ZodUnion<readonly [z.ZodDiscriminate
908
908
  internationalContext: z.ZodOptional<z.ZodObject<{
909
909
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
910
910
  languageSource: z.ZodNullable<z.ZodEnum<{
911
+ shopify: "shopify";
911
912
  "customer-explicit": "customer-explicit";
912
913
  detected: "detected";
913
- shopify: "shopify";
914
914
  "merchant-default": "merchant-default";
915
915
  "platform-default": "platform-default";
916
916
  }>>;
@@ -940,9 +940,9 @@ declare const ShopifyRecoveryEventSchema: z.ZodUnion<readonly [z.ZodDiscriminate
940
940
  internationalContext: z.ZodOptional<z.ZodObject<{
941
941
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
942
942
  languageSource: z.ZodNullable<z.ZodEnum<{
943
+ shopify: "shopify";
943
944
  "customer-explicit": "customer-explicit";
944
945
  detected: "detected";
945
- shopify: "shopify";
946
946
  "merchant-default": "merchant-default";
947
947
  "platform-default": "platform-default";
948
948
  }>>;
@@ -975,9 +975,9 @@ declare const ShopifyRecoveryEventSchema: z.ZodUnion<readonly [z.ZodDiscriminate
975
975
  internationalContext: z.ZodOptional<z.ZodObject<{
976
976
  languageTag: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
977
977
  languageSource: z.ZodNullable<z.ZodEnum<{
978
+ shopify: "shopify";
978
979
  "customer-explicit": "customer-explicit";
979
980
  detected: "detected";
980
- shopify: "shopify";
981
981
  "merchant-default": "merchant-default";
982
982
  "platform-default": "platform-default";
983
983
  }>>;
@@ -1019,7 +1019,7 @@ declare function safeParseShopifyRecoveryEvent(input: unknown): z.ZodSafeParseRe
1019
1019
  };
1020
1020
  internationalContext?: {
1021
1021
  languageTag: string | null;
1022
- languageSource: "customer-explicit" | "detected" | "shopify" | "merchant-default" | "platform-default" | null;
1022
+ languageSource: "shopify" | "customer-explicit" | "detected" | "merchant-default" | "platform-default" | null;
1023
1023
  countryCode: string | null;
1024
1024
  currencyCode: string | null;
1025
1025
  timeZone: string | null;
@@ -1046,7 +1046,7 @@ declare function safeParseShopifyRecoveryEvent(input: unknown): z.ZodSafeParseRe
1046
1046
  };
1047
1047
  internationalContext?: {
1048
1048
  languageTag: string | null;
1049
- languageSource: "customer-explicit" | "detected" | "shopify" | "merchant-default" | "platform-default" | null;
1049
+ languageSource: "shopify" | "customer-explicit" | "detected" | "merchant-default" | "platform-default" | null;
1050
1050
  countryCode: string | null;
1051
1051
  currencyCode: string | null;
1052
1052
  timeZone: string | null;
@@ -1072,7 +1072,7 @@ declare function safeParseShopifyRecoveryEvent(input: unknown): z.ZodSafeParseRe
1072
1072
  };
1073
1073
  internationalContext?: {
1074
1074
  languageTag: string | null;
1075
- languageSource: "customer-explicit" | "detected" | "shopify" | "merchant-default" | "platform-default" | null;
1075
+ languageSource: "shopify" | "customer-explicit" | "detected" | "merchant-default" | "platform-default" | null;
1076
1076
  countryCode: string | null;
1077
1077
  currencyCode: string | null;
1078
1078
  timeZone: string | null;
@@ -1101,7 +1101,7 @@ declare function safeParseShopifyRecoveryEvent(input: unknown): z.ZodSafeParseRe
1101
1101
  };
1102
1102
  internationalContext?: {
1103
1103
  languageTag: string | null;
1104
- languageSource: "customer-explicit" | "detected" | "shopify" | "merchant-default" | "platform-default" | null;
1104
+ languageSource: "shopify" | "customer-explicit" | "detected" | "merchant-default" | "platform-default" | null;
1105
1105
  countryCode: string | null;
1106
1106
  currencyCode: string | null;
1107
1107
  timeZone: string | null;
@@ -1130,7 +1130,7 @@ declare function safeParseShopifyRecoveryEvent(input: unknown): z.ZodSafeParseRe
1130
1130
  };
1131
1131
  internationalContext?: {
1132
1132
  languageTag: string | null;
1133
- languageSource: "customer-explicit" | "detected" | "shopify" | "merchant-default" | "platform-default" | null;
1133
+ languageSource: "shopify" | "customer-explicit" | "detected" | "merchant-default" | "platform-default" | null;
1134
1134
  countryCode: string | null;
1135
1135
  currencyCode: string | null;
1136
1136
  timeZone: string | null;
@@ -4,7 +4,7 @@ import { S as StructuredLogger } from './types-B32wDMLO.js';
4
4
  /** Finite, non-customer-facing failure taxonomy for Commerce hosts and logs. */
5
5
  declare const RUNNER_DIAGNOSTIC_STAGES: readonly ["preflight.validate", "preflight.contract", "runtime.cancellation", "runtime.deadline", "runtime.operation", "tools.resolve", "model.invoke", "model.validate", "model.route", "tool.authorize", "tool.validate", "tool.execute", "tool.result", "evidence.validate", "final.validate", "graph.execute"];
6
6
  type RunnerDiagnosticStage = typeof RUNNER_DIAGNOSTIC_STAGES[number];
7
- declare const RUNNER_DIAGNOSTIC_REASONS: readonly ["CANCELLED_BY_CALLER", "TURN_DEADLINE_EXCEEDED", "OPERATION_TIMEOUT", "UPSTREAM_OPERATION_FAILED", "UNEXPECTED_EXCEPTION", "INPUT_TOO_LARGE", "TURN_INVALID", "GRANT_INVALID", "MANIFEST_INVALID", "GRANT_MISMATCH", "RUNNER_VERSION_INCOMPATIBLE", "RESPONSE_CONTRACT_INVALID", "LANGUAGE_INVALID", "BUDGET_INVALID", "CONTEXT_INVALID", "TOOL_REGISTRATION_INVALID", "MODEL_STEP_BUDGET_EXHAUSTED", "MODEL_INVOCATION_FAILED", "MODEL_STEP_NOT_OBJECT", "MODEL_STEP_CALLS_MISSING", "MODEL_STEP_CALLS_NOT_ARRAY", "MODEL_OUTPUT_TOKENS_MISSING", "MODEL_OUTPUT_TOKENS_NOT_SAFE_INTEGER", "MODEL_OUTPUT_TOKENS_NEGATIVE", "MODEL_OUTPUT_TOKENS_OVER_BUDGET", "MODEL_TOOL_CALL_LIMIT_EXCEEDED", "MODEL_TOOL_CALL_NULL", "MODEL_TOOL_CALL_NOT_OBJECT", "MODEL_TOOL_CALL_NAME_MISSING", "MODEL_TOOL_CALL_NAME_NOT_STRING", "MODEL_TOOL_CALL_NAME_EMPTY", "MODEL_TOOL_CALL_NAME_TOO_LONG", "MODEL_TOOL_CALL_ARGUMENTS_MISSING", "MODEL_TOOL_CALL_ARGUMENTS_NULL", "MODEL_TOOL_CALL_ARGUMENTS_NOT_OBJECT", "MODEL_STEP_TOO_LARGE", "MODEL_STEP_NOT_SERIALIZABLE", "MODEL_STEP_VALIDATION_FAILED", "MODEL_CALL_ROUTE_INVALID", "MODEL_RESULT_MISSING", "REQUEST_INVALID", "MODEL_REQUEST_HISTORY_INVALID", "MODEL_REQUEST_TOOL_RESULT_INVALID", "MODEL_CONFIGURATION_INVALID", "MODEL_CREDENTIAL_INVALID", "MODEL_ADAPTER_INITIALIZATION_FAILED", "MODEL_REQUEST_CANCELLED", "TOOL_BIND_FAILED", "PROVIDER_AUTH_FAILED", "PROVIDER_RATE_LIMITED", "PROVIDER_HTTP_ERROR", "PROVIDER_REQUEST_FAILED", "PROVIDER_RESPONSE_INVALID", "PROVIDER_RESPONSE_USAGE_INVALID", "PROVIDER_RESPONSE_TOOL_CALL_INVALID", "TOOL_DESCRIPTOR_MISMATCH", "TOOL_AUTHORIZATION_FAILED", "TOOL_INPUT_INVALID", "TOOL_CALL_BUDGET_EXHAUSTED", "TOOL_EXECUTION_FAILED", "TOOL_RESULT_TOO_LARGE", "TOOL_RESULT_INVALID", "TOOL_RESULT_MISSING", "TOOL_STALE_TURN", "EVIDENCE_INVALID", "FINAL_SCHEMA_INVALID", "FINAL_LANGUAGE_MISMATCH", "FINAL_REFERRAL_REQUIRED", "FINAL_EVIDENCE_INVALID", "FINAL_EVIDENCE_EXPIRED", "EVIDENCE_RESERVATION_EXCEEDED"];
7
+ declare const RUNNER_DIAGNOSTIC_REASONS: readonly ["CANCELLED_BY_CALLER", "TURN_DEADLINE_EXCEEDED", "OPERATION_TIMEOUT", "UPSTREAM_OPERATION_FAILED", "UNEXPECTED_EXCEPTION", "INPUT_TOO_LARGE", "TURN_INVALID", "GRANT_INVALID", "MANIFEST_INVALID", "GRANT_MISMATCH", "RUNNER_VERSION_INCOMPATIBLE", "RESPONSE_CONTRACT_INVALID", "LANGUAGE_INVALID", "BUDGET_INVALID", "CONTEXT_INVALID", "TOOL_REGISTRATION_INVALID", "MODEL_STEP_BUDGET_EXHAUSTED", "MODEL_INVOCATION_FAILED", "MODEL_STEP_NOT_OBJECT", "MODEL_STEP_CALLS_MISSING", "MODEL_STEP_CALLS_NOT_ARRAY", "MODEL_OUTPUT_TOKENS_MISSING", "MODEL_OUTPUT_TOKENS_NOT_SAFE_INTEGER", "MODEL_OUTPUT_TOKENS_NEGATIVE", "MODEL_OUTPUT_TOKENS_OVER_BUDGET", "MODEL_TOOL_CALL_LIMIT_EXCEEDED", "MODEL_TOOL_CALL_NULL", "MODEL_TOOL_CALL_NOT_OBJECT", "MODEL_TOOL_CALL_NAME_MISSING", "MODEL_TOOL_CALL_NAME_NOT_STRING", "MODEL_TOOL_CALL_NAME_EMPTY", "MODEL_TOOL_CALL_NAME_TOO_LONG", "MODEL_TOOL_CALL_ARGUMENTS_MISSING", "MODEL_TOOL_CALL_ARGUMENTS_NULL", "MODEL_TOOL_CALL_ARGUMENTS_NOT_OBJECT", "MODEL_TEXT_NOT_STRING", "MODEL_TEXT_TOO_LONG", "MODEL_STEP_TOO_LARGE", "MODEL_STEP_NOT_SERIALIZABLE", "MODEL_STEP_VALIDATION_FAILED", "MODEL_CALL_ROUTE_INVALID", "MODEL_RESULT_MISSING", "REQUEST_INVALID", "MODEL_REQUEST_HISTORY_INVALID", "MODEL_REQUEST_TOOL_RESULT_INVALID", "MODEL_CONFIGURATION_INVALID", "MODEL_CREDENTIAL_INVALID", "MODEL_ADAPTER_INITIALIZATION_FAILED", "MODEL_REQUEST_CANCELLED", "TOOL_BIND_FAILED", "PROVIDER_AUTH_FAILED", "PROVIDER_RATE_LIMITED", "PROVIDER_HTTP_ERROR", "PROVIDER_REQUEST_FAILED", "PROVIDER_RESPONSE_INVALID", "PROVIDER_RESPONSE_USAGE_INVALID", "PROVIDER_RESPONSE_TOOL_CALL_INVALID", "TOOL_DESCRIPTOR_MISMATCH", "TOOL_AUTHORIZATION_FAILED", "TOOL_INPUT_INVALID", "TOOL_CALL_BUDGET_EXHAUSTED", "TOOL_EXECUTION_FAILED", "TOOL_RESULT_TOO_LARGE", "TOOL_RESULT_INVALID", "TOOL_RESULT_MISSING", "TOOL_STALE_TURN", "EVIDENCE_INVALID", "FINAL_SCHEMA_INVALID", "FINAL_LANGUAGE_MISMATCH", "FINAL_REFERRAL_REQUIRED", "FINAL_EVIDENCE_INVALID", "FINAL_EVIDENCE_EXPIRED", "EVIDENCE_RESERVATION_EXCEEDED"];
8
8
  type RunnerDiagnosticReason = typeof RUNNER_DIAGNOSTIC_REASONS[number];
9
9
  /** Recognised transport failures, never arbitrary OS/provider error codes. */
10
10
  declare const RUNNER_TRANSPORT_CODES: readonly ["ENOTFOUND", "EAI_AGAIN", "ECONNREFUSED", "ECONNRESET", "ETIMEDOUT", "ENETUNREACH", "EHOSTUNREACH", "EPIPE", "UND_ERR_CONNECT_TIMEOUT", "UND_ERR_HEADERS_TIMEOUT", "UND_ERR_SOCKET", "CERT_HAS_EXPIRED", "UNABLE_TO_VERIFY_LEAF_SIGNATURE", "DEPTH_ZERO_SELF_SIGNED_CERT", "ERR_TLS_CERT_ALTNAME_INVALID"];
@@ -38,9 +38,11 @@ type ModelCall = {
38
38
  name: string;
39
39
  arguments: unknown;
40
40
  };
41
+ /** `text` is optional so existing structured-tool invokers remain compatible. */
41
42
  type ModelStep = {
42
43
  calls: ModelCall[];
43
44
  outputTokens: number;
45
+ text?: string;
44
46
  };
45
47
  type ModelRequest = {
46
48
  instructions: readonly string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modainteract/moda-interact-shared",
3
- "version": "1.5.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -95,6 +95,10 @@
95
95
  "./merchant-knowledge/node": {
96
96
  "types": "./dist/merchant-knowledge/node.d.ts",
97
97
  "import": "./dist/merchant-knowledge/node.js"
98
+ },
99
+ "./incidents": {
100
+ "types": "./dist/incidents/index.d.ts",
101
+ "import": "./dist/incidents/index.js"
98
102
  }
99
103
  },
100
104
  "files": [
@@ -114,7 +118,8 @@
114
118
  "prepack": "npm run build",
115
119
  "validate:commerce-entrypoints": "node scripts/validate-commerce-entrypoints.mjs",
116
120
  "validate:arch023-shared-entrypoints": "npm run build && node scripts/validate-arch023-shared-entrypoints.mjs",
117
- "validate:arch024-model-entrypoints": "npm run build && node scripts/validate-arch024-model-entrypoints.mjs"
121
+ "validate:arch024-model-entrypoints": "npm run build && node scripts/validate-arch024-model-entrypoints.mjs",
122
+ "test:arch033-incidents:postgres": "node scripts/test-arch033-incidents-postgres.mjs"
118
123
  },
119
124
  "dependencies": {
120
125
  "@opentelemetry/api": "^1.9.0",