@happyvertical/smrt-sales 0.40.0 → 0.40.2

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.
Files changed (54) hide show
  1. package/AGENTS.md +14 -6
  2. package/dist/agreements/collections/AgreementExecutionCollection.d.ts +106 -0
  3. package/dist/agreements/collections/AgreementExecutionCollection.d.ts.map +1 -0
  4. package/dist/agreements/collections/AgreementExecutionEventCollection.d.ts +16 -0
  5. package/dist/agreements/collections/AgreementExecutionEventCollection.d.ts.map +1 -0
  6. package/dist/agreements/collections/ExecutedAgreementCollection.d.ts +17 -0
  7. package/dist/agreements/collections/ExecutedAgreementCollection.d.ts.map +1 -0
  8. package/dist/agreements/index.d.ts +18 -0
  9. package/dist/agreements/index.d.ts.map +1 -0
  10. package/dist/agreements/models/AgreementExecution.d.ts +66 -0
  11. package/dist/agreements/models/AgreementExecution.d.ts.map +1 -0
  12. package/dist/agreements/models/AgreementExecutionEvent.d.ts +25 -0
  13. package/dist/agreements/models/AgreementExecutionEvent.d.ts.map +1 -0
  14. package/dist/agreements/models/ExecutedAgreement.d.ts +35 -0
  15. package/dist/agreements/models/ExecutedAgreement.d.ts.map +1 -0
  16. package/dist/agreements/services/AgreementExecutionService.d.ts +85 -0
  17. package/dist/agreements/services/AgreementExecutionService.d.ts.map +1 -0
  18. package/dist/agreements/types.d.ts +140 -0
  19. package/dist/agreements/types.d.ts.map +1 -0
  20. package/dist/agreements.d.ts +2 -0
  21. package/dist/agreements.d.ts.map +1 -0
  22. package/dist/agreements.js +3 -0
  23. package/dist/chunks/__smrt-register__-Cwn1Fb4I.js +6 -0
  24. package/dist/chunks/{__smrt-register__-DGR1u9K2.js.map → __smrt-register__-Cwn1Fb4I.js.map} +1 -1
  25. package/dist/chunks/agreements-DbzW1Pcq.js +1640 -0
  26. package/dist/chunks/agreements-DbzW1Pcq.js.map +1 -0
  27. package/dist/chunks/{referrals-CzkrPeKv.js → referrals-cYYg4YZw.js} +163 -40
  28. package/dist/chunks/referrals-cYYg4YZw.js.map +1 -0
  29. package/dist/commissions.js +1 -1
  30. package/dist/crm.js +1 -1
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +4 -3
  34. package/dist/manifest.json +1853 -99
  35. package/dist/referrals/collections/ReferralAgreementCollection.d.ts +0 -4
  36. package/dist/referrals/collections/ReferralAgreementCollection.d.ts.map +1 -1
  37. package/dist/referrals/index.d.ts +1 -0
  38. package/dist/referrals/index.d.ts.map +1 -1
  39. package/dist/referrals/models/ReferralAgreement.d.ts +8 -19
  40. package/dist/referrals/models/ReferralAgreement.d.ts.map +1 -1
  41. package/dist/referrals/services/ReferralAgreementExecutionService.d.ts +55 -0
  42. package/dist/referrals/services/ReferralAgreementExecutionService.d.ts.map +1 -0
  43. package/dist/referrals/types.d.ts +2 -4
  44. package/dist/referrals/types.d.ts.map +1 -1
  45. package/dist/referrals.js +3 -3
  46. package/dist/smrt-knowledge.json +780 -33
  47. package/dist/svelte/components/ExecutedAgreementsList.svelte +36 -10
  48. package/dist/svelte/components/ExecutedAgreementsList.svelte.d.ts +2 -0
  49. package/dist/svelte/components/ExecutedAgreementsList.svelte.d.ts.map +1 -1
  50. package/dist/svelte/types.d.ts +7 -3
  51. package/dist/svelte/types.d.ts.map +1 -1
  52. package/package.json +13 -7
  53. package/dist/chunks/__smrt-register__-DGR1u9K2.js +0 -6
  54. package/dist/chunks/referrals-CzkrPeKv.js.map +0 -1
@@ -0,0 +1,1640 @@
1
+ import { SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, smrt } from "@happyvertical/smrt-core";
2
+ import { TenantScoped, requireTenantId, tenantId } from "@happyvertical/smrt-tenancy";
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ //#region src/agreements/types.ts
5
+ var AGREEMENT_EXECUTION_STATUSES = [
6
+ "prepared",
7
+ "sent",
8
+ "delivered",
9
+ "viewed",
10
+ "partially_signed",
11
+ "completed",
12
+ "declined",
13
+ "cancelled",
14
+ "expired",
15
+ "failed"
16
+ ];
17
+ function sanitizeSignerIntent(signers) {
18
+ return signers.map((signer) => ({
19
+ name: signer.name,
20
+ email: signer.email,
21
+ ...signer.role ? { role: signer.role } : {},
22
+ ...signer.order !== void 0 ? { order: signer.order } : {},
23
+ authenticationMethod: signer.authentication?.method ?? "none"
24
+ }));
25
+ }
26
+ function sanitizeSignerEvidence(signers) {
27
+ return signers.map((signer) => ({
28
+ ...signer.id ? { id: signer.id } : {},
29
+ name: signer.name,
30
+ email: signer.email,
31
+ ...signer.role ? { role: signer.role } : {},
32
+ ...signer.order !== void 0 ? { order: signer.order } : {},
33
+ status: signer.status,
34
+ ...signer.authenticationMethod ? { authenticationMethod: signer.authenticationMethod } : {},
35
+ ...signer.viewed !== void 0 ? { viewed: signer.viewed } : {},
36
+ ...signer.deliveryFailed !== void 0 ? { deliveryFailed: signer.deliveryFailed } : {}
37
+ }));
38
+ }
39
+ function coerceAgreementDate(value) {
40
+ if (value == null) return null;
41
+ if (value instanceof Date) return value;
42
+ if (typeof value === "string" || typeof value === "number") {
43
+ const date = new Date(value);
44
+ return Number.isNaN(date.getTime()) ? null : date;
45
+ }
46
+ return null;
47
+ }
48
+ //#endregion
49
+ //#region src/agreements/models/AgreementExecution.ts
50
+ var __defProp$2 = Object.defineProperty;
51
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
52
+ var __decorateClass$2 = (decorators, target, key, kind) => {
53
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
54
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
55
+ if (kind && result) __defProp$2(target, key, result);
56
+ return result;
57
+ };
58
+ var persistedExecutionIdentity = /* @__PURE__ */ new WeakMap();
59
+ var AgreementExecution = class extends SmrtObject {
60
+ tenantId = "";
61
+ provider = "";
62
+ /** Non-secret provider account/region reference used for operations. */
63
+ providerAccountRef = "";
64
+ /** SDK/SMRT secret-store reference only; never a credential value. */
65
+ credentialRef = "";
66
+ idempotencyKey = "";
67
+ sourceKind = "";
68
+ sourceId = "";
69
+ sourceVersion = 1;
70
+ sourceAssetId = "";
71
+ sourceSha256 = "";
72
+ sourceSizeBytes = 0;
73
+ requestIntentSha256 = "";
74
+ title = "";
75
+ signerIntent = "[]";
76
+ providerRequestId = "";
77
+ providerRequestKey = null;
78
+ status = "prepared";
79
+ expiresAt = null;
80
+ cancellationReason = "";
81
+ lastProviderEventAt = null;
82
+ lastReconciledAt = null;
83
+ completedAt = null;
84
+ effectiveFrom = null;
85
+ effectiveTo = null;
86
+ /**
87
+ * Opaque prior-evidence provenance copied into the immutable result.
88
+ * The authoritative relationship is on ExecutedAgreement; making this
89
+ * mutable orchestration hint a foreign key would create a registry cycle.
90
+ */
91
+ supersedesExecutedAgreementId = "";
92
+ signedDocumentAssetId = "";
93
+ signedDocumentSha256 = "";
94
+ signedDocumentSizeBytes = 0;
95
+ signedDocumentMediaType = "";
96
+ signedDocumentFilename = "";
97
+ auditTrailAssetId = "";
98
+ auditTrailSha256 = "";
99
+ auditTrailSizeBytes = 0;
100
+ auditTrailMediaType = "";
101
+ auditTrailFilename = "";
102
+ attemptCount = 0;
103
+ /** Operation id holding the current provider-create lease. */
104
+ createLeaseId = "";
105
+ /** A crashed create attempt stops fencing recovery after this instant. */
106
+ createLeaseExpiresAt = null;
107
+ lastError = "";
108
+ metadata = "{}";
109
+ constructor(options = {}) {
110
+ super(options);
111
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
112
+ if (options.provider !== void 0) this.provider = options.provider;
113
+ if (options.providerAccountRef !== void 0) this.providerAccountRef = options.providerAccountRef;
114
+ if (options.credentialRef !== void 0) this.credentialRef = options.credentialRef;
115
+ if (options.idempotencyKey !== void 0) this.idempotencyKey = options.idempotencyKey;
116
+ if (options.sourceKind !== void 0) this.sourceKind = options.sourceKind;
117
+ if (options.sourceId !== void 0) this.sourceId = options.sourceId;
118
+ if (options.sourceVersion !== void 0) this.sourceVersion = options.sourceVersion;
119
+ if (options.sourceAssetId !== void 0) this.sourceAssetId = options.sourceAssetId;
120
+ if (options.sourceSha256 !== void 0) this.sourceSha256 = options.sourceSha256;
121
+ if (options.sourceSizeBytes !== void 0) this.sourceSizeBytes = options.sourceSizeBytes;
122
+ if (options.requestIntentSha256 !== void 0) this.requestIntentSha256 = options.requestIntentSha256;
123
+ if (options.title !== void 0) this.title = options.title;
124
+ if (options.signerIntent !== void 0) this.signerIntent = options.signerIntent;
125
+ if (options.providerRequestId !== void 0) this.providerRequestId = options.providerRequestId;
126
+ if (options.providerRequestKey !== void 0) this.providerRequestKey = options.providerRequestKey;
127
+ this.syncProviderRequestKey();
128
+ if (options.status !== void 0) this.status = options.status;
129
+ if (options.expiresAt !== void 0) this.expiresAt = coerceAgreementDate(options.expiresAt);
130
+ if (options.cancellationReason !== void 0) this.cancellationReason = options.cancellationReason;
131
+ if (options.lastProviderEventAt !== void 0) this.lastProviderEventAt = coerceAgreementDate(options.lastProviderEventAt);
132
+ if (options.lastReconciledAt !== void 0) this.lastReconciledAt = coerceAgreementDate(options.lastReconciledAt);
133
+ if (options.completedAt !== void 0) this.completedAt = coerceAgreementDate(options.completedAt);
134
+ if (options.effectiveFrom !== void 0) this.effectiveFrom = coerceAgreementDate(options.effectiveFrom);
135
+ if (options.effectiveTo !== void 0) this.effectiveTo = coerceAgreementDate(options.effectiveTo);
136
+ if (options.supersedesExecutedAgreementId !== void 0) this.supersedesExecutedAgreementId = options.supersedesExecutedAgreementId;
137
+ if (options.signedDocumentAssetId !== void 0) this.signedDocumentAssetId = options.signedDocumentAssetId;
138
+ if (options.signedDocumentSha256 !== void 0) this.signedDocumentSha256 = options.signedDocumentSha256;
139
+ if (options.signedDocumentSizeBytes !== void 0) this.signedDocumentSizeBytes = options.signedDocumentSizeBytes;
140
+ if (options.signedDocumentMediaType !== void 0) this.signedDocumentMediaType = options.signedDocumentMediaType;
141
+ if (options.signedDocumentFilename !== void 0) this.signedDocumentFilename = options.signedDocumentFilename;
142
+ if (options.auditTrailAssetId !== void 0) this.auditTrailAssetId = options.auditTrailAssetId;
143
+ if (options.auditTrailSha256 !== void 0) this.auditTrailSha256 = options.auditTrailSha256;
144
+ if (options.auditTrailSizeBytes !== void 0) this.auditTrailSizeBytes = options.auditTrailSizeBytes;
145
+ if (options.auditTrailMediaType !== void 0) this.auditTrailMediaType = options.auditTrailMediaType;
146
+ if (options.auditTrailFilename !== void 0) this.auditTrailFilename = options.auditTrailFilename;
147
+ if (options.attemptCount !== void 0) this.attemptCount = options.attemptCount;
148
+ if (options.createLeaseId !== void 0) this.createLeaseId = options.createLeaseId;
149
+ if (options.createLeaseExpiresAt !== void 0) this.createLeaseExpiresAt = coerceAgreementDate(options.createLeaseExpiresAt);
150
+ if (options.lastError !== void 0) this.lastError = options.lastError;
151
+ if (options.metadata !== void 0) this.metadata = options.metadata;
152
+ }
153
+ async initialize() {
154
+ await super.initialize();
155
+ this.expiresAt = coerceAgreementDate(this.expiresAt);
156
+ this.lastProviderEventAt = coerceAgreementDate(this.lastProviderEventAt);
157
+ this.lastReconciledAt = coerceAgreementDate(this.lastReconciledAt);
158
+ this.completedAt = coerceAgreementDate(this.completedAt);
159
+ this.effectiveFrom = coerceAgreementDate(this.effectiveFrom);
160
+ this.effectiveTo = coerceAgreementDate(this.effectiveTo);
161
+ this.createLeaseExpiresAt = coerceAgreementDate(this.createLeaseExpiresAt);
162
+ if (this.isPersisted) persistedExecutionIdentity.set(this, this.captureIdentity());
163
+ return this;
164
+ }
165
+ async save() {
166
+ const captured = persistedExecutionIdentity.get(this);
167
+ if (captured) this.assertBoundValueUnchanged("provider request", captured.providerRequestId, this.providerRequestId);
168
+ this.syncProviderRequestKey();
169
+ if (captured) {
170
+ if (captured.intent !== this.serializeIntent()) throw new Error(`AgreementExecution ${this.id ?? "<new>"}: execution identity is immutable`);
171
+ this.assertBoundValueUnchanged("provider request key", captured.providerRequestKey ?? "", this.providerRequestKey ?? "");
172
+ this.assertBoundValueUnchanged("signed document evidence", captured.signedDocumentEvidence, this.serializeSignedDocumentEvidence());
173
+ this.assertBoundValueUnchanged("audit trail evidence", captured.auditTrailEvidence, this.serializeAuditTrailEvidence());
174
+ }
175
+ const saved = await super.save();
176
+ persistedExecutionIdentity.set(this, this.captureIdentity());
177
+ return saved;
178
+ }
179
+ getSignerIntent() {
180
+ try {
181
+ const value = JSON.parse(this.signerIntent);
182
+ return Array.isArray(value) ? value : [];
183
+ } catch {
184
+ return [];
185
+ }
186
+ }
187
+ captureIdentity() {
188
+ return {
189
+ intent: this.serializeIntent(),
190
+ providerRequestId: this.providerRequestId,
191
+ providerRequestKey: this.providerRequestKey,
192
+ signedDocumentEvidence: this.serializeSignedDocumentEvidence(),
193
+ auditTrailEvidence: this.serializeAuditTrailEvidence()
194
+ };
195
+ }
196
+ serializeIntent() {
197
+ return JSON.stringify({
198
+ tenantId: this.tenantId,
199
+ provider: this.provider,
200
+ providerAccountRef: this.providerAccountRef,
201
+ credentialRef: this.credentialRef,
202
+ idempotencyKey: this.idempotencyKey,
203
+ sourceKind: this.sourceKind,
204
+ sourceId: this.sourceId,
205
+ sourceVersion: this.sourceVersion,
206
+ sourceAssetId: this.sourceAssetId,
207
+ sourceSha256: this.sourceSha256,
208
+ sourceSizeBytes: this.sourceSizeBytes,
209
+ requestIntentSha256: this.requestIntentSha256,
210
+ title: this.title,
211
+ signerIntent: this.signerIntent,
212
+ effectiveFrom: this.effectiveFrom?.toISOString() ?? null,
213
+ effectiveTo: this.effectiveTo?.toISOString() ?? null,
214
+ supersedesExecutedAgreementId: this.supersedesExecutedAgreementId,
215
+ metadata: this.metadata
216
+ });
217
+ }
218
+ serializeSignedDocumentEvidence() {
219
+ if (!this.signedDocumentAssetId) return "";
220
+ return JSON.stringify({
221
+ assetId: this.signedDocumentAssetId,
222
+ sha256: this.signedDocumentSha256,
223
+ sizeBytes: this.signedDocumentSizeBytes,
224
+ mediaType: this.signedDocumentMediaType,
225
+ filename: this.signedDocumentFilename
226
+ });
227
+ }
228
+ serializeAuditTrailEvidence() {
229
+ if (!this.auditTrailAssetId) return "";
230
+ return JSON.stringify({
231
+ assetId: this.auditTrailAssetId,
232
+ sha256: this.auditTrailSha256,
233
+ sizeBytes: this.auditTrailSizeBytes,
234
+ mediaType: this.auditTrailMediaType,
235
+ filename: this.auditTrailFilename
236
+ });
237
+ }
238
+ assertBoundValueUnchanged(label, captured, current) {
239
+ if (captured && captured !== current) throw new Error(`AgreementExecution ${this.id ?? "<new>"}: ${label} is immutable once bound`);
240
+ }
241
+ syncProviderRequestKey() {
242
+ if (!this.providerRequestId) {
243
+ this.providerRequestKey = null;
244
+ return;
245
+ }
246
+ if (!this.tenantId || !this.provider) throw new Error("AgreementExecution provider request binding requires tenant and provider");
247
+ const expected = `${this.tenantId}:${this.provider}:${this.providerRequestId}`;
248
+ if (this.providerRequestKey && this.providerRequestKey !== expected) throw new Error(`AgreementExecution ${this.id ?? "<new>"}: provider request key does not match its binding`);
249
+ this.providerRequestKey = expected;
250
+ }
251
+ };
252
+ __decorateClass$2([tenantId()], AgreementExecution.prototype, "tenantId", 2);
253
+ __decorateClass$2([field({ required: true })], AgreementExecution.prototype, "provider", 2);
254
+ __decorateClass$2([field({ required: true })], AgreementExecution.prototype, "idempotencyKey", 2);
255
+ __decorateClass$2([field({ required: true })], AgreementExecution.prototype, "sourceKind", 2);
256
+ __decorateClass$2([field({ required: true })], AgreementExecution.prototype, "sourceId", 2);
257
+ __decorateClass$2([crossPackageRef("@happyvertical/smrt-assets:Asset")], AgreementExecution.prototype, "sourceAssetId", 2);
258
+ __decorateClass$2([field({
259
+ type: "text",
260
+ nullable: true,
261
+ unique: true
262
+ })], AgreementExecution.prototype, "providerRequestKey", 2);
263
+ __decorateClass$2([crossPackageRef("@happyvertical/smrt-assets:Asset")], AgreementExecution.prototype, "signedDocumentAssetId", 2);
264
+ __decorateClass$2([crossPackageRef("@happyvertical/smrt-assets:Asset")], AgreementExecution.prototype, "auditTrailAssetId", 2);
265
+ AgreementExecution = __decorateClass$2([TenantScoped({ mode: "required" }), smrt({
266
+ conflictColumns: ["tenant_id", "idempotency_key"],
267
+ api: false,
268
+ mcp: false,
269
+ cli: false
270
+ })], AgreementExecution);
271
+ //#endregion
272
+ //#region src/agreements/collections/AgreementExecutionCollection.ts
273
+ var AgreementExecutionCollection = class extends SmrtCollection {
274
+ static _itemClass = AgreementExecution;
275
+ async findByIdempotencyKey(idempotencyKey) {
276
+ return (await this.list({
277
+ where: { idempotencyKey },
278
+ limit: 1
279
+ }))[0] ?? null;
280
+ }
281
+ async findByProviderRequest(provider, providerRequestId) {
282
+ return (await this.list({
283
+ where: {
284
+ provider,
285
+ providerRequestId
286
+ },
287
+ limit: 1
288
+ }))[0] ?? null;
289
+ }
290
+ /**
291
+ * Atomically claim an expired or cleared provider-create lease.
292
+ *
293
+ * The explicit tenant predicate and expected-state predicates make the raw
294
+ * compare-and-swap fail closed across workers. `RETURNING id` is used
295
+ * because not every database adapter reports affected-row counts reliably.
296
+ */
297
+ async claimCreateAttempt(input) {
298
+ if (requireTenantId() !== input.tenantId) throw new Error("AgreementExecution create-lease tenant mismatch");
299
+ const claimed = (await this._db.query(`UPDATE ${this.tableName}
300
+ SET attempt_count = attempt_count + 1,
301
+ status = ?,
302
+ last_error = '',
303
+ create_lease_id = ?,
304
+ create_lease_expires_at = ?
305
+ WHERE id = ?
306
+ AND tenant_id = ?
307
+ AND attempt_count = ?
308
+ AND status = ?
309
+ AND COALESCE(last_error, '') = ?
310
+ AND COALESCE(create_lease_id, '') = ?
311
+ AND (provider_request_id IS NULL OR provider_request_id = '')
312
+ AND (create_lease_expires_at IS NULL OR create_lease_expires_at <= ?)
313
+ RETURNING id`, "prepared", input.operationId, input.leaseExpiresAt.toISOString(), input.executionId, input.tenantId, input.expectedAttemptCount, input.expectedStatus, input.expectedLastError, input.expectedLeaseId, input.now.toISOString())).rows[0];
314
+ if (typeof claimed?.id !== "string") return null;
315
+ return await this.get({ id: claimed.id });
316
+ }
317
+ /**
318
+ * Persist a provider-create result only while the caller still owns its
319
+ * lease. This prevents a slow, expired attempt from overwriting a newer
320
+ * recovery attempt after returning from the provider.
321
+ */
322
+ async completeCreateAttempt(input) {
323
+ this.assertCreateAttemptTenant(input.tenantId);
324
+ const providerRequestKey = `${input.tenantId}:${input.provider}:${input.providerRequestId}`;
325
+ const result = await this._db.query(`UPDATE ${this.tableName}
326
+ SET provider_request_id = ?,
327
+ provider_request_key = ?,
328
+ status = ?,
329
+ expires_at = ?,
330
+ last_error = '',
331
+ create_lease_id = '',
332
+ create_lease_expires_at = NULL
333
+ WHERE id = ?
334
+ AND tenant_id = ?
335
+ AND provider = ?
336
+ AND COALESCE(create_lease_id, '') = ?
337
+ AND (provider_request_id IS NULL OR provider_request_id = '')
338
+ RETURNING id`, input.providerRequestId, providerRequestKey, input.status, input.expiresAt?.toISOString() ?? null, input.executionId, input.tenantId, input.provider, input.operationId);
339
+ return await this.getReturnedExecution(result);
340
+ }
341
+ /** Record a create failure only while the caller still owns its lease. */
342
+ async failCreateAttempt(input) {
343
+ this.assertCreateAttemptTenant(input.tenantId);
344
+ const result = await this._db.query(`UPDATE ${this.tableName}
345
+ SET status = ?,
346
+ last_error = ?,
347
+ create_lease_id = '',
348
+ create_lease_expires_at = NULL
349
+ WHERE id = ?
350
+ AND tenant_id = ?
351
+ AND COALESCE(create_lease_id, '') = ?
352
+ AND (provider_request_id IS NULL OR provider_request_id = '')
353
+ RETURNING id`, "failed", input.lastError, input.executionId, input.tenantId, input.operationId);
354
+ return await this.getReturnedExecution(result);
355
+ }
356
+ /** Bind an adopted provider request without saving a stale object snapshot. */
357
+ async bindProviderRequest(input) {
358
+ this.assertCreateAttemptTenant(input.tenantId);
359
+ const providerRequestKey = `${input.tenantId}:${input.provider}:${input.providerRequestId}`;
360
+ const result = await this._db.query(`UPDATE ${this.tableName}
361
+ SET provider_request_id = ?,
362
+ provider_request_key = ?
363
+ WHERE id = ?
364
+ AND tenant_id = ?
365
+ AND provider = ?
366
+ AND (provider_request_id IS NULL OR provider_request_id = '' OR provider_request_id = ?)
367
+ RETURNING id`, input.providerRequestId, providerRequestKey, input.executionId, input.tenantId, input.provider, input.providerRequestId);
368
+ return await this.getReturnedExecution(result);
369
+ }
370
+ /**
371
+ * Apply provider lifecycle state only while status still matches the
372
+ * caller's snapshot and the candidate observation is not older than the
373
+ * persisted provider event. Service retries re-read the winner before
374
+ * deciding whether the candidate state remains monotonic.
375
+ */
376
+ async compareAndSetLifecycle(input) {
377
+ this.assertCreateAttemptTenant(input.tenantId);
378
+ const result = await this._db.query(`UPDATE ${this.tableName}
379
+ SET status = ?,
380
+ expires_at = ?,
381
+ cancellation_reason = ?,
382
+ last_provider_event_at = ?,
383
+ last_reconciled_at = ?,
384
+ completed_at = ?,
385
+ last_error = ''
386
+ WHERE id = ?
387
+ AND tenant_id = ?
388
+ AND status = ?
389
+ AND (last_provider_event_at IS NULL OR last_provider_event_at <= ?)
390
+ RETURNING id`, input.status, input.expiresAt?.toISOString() ?? null, input.cancellationReason, input.lastProviderEventAt.toISOString(), input.lastReconciledAt?.toISOString() ?? null, input.completedAt?.toISOString() ?? null, input.executionId, input.tenantId, input.expectedStatus, input.lastProviderEventAt.toISOString());
391
+ return await this.getReturnedExecution(result);
392
+ }
393
+ /** Increment audit attempts without writing any lifecycle columns. */
394
+ async incrementAttemptCount(tenantId, executionId) {
395
+ this.assertCreateAttemptTenant(tenantId);
396
+ const result = await this._db.query(`UPDATE ${this.tableName}
397
+ SET attempt_count = attempt_count + 1
398
+ WHERE id = ?
399
+ AND tenant_id = ?
400
+ RETURNING id`, executionId, tenantId);
401
+ const execution = await this.getReturnedExecution(result);
402
+ if (!execution) throw new Error(`AgreementExecution '${executionId}' was not found`);
403
+ return execution;
404
+ }
405
+ /** Update failure diagnostics without writing a stale lifecycle snapshot. */
406
+ async updateLastError(input) {
407
+ this.assertCreateAttemptTenant(input.tenantId);
408
+ const result = await this._db.query(`UPDATE ${this.tableName}
409
+ SET last_error = ?
410
+ WHERE id = ?
411
+ AND tenant_id = ?
412
+ RETURNING id`, input.lastError, input.executionId, input.tenantId);
413
+ const execution = await this.getReturnedExecution(result);
414
+ if (!execution) throw new Error(`AgreementExecution '${input.executionId}' was not found`);
415
+ return execution;
416
+ }
417
+ /**
418
+ * Bind one immutable evidence artifact with a tenant-fenced compare-and-swap.
419
+ * Concurrent finalizers can only publish one asset identity for each kind.
420
+ */
421
+ async bindEvidenceArtifact(input) {
422
+ this.assertCreateAttemptTenant(input.tenantId);
423
+ const columns = input.kind === "signed_document" ? {
424
+ assetId: "signed_document_asset_id",
425
+ sha256: "signed_document_sha256",
426
+ sizeBytes: "signed_document_size_bytes",
427
+ mediaType: "signed_document_media_type",
428
+ filename: "signed_document_filename"
429
+ } : {
430
+ assetId: "audit_trail_asset_id",
431
+ sha256: "audit_trail_sha256",
432
+ sizeBytes: "audit_trail_size_bytes",
433
+ mediaType: "audit_trail_media_type",
434
+ filename: "audit_trail_filename"
435
+ };
436
+ const result = await this._db.query(`UPDATE ${this.tableName}
437
+ SET ${columns.assetId} = ?,
438
+ ${columns.sha256} = ?,
439
+ ${columns.sizeBytes} = ?,
440
+ ${columns.mediaType} = ?,
441
+ ${columns.filename} = ?
442
+ WHERE id = ?
443
+ AND tenant_id = ?
444
+ AND provider_request_id IS NOT NULL
445
+ AND provider_request_id <> ''
446
+ AND ${columns.assetId} IS NULL
447
+ RETURNING id`, input.assetId, input.sha256, input.sizeBytes, input.mediaType, input.filename, input.executionId, input.tenantId);
448
+ return await this.getReturnedExecution(result);
449
+ }
450
+ async findBySource(sourceKind, sourceId) {
451
+ return await this.list({
452
+ where: {
453
+ sourceKind,
454
+ sourceId
455
+ },
456
+ orderBy: "source_version DESC"
457
+ });
458
+ }
459
+ assertCreateAttemptTenant(tenantId) {
460
+ if (requireTenantId() !== tenantId) throw new Error("AgreementExecution create-lease tenant mismatch");
461
+ }
462
+ async getReturnedExecution(result) {
463
+ const returned = result.rows?.[0];
464
+ if (typeof returned?.id !== "string") return null;
465
+ return await this.get({ id: returned.id });
466
+ }
467
+ };
468
+ //#endregion
469
+ //#region src/agreements/models/AgreementExecutionEvent.ts
470
+ var __defProp$1 = Object.defineProperty;
471
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
472
+ var __decorateClass$1 = (decorators, target, key, kind) => {
473
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
474
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
475
+ if (kind && result) __defProp$1(target, key, result);
476
+ return result;
477
+ };
478
+ var persistedEventState = /* @__PURE__ */ new WeakMap();
479
+ var AgreementExecutionEvent = class extends SmrtObject {
480
+ tenantId = "";
481
+ executionId = "";
482
+ provider = "";
483
+ providerEventId = "";
484
+ eventOrigin = "provider_webhook";
485
+ operationId = "";
486
+ dedupeKey = "";
487
+ orderingKey = "";
488
+ eventType = "";
489
+ status = "prepared";
490
+ occurredAt = /* @__PURE__ */ new Date();
491
+ receivedAt = /* @__PURE__ */ new Date();
492
+ payloadSha256 = "";
493
+ signerEvidence = "[]";
494
+ payload = "{}";
495
+ constructor(options = {}) {
496
+ super(options);
497
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
498
+ if (options.executionId !== void 0) this.executionId = options.executionId;
499
+ if (options.provider !== void 0) this.provider = options.provider;
500
+ if (options.providerEventId !== void 0) this.providerEventId = options.providerEventId;
501
+ if (options.eventOrigin !== void 0) this.eventOrigin = options.eventOrigin;
502
+ if (options.operationId !== void 0) this.operationId = options.operationId;
503
+ if (options.dedupeKey !== void 0) this.dedupeKey = options.dedupeKey;
504
+ if (options.orderingKey !== void 0) this.orderingKey = options.orderingKey;
505
+ if (options.eventType !== void 0) this.eventType = options.eventType;
506
+ if (options.status !== void 0) this.status = options.status;
507
+ if (options.occurredAt !== void 0) this.occurredAt = coerceAgreementDate(options.occurredAt) ?? /* @__PURE__ */ new Date();
508
+ if (options.receivedAt !== void 0) this.receivedAt = coerceAgreementDate(options.receivedAt) ?? /* @__PURE__ */ new Date();
509
+ if (options.payloadSha256 !== void 0) this.payloadSha256 = options.payloadSha256;
510
+ if (options.signerEvidence !== void 0) this.signerEvidence = options.signerEvidence;
511
+ if (options.payload !== void 0) this.payload = options.payload;
512
+ }
513
+ async initialize() {
514
+ await super.initialize();
515
+ this.occurredAt = coerceAgreementDate(this.occurredAt) ?? /* @__PURE__ */ new Date();
516
+ this.receivedAt = coerceAgreementDate(this.receivedAt) ?? /* @__PURE__ */ new Date();
517
+ if (this.isPersisted) persistedEventState.set(this, this.serializeState());
518
+ return this;
519
+ }
520
+ async save() {
521
+ const captured = persistedEventState.get(this);
522
+ if (captured !== void 0 && captured !== this.serializeState()) throw new Error(`AgreementExecutionEvent ${this.id ?? "<new>"}: verified events are immutable`);
523
+ if (captured === void 0 && !this.isPersisted) this.requireInsertOnSave();
524
+ const result = await super.save();
525
+ persistedEventState.set(this, this.serializeState());
526
+ return result;
527
+ }
528
+ serializeState() {
529
+ return JSON.stringify({
530
+ tenantId: this.tenantId,
531
+ executionId: this.executionId,
532
+ provider: this.provider,
533
+ providerEventId: this.providerEventId,
534
+ eventOrigin: this.eventOrigin,
535
+ operationId: this.operationId,
536
+ dedupeKey: this.dedupeKey,
537
+ orderingKey: this.orderingKey,
538
+ eventType: this.eventType,
539
+ status: this.status,
540
+ occurredAt: this.occurredAt.toISOString(),
541
+ receivedAt: this.receivedAt.toISOString(),
542
+ payloadSha256: this.payloadSha256,
543
+ signerEvidence: this.signerEvidence,
544
+ payload: this.payload
545
+ });
546
+ }
547
+ };
548
+ __decorateClass$1([tenantId()], AgreementExecutionEvent.prototype, "tenantId", 2);
549
+ __decorateClass$1([foreignKey("AgreementExecution", { required: true })], AgreementExecutionEvent.prototype, "executionId", 2);
550
+ __decorateClass$1([field({ required: true })], AgreementExecutionEvent.prototype, "dedupeKey", 2);
551
+ AgreementExecutionEvent = __decorateClass$1([TenantScoped({ mode: "required" }), smrt({
552
+ conflictColumns: ["tenant_id", "dedupe_key"],
553
+ api: false,
554
+ mcp: false,
555
+ cli: false
556
+ })], AgreementExecutionEvent);
557
+ //#endregion
558
+ //#region src/agreements/collections/AgreementExecutionEventCollection.ts
559
+ var AgreementExecutionEventCollection = class extends SmrtCollection {
560
+ static _itemClass = AgreementExecutionEvent;
561
+ async findByDedupeKey(dedupeKey) {
562
+ return (await this.list({
563
+ where: { dedupeKey },
564
+ limit: 1
565
+ }))[0] ?? null;
566
+ }
567
+ async recordVerified(options) {
568
+ if (!options.dedupeKey) throw new Error("AgreementExecutionEvent requires a dedupe key");
569
+ const occurredAt = coerceAgreementDate(options.occurredAt);
570
+ if (!occurredAt) throw new Error("AgreementExecutionEvent requires a valid occurredAt");
571
+ const receivedAt = coerceAgreementDate(options.receivedAt);
572
+ if (!receivedAt) throw new Error("AgreementExecutionEvent requires a valid receivedAt");
573
+ const existing = await this.findByDedupeKey(options.dedupeKey);
574
+ if (existing) {
575
+ this.assertSameVerifiedEvent(existing, options, occurredAt);
576
+ return {
577
+ event: existing,
578
+ created: false
579
+ };
580
+ }
581
+ const { occurredAt: _occurredAt, receivedAt: _receivedAt, ...rest } = options;
582
+ try {
583
+ return {
584
+ event: await this.create({
585
+ ...rest,
586
+ occurredAt,
587
+ receivedAt,
588
+ _insertOnly: true
589
+ }),
590
+ created: true
591
+ };
592
+ } catch (error) {
593
+ const raced = await this.findByDedupeKey(options.dedupeKey);
594
+ if (!raced) throw error;
595
+ this.assertSameVerifiedEvent(raced, options, occurredAt);
596
+ return {
597
+ event: raced,
598
+ created: false
599
+ };
600
+ }
601
+ }
602
+ assertSameVerifiedEvent(existing, options, occurredAt) {
603
+ if (existing.executionId !== options.executionId || existing.provider !== options.provider || existing.providerEventId !== (options.providerEventId ?? "") || existing.eventOrigin !== (options.eventOrigin ?? "provider_webhook") || existing.operationId !== (options.operationId ?? "") || existing.orderingKey !== (options.orderingKey ?? "") || existing.eventType !== (options.eventType ?? "") || existing.status !== options.status || existing.occurredAt.toISOString() !== occurredAt.toISOString() || existing.payloadSha256 !== (options.payloadSha256 ?? "") || existing.signerEvidence !== (options.signerEvidence ?? "[]") || existing.payload !== (options.payload ?? "{}")) throw new Error(`AgreementExecutionEvent dedupe key '${options.dedupeKey}' collides with different verified evidence`);
604
+ }
605
+ async findByExecution(executionId) {
606
+ return await this.list({
607
+ where: { executionId },
608
+ orderBy: "occurred_at ASC"
609
+ });
610
+ }
611
+ async findProviderEventsByExecution(executionId) {
612
+ return await this.list({
613
+ where: {
614
+ executionId,
615
+ eventOrigin: "provider_webhook"
616
+ },
617
+ orderBy: "occurred_at ASC"
618
+ });
619
+ }
620
+ };
621
+ //#endregion
622
+ //#region src/agreements/models/ExecutedAgreement.ts
623
+ var __defProp = Object.defineProperty;
624
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
625
+ var __decorateClass = (decorators, target, key, kind) => {
626
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
627
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
628
+ if (kind && result) __defProp(target, key, result);
629
+ return result;
630
+ };
631
+ var persistedAgreementState = /* @__PURE__ */ new WeakMap();
632
+ var ExecutedAgreement = class extends SmrtObject {
633
+ tenantId = "";
634
+ executionId = "";
635
+ sourceKind = "";
636
+ sourceId = "";
637
+ sourceVersion = 1;
638
+ sourceAssetId = "";
639
+ sourceSha256 = "";
640
+ sourceSizeBytes = 0;
641
+ signedDocumentAssetId = "";
642
+ signedDocumentSha256 = "";
643
+ signedDocumentSizeBytes = 0;
644
+ signedDocumentMediaType = "";
645
+ signedDocumentFilename = "";
646
+ auditTrailAssetId = "";
647
+ auditTrailSha256 = "";
648
+ auditTrailSizeBytes = 0;
649
+ auditTrailMediaType = "";
650
+ auditTrailFilename = "";
651
+ signerEvidence = "[]";
652
+ acceptedAt = /* @__PURE__ */ new Date();
653
+ effectiveFrom = null;
654
+ effectiveTo = null;
655
+ supersedesExecutedAgreementId = "";
656
+ metadata = "{}";
657
+ constructor(options = {}) {
658
+ super(options);
659
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
660
+ if (options.executionId !== void 0) this.executionId = options.executionId;
661
+ if (options.sourceKind !== void 0) this.sourceKind = options.sourceKind;
662
+ if (options.sourceId !== void 0) this.sourceId = options.sourceId;
663
+ if (options.sourceVersion !== void 0) this.sourceVersion = options.sourceVersion;
664
+ if (options.sourceAssetId !== void 0) this.sourceAssetId = options.sourceAssetId;
665
+ if (options.sourceSha256 !== void 0) this.sourceSha256 = options.sourceSha256;
666
+ if (options.sourceSizeBytes !== void 0) this.sourceSizeBytes = options.sourceSizeBytes;
667
+ if (options.signedDocumentAssetId !== void 0) this.signedDocumentAssetId = options.signedDocumentAssetId;
668
+ if (options.signedDocumentSha256 !== void 0) this.signedDocumentSha256 = options.signedDocumentSha256;
669
+ if (options.signedDocumentSizeBytes !== void 0) this.signedDocumentSizeBytes = options.signedDocumentSizeBytes;
670
+ if (options.signedDocumentMediaType !== void 0) this.signedDocumentMediaType = options.signedDocumentMediaType;
671
+ if (options.signedDocumentFilename !== void 0) this.signedDocumentFilename = options.signedDocumentFilename;
672
+ if (options.auditTrailAssetId !== void 0) this.auditTrailAssetId = options.auditTrailAssetId;
673
+ if (options.auditTrailSha256 !== void 0) this.auditTrailSha256 = options.auditTrailSha256;
674
+ if (options.auditTrailSizeBytes !== void 0) this.auditTrailSizeBytes = options.auditTrailSizeBytes;
675
+ if (options.auditTrailMediaType !== void 0) this.auditTrailMediaType = options.auditTrailMediaType;
676
+ if (options.auditTrailFilename !== void 0) this.auditTrailFilename = options.auditTrailFilename;
677
+ if (options.signerEvidence !== void 0) this.signerEvidence = options.signerEvidence;
678
+ if (options.acceptedAt !== void 0) this.acceptedAt = coerceAgreementDate(options.acceptedAt) ?? /* @__PURE__ */ new Date();
679
+ if (options.effectiveFrom !== void 0) this.effectiveFrom = coerceAgreementDate(options.effectiveFrom);
680
+ if (options.effectiveTo !== void 0) this.effectiveTo = coerceAgreementDate(options.effectiveTo);
681
+ if (options.supersedesExecutedAgreementId !== void 0) this.supersedesExecutedAgreementId = options.supersedesExecutedAgreementId;
682
+ if (options.metadata !== void 0) this.metadata = options.metadata;
683
+ }
684
+ async initialize() {
685
+ await super.initialize();
686
+ this.acceptedAt = coerceAgreementDate(this.acceptedAt) ?? /* @__PURE__ */ new Date();
687
+ this.effectiveFrom = coerceAgreementDate(this.effectiveFrom);
688
+ this.effectiveTo = coerceAgreementDate(this.effectiveTo);
689
+ if (this.isPersisted) persistedAgreementState.set(this, this.serializeState());
690
+ return this;
691
+ }
692
+ isEffectiveAt(at) {
693
+ return !(this.effectiveFrom && at < this.effectiveFrom || this.effectiveTo && at > this.effectiveTo);
694
+ }
695
+ async save() {
696
+ const captured = persistedAgreementState.get(this);
697
+ if (captured !== void 0 && captured !== this.serializeState()) throw new Error(`ExecutedAgreement ${this.id ?? "<new>"}: executed agreements are immutable; create a versioned amendment`);
698
+ if (captured === void 0 && !this.isPersisted) this.requireInsertOnSave();
699
+ const result = await super.save();
700
+ persistedAgreementState.set(this, this.serializeState());
701
+ return result;
702
+ }
703
+ serializeState() {
704
+ return JSON.stringify({
705
+ tenantId: this.tenantId,
706
+ executionId: this.executionId,
707
+ sourceKind: this.sourceKind,
708
+ sourceId: this.sourceId,
709
+ sourceVersion: this.sourceVersion,
710
+ sourceAssetId: this.sourceAssetId,
711
+ sourceSha256: this.sourceSha256,
712
+ sourceSizeBytes: this.sourceSizeBytes,
713
+ signedDocumentAssetId: this.signedDocumentAssetId,
714
+ signedDocumentSha256: this.signedDocumentSha256,
715
+ signedDocumentSizeBytes: this.signedDocumentSizeBytes,
716
+ signedDocumentMediaType: this.signedDocumentMediaType,
717
+ signedDocumentFilename: this.signedDocumentFilename,
718
+ auditTrailAssetId: this.auditTrailAssetId,
719
+ auditTrailSha256: this.auditTrailSha256,
720
+ auditTrailSizeBytes: this.auditTrailSizeBytes,
721
+ auditTrailMediaType: this.auditTrailMediaType,
722
+ auditTrailFilename: this.auditTrailFilename,
723
+ signerEvidence: this.signerEvidence,
724
+ acceptedAt: this.acceptedAt.toISOString(),
725
+ effectiveFrom: this.effectiveFrom?.toISOString() ?? null,
726
+ effectiveTo: this.effectiveTo?.toISOString() ?? null,
727
+ supersedesExecutedAgreementId: this.supersedesExecutedAgreementId,
728
+ metadata: this.metadata
729
+ });
730
+ }
731
+ };
732
+ __decorateClass([tenantId()], ExecutedAgreement.prototype, "tenantId", 2);
733
+ __decorateClass([foreignKey("AgreementExecution", { required: true })], ExecutedAgreement.prototype, "executionId", 2);
734
+ __decorateClass([field({ required: true })], ExecutedAgreement.prototype, "sourceKind", 2);
735
+ __decorateClass([field({ required: true })], ExecutedAgreement.prototype, "sourceId", 2);
736
+ __decorateClass([crossPackageRef("@happyvertical/smrt-assets:Asset")], ExecutedAgreement.prototype, "sourceAssetId", 2);
737
+ __decorateClass([crossPackageRef("@happyvertical/smrt-assets:Asset")], ExecutedAgreement.prototype, "signedDocumentAssetId", 2);
738
+ __decorateClass([crossPackageRef("@happyvertical/smrt-assets:Asset")], ExecutedAgreement.prototype, "auditTrailAssetId", 2);
739
+ __decorateClass([foreignKey("ExecutedAgreement")], ExecutedAgreement.prototype, "supersedesExecutedAgreementId", 2);
740
+ ExecutedAgreement = __decorateClass([TenantScoped({ mode: "required" }), smrt({
741
+ conflictColumns: ["tenant_id", "execution_id"],
742
+ api: { include: ["list", "get"] },
743
+ mcp: { include: ["list", "get"] },
744
+ cli: false
745
+ })], ExecutedAgreement);
746
+ //#endregion
747
+ //#region src/agreements/collections/ExecutedAgreementCollection.ts
748
+ var ExecutedAgreementCollection = class extends SmrtCollection {
749
+ static _itemClass = ExecutedAgreement;
750
+ async findByExecution(executionId) {
751
+ return (await this.list({
752
+ where: { executionId },
753
+ limit: 1
754
+ }))[0] ?? null;
755
+ }
756
+ async createImmutable(options) {
757
+ if (!options.executionId) throw new Error("ExecutedAgreement requires an executionId");
758
+ this.assertCompleteEvidence(options);
759
+ const existing = await this.findByExecution(options.executionId);
760
+ if (existing) {
761
+ this.assertSameEvidence(existing, options);
762
+ return {
763
+ agreement: existing,
764
+ created: false
765
+ };
766
+ }
767
+ const { acceptedAt, effectiveFrom, effectiveTo, ...rest } = options;
768
+ try {
769
+ return {
770
+ agreement: await this.create({
771
+ ...rest,
772
+ ...acceptedAt !== void 0 ? { acceptedAt: coerceAgreementDate(acceptedAt) ?? /* @__PURE__ */ new Date() } : {},
773
+ ...effectiveFrom !== void 0 ? { effectiveFrom: coerceAgreementDate(effectiveFrom) } : {},
774
+ ...effectiveTo !== void 0 ? { effectiveTo: coerceAgreementDate(effectiveTo) } : {},
775
+ _insertOnly: true
776
+ }),
777
+ created: true
778
+ };
779
+ } catch (error) {
780
+ const raced = await this.findByExecution(options.executionId);
781
+ if (!raced) throw error;
782
+ this.assertSameEvidence(raced, options);
783
+ return {
784
+ agreement: raced,
785
+ created: false
786
+ };
787
+ }
788
+ }
789
+ assertSameEvidence(existing, options) {
790
+ const acceptedAt = coerceAgreementDate(options.acceptedAt);
791
+ const effectiveFrom = coerceAgreementDate(options.effectiveFrom);
792
+ const effectiveTo = coerceAgreementDate(options.effectiveTo);
793
+ if (existing.sourceKind !== options.sourceKind || existing.sourceId !== options.sourceId || existing.sourceVersion !== options.sourceVersion || existing.sourceAssetId !== options.sourceAssetId || existing.sourceSha256 !== (options.sourceSha256 ?? "") || existing.sourceSizeBytes !== options.sourceSizeBytes || existing.signedDocumentAssetId !== options.signedDocumentAssetId || existing.signedDocumentSha256 !== (options.signedDocumentSha256 ?? "") || existing.signedDocumentSizeBytes !== options.signedDocumentSizeBytes || existing.signedDocumentMediaType !== options.signedDocumentMediaType || existing.signedDocumentFilename !== options.signedDocumentFilename || existing.auditTrailAssetId !== options.auditTrailAssetId || existing.auditTrailSha256 !== (options.auditTrailSha256 ?? "") || existing.auditTrailSizeBytes !== options.auditTrailSizeBytes || existing.auditTrailMediaType !== options.auditTrailMediaType || existing.auditTrailFilename !== options.auditTrailFilename || existing.signerEvidence !== options.signerEvidence || existing.acceptedAt.toISOString() !== acceptedAt?.toISOString() || (existing.effectiveFrom?.toISOString() ?? null) !== (effectiveFrom?.toISOString() ?? null) || (existing.effectiveTo?.toISOString() ?? null) !== (effectiveTo?.toISOString() ?? null) || (existing.supersedesExecutedAgreementId ?? "") !== (options.supersedesExecutedAgreementId ?? "") || existing.metadata !== (options.metadata ?? "{}")) throw new Error(`ExecutedAgreement execution '${options.executionId}' collides with different immutable evidence`);
794
+ }
795
+ assertCompleteEvidence(options) {
796
+ for (const [label, value] of [
797
+ ["sourceKind", options.sourceKind],
798
+ ["sourceId", options.sourceId],
799
+ ["sourceAssetId", options.sourceAssetId],
800
+ ["sourceSha256", options.sourceSha256],
801
+ ["signedDocumentAssetId", options.signedDocumentAssetId],
802
+ ["signedDocumentSha256", options.signedDocumentSha256],
803
+ ["signedDocumentMediaType", options.signedDocumentMediaType],
804
+ ["signedDocumentFilename", options.signedDocumentFilename],
805
+ ["auditTrailAssetId", options.auditTrailAssetId],
806
+ ["auditTrailSha256", options.auditTrailSha256],
807
+ ["auditTrailMediaType", options.auditTrailMediaType],
808
+ ["auditTrailFilename", options.auditTrailFilename],
809
+ ["signerEvidence", options.signerEvidence]
810
+ ]) if (typeof value !== "string" || !value) throw new Error(`ExecutedAgreement requires ${label}`);
811
+ if (typeof options.sourceVersion !== "number" || !Number.isSafeInteger(options.sourceVersion) || options.sourceVersion < 1) throw new Error("ExecutedAgreement requires a positive sourceVersion");
812
+ for (const [label, value] of [
813
+ ["sourceSizeBytes", options.sourceSizeBytes],
814
+ ["signedDocumentSizeBytes", options.signedDocumentSizeBytes],
815
+ ["auditTrailSizeBytes", options.auditTrailSizeBytes]
816
+ ]) if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) throw new Error(`ExecutedAgreement requires a positive ${label}`);
817
+ if (!coerceAgreementDate(options.acceptedAt)) throw new Error("ExecutedAgreement requires a valid acceptedAt");
818
+ try {
819
+ const signers = JSON.parse(options.signerEvidence ?? "");
820
+ if (!Array.isArray(signers) || signers.length === 0 || signers.some((signer) => !signer || typeof signer !== "object" || signer.status !== "signed")) throw new Error("incomplete");
821
+ } catch {
822
+ throw new Error("ExecutedAgreement requires non-empty signed signerEvidence");
823
+ }
824
+ }
825
+ async findVersionsBySource(sourceKind, sourceId) {
826
+ return await this.list({
827
+ where: {
828
+ sourceKind,
829
+ sourceId
830
+ },
831
+ orderBy: "source_version DESC"
832
+ });
833
+ }
834
+ async effectiveForSource(sourceKind, sourceId, at = /* @__PURE__ */ new Date()) {
835
+ return (await this.findVersionsBySource(sourceKind, sourceId)).find((agreement) => agreement.isEffectiveAt(at)) ?? null;
836
+ }
837
+ };
838
+ //#endregion
839
+ //#region src/agreements/services/AgreementExecutionService.ts
840
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
841
+ "completed",
842
+ "declined",
843
+ "cancelled",
844
+ "expired",
845
+ "failed"
846
+ ]);
847
+ var STATUS_PROGRESS = /* @__PURE__ */ new Map([
848
+ ["prepared", 0],
849
+ ["sent", 1],
850
+ ["delivered", 2],
851
+ ["viewed", 3],
852
+ ["partially_signed", 4]
853
+ ]);
854
+ var DEFAULT_CREATE_LEASE_DURATION_MS = 300 * 1e3;
855
+ var AgreementExecutionService = class {
856
+ constructor(deps) {
857
+ this.deps = deps;
858
+ this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
859
+ this.createLeaseDurationMs = deps.createLeaseDurationMs ?? DEFAULT_CREATE_LEASE_DURATION_MS;
860
+ if (!Number.isFinite(this.createLeaseDurationMs) || this.createLeaseDurationMs <= 0) throw new Error("Agreement execution create lease must be positive");
861
+ }
862
+ deps;
863
+ now;
864
+ createLeaseDurationMs;
865
+ async createExecution(input) {
866
+ this.assertTenant(input.tenantId);
867
+ this.assertProviderCapabilities();
868
+ this.assertCreateInput(input);
869
+ const sourceBytes = await readByteSource(input.document.data);
870
+ if (sourceBytes.byteLength === 0) throw new Error("Agreement execution document must not be empty");
871
+ const sourceSha256 = sha256(sourceBytes);
872
+ const effectiveFrom = normalizeOptionalAgreementDate(input.effectiveFrom, "effectiveFrom");
873
+ const effectiveTo = normalizeOptionalAgreementDate(input.effectiveTo, "effectiveTo");
874
+ if (effectiveFrom && effectiveTo && effectiveTo < effectiveFrom) throw new Error("Agreement execution effectiveTo must not precede effectiveFrom");
875
+ const requestIntentSha256 = hashRequestIntent(input, this.deps.provider.capabilities.id, sourceSha256, effectiveFrom, effectiveTo);
876
+ const existing = await this.deps.executions.findByIdempotencyKey(input.idempotencyKey);
877
+ if (existing) {
878
+ this.assertSameIntent(existing, input, requestIntentSha256);
879
+ return await this.resumeOrReplayCreate(existing, input, sourceBytes);
880
+ }
881
+ const sourceAsset = await this.deps.assets.storeSourceAsset(input.document.name, sourceBytes, {
882
+ mimeType: input.document.mediaType,
883
+ typeSlug: "agreement-source",
884
+ sourceType: "agreement-execution",
885
+ externalId: input.idempotencyKey,
886
+ metadata: {
887
+ sourceKind: input.sourceKind,
888
+ sourceId: input.sourceId,
889
+ sourceVersion: input.sourceVersion,
890
+ sha256: sourceSha256
891
+ }
892
+ });
893
+ this.assertTenantValue(sourceAsset.tenantId, input.tenantId, "source asset");
894
+ if (!sourceAsset.id) throw new Error("Stored source Asset has no id");
895
+ const operationId = randomUUID();
896
+ const createLeaseExpiresAt = this.newCreateLeaseExpiry();
897
+ let execution;
898
+ try {
899
+ execution = await this.deps.executions.create({
900
+ tenantId: input.tenantId,
901
+ provider: this.deps.provider.capabilities.id,
902
+ providerAccountRef: input.providerAccountRef ?? "",
903
+ credentialRef: input.credentialRef ?? "",
904
+ idempotencyKey: input.idempotencyKey,
905
+ sourceKind: input.sourceKind,
906
+ sourceId: input.sourceId,
907
+ sourceVersion: input.sourceVersion,
908
+ sourceAssetId: sourceAsset.id,
909
+ sourceSha256,
910
+ sourceSizeBytes: sourceBytes.byteLength,
911
+ requestIntentSha256,
912
+ title: input.title,
913
+ signerIntent: JSON.stringify(sanitizeSignerIntent(input.signers)),
914
+ status: "prepared",
915
+ effectiveFrom,
916
+ effectiveTo,
917
+ supersedesExecutedAgreementId: input.supersedesExecutedAgreementId ?? "",
918
+ metadata: JSON.stringify(input.metadata ?? {}),
919
+ attemptCount: 1,
920
+ createLeaseId: operationId,
921
+ createLeaseExpiresAt,
922
+ _insertOnly: true
923
+ });
924
+ } catch (error) {
925
+ const raced = await this.deps.executions.findByIdempotencyKey(input.idempotencyKey);
926
+ if (!raced || raced.sourceAssetId !== sourceAsset.id) await this.removeStoredAsset(sourceAsset);
927
+ if (!raced) throw error;
928
+ this.assertSameIntent(raced, input, requestIntentSha256);
929
+ return await this.resumeOrReplayCreate(raced, input, sourceBytes);
930
+ }
931
+ try {
932
+ await this.ensureAssetAssociation("AgreementExecution", execution.id ?? "", sourceAsset.id, "source_document");
933
+ } catch (error) {
934
+ await this.failOwnedCreateAttempt(execution, operationId, summarizeError(error, false));
935
+ throw error;
936
+ }
937
+ return await this.sendProviderRequest(execution, input, sourceBytes, operationId);
938
+ }
939
+ async sendProviderRequest(execution, input, sourceBytes, operationId) {
940
+ if (execution.createLeaseId !== operationId) throw new Error(`AgreementExecution ${execution.id}: provider-create lease is not owned by this operation`);
941
+ try {
942
+ await this.recordOperationAudit(execution, operationId, "create.started", "system", { attemptCount: execution.attemptCount });
943
+ } catch (error) {
944
+ await this.failOwnedCreateAttempt(execution, operationId, summarizeError(error, false));
945
+ throw error;
946
+ }
947
+ let request;
948
+ let providerResponded = false;
949
+ try {
950
+ request = await this.deps.provider.createRequest({
951
+ tenantId: input.tenantId,
952
+ idempotencyKey: input.idempotencyKey,
953
+ title: input.title,
954
+ ...input.message ? { message: input.message } : {},
955
+ documents: [{
956
+ name: input.document.name,
957
+ mediaType: input.document.mediaType,
958
+ data: sourceBytes
959
+ }],
960
+ signers: input.signers,
961
+ ...input.signingOrder !== void 0 ? { signingOrder: input.signingOrder } : {},
962
+ ...input.expiresInDays !== void 0 ? { expiresInDays: input.expiresInDays } : {},
963
+ metadata: {
964
+ ...input.metadata ?? {},
965
+ smrtExecutionId: execution.id ?? "",
966
+ smrtSourceKind: input.sourceKind,
967
+ smrtSourceId: input.sourceId,
968
+ smrtSourceVersion: String(input.sourceVersion),
969
+ smrtSourceSha256: execution.sourceSha256
970
+ },
971
+ ...input.signal ? { signal: input.signal } : {}
972
+ });
973
+ providerResponded = true;
974
+ this.assertProviderRequest(request, input.tenantId);
975
+ await this.assertProviderRequestAvailable(execution, request.id);
976
+ const completed = await this.deps.executions.completeCreateAttempt({
977
+ tenantId: input.tenantId,
978
+ executionId: execution.id ?? "",
979
+ operationId,
980
+ provider: execution.provider,
981
+ providerRequestId: request.id,
982
+ status: request.status,
983
+ expiresAt: request.expiresAt ?? null
984
+ });
985
+ if (completed) execution = completed;
986
+ else {
987
+ const latest = await this.requireExecution(execution.id ?? "");
988
+ if (latest.providerRequestId !== request.id) throw new Error(`AgreementExecution ${execution.id}: provider-create lease ownership changed before the provider result was persisted; reconcile the current attempt`);
989
+ execution = latest;
990
+ }
991
+ } catch (error) {
992
+ const mayHaveSucceeded = providerResponded || requestMayHaveSucceeded(error);
993
+ const lastError = summarizeError(error, mayHaveSucceeded);
994
+ execution = await this.failOwnedCreateAttempt(execution, operationId, lastError);
995
+ await this.recordOperationAudit(execution, operationId, mayHaveSucceeded ? "create.uncertain" : "create.failed", "provider_operation", { error: lastError });
996
+ throw error;
997
+ }
998
+ await this.recordOperationAudit(execution, operationId, "create.succeeded", "provider_operation", { providerRequestId: request.id });
999
+ return this.result(execution, execution.attemptCount > 1);
1000
+ }
1001
+ async resumeOrReplayCreate(execution, input, sourceBytes) {
1002
+ if (execution.providerRequestId) return this.result(execution, true);
1003
+ const now = this.now();
1004
+ if (this.isCreateLeaseActive(execution, now)) return this.result(execution, true);
1005
+ const abandonedCreate = this.isAbandonedCreate(execution);
1006
+ if (abandonedCreate && !this.deps.provider.capabilities.providerEnforcedIdempotency) throw new Error(`AgreementExecution ${execution.id}: the provider-create lease expired without a confirmed request id; reconcile or adopt the provider request before retrying`);
1007
+ if (!abandonedCreate && !this.canSafelyRetryCreate(execution)) throw new Error(`AgreementExecution ${execution.id}: the prior provider create has no confirmed request id; reconcile or adopt the provider request before retrying`);
1008
+ await this.ensureAssetAssociation("AgreementExecution", execution.id ?? "", execution.sourceAssetId, "source_document");
1009
+ const operationId = randomUUID();
1010
+ const claimed = await this.deps.executions.claimCreateAttempt({
1011
+ tenantId: input.tenantId,
1012
+ executionId: execution.id ?? "",
1013
+ expectedAttemptCount: execution.attemptCount,
1014
+ expectedLeaseId: execution.createLeaseId,
1015
+ expectedStatus: execution.status,
1016
+ expectedLastError: execution.lastError,
1017
+ operationId,
1018
+ leaseExpiresAt: this.newCreateLeaseExpiry(now),
1019
+ now
1020
+ });
1021
+ if (!claimed) {
1022
+ const latest = await this.requireExecution(execution.id ?? "");
1023
+ if (latest.providerRequestId || this.isCreateLeaseActive(latest, this.now())) return this.result(latest, true);
1024
+ throw new Error(`AgreementExecution ${execution.id}: provider-create state changed while claiming recovery; retry the operation`);
1025
+ }
1026
+ return await this.sendProviderRequest(claimed, input, sourceBytes, operationId);
1027
+ }
1028
+ /**
1029
+ * Bind an operator-reconciled provider request after an uncertain create.
1030
+ * This is the safe recovery path for providers without atomic idempotency.
1031
+ */
1032
+ async adoptProviderRequest(input) {
1033
+ this.assertTenant(input.tenantId);
1034
+ let execution = await this.requireExecution(input.executionId);
1035
+ if (execution.providerRequestId && execution.providerRequestId !== input.providerRequestId) throw new Error(`AgreementExecution ${execution.id} is already bound to a different provider request`);
1036
+ const started = await this.beginAuditedOperation(execution, "adopt", "operator");
1037
+ execution = started.execution;
1038
+ const { operationId } = started;
1039
+ try {
1040
+ const request = await this.deps.provider.getRequest({
1041
+ tenantId: input.tenantId,
1042
+ requestId: input.providerRequestId,
1043
+ ...input.signal ? { signal: input.signal } : {}
1044
+ });
1045
+ this.assertProviderRequest(request, input.tenantId, input.providerRequestId);
1046
+ await this.assertProviderRequestAvailable(execution, request.id);
1047
+ const linkedExecutionId = request.metadata?.smrtExecutionId;
1048
+ if (linkedExecutionId && linkedExecutionId !== execution.id) throw new Error(`Provider request '${request.id}' belongs to a different agreement execution`);
1049
+ execution = await this.bindProviderRequest(execution, request.id);
1050
+ const observedAt = this.now();
1051
+ execution = await this.applyProviderState(execution, request, observedAt, { lastReconciledAt: observedAt });
1052
+ await this.completeAuditedOperation(execution, operationId, "adopt", { providerRequestId: request.id });
1053
+ return this.result(execution, false);
1054
+ } catch (error) {
1055
+ await this.failAuditedOperation(execution, operationId, "adopt", error, false);
1056
+ throw error;
1057
+ }
1058
+ }
1059
+ async reconcile(input) {
1060
+ this.assertTenant(input.tenantId);
1061
+ let execution = await this.requireExecution(input.executionId);
1062
+ this.requireProviderRequestId(execution);
1063
+ const started = await this.beginAuditedOperation(execution, "reconcile", "system");
1064
+ execution = started.execution;
1065
+ const { operationId } = started;
1066
+ try {
1067
+ const request = await this.deps.provider.getRequest({
1068
+ tenantId: input.tenantId,
1069
+ requestId: execution.providerRequestId,
1070
+ ...input.signal ? { signal: input.signal } : {}
1071
+ });
1072
+ this.assertProviderRequest(request, input.tenantId, execution.providerRequestId);
1073
+ const observedAt = this.now();
1074
+ execution = await this.applyProviderState(execution, request, observedAt, { lastReconciledAt: observedAt });
1075
+ if (execution.status === "completed") await this.finalizeExecution(execution);
1076
+ await this.completeAuditedOperation(execution, operationId, "reconcile", { providerStatus: request.status });
1077
+ return this.result(execution, false);
1078
+ } catch (error) {
1079
+ await this.failAuditedOperation(execution, operationId, "reconcile", error, false);
1080
+ throw error;
1081
+ }
1082
+ }
1083
+ async cancel(input) {
1084
+ this.assertTenant(input.tenantId);
1085
+ if (!input.reason.trim()) throw new Error("Cancellation reason is required");
1086
+ let execution = await this.requireExecution(input.executionId);
1087
+ this.requireProviderRequestId(execution);
1088
+ if (execution.status === "cancelled") return this.result(execution, true);
1089
+ this.assertNonTerminalOperation(execution, "cancel");
1090
+ const started = await this.beginAuditedOperation(execution, "cancel", "operator", { reason: input.reason });
1091
+ execution = started.execution;
1092
+ const { operationId } = started;
1093
+ let providerResponded = false;
1094
+ try {
1095
+ this.assertNonTerminalOperation(execution, "cancel");
1096
+ const request = await this.deps.provider.cancelRequest({
1097
+ tenantId: input.tenantId,
1098
+ requestId: execution.providerRequestId,
1099
+ reason: input.reason,
1100
+ ...input.signal ? { signal: input.signal } : {}
1101
+ });
1102
+ providerResponded = true;
1103
+ this.assertProviderRequest(request, input.tenantId, execution.providerRequestId);
1104
+ execution = await this.applyProviderState(execution, request, this.now(), { cancellationReason: input.reason });
1105
+ await this.completeAuditedOperation(execution, operationId, "cancel", { providerStatus: request.status });
1106
+ return this.result(execution, false);
1107
+ } catch (error) {
1108
+ await this.failAuditedOperation(execution, operationId, "cancel", error, true, providerResponded);
1109
+ throw error;
1110
+ }
1111
+ }
1112
+ async extendExpiry(input) {
1113
+ this.assertTenant(input.tenantId);
1114
+ let execution = await this.requireExecution(input.executionId);
1115
+ this.requireProviderRequestId(execution);
1116
+ this.assertNonTerminalOperation(execution, "extend expiry for");
1117
+ const started = await this.beginAuditedOperation(execution, "extend_expiry", "operator", { expiresAt: normalizeRequiredAgreementDate(input.expiresAt).toISOString() });
1118
+ execution = started.execution;
1119
+ const { operationId } = started;
1120
+ let providerResponded = false;
1121
+ try {
1122
+ this.assertNonTerminalOperation(execution, "extend expiry for");
1123
+ const request = await this.deps.provider.extendExpiry({
1124
+ tenantId: input.tenantId,
1125
+ requestId: execution.providerRequestId,
1126
+ expiresAt: input.expiresAt,
1127
+ ...input.warnPrior !== void 0 ? { warnPrior: input.warnPrior } : {},
1128
+ ...input.signal ? { signal: input.signal } : {}
1129
+ });
1130
+ providerResponded = true;
1131
+ this.assertProviderRequest(request, input.tenantId, execution.providerRequestId);
1132
+ execution = await this.applyProviderState(execution, request, this.now());
1133
+ await this.completeAuditedOperation(execution, operationId, "extend_expiry", { expiresAt: request.expiresAt?.toISOString() ?? null });
1134
+ return this.result(execution, false);
1135
+ } catch (error) {
1136
+ await this.failAuditedOperation(execution, operationId, "extend_expiry", error, true, providerResponded);
1137
+ throw error;
1138
+ }
1139
+ }
1140
+ async ingestWebhook(input) {
1141
+ this.assertTenant(input.tenantId);
1142
+ const providerEvent = this.deps.provider.parseWebhook({
1143
+ payload: input.payload,
1144
+ signature: input.signature
1145
+ });
1146
+ this.assertTenantValue(providerEvent.tenantId, input.tenantId, "verified webhook");
1147
+ if (providerEvent.provider !== this.deps.provider.capabilities.id) throw new Error("Verified webhook provider does not match the adapter");
1148
+ let execution = await this.deps.executions.findByProviderRequest(providerEvent.provider, providerEvent.requestId);
1149
+ if (!execution) throw new Error(`No AgreementExecution is bound to provider request '${providerEvent.requestId}'`);
1150
+ const dedupeKey = `${input.tenantId}:${providerEvent.provider}:${providerEvent.replay.deduplicationKey}`;
1151
+ const recorded = await this.deps.events.recordVerified({
1152
+ tenantId: input.tenantId,
1153
+ executionId: execution.id ?? "",
1154
+ provider: providerEvent.provider,
1155
+ providerEventId: providerEvent.id,
1156
+ eventOrigin: "provider_webhook",
1157
+ dedupeKey,
1158
+ orderingKey: providerEvent.replay.orderingKey,
1159
+ eventType: providerEvent.type,
1160
+ status: providerEvent.status,
1161
+ occurredAt: providerEvent.createdAt,
1162
+ receivedAt: this.now(),
1163
+ payloadSha256: sha256(Buffer.from(input.payload, "utf8")),
1164
+ signerEvidence: JSON.stringify(sanitizeSignerEvidence(providerEvent.signers)),
1165
+ payload: input.payload
1166
+ });
1167
+ execution = await this.persistLifecycleState(execution, {
1168
+ status: providerEvent.status,
1169
+ observedAt: providerEvent.createdAt,
1170
+ completedAt: providerEvent.status === "completed" ? providerEvent.createdAt : void 0
1171
+ });
1172
+ let executedAgreement = null;
1173
+ if (execution.status === "completed") {
1174
+ const started = await this.beginAuditedOperation(execution, "finalize", "system", { providerEventId: providerEvent.id });
1175
+ execution = started.execution;
1176
+ const { operationId } = started;
1177
+ try {
1178
+ executedAgreement = await this.finalizeExecution(execution, providerEvent);
1179
+ await this.completeAuditedOperation(execution, operationId, "finalize", { executedAgreementId: executedAgreement.id ?? "" });
1180
+ } catch (error) {
1181
+ await this.failAuditedOperation(execution, operationId, "finalize", error, false);
1182
+ throw error;
1183
+ }
1184
+ }
1185
+ return {
1186
+ executionId: execution.id ?? "",
1187
+ eventId: recorded.event.id ?? "",
1188
+ replayed: !recorded.created,
1189
+ ...executedAgreement?.id ? { executedAgreementId: executedAgreement.id } : {}
1190
+ };
1191
+ }
1192
+ async finalizeExecution(execution, verifiedEvent) {
1193
+ this.assertTenant(execution.tenantId);
1194
+ const existing = await this.deps.executedAgreements.findByExecution(execution.id ?? "");
1195
+ if (existing) {
1196
+ await this.ensureExecutedAgreementAssociations(existing);
1197
+ return existing;
1198
+ }
1199
+ this.requireProviderRequestId(execution);
1200
+ const request = await this.deps.provider.getRequest({
1201
+ tenantId: execution.tenantId,
1202
+ requestId: execution.providerRequestId
1203
+ });
1204
+ this.assertProviderRequest(request, execution.tenantId, execution.providerRequestId);
1205
+ if (request.status !== "completed") throw new Error(`Provider request '${request.id}' is '${request.status}', not completed`);
1206
+ execution = (await this.ensureEvidenceArtifact(execution, "signed_document")).execution;
1207
+ const auditResult = await this.ensureEvidenceArtifact(execution, "audit_trail");
1208
+ execution = await this.requireExecution(auditResult.execution.id ?? "");
1209
+ const signed = this.requireStoredArtifactMetadata(execution, "signed_document");
1210
+ const audit = this.requireStoredArtifactMetadata(execution, "audit_trail");
1211
+ const signerEvidence = sanitizeSignerEvidence(request.signers);
1212
+ const acceptedAt = execution.completedAt ?? verifiedEvent?.createdAt ?? this.now();
1213
+ const created = await this.deps.executedAgreements.createImmutable({
1214
+ tenantId: execution.tenantId,
1215
+ executionId: execution.id ?? "",
1216
+ sourceKind: execution.sourceKind,
1217
+ sourceId: execution.sourceId,
1218
+ sourceVersion: execution.sourceVersion,
1219
+ sourceAssetId: execution.sourceAssetId,
1220
+ sourceSha256: execution.sourceSha256,
1221
+ sourceSizeBytes: execution.sourceSizeBytes,
1222
+ signedDocumentAssetId: signed.assetId,
1223
+ signedDocumentSha256: signed.sha256,
1224
+ signedDocumentSizeBytes: signed.sizeBytes,
1225
+ signedDocumentMediaType: signed.mediaType,
1226
+ signedDocumentFilename: signed.filename,
1227
+ auditTrailAssetId: audit.assetId,
1228
+ auditTrailSha256: audit.sha256,
1229
+ auditTrailSizeBytes: audit.sizeBytes,
1230
+ auditTrailMediaType: audit.mediaType,
1231
+ auditTrailFilename: audit.filename,
1232
+ signerEvidence: JSON.stringify(signerEvidence),
1233
+ acceptedAt,
1234
+ effectiveFrom: execution.effectiveFrom ?? acceptedAt,
1235
+ effectiveTo: execution.effectiveTo,
1236
+ supersedesExecutedAgreementId: execution.supersedesExecutedAgreementId,
1237
+ metadata: execution.metadata
1238
+ });
1239
+ await this.ensureExecutedAgreementAssociations(created.agreement);
1240
+ return created.agreement;
1241
+ }
1242
+ async ensureEvidenceArtifact(execution, kind) {
1243
+ const existing = this.getStoredArtifactMetadata(execution, kind);
1244
+ if (existing) {
1245
+ await this.ensureAssetAssociation("AgreementExecution", execution.id ?? "", existing.assetId, kind);
1246
+ return {
1247
+ execution,
1248
+ metadata: existing
1249
+ };
1250
+ }
1251
+ const stored = await this.retrieveAndStoreArtifact(execution, kind);
1252
+ let bound;
1253
+ try {
1254
+ bound = await this.deps.executions.bindEvidenceArtifact({
1255
+ tenantId: execution.tenantId,
1256
+ executionId: execution.id ?? "",
1257
+ kind,
1258
+ ...stored.metadata
1259
+ });
1260
+ } catch (error) {
1261
+ const latest = await this.requireExecution(execution.id ?? "");
1262
+ if (this.getStoredArtifactMetadata(latest, kind)?.assetId !== stored.asset.id) await this.removeStoredAsset(stored.asset);
1263
+ throw error;
1264
+ }
1265
+ if (!bound) {
1266
+ await this.removeStoredAsset(stored.asset);
1267
+ bound = await this.requireExecution(execution.id ?? "");
1268
+ }
1269
+ const metadata = this.requireStoredArtifactMetadata(bound, kind);
1270
+ await this.ensureAssetAssociation("AgreementExecution", bound.id ?? "", metadata.assetId, kind);
1271
+ return {
1272
+ execution: bound,
1273
+ metadata
1274
+ };
1275
+ }
1276
+ async retrieveAndStoreArtifact(execution, kind) {
1277
+ const artifact = await this.deps.provider.downloadArtifact({
1278
+ tenantId: execution.tenantId,
1279
+ requestId: execution.providerRequestId,
1280
+ kind
1281
+ });
1282
+ this.assertArtifact(artifact, execution, kind);
1283
+ const bytes = await readReadableStream(artifact.stream);
1284
+ if (bytes.byteLength === 0) throw new Error(`Downloaded ${kind} artifact was empty`);
1285
+ const computed = sha256(bytes);
1286
+ if (computed !== (await artifact.sha256).toLowerCase()) throw new Error(`Downloaded ${kind} hash did not match the SDK evidence hash`);
1287
+ const asset = await this.deps.assets.storeSourceAsset(artifact.filename, bytes, {
1288
+ mimeType: artifact.mediaType,
1289
+ typeSlug: "agreement-evidence",
1290
+ sourceType: execution.provider,
1291
+ externalId: `${execution.providerRequestId}:${kind}`,
1292
+ metadata: {
1293
+ executionId: execution.id,
1294
+ provider: execution.provider,
1295
+ providerRequestId: execution.providerRequestId,
1296
+ artifactKind: kind,
1297
+ sha256: computed,
1298
+ retrievedAt: artifact.retrievedAt.toISOString()
1299
+ }
1300
+ });
1301
+ this.assertTenantValue(asset.tenantId, execution.tenantId, `${kind} asset`);
1302
+ if (!asset.id) throw new Error(`Stored ${kind} Asset has no id`);
1303
+ return {
1304
+ asset,
1305
+ metadata: {
1306
+ assetId: asset.id,
1307
+ sha256: computed,
1308
+ sizeBytes: bytes.byteLength,
1309
+ filename: artifact.filename,
1310
+ mediaType: artifact.mediaType
1311
+ }
1312
+ };
1313
+ }
1314
+ getStoredArtifactMetadata(execution, kind) {
1315
+ const metadata = kind === "signed_document" ? {
1316
+ assetId: execution.signedDocumentAssetId,
1317
+ sha256: execution.signedDocumentSha256,
1318
+ sizeBytes: execution.signedDocumentSizeBytes,
1319
+ mediaType: execution.signedDocumentMediaType,
1320
+ filename: execution.signedDocumentFilename
1321
+ } : {
1322
+ assetId: execution.auditTrailAssetId,
1323
+ sha256: execution.auditTrailSha256,
1324
+ sizeBytes: execution.auditTrailSizeBytes,
1325
+ mediaType: execution.auditTrailMediaType,
1326
+ filename: execution.auditTrailFilename
1327
+ };
1328
+ if (!metadata.assetId) return null;
1329
+ if (!metadata.sha256 || !Number.isSafeInteger(metadata.sizeBytes) || metadata.sizeBytes < 1 || !metadata.mediaType || !metadata.filename) throw new Error(`AgreementExecution ${execution.id}: ${kind} evidence is incomplete`);
1330
+ return metadata;
1331
+ }
1332
+ requireStoredArtifactMetadata(execution, kind) {
1333
+ const metadata = this.getStoredArtifactMetadata(execution, kind);
1334
+ if (!metadata) throw new Error(`AgreementExecution ${execution.id}: ${kind} evidence was not bound`);
1335
+ return metadata;
1336
+ }
1337
+ async removeStoredAsset(asset) {
1338
+ await this.deps.assets.store.remove(asset);
1339
+ }
1340
+ async ensureExecutedAgreementAssociations(agreement) {
1341
+ if (!agreement.id) throw new Error("ExecutedAgreement has no id");
1342
+ await this.ensureAssetAssociation("ExecutedAgreement", agreement.id, agreement.sourceAssetId, "source_document");
1343
+ await this.ensureAssetAssociation("ExecutedAgreement", agreement.id, agreement.signedDocumentAssetId, "signed_document");
1344
+ await this.ensureAssetAssociation("ExecutedAgreement", agreement.id, agreement.auditTrailAssetId, "audit_trail");
1345
+ }
1346
+ async applyProviderState(execution, request, observedAt, options = {}) {
1347
+ return await this.persistLifecycleState(execution, {
1348
+ status: request.status,
1349
+ observedAt,
1350
+ enforceEventOrder: false,
1351
+ ...request.expiresAt ? { expiresAt: request.expiresAt } : {},
1352
+ ...options.cancellationReason !== void 0 ? { cancellationReason: options.cancellationReason } : {},
1353
+ ...options.lastReconciledAt ? { lastReconciledAt: options.lastReconciledAt } : {},
1354
+ ...request.status === "completed" ? { completedAt: observedAt } : {}
1355
+ });
1356
+ }
1357
+ async persistLifecycleState(execution, input) {
1358
+ const executionId = execution.id ?? "";
1359
+ let current = await this.requireExecution(executionId);
1360
+ for (let attempt = 0; attempt < 8; attempt += 1) {
1361
+ if (input.enforceEventOrder !== false && current.lastProviderEventAt && input.observedAt < current.lastProviderEventAt) return current;
1362
+ if (!canAdvanceStatus(current.status, input.status)) return current;
1363
+ const persistedObservedAt = input.enforceEventOrder === false && current.lastProviderEventAt && current.lastProviderEventAt > input.observedAt ? current.lastProviderEventAt : input.observedAt;
1364
+ const updated = await this.deps.executions.compareAndSetLifecycle({
1365
+ tenantId: current.tenantId,
1366
+ executionId,
1367
+ expectedStatus: current.status,
1368
+ status: input.status,
1369
+ expiresAt: input.expiresAt ?? current.expiresAt,
1370
+ cancellationReason: input.cancellationReason ?? current.cancellationReason,
1371
+ lastProviderEventAt: persistedObservedAt,
1372
+ lastReconciledAt: input.lastReconciledAt ?? current.lastReconciledAt,
1373
+ completedAt: input.status === "completed" ? current.completedAt ?? input.completedAt ?? input.observedAt : current.completedAt
1374
+ });
1375
+ if (updated) return updated;
1376
+ current = await this.requireExecution(executionId);
1377
+ }
1378
+ throw new Error(`AgreementExecution ${executionId}: lifecycle state remained contended; retry the operation`);
1379
+ }
1380
+ assertProviderRequest(request, tenantId, expectedRequestId) {
1381
+ this.assertTenantValue(request.tenantId, tenantId, "provider request");
1382
+ if (request.provider !== this.deps.provider.capabilities.id) throw new Error("Provider request does not match the configured adapter");
1383
+ if (!request.id) throw new Error("Provider request has no id");
1384
+ if (expectedRequestId && request.id !== expectedRequestId) throw new Error(`Provider returned request '${request.id}' while '${expectedRequestId}' was requested`);
1385
+ }
1386
+ assertArtifact(artifact, execution, kind) {
1387
+ this.assertTenantValue(artifact.tenantId, execution.tenantId, kind);
1388
+ if (artifact.provider !== execution.provider || artifact.requestId !== execution.providerRequestId || artifact.kind !== kind) throw new Error(`Downloaded ${kind} does not match its execution`);
1389
+ }
1390
+ assertProviderCapabilities() {
1391
+ const caps = this.deps.provider.capabilities;
1392
+ if (!caps.supportsWebhooks || !caps.supportsCancellation || !caps.supportsExpiryExtension || !caps.supportsSignedDocument || !caps.supportsAuditTrail) throw new Error(`Signature provider '${caps.id}' lacks required agreement-execution capabilities`);
1393
+ }
1394
+ assertSameIntent(existing, input, requestIntentSha256) {
1395
+ if (existing.provider !== this.deps.provider.capabilities.id || existing.sourceKind !== input.sourceKind || existing.sourceId !== input.sourceId || existing.sourceVersion !== input.sourceVersion || existing.title !== input.title || existing.requestIntentSha256 !== requestIntentSha256) throw new Error(`Idempotency key '${input.idempotencyKey}' belongs to a different agreement execution`);
1396
+ }
1397
+ assertCreateInput(input) {
1398
+ for (const [label, value] of [
1399
+ ["idempotencyKey", input.idempotencyKey],
1400
+ ["sourceKind", input.sourceKind],
1401
+ ["sourceId", input.sourceId],
1402
+ ["title", input.title],
1403
+ ["document.name", input.document.name],
1404
+ ["document.mediaType", input.document.mediaType]
1405
+ ]) if (!value.trim()) throw new Error(`Agreement execution ${label} is required`);
1406
+ if (!Number.isSafeInteger(input.sourceVersion) || input.sourceVersion < 1) throw new Error("Agreement execution sourceVersion must be a positive integer");
1407
+ if (input.expiresInDays !== void 0 && (!Number.isSafeInteger(input.expiresInDays) || input.expiresInDays < 1)) throw new Error("Agreement execution expiresInDays must be a positive integer");
1408
+ if (input.credentialRef && !/^[a-z][a-z0-9+.-]*:\/\//i.test(input.credentialRef)) throw new Error("Agreement execution credentialRef must be a secret-store reference URI");
1409
+ if (input.signers.length === 0) throw new Error("Agreement execution requires at least one signer");
1410
+ }
1411
+ async requireExecution(id) {
1412
+ const execution = await this.deps.executions.get({ id });
1413
+ if (!execution) throw new Error(`AgreementExecution '${id}' was not found`);
1414
+ return execution;
1415
+ }
1416
+ requireProviderRequestId(execution) {
1417
+ if (!execution.providerRequestId) throw new Error(`AgreementExecution ${execution.id}: provider request is not confirmed; reconcile or adopt it first`);
1418
+ }
1419
+ async assertProviderRequestAvailable(execution, providerRequestId) {
1420
+ const bound = await this.deps.executions.findByProviderRequest(execution.provider, providerRequestId);
1421
+ if (bound && bound.id !== execution.id) throw new Error(`Provider request '${providerRequestId}' is already bound to another AgreementExecution`);
1422
+ }
1423
+ async bindProviderRequest(execution, providerRequestId) {
1424
+ if (execution.providerRequestId === providerRequestId) return execution;
1425
+ const bound = await this.deps.executions.bindProviderRequest({
1426
+ tenantId: execution.tenantId,
1427
+ executionId: execution.id ?? "",
1428
+ provider: execution.provider,
1429
+ providerRequestId
1430
+ });
1431
+ if (!bound) {
1432
+ const current = await this.requireExecution(execution.id ?? "");
1433
+ if (current.providerRequestId === providerRequestId) return current;
1434
+ throw new Error(`AgreementExecution ${execution.id}: provider request binding changed concurrently`);
1435
+ }
1436
+ return bound;
1437
+ }
1438
+ canSafelyRetryCreate(execution) {
1439
+ if (execution.attemptCount === 0 && execution.status === "prepared") return true;
1440
+ try {
1441
+ return JSON.parse(execution.lastError).requestMayHaveSucceeded === false;
1442
+ } catch {
1443
+ return false;
1444
+ }
1445
+ }
1446
+ isAbandonedCreate(execution) {
1447
+ return execution.status === "prepared" && execution.attemptCount > 0 && !execution.providerRequestId && !execution.lastError;
1448
+ }
1449
+ isCreateLeaseActive(execution, at) {
1450
+ return Boolean(this.isAbandonedCreate(execution) && execution.createLeaseId && execution.createLeaseExpiresAt && execution.createLeaseExpiresAt.getTime() > at.getTime());
1451
+ }
1452
+ newCreateLeaseExpiry(from = this.now()) {
1453
+ return new Date(from.getTime() + this.createLeaseDurationMs);
1454
+ }
1455
+ async failOwnedCreateAttempt(execution, operationId, lastError) {
1456
+ return await this.deps.executions.failCreateAttempt({
1457
+ tenantId: execution.tenantId,
1458
+ executionId: execution.id ?? "",
1459
+ operationId,
1460
+ lastError
1461
+ }) ?? await this.requireExecution(execution.id ?? "");
1462
+ }
1463
+ async ensureAssetAssociation(ownerType, ownerId, assetId, role) {
1464
+ if (!ownerId || !assetId) throw new Error(`Cannot link ${role}: owner and Asset ids are required`);
1465
+ await this.deps.assets.associations.attach(`@happyvertical/smrt-sales:${ownerType}`, ownerId, assetId, { role });
1466
+ }
1467
+ async beginAuditedOperation(execution, operation, origin, metadata = {}) {
1468
+ const current = await this.deps.executions.incrementAttemptCount(execution.tenantId, execution.id ?? "");
1469
+ const operationId = randomUUID();
1470
+ try {
1471
+ await this.recordOperationAudit(current, operationId, `${operation}.started`, origin, {
1472
+ attemptCount: current.attemptCount,
1473
+ ...metadata
1474
+ });
1475
+ } catch (error) {
1476
+ await this.deps.executions.updateLastError({
1477
+ tenantId: current.tenantId,
1478
+ executionId: current.id ?? "",
1479
+ lastError: summarizeError(error, false)
1480
+ });
1481
+ throw error;
1482
+ }
1483
+ return {
1484
+ execution: current,
1485
+ operationId
1486
+ };
1487
+ }
1488
+ async completeAuditedOperation(execution, operationId, operation, metadata) {
1489
+ await this.recordOperationAudit(execution, operationId, `${operation}.succeeded`, "provider_operation", metadata);
1490
+ }
1491
+ async failAuditedOperation(execution, operationId, operation, error, mayMutateProvider, providerMutationConfirmed = false) {
1492
+ const current = await this.deps.executions.updateLastError({
1493
+ tenantId: execution.tenantId,
1494
+ executionId: execution.id ?? "",
1495
+ lastError: summarizeError(error)
1496
+ });
1497
+ const uncertain = mayMutateProvider && (providerMutationConfirmed || isPotentiallyUncertain(error));
1498
+ await this.recordOperationAudit(current, operationId, `${operation}.${uncertain ? "uncertain" : "failed"}`, "provider_operation", { error: current.lastError });
1499
+ }
1500
+ async recordOperationAudit(execution, operationId, eventType, eventOrigin, metadata) {
1501
+ const occurredAt = this.now();
1502
+ const payload = JSON.stringify(metadata);
1503
+ await this.deps.events.recordVerified({
1504
+ tenantId: execution.tenantId,
1505
+ executionId: execution.id ?? "",
1506
+ provider: execution.provider,
1507
+ providerEventId: "",
1508
+ eventOrigin,
1509
+ operationId,
1510
+ dedupeKey: `${execution.tenantId}:${execution.provider}:operation:${operationId}:${eventType}`,
1511
+ orderingKey: execution.providerRequestId || execution.id || operationId,
1512
+ eventType: `operation.${eventType}`,
1513
+ status: execution.status,
1514
+ occurredAt,
1515
+ receivedAt: occurredAt,
1516
+ payloadSha256: sha256(Buffer.from(payload, "utf8")),
1517
+ signerEvidence: "[]",
1518
+ payload
1519
+ });
1520
+ }
1521
+ assertNonTerminalOperation(execution, operation) {
1522
+ if (TERMINAL_STATUSES.has(execution.status)) throw new Error(`Cannot ${operation} terminal AgreementExecution ${execution.id} in status '${execution.status}'`);
1523
+ }
1524
+ result(execution, replayed) {
1525
+ return {
1526
+ executionId: execution.id ?? "",
1527
+ ...execution.providerRequestId ? { providerRequestId: execution.providerRequestId } : {},
1528
+ status: execution.status,
1529
+ replayed
1530
+ };
1531
+ }
1532
+ assertTenant(expected) {
1533
+ this.assertTenantValue(requireTenantId(), expected, "tenant context");
1534
+ }
1535
+ assertTenantValue(actual, expected, context) {
1536
+ if (actual !== expected) throw new Error(`Agreement execution tenant mismatch for ${context}: expected '${expected}'`);
1537
+ }
1538
+ };
1539
+ function sha256(data) {
1540
+ return createHash("sha256").update(data).digest("hex");
1541
+ }
1542
+ function hashRequestIntent(input, provider, sourceSha256, effectiveFrom, effectiveTo) {
1543
+ const sortedMetadata = Object.fromEntries(Object.entries(input.metadata ?? {}).sort(([left], [right]) => left.localeCompare(right)));
1544
+ return sha256(Buffer.from(JSON.stringify({
1545
+ provider,
1546
+ providerAccountRef: input.providerAccountRef ?? "",
1547
+ credentialRef: input.credentialRef ?? "",
1548
+ sourceKind: input.sourceKind,
1549
+ sourceId: input.sourceId,
1550
+ sourceVersion: input.sourceVersion,
1551
+ sourceSha256,
1552
+ documentName: input.document.name,
1553
+ documentMediaType: input.document.mediaType,
1554
+ title: input.title,
1555
+ messageSha256: input.message ? sha256(Buffer.from(input.message, "utf8")) : "",
1556
+ signers: input.signers.map((signer) => ({
1557
+ ...sanitizeSignerIntent([signer])[0],
1558
+ privateMessageSha256: signer.privateMessage ? sha256(Buffer.from(signer.privateMessage, "utf8")) : "",
1559
+ phoneSha256: signer.authentication?.phone ? sha256(Buffer.from(`${signer.authentication.phone.countryCode}:${signer.authentication.phone.number}`, "utf8")) : "",
1560
+ identityVerification: signer.authentication?.identityVerification ?? null,
1561
+ fields: signer.fields.map((field) => ({
1562
+ id: field.id,
1563
+ type: field.type,
1564
+ page: field.page,
1565
+ bounds: field.bounds,
1566
+ required: field.required ?? true,
1567
+ valueSha256: field.value ? sha256(Buffer.from(field.value, "utf8")) : ""
1568
+ }))
1569
+ })),
1570
+ signingOrder: input.signingOrder ?? false,
1571
+ expiresInDays: input.expiresInDays ?? null,
1572
+ effectiveFrom: effectiveFrom?.toISOString() ?? null,
1573
+ effectiveTo: effectiveTo?.toISOString() ?? null,
1574
+ supersedesExecutedAgreementId: input.supersedesExecutedAgreementId ?? "",
1575
+ metadata: sortedMetadata
1576
+ }), "utf8"));
1577
+ }
1578
+ function normalizeOptionalAgreementDate(value, label) {
1579
+ if (value == null) return null;
1580
+ const date = coerceAgreementDate(value);
1581
+ if (!date) throw new Error(`Agreement execution ${label} is invalid`);
1582
+ return date;
1583
+ }
1584
+ function normalizeRequiredAgreementDate(value) {
1585
+ const date = coerceAgreementDate(value);
1586
+ if (!date) throw new Error("Agreement execution expiresAt is invalid");
1587
+ return date;
1588
+ }
1589
+ async function readByteSource(source) {
1590
+ if (source instanceof Uint8Array) return Buffer.from(source);
1591
+ if (isReadableStream(source)) return await readReadableStream(source);
1592
+ const chunks = [];
1593
+ for await (const chunk of source) chunks.push(chunk);
1594
+ return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)));
1595
+ }
1596
+ async function readReadableStream(stream) {
1597
+ const reader = stream.getReader();
1598
+ const chunks = [];
1599
+ try {
1600
+ while (true) {
1601
+ const result = await reader.read();
1602
+ if (result.done) break;
1603
+ chunks.push(Buffer.from(result.value));
1604
+ }
1605
+ } finally {
1606
+ reader.releaseLock();
1607
+ }
1608
+ return Buffer.concat(chunks);
1609
+ }
1610
+ function isReadableStream(value) {
1611
+ return typeof value.getReader === "function";
1612
+ }
1613
+ function summarizeError(error, requestMayHaveSucceededOverride) {
1614
+ const candidate = error && typeof error === "object" ? error : {};
1615
+ return JSON.stringify({
1616
+ name: typeof candidate.name === "string" ? candidate.name : "Error",
1617
+ ...typeof candidate.code === "string" ? { code: candidate.code } : {},
1618
+ ...typeof candidate.status === "number" ? { status: candidate.status } : {},
1619
+ ...typeof candidate.retryable === "boolean" ? { retryable: candidate.retryable } : {},
1620
+ ...requestMayHaveSucceededOverride !== void 0 ? { requestMayHaveSucceeded: requestMayHaveSucceededOverride } : typeof candidate.requestMayHaveSucceeded === "boolean" ? { requestMayHaveSucceeded: candidate.requestMayHaveSucceeded } : {}
1621
+ });
1622
+ }
1623
+ function requestMayHaveSucceeded(error) {
1624
+ return Boolean(error && typeof error === "object" && error.requestMayHaveSucceeded === true);
1625
+ }
1626
+ function isPotentiallyUncertain(error) {
1627
+ if (requestMayHaveSucceeded(error)) return true;
1628
+ return Boolean(error && typeof error === "object" && error.retryable === true);
1629
+ }
1630
+ function canAdvanceStatus(current, next) {
1631
+ if (current === next) return true;
1632
+ if (current === "failed") return true;
1633
+ if (TERMINAL_STATUSES.has(current)) return false;
1634
+ if (TERMINAL_STATUSES.has(next)) return true;
1635
+ return (STATUS_PROGRESS.get(next) ?? -1) >= (STATUS_PROGRESS.get(current) ?? -1);
1636
+ }
1637
+ //#endregion
1638
+ export { AgreementExecutionEvent as a, AGREEMENT_EXECUTION_STATUSES as c, sanitizeSignerIntent as d, AgreementExecutionEventCollection as i, coerceAgreementDate as l, ExecutedAgreementCollection as n, AgreementExecutionCollection as o, ExecutedAgreement as r, AgreementExecution as s, AgreementExecutionService as t, sanitizeSignerEvidence as u };
1639
+
1640
+ //# sourceMappingURL=agreements-DbzW1Pcq.js.map