@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
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-07-15T06:00:05.222Z",
3
+ "generatedAt": "2026-07-15T07:44:10.429Z",
4
4
  "packageName": "@happyvertical/smrt-sales",
5
- "packageVersion": "0.40.0",
5
+ "packageVersion": "0.40.2",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "d064ad9d003895cb4cba97babf183f8293e7299d531aed09bdac959011808d80",
10
- "packageJson": "a5de39608fc5e0b58b181cd794117d1fc58b7cb2e859fdd2292f5ec8ce6f22e7",
11
- "agents": "c5fc29e1fc3076ba7ed4207638cde96876f220c4661b831ccfa3e03b5b291a5c"
9
+ "manifest": "624ac5707cc54575942ba85317caa3be35e1c1092911220059752c36614209c5",
10
+ "packageJson": "72e8823437b4bad3c5b474063cd3fdf9f7f5d6d8a3df14bc42555c4157ee14bf",
11
+ "agents": "c9058f7518134426831d1e292e6bba9e661e5f5be1d72d9bd33df2a4148d9489"
12
12
  },
13
13
  "exports": [
14
14
  ".",
15
+ "./agreements",
15
16
  "./commissions",
16
17
  "./crm",
17
18
  "./manifest",
@@ -20,6 +21,8 @@
20
21
  "./svelte"
21
22
  ],
22
23
  "dependencies": {
24
+ "@happyvertical/signatures": "catalog:",
25
+ "@happyvertical/smrt-assets": "workspace:*",
23
26
  "@happyvertical/smrt-core": "workspace:*",
24
27
  "@happyvertical/smrt-tenancy": "workspace:*",
25
28
  "@happyvertical/smrt-ui": "workspace:*",
@@ -35,6 +38,7 @@
35
38
  "vitest": "^4.1.9"
36
39
  },
37
40
  "smrtDependencies": [
41
+ "@happyvertical/smrt-assets",
38
42
  "@happyvertical/smrt-core",
39
43
  "@happyvertical/smrt-tenancy",
40
44
  "@happyvertical/smrt-ui",
@@ -46,6 +50,726 @@
46
50
  "tags": [],
47
51
  "risks": [],
48
52
  "objects": [
53
+ {
54
+ "name": "AgreementExecutionCollection",
55
+ "qualifiedName": "@happyvertical/smrt-sales:AgreementExecutionCollection",
56
+ "collection": "agreementexecutions",
57
+ "tableName": "agreement_execution_collections",
58
+ "packageName": "@happyvertical/smrt-sales",
59
+ "extends": "SmrtCollection",
60
+ "fields": [],
61
+ "relationships": [],
62
+ "methods": [
63
+ "bindEvidenceArtifact",
64
+ "bindProviderRequest",
65
+ "claimCreateAttempt",
66
+ "compareAndSetLifecycle",
67
+ "completeCreateAttempt",
68
+ "failCreateAttempt",
69
+ "findByIdempotencyKey",
70
+ "findByProviderRequest",
71
+ "findBySource",
72
+ "incrementAttemptCount",
73
+ "updateLastError"
74
+ ],
75
+ "surfaces": [],
76
+ "relationshipFeatures": [
77
+ "uuidColumns"
78
+ ],
79
+ "tags": [],
80
+ "risks": []
81
+ },
82
+ {
83
+ "name": "AgreementExecutionEventCollection",
84
+ "qualifiedName": "@happyvertical/smrt-sales:AgreementExecutionEventCollection",
85
+ "collection": "agreementexecutionevents",
86
+ "tableName": "agreement_execution_event_collections",
87
+ "packageName": "@happyvertical/smrt-sales",
88
+ "extends": "SmrtCollection",
89
+ "fields": [],
90
+ "relationships": [],
91
+ "methods": [
92
+ "findByDedupeKey",
93
+ "findByExecution",
94
+ "findProviderEventsByExecution",
95
+ "recordVerified"
96
+ ],
97
+ "surfaces": [],
98
+ "relationshipFeatures": [
99
+ "uuidColumns"
100
+ ],
101
+ "tags": [],
102
+ "risks": []
103
+ },
104
+ {
105
+ "name": "ExecutedAgreementCollection",
106
+ "qualifiedName": "@happyvertical/smrt-sales:ExecutedAgreementCollection",
107
+ "collection": "executedagreements",
108
+ "tableName": "executed_agreement_collections",
109
+ "packageName": "@happyvertical/smrt-sales",
110
+ "extends": "SmrtCollection",
111
+ "fields": [],
112
+ "relationships": [],
113
+ "methods": [
114
+ "createImmutable",
115
+ "effectiveForSource",
116
+ "findByExecution",
117
+ "findVersionsBySource"
118
+ ],
119
+ "surfaces": [],
120
+ "relationshipFeatures": [
121
+ "uuidColumns"
122
+ ],
123
+ "tags": [],
124
+ "risks": []
125
+ },
126
+ {
127
+ "name": "AgreementExecution",
128
+ "qualifiedName": "@happyvertical/smrt-sales:AgreementExecution",
129
+ "collection": "agreementexecutions",
130
+ "tableName": "agreement_executions",
131
+ "packageName": "@happyvertical/smrt-sales",
132
+ "extends": "SmrtObject",
133
+ "fields": [
134
+ {
135
+ "name": "tenantId",
136
+ "type": "text",
137
+ "required": true,
138
+ "columnType": "UUID"
139
+ },
140
+ {
141
+ "name": "provider",
142
+ "type": "text",
143
+ "required": true,
144
+ "columnType": "TEXT"
145
+ },
146
+ {
147
+ "name": "providerAccountRef",
148
+ "type": "text",
149
+ "required": false,
150
+ "columnType": "TEXT"
151
+ },
152
+ {
153
+ "name": "credentialRef",
154
+ "type": "text",
155
+ "required": false,
156
+ "columnType": "TEXT"
157
+ },
158
+ {
159
+ "name": "idempotencyKey",
160
+ "type": "text",
161
+ "required": true,
162
+ "columnType": "TEXT"
163
+ },
164
+ {
165
+ "name": "sourceKind",
166
+ "type": "text",
167
+ "required": true,
168
+ "columnType": "TEXT"
169
+ },
170
+ {
171
+ "name": "sourceId",
172
+ "type": "text",
173
+ "required": true,
174
+ "columnType": "TEXT"
175
+ },
176
+ {
177
+ "name": "sourceVersion",
178
+ "type": "integer",
179
+ "required": false,
180
+ "columnType": "INTEGER"
181
+ },
182
+ {
183
+ "name": "sourceAssetId",
184
+ "type": "crossPackageRef",
185
+ "required": false,
186
+ "related": "@happyvertical/smrt-assets:Asset",
187
+ "columnType": "UUID"
188
+ },
189
+ {
190
+ "name": "sourceSha256",
191
+ "type": "text",
192
+ "required": false,
193
+ "columnType": "TEXT"
194
+ },
195
+ {
196
+ "name": "sourceSizeBytes",
197
+ "type": "integer",
198
+ "required": false,
199
+ "columnType": "INTEGER"
200
+ },
201
+ {
202
+ "name": "requestIntentSha256",
203
+ "type": "text",
204
+ "required": false,
205
+ "columnType": "TEXT"
206
+ },
207
+ {
208
+ "name": "title",
209
+ "type": "text",
210
+ "required": false,
211
+ "columnType": "TEXT"
212
+ },
213
+ {
214
+ "name": "signerIntent",
215
+ "type": "text",
216
+ "required": false,
217
+ "columnType": "TEXT"
218
+ },
219
+ {
220
+ "name": "providerRequestId",
221
+ "type": "text",
222
+ "required": false,
223
+ "columnType": "TEXT"
224
+ },
225
+ {
226
+ "name": "providerRequestKey",
227
+ "type": "text",
228
+ "required": false,
229
+ "columnType": "TEXT"
230
+ },
231
+ {
232
+ "name": "status",
233
+ "type": "text",
234
+ "required": false,
235
+ "columnType": "TEXT"
236
+ },
237
+ {
238
+ "name": "expiresAt",
239
+ "type": "datetime",
240
+ "required": false,
241
+ "columnType": "TIMESTAMP"
242
+ },
243
+ {
244
+ "name": "cancellationReason",
245
+ "type": "text",
246
+ "required": false,
247
+ "columnType": "TEXT"
248
+ },
249
+ {
250
+ "name": "lastProviderEventAt",
251
+ "type": "datetime",
252
+ "required": false,
253
+ "columnType": "TIMESTAMP"
254
+ },
255
+ {
256
+ "name": "lastReconciledAt",
257
+ "type": "datetime",
258
+ "required": false,
259
+ "columnType": "TIMESTAMP"
260
+ },
261
+ {
262
+ "name": "completedAt",
263
+ "type": "datetime",
264
+ "required": false,
265
+ "columnType": "TIMESTAMP"
266
+ },
267
+ {
268
+ "name": "effectiveFrom",
269
+ "type": "datetime",
270
+ "required": false,
271
+ "columnType": "TIMESTAMP"
272
+ },
273
+ {
274
+ "name": "effectiveTo",
275
+ "type": "datetime",
276
+ "required": false,
277
+ "columnType": "TIMESTAMP"
278
+ },
279
+ {
280
+ "name": "supersedesExecutedAgreementId",
281
+ "type": "text",
282
+ "required": false,
283
+ "columnType": "TEXT"
284
+ },
285
+ {
286
+ "name": "signedDocumentAssetId",
287
+ "type": "crossPackageRef",
288
+ "required": false,
289
+ "related": "@happyvertical/smrt-assets:Asset",
290
+ "columnType": "UUID"
291
+ },
292
+ {
293
+ "name": "signedDocumentSha256",
294
+ "type": "text",
295
+ "required": false,
296
+ "columnType": "TEXT"
297
+ },
298
+ {
299
+ "name": "signedDocumentSizeBytes",
300
+ "type": "integer",
301
+ "required": false,
302
+ "columnType": "INTEGER"
303
+ },
304
+ {
305
+ "name": "signedDocumentMediaType",
306
+ "type": "text",
307
+ "required": false,
308
+ "columnType": "TEXT"
309
+ },
310
+ {
311
+ "name": "signedDocumentFilename",
312
+ "type": "text",
313
+ "required": false,
314
+ "columnType": "TEXT"
315
+ },
316
+ {
317
+ "name": "auditTrailAssetId",
318
+ "type": "crossPackageRef",
319
+ "required": false,
320
+ "related": "@happyvertical/smrt-assets:Asset",
321
+ "columnType": "UUID"
322
+ },
323
+ {
324
+ "name": "auditTrailSha256",
325
+ "type": "text",
326
+ "required": false,
327
+ "columnType": "TEXT"
328
+ },
329
+ {
330
+ "name": "auditTrailSizeBytes",
331
+ "type": "integer",
332
+ "required": false,
333
+ "columnType": "INTEGER"
334
+ },
335
+ {
336
+ "name": "auditTrailMediaType",
337
+ "type": "text",
338
+ "required": false,
339
+ "columnType": "TEXT"
340
+ },
341
+ {
342
+ "name": "auditTrailFilename",
343
+ "type": "text",
344
+ "required": false,
345
+ "columnType": "TEXT"
346
+ },
347
+ {
348
+ "name": "attemptCount",
349
+ "type": "integer",
350
+ "required": false,
351
+ "columnType": "INTEGER"
352
+ },
353
+ {
354
+ "name": "createLeaseId",
355
+ "type": "text",
356
+ "required": false,
357
+ "columnType": "TEXT"
358
+ },
359
+ {
360
+ "name": "createLeaseExpiresAt",
361
+ "type": "datetime",
362
+ "required": false,
363
+ "columnType": "TIMESTAMP"
364
+ },
365
+ {
366
+ "name": "lastError",
367
+ "type": "text",
368
+ "required": false,
369
+ "columnType": "TEXT"
370
+ },
371
+ {
372
+ "name": "metadata",
373
+ "type": "text",
374
+ "required": false,
375
+ "columnType": "TEXT"
376
+ }
377
+ ],
378
+ "relationships": [
379
+ {
380
+ "name": "sourceAssetId",
381
+ "type": "crossPackageRef",
382
+ "required": false,
383
+ "related": "@happyvertical/smrt-assets:Asset",
384
+ "columnType": "UUID"
385
+ },
386
+ {
387
+ "name": "signedDocumentAssetId",
388
+ "type": "crossPackageRef",
389
+ "required": false,
390
+ "related": "@happyvertical/smrt-assets:Asset",
391
+ "columnType": "UUID"
392
+ },
393
+ {
394
+ "name": "auditTrailAssetId",
395
+ "type": "crossPackageRef",
396
+ "required": false,
397
+ "related": "@happyvertical/smrt-assets:Asset",
398
+ "columnType": "UUID"
399
+ }
400
+ ],
401
+ "methods": [
402
+ "getSignerIntent",
403
+ "initialize",
404
+ "save"
405
+ ],
406
+ "surfaces": [],
407
+ "relationshipFeatures": [
408
+ "crossPackageRef",
409
+ "uuidColumns"
410
+ ],
411
+ "tags": [],
412
+ "risks": []
413
+ },
414
+ {
415
+ "name": "AgreementExecutionEvent",
416
+ "qualifiedName": "@happyvertical/smrt-sales:AgreementExecutionEvent",
417
+ "collection": "agreementexecutionevents",
418
+ "tableName": "agreement_execution_events",
419
+ "packageName": "@happyvertical/smrt-sales",
420
+ "extends": "SmrtObject",
421
+ "fields": [
422
+ {
423
+ "name": "tenantId",
424
+ "type": "text",
425
+ "required": true,
426
+ "columnType": "UUID"
427
+ },
428
+ {
429
+ "name": "executionId",
430
+ "type": "foreignKey",
431
+ "required": true,
432
+ "related": "AgreementExecution",
433
+ "columnType": "UUID"
434
+ },
435
+ {
436
+ "name": "provider",
437
+ "type": "text",
438
+ "required": false,
439
+ "columnType": "TEXT"
440
+ },
441
+ {
442
+ "name": "providerEventId",
443
+ "type": "text",
444
+ "required": false,
445
+ "columnType": "TEXT"
446
+ },
447
+ {
448
+ "name": "eventOrigin",
449
+ "type": "text",
450
+ "required": false,
451
+ "columnType": "TEXT"
452
+ },
453
+ {
454
+ "name": "operationId",
455
+ "type": "text",
456
+ "required": false,
457
+ "columnType": "TEXT"
458
+ },
459
+ {
460
+ "name": "dedupeKey",
461
+ "type": "text",
462
+ "required": true,
463
+ "columnType": "TEXT"
464
+ },
465
+ {
466
+ "name": "orderingKey",
467
+ "type": "text",
468
+ "required": false,
469
+ "columnType": "TEXT"
470
+ },
471
+ {
472
+ "name": "eventType",
473
+ "type": "text",
474
+ "required": false,
475
+ "columnType": "TEXT"
476
+ },
477
+ {
478
+ "name": "status",
479
+ "type": "text",
480
+ "required": false,
481
+ "columnType": "TEXT"
482
+ },
483
+ {
484
+ "name": "occurredAt",
485
+ "type": "datetime",
486
+ "required": false,
487
+ "columnType": "TIMESTAMP"
488
+ },
489
+ {
490
+ "name": "receivedAt",
491
+ "type": "datetime",
492
+ "required": false,
493
+ "columnType": "TIMESTAMP"
494
+ },
495
+ {
496
+ "name": "payloadSha256",
497
+ "type": "text",
498
+ "required": false,
499
+ "columnType": "TEXT"
500
+ },
501
+ {
502
+ "name": "signerEvidence",
503
+ "type": "text",
504
+ "required": false,
505
+ "columnType": "TEXT"
506
+ },
507
+ {
508
+ "name": "payload",
509
+ "type": "text",
510
+ "required": false,
511
+ "columnType": "TEXT"
512
+ }
513
+ ],
514
+ "relationships": [
515
+ {
516
+ "name": "executionId",
517
+ "type": "foreignKey",
518
+ "required": true,
519
+ "related": "AgreementExecution",
520
+ "columnType": "UUID"
521
+ }
522
+ ],
523
+ "methods": [
524
+ "initialize",
525
+ "save"
526
+ ],
527
+ "surfaces": [],
528
+ "relationshipFeatures": [
529
+ "foreignKey",
530
+ "uuidColumns"
531
+ ],
532
+ "tags": [],
533
+ "risks": []
534
+ },
535
+ {
536
+ "name": "ExecutedAgreement",
537
+ "qualifiedName": "@happyvertical/smrt-sales:ExecutedAgreement",
538
+ "collection": "executedagreements",
539
+ "tableName": "executed_agreements",
540
+ "packageName": "@happyvertical/smrt-sales",
541
+ "extends": "SmrtObject",
542
+ "fields": [
543
+ {
544
+ "name": "tenantId",
545
+ "type": "text",
546
+ "required": true,
547
+ "columnType": "UUID"
548
+ },
549
+ {
550
+ "name": "executionId",
551
+ "type": "foreignKey",
552
+ "required": true,
553
+ "related": "AgreementExecution",
554
+ "columnType": "UUID"
555
+ },
556
+ {
557
+ "name": "sourceKind",
558
+ "type": "text",
559
+ "required": true,
560
+ "columnType": "TEXT"
561
+ },
562
+ {
563
+ "name": "sourceId",
564
+ "type": "text",
565
+ "required": true,
566
+ "columnType": "TEXT"
567
+ },
568
+ {
569
+ "name": "sourceVersion",
570
+ "type": "integer",
571
+ "required": false,
572
+ "columnType": "INTEGER"
573
+ },
574
+ {
575
+ "name": "sourceAssetId",
576
+ "type": "crossPackageRef",
577
+ "required": false,
578
+ "related": "@happyvertical/smrt-assets:Asset",
579
+ "columnType": "UUID"
580
+ },
581
+ {
582
+ "name": "sourceSha256",
583
+ "type": "text",
584
+ "required": false,
585
+ "columnType": "TEXT"
586
+ },
587
+ {
588
+ "name": "sourceSizeBytes",
589
+ "type": "integer",
590
+ "required": false,
591
+ "columnType": "INTEGER"
592
+ },
593
+ {
594
+ "name": "signedDocumentAssetId",
595
+ "type": "crossPackageRef",
596
+ "required": false,
597
+ "related": "@happyvertical/smrt-assets:Asset",
598
+ "columnType": "UUID"
599
+ },
600
+ {
601
+ "name": "signedDocumentSha256",
602
+ "type": "text",
603
+ "required": false,
604
+ "columnType": "TEXT"
605
+ },
606
+ {
607
+ "name": "signedDocumentSizeBytes",
608
+ "type": "integer",
609
+ "required": false,
610
+ "columnType": "INTEGER"
611
+ },
612
+ {
613
+ "name": "signedDocumentMediaType",
614
+ "type": "text",
615
+ "required": false,
616
+ "columnType": "TEXT"
617
+ },
618
+ {
619
+ "name": "signedDocumentFilename",
620
+ "type": "text",
621
+ "required": false,
622
+ "columnType": "TEXT"
623
+ },
624
+ {
625
+ "name": "auditTrailAssetId",
626
+ "type": "crossPackageRef",
627
+ "required": false,
628
+ "related": "@happyvertical/smrt-assets:Asset",
629
+ "columnType": "UUID"
630
+ },
631
+ {
632
+ "name": "auditTrailSha256",
633
+ "type": "text",
634
+ "required": false,
635
+ "columnType": "TEXT"
636
+ },
637
+ {
638
+ "name": "auditTrailSizeBytes",
639
+ "type": "integer",
640
+ "required": false,
641
+ "columnType": "INTEGER"
642
+ },
643
+ {
644
+ "name": "auditTrailMediaType",
645
+ "type": "text",
646
+ "required": false,
647
+ "columnType": "TEXT"
648
+ },
649
+ {
650
+ "name": "auditTrailFilename",
651
+ "type": "text",
652
+ "required": false,
653
+ "columnType": "TEXT"
654
+ },
655
+ {
656
+ "name": "signerEvidence",
657
+ "type": "text",
658
+ "required": false,
659
+ "columnType": "TEXT"
660
+ },
661
+ {
662
+ "name": "acceptedAt",
663
+ "type": "datetime",
664
+ "required": false,
665
+ "columnType": "TIMESTAMP"
666
+ },
667
+ {
668
+ "name": "effectiveFrom",
669
+ "type": "datetime",
670
+ "required": false,
671
+ "columnType": "TIMESTAMP"
672
+ },
673
+ {
674
+ "name": "effectiveTo",
675
+ "type": "datetime",
676
+ "required": false,
677
+ "columnType": "TIMESTAMP"
678
+ },
679
+ {
680
+ "name": "supersedesExecutedAgreementId",
681
+ "type": "foreignKey",
682
+ "required": false,
683
+ "related": "ExecutedAgreement",
684
+ "columnType": "UUID"
685
+ },
686
+ {
687
+ "name": "metadata",
688
+ "type": "text",
689
+ "required": false,
690
+ "columnType": "TEXT"
691
+ }
692
+ ],
693
+ "relationships": [
694
+ {
695
+ "name": "executionId",
696
+ "type": "foreignKey",
697
+ "required": true,
698
+ "related": "AgreementExecution",
699
+ "columnType": "UUID"
700
+ },
701
+ {
702
+ "name": "sourceAssetId",
703
+ "type": "crossPackageRef",
704
+ "required": false,
705
+ "related": "@happyvertical/smrt-assets:Asset",
706
+ "columnType": "UUID"
707
+ },
708
+ {
709
+ "name": "signedDocumentAssetId",
710
+ "type": "crossPackageRef",
711
+ "required": false,
712
+ "related": "@happyvertical/smrt-assets:Asset",
713
+ "columnType": "UUID"
714
+ },
715
+ {
716
+ "name": "auditTrailAssetId",
717
+ "type": "crossPackageRef",
718
+ "required": false,
719
+ "related": "@happyvertical/smrt-assets:Asset",
720
+ "columnType": "UUID"
721
+ },
722
+ {
723
+ "name": "supersedesExecutedAgreementId",
724
+ "type": "foreignKey",
725
+ "required": false,
726
+ "related": "ExecutedAgreement",
727
+ "columnType": "UUID"
728
+ }
729
+ ],
730
+ "methods": [
731
+ "initialize",
732
+ "isEffectiveAt",
733
+ "save"
734
+ ],
735
+ "surfaces": [
736
+ {
737
+ "kind": "api",
738
+ "name": "executedagreements.list",
739
+ "operation": "list",
740
+ "objectName": "@happyvertical/smrt-sales:ExecutedAgreement",
741
+ "path": "/executedagreements",
742
+ "method": "GET"
743
+ },
744
+ {
745
+ "kind": "api",
746
+ "name": "executedagreements.get",
747
+ "operation": "get",
748
+ "objectName": "@happyvertical/smrt-sales:ExecutedAgreement",
749
+ "path": "/executedagreements/[id]",
750
+ "method": "GET"
751
+ },
752
+ {
753
+ "kind": "mcp",
754
+ "name": "executedagreement_list",
755
+ "operation": "list",
756
+ "objectName": "@happyvertical/smrt-sales:ExecutedAgreement"
757
+ },
758
+ {
759
+ "kind": "mcp",
760
+ "name": "executedagreement_get",
761
+ "operation": "get",
762
+ "objectName": "@happyvertical/smrt-sales:ExecutedAgreement"
763
+ }
764
+ ],
765
+ "relationshipFeatures": [
766
+ "crossPackageRef",
767
+ "foreignKey",
768
+ "uuidColumns"
769
+ ],
770
+ "tags": [],
771
+ "risks": []
772
+ },
49
773
  {
50
774
  "name": "CommissionAdjustmentCollection",
51
775
  "qualifiedName": "@happyvertical/smrt-sales:CommissionAdjustmentCollection",
@@ -3519,29 +4243,18 @@
3519
4243
  "columnType": "TEXT"
3520
4244
  },
3521
4245
  {
3522
- "name": "contractRef",
3523
- "type": "crossPackageRef",
4246
+ "name": "executionId",
4247
+ "type": "foreignKey",
3524
4248
  "required": false,
3525
- "related": "@happyvertical/smrt-commerce:Contract",
4249
+ "related": "AgreementExecution",
3526
4250
  "columnType": "UUID"
3527
4251
  },
3528
4252
  {
3529
- "name": "executedArtifactUrl",
3530
- "type": "text",
3531
- "required": false,
3532
- "columnType": "TEXT"
3533
- },
3534
- {
3535
- "name": "executedArtifactHash",
3536
- "type": "text",
3537
- "required": false,
3538
- "columnType": "TEXT"
3539
- },
3540
- {
3541
- "name": "acceptanceEvidence",
3542
- "type": "text",
4253
+ "name": "executedAgreementId",
4254
+ "type": "foreignKey",
3543
4255
  "required": false,
3544
- "columnType": "TEXT"
4256
+ "related": "ExecutedAgreement",
4257
+ "columnType": "UUID"
3545
4258
  },
3546
4259
  {
3547
4260
  "name": "metadata",
@@ -3566,23 +4279,30 @@
3566
4279
  "columnType": "UUID"
3567
4280
  },
3568
4281
  {
3569
- "name": "contractRef",
3570
- "type": "crossPackageRef",
4282
+ "name": "executionId",
4283
+ "type": "foreignKey",
3571
4284
  "required": false,
3572
- "related": "@happyvertical/smrt-commerce:Contract",
4285
+ "related": "AgreementExecution",
4286
+ "columnType": "UUID"
4287
+ },
4288
+ {
4289
+ "name": "executedAgreementId",
4290
+ "type": "foreignKey",
4291
+ "required": false,
4292
+ "related": "ExecutedAgreement",
3573
4293
  "columnType": "UUID"
3574
4294
  }
3575
4295
  ],
3576
4296
  "methods": [
3577
4297
  "activate",
3578
- "getAcceptanceEvidence",
4298
+ "bindExecutedAgreement",
4299
+ "bindExecution",
3579
4300
  "getMetadata",
3580
4301
  "initialize",
3581
4302
  "isActive",
3582
4303
  "isDraft",
3583
4304
  "isEffectiveAt",
3584
4305
  "save",
3585
- "setAcceptanceEvidence",
3586
4306
  "setMetadata",
3587
4307
  "supersede",
3588
4308
  "terminate"
@@ -3656,7 +4376,6 @@
3656
4376
  }
3657
4377
  ],
3658
4378
  "relationshipFeatures": [
3659
- "crossPackageRef",
3660
4379
  "foreignKey",
3661
4380
  "uuidColumns"
3662
4381
  ],
@@ -4433,6 +5152,34 @@
4433
5152
  }
4434
5153
  ],
4435
5154
  "surfaces": [
5155
+ {
5156
+ "kind": "api",
5157
+ "name": "executedagreements.list",
5158
+ "operation": "list",
5159
+ "objectName": "@happyvertical/smrt-sales:ExecutedAgreement",
5160
+ "path": "/executedagreements",
5161
+ "method": "GET"
5162
+ },
5163
+ {
5164
+ "kind": "api",
5165
+ "name": "executedagreements.get",
5166
+ "operation": "get",
5167
+ "objectName": "@happyvertical/smrt-sales:ExecutedAgreement",
5168
+ "path": "/executedagreements/[id]",
5169
+ "method": "GET"
5170
+ },
5171
+ {
5172
+ "kind": "mcp",
5173
+ "name": "executedagreement_list",
5174
+ "operation": "list",
5175
+ "objectName": "@happyvertical/smrt-sales:ExecutedAgreement"
5176
+ },
5177
+ {
5178
+ "kind": "mcp",
5179
+ "name": "executedagreement_get",
5180
+ "operation": "get",
5181
+ "objectName": "@happyvertical/smrt-sales:ExecutedAgreement"
5182
+ },
4436
5183
  {
4437
5184
  "kind": "api",
4438
5185
  "name": "commissions.list",
@@ -5746,12 +6493,12 @@
5746
6493
  ],
5747
6494
  "prompts": [],
5748
6495
  "relationshipsV2": {
5749
- "foreignKeyFields": 30,
5750
- "crossPackageRefFields": 9,
6496
+ "foreignKeyFields": 35,
6497
+ "crossPackageRefFields": 14,
5751
6498
  "junctionCollections": 0,
5752
6499
  "hierarchicalObjects": 0,
5753
6500
  "polymorphicAssociations": 0,
5754
- "uuidColumns": 108
6501
+ "uuidColumns": 127
5755
6502
  },
5756
- "agentDoc": "# @happyvertical/smrt-sales\n\nModular sales: CRM (Leads, Opportunities, Pipelines), referral intake with versioned attribution policies, a neutral commissions financial core with immutable term snapshots, and reusable Svelte surfaces. One installable package, four modules with distinct subpath exports:\n\n- `@happyvertical/smrt-sales/crm`\n- `@happyvertical/smrt-sales/referrals`\n- `@happyvertical/smrt-sales/commissions`\n- `@happyvertical/smrt-sales/svelte`\n\nThe root export re-exports every TS module. Internal module dependency order is `crm → commissions` and `referrals → commissions` (by string references only); `commissions` never imports from `crm`/`referrals` and never assumes advertising, Referral, Lead, or Opportunity semantics.\n\n## Validation\n\nRun `pnpm --filter @happyvertical/smrt-sales test` and `pnpm --filter @happyvertical/smrt-sales typecheck` for package changes. PostgreSQL-sensitive changes must also run `pnpm --filter @happyvertical/smrt-sales test:postgres`; the command uses the repository's disposable PostgreSQL harness and is registered in the PostgreSQL CI shard.\n\n## Roles vs. money\n\nReferrers and Sales Representatives are **distinct roles** and stay that way. Both connect to money through one neutral financial account:\n\n- **Earner** (commissions): payout identity — method, threshold, currency, status. Referenced by every Commission and CommissionPayout.\n- **SalesRepresentative** (crm) and **Referrer** (referrals): role models, each holding `profileId` (cross-package string ref to smrt-profiles) and `earnerId`.\n\n## Modules\n\n### commissions — neutral financial core\n\n- **Earner**: `profileId`, `status` (`pending|active|suspended`), `payoutMethod` (`bank_transfer|check|paypal|credit|other`), `payoutThresholdCents`, `payoutScheduleKey` (open string: `manual`, `monthly`, …), `currency`, `metadata`.\n- **EarnerSourceAttribution** (#1986): indexed external attribution mapping `(sourceKind, sourceId) → earnerId` with `status` (`active|inactive`) — the queryable replacement for stashing associations in `Earner.metadata`. Natural key `(tenant_id, source_kind, source_id)` (one mapping per key per tenant; a re-`create` upserts/re-points; the adapters' null-aware upsert dedups NULL-tenant keys too, but duplicate global rows minted outside the model layer remain representable). The attribution kind space is consumer-defined and need not match earning-event source kinds. `EarnerAttributionService` is the public API: `registerAttribution` (idempotent; reports `created`/`previousEarnerId`; also the documented metadata-migration backfill primitive — loop earners, register each association, verify, drop the metadata key) and `resolveActiveEarnerBySource`/`resolveActiveEarnersBySources` (single/batched; 2 queries bounded by the REQUESTED ids, never a scan of active earners; fail-closed typed reasons `no_mapping|mapping_inactive|ambiguous_mapping|earner_not_found|earner_not_active` — more than one ACTIVE row for a key refuses rather than guessing). In tenant context mappings resolve within that tenant only; without context, lookups span all rows and cross-tenant duplicates surface as `ambiguous_mapping`.\n- **CommissionPlan**: versioned calculation terms. Natural key `(tenant_id, plan_key, version)` (per-tenant keys; the adapters' null-aware upsert dedups NULL-tenant keys through the model layer, though duplicate global rows minted via raw SQL remain representable); `status` (`draft|active|superseded|retired`); `effectiveFrom`. Components are a JSON-string array (`getComponents()`): each component has `key`, `trigger` (earning-event kind or `*`), `basis` (`fixed|gross|net|margin|custom`), `rate` or `fixedAmountCents`, `recurrence` (`one_time` or `recurring` with optional `maxOccurrences`/`windowMonths`). **Immutable once active** — amendments create a new row with `version + 1`; prior versions are never rewritten. `latestActiveByKey(key, at?)` resolves the highest active version **already in effect at `at`** — a future-dated amendment can be activated ahead of time without governing earlier qualifications (same rule on `AttributionPolicyCollection`).\n- **EarningEvent**: immutable commercial-event evidence (`conversion`, `agreement_execution`, `invoice_payment`, `collected_revenue`, `recognized_margin`, or any extensible kind). Carries `sourceKind`/`sourceId` (generic earning source), `grossAmountCents`/`netAmountCents`/`marginCents`, `currency`, `occurredAt`, and a `dedupeKey` natural key for idempotent ingestion. No update/delete surface, plus a save-time immutability guard: hydrated edits, blind id overwrites, and fresh creates onto an existing dedupe key all throw (the upsert would rotate the row id and orphan referencing Commissions) — idempotent ingestion goes through `getOrCreateByDedupeKey()`.\n- **Commission**: one earning record per earner per plan component per event occurrence. Integer-cents amounts (`baseAmountCents`, `amountCents`), decimal `rate`, `basis`, snapshot references (`planKey`/`planVersion`, generic `termsSnapshotKind`/`termsSnapshotId`), a JSON `calculationTrace` sufficient to reproduce the amount, split support (`splitGroupId`, `shareFraction`), and a `dedupeKey` for idempotent creation. Lifecycle `pending → earned → approved → payable → paid`, enforced by a save-time transition guard with an authoritative prior-status re-read (commerce pattern).\n- **CommissionAdjustment**: append-only corrections (`refund|credit|chargeback|dispute|correction`), signed `amountCents`, required `reason`, immutable once created. Earned/paid Commissions are never rewritten — adjustments append to them.\n- **Payable balance**: computed, not stored — `CommissionBalanceService` sums payable Commissions plus unsettled Adjustments per Earner/currency.\n- **CommissionPayout**: settlement batch per Earner. `pending → approved → processing → completed | failed` (`resetFromFailed()` is the only exit from failed), plus the terminal operator decline `rejected` reachable from pending/approved (#1987 — reject through `transitionPayoutForSource`, which also RELEASES the batch's membership so the rows settle through a future batch; `reject()` on the model mutates status only). Settles rows via the collections' **conditional `claimForPayout`** (model-layer get/save/re-read — tenancy- and dialect-safe; a row owned by another batch is never re-claimed) and stores totals recomputed from the VERIFIED claimed membership; idempotent via `idempotencyKey` natural key (default `${earnerId}:${currency}:${YYYY-MM-DD}`) — a clean replay touches nothing, while a pending payout whose totals disagree with its stamped rows (interrupted claim pass) is repaired on replay (the claim pass re-checks the payout is still pending first). Each batch/repair pass also derives the payout's **single-source stamp** (`sourceKind`/`sourceId`, indexed): set when every claimed commission — and every claimed adjustment through its parent commission — shares exactly one non-empty source; empty for mixed/unknown/empty membership. `completePayout` flips member commissions to `paid` BEFORE the terminal transition, so a mid-loop failure stays retryable. Enforces `totalAmountCents = commissionTotalCents + adjustmentTotalCents` at save; retains `paymentReference`/`providerRef`; optional `invoiceId` cross-package string ref to a commerce Invoice. Generated surface is read-only on ALL doors (api/mcp/cli `list`/`get`) — writes go through `CommissionPayoutService`. `createPayoutBatch` settles the whole earner+currency by default, or a **scoped** subset: pass `sourceKind`+`sourceId` to settle just one earning source (e.g. one ad network), or `commissionIds` for an explicit set (each validated payable+unsettled+earner+currency; requires its own `idempotencyKey`) — adjustments are narrowed to the same scope, and the default idempotency key folds the source in. Scoping is how concurrent per-source batches settle safely: batches scoped to different sources (or disjoint `commissionIds`) gather **disjoint** row sets and never contend. The collection layer has no cross-row transaction, so OVERLAPPING concurrent scopes (a source batch + the earner-wide batch, intersecting id sets, or a batch replay racing a lifecycle transition of the same payout) must be serialized by the caller — `claimForPayout` still won't double-own a row, but a shared commission and its adjustment could split across the two batches.\n- **Source-scoped payout history** (#1985): `CommissionPayoutService.getSourcePayoutHistory({ sourceKind, sourceId, limit?, offset? })` — one page of the payouts belonging to ONE earning source, newest first (`created_at DESC, id DESC`), offset contract (`nextOffset` advances by the scanned count; `null` when exhausted). Candidates come from the indexed stamp, so work is bounded by page size (a sparse source never scans the global history); each page is re-verified against actual membership in three batched queries (no per-payout N+1) — every member commission and every adjustment's parent must carry exactly the requested source and agree on earner/currency/tenant; unprovable rows (mixed-source, missing parents, memberless artifacts, released/rejected batches) are excluded fail-closed and reported in `excluded` with reasons. Adjustment-only payouts prove ownership through parent commissions and list normally. Payouts minted before the stamp existed are invisible until backfilled with `restampPayoutSource(payoutId)` (idempotent, any status).\n- **Source-authorized lifecycle transitions** (#1987): `CommissionPayoutService.transitionPayoutForSource({ payoutId, sourceKind, sourceId, action, expectedStatus?, paymentReference?, reason? })` — atomic alternative to load-verify-transition. Actions `approve | mark_processing | complete | fail | reject`. Everything runs on one transaction database: the payout row is locked (PostgreSQL `SELECT … FOR UPDATE`, replica-safe; single-connection engines serialize transitions per database in-process), membership is re-verified under the lock (every commission and every adjustment through its parent must carry exactly the requested source and matching earner/currency/tenant — else typed fail-closed refusals with no payout/member data), and only authorized calls receive status-derived `already_applied` or `status_conflict` outcomes. Totals are then recomputed under the lock (drift refuses the money-forward actions with `totals_drift`; repair = `createPayoutBatch` replay while pending; `fail`/`reject` proceed despite drift — reject IS the remedy), and the transition plus member writes commit or roll back together (`complete` flips members to paid in the same transaction; `reject` releases membership then declines). Outcomes: `transitioned`, `already_applied` (idempotent echo — terminal completion NEVER rewrites `paymentReference`/`providerRef`/`paidAt`), or `refused` with `{ reason, detail }`. Concurrent duplicate calls for actions that retain membership resolve as one `transitioned` + `already_applied` echoes; `reject` releases its membership evidence, so serialized replays fail `membership_empty` rather than expose rejected state. `expectedStatus` adds optimistic-concurrency strictness after authorization. Memberless batch artifacts cannot pass the source-authorized door (`membership_empty`) — they stay operator-level.\n\n### crm\n\n- **SalesRepresentative**: role model (`profileId`, `earnerId`, `status`).\n- **Lead**: identified prospect with owner assignment, generic acquisition source (`sourceKind`/`sourceId`), preserved `acquisitionContext` JSON, and audited merge (`mergedIntoId`; merges preserve activity + acquisition history on both sides).\n- **PipelineDefinition / PipelineStage**: configurable ordered stages with default `new → qualified → discovery → proposal → negotiation → closed_won | closed_lost` (seeded via `ensureDefaultPipeline()`); stages carry `probability` and `isWon`/`isLost` terminal flags.\n- **Opportunity**: qualified engagement — owner, pipeline + stage, `expectedValueCents`, `probability`, `expectedCloseAt`, outcome. Stage movement validated against the pipeline; terminal stages set `won|lost` status.\n- **SalesActivity**: activity/next-action trail for Leads and Opportunities (`subjectKind`/`subjectId`), also the audit trail for assignment, qualification, merges, and stage movement.\n- **OpportunityConversion**: idempotent conversion links (`targetKind`/`targetId` — client, project, contract, subscription, …) with a composite natural key. CRM never creates downstream records itself and never mutates referral or commission state.\n\n### referrals\n\n- **Referrer**: role model (`profileId`, `earnerId`, `status`).\n- **ReferralProgram**: program defaults — default commission plan key, default attribution policy key, eligibility defaults.\n- **AttributionPolicy**: versioned `(tenant_id, policy_key, version)` policy (per-tenant keys) — attribution `windowDays`, credit mode (`first_touch|last_touch|assigned|split`), split shares, self-referral/existing-client eligibility, eligible services/campaigns/regions, conflict behavior. Immutable once active; amendments bump `version`.\n- **ReferralLink**: shareable link/code per Referrer+Program. Codes are crypto-random, uniqueness-checked, with click counting.\n- **ReferralTouch**: immutable attribution evidence (`click|code_entry|manual_assignment|partner_entry`) with subject hints and evidence JSON.\n- **Referral**: the introduction — generic qualifying target (`targetKind`/`targetId`: lead, opportunity, client, project, subscription, …), resolved policy version, credit fraction (splits create sibling Referrals sharing `splitGroupId`), lifecycle `pending → attributed → qualified | disqualified | expired | under_review`.\n- **AttributionException**: conflict review queue. Conclusive policy resolves automatically; ambiguity creates an exception; overrides require a `resolutionReason` and are audited.\n- **ReferralAgreement**: versioned per-Referrer terms binding — pins `commissionPlanKey`/`planVersion`, effective dating, optional cross-package string refs to a commerce Contract and executed-artifact evidence (e-signature execution lives downstream).\n- **ReferralTermSnapshot**: immutable at qualification — freezes agreement/plan/policy version refs plus the calculation inputs needed to reproduce every later earning.\n- **Services**: `AttributionService` (resolve touches → Referral(s) or exception; typed refusals `existing_client_ineligible | no_active_policy | no_eligible_touches`; `override()` requires a reason, audits via a resolved AttributionException, pins the displaced credit's policy version, and throws `QualifiedReferralOverrideError` on qualified referrals), `ReferralQualificationService` (eligibility + frozen snapshot + transition; `requalify()` is the explicit apply-amendment path), `ReferralCommissionService` (bridge: qualified Referral + EarningEvent → Commissions through the snapshot with `termsSnapshotKind = REFERRAL_TERMS_SNAPSHOT_KIND`; occurrence limits counted per `(termsSnapshotId, componentKey)`; fully idempotent on replay).\n\n### svelte\n\nProps-driven presentational components (no data fetching, no model-class imports, Provider-free smrt-ui primitives, `--smrt-*` tokens only): CRM — `SalesDashboard`, `LeadList`, `OpportunityBoard`, `OpportunityDetail`; referrer portal — `ReferralLinkManager`, `ReferralStatusList`, `ReferrerEarningsSummary`, `CommissionBreakdown` (trace-explained amounts), `PayoutHistoryList`, `ExecutedAgreementsList`; operator — `AttributionConflictQueue` (award editor + required resolution reason), `PayoutBatchReview`, `CommissionExpenseSummary` (explicitly distinct from client invoices). Monetary props stay integer cents; `format.ts` converts at render. View-model prop types are exported interfaces (never inline intersected generics in `$props()`); pure helpers (dashboard math, award validation mirroring the service, payout action gating) are unit-tested while components are svelte-check-gated.\n\n## Currency\n\n**All monetary fields are integer cents** with `*Cents` suffixes; rates are decimal (`0`–`1`). Rounding happens once per calculation step via `roundCents()` (half-away-from-zero) and every Commission stores its `calculationTrace` so amounts are reproducible. Convert at display/commerce boundaries with `centsToAmount()`/`amountToCents()`.\n\n## Tenancy\n\nEvery model is `@TenantScoped({ mode: 'optional' })` with `@tenantId({ nullable: true })`. This deliberately departs from legacy smrt-affiliates (which was cross-tenant by design for ad networks): sales programs, earners, and payouts are tenant-owned; `tenantId: null` remains available for global/operator-level rows.\n\n## Cross-package references (plain strings)\n\n`profileId` → smrt-profiles Profile; `invoiceId` (CommissionPayout) → smrt-commerce Invoice; `contractRef` (ReferralAgreement) → smrt-commerce Contract. No static imports of sibling domain packages.\n\n## smrt-affiliates migration\n\n`@happyvertical/smrt-affiliates` is now a deprecated compatibility package that re-exports this package's commissions core under the legacy names (`Partner` → `Earner`, `Payout` → `CommissionPayout`, `Commission`, collections, enums) with `@deprecated` guidance and **no duplicate persistence model**. The data/API mapping lives in `packages/affiliates/MIGRATION.md`.\n\n## Gotchas\n\n- **Cents vs. rates**: `*Cents` fields are INTEGER (`= 0` defaults); `rate`/`probability`/`shareFraction` are DECIMAL (`= 0.0` defaults). Don't mix the two conventions on one field.\n- **Versioned terms are rows, not edits**: CommissionPlan / AttributionPolicy / ReferralAgreement amendments insert `version + 1`; active versions are save-guarded immutable.\n- **Idempotency is dedupeKey-based**: EarningEvent, Commission, and CommissionPayout carry natural keys (`conflictColumns`) — retried ingestion/settlement upserts instead of duplicating.\n- **Adjustments never rewrite**: correcting an earned/paid Commission means appending a CommissionAdjustment, not editing the Commission.\n- **CommissionPayout, not Payout**: avoids the pre-existing global table-name collision between commerce `Payout` and legacy affiliates `Payout` (`payouts`).\n- **Table names are global**: new models were named to avoid collisions across packages (`commission_payouts`, `sales_activities`, `attribution_policies`, …).\n- **Svelte module is svelte-check-gated**: no runtime component tests; `typecheck` runs `svelte-check` via `scripts/svelte-check-a11y.mjs`. Ship raw `.svelte` via `svelte-package`.\n- **Model transition methods don't save**: `markEarned()/approve()/markPayable()/markPaid()` (Commission) and the payout transitions mutate + stamp timestamps only — callers save; the settlement/payout services do both.\n- **`reject()` alone strands rows**: the model method only flips status — always reject through `transitionPayoutForSource`, which releases the batch's membership in the same transaction; stamped rows on a rejected payout would be unsettleable forever.\n- **The payout source stamp is derived data, never authorization**: `sourceKind`/`sourceId` on CommissionPayout index the history listing; both the listing and the lifecycle service re-verify actual membership and fail closed when the stamp cannot be proven.\n- **`sweepClearing` treats `clearingEndsAt: null` as immediately sweepable** (no clearing configured ⇒ nothing to wait for).\n- **Agreement freeze scope**: activating a ReferralAgreement freezes referrer/program/version/plan refs/clearingDays/approvalMode/effectiveFrom; `effectiveTo` (end-dating) and the evidence fields (`contractRef`, artifact url/hash, `acceptanceEvidence`) stay writable — e-signature completes downstream.\n- **Circular FK pair by design**: `Referral.snapshotId ↔ ReferralTermSnapshot.referralId`. Fine on SQLite (and no cross-table DDL constraints are emitted for these string FKs); keep an eye on strict-DDL environments.\n- **Attribution resolution is single-writer per (target, program)**: `resolve()` is idempotent against PERSISTED state, but two workers resolving the same target concurrently can both pass the existence check and create duplicate credit — the collection layer has no cross-row transaction (the same stance as payout batching, which relies on disjoint scopes for concurrency). Run intake resolution serially per target (it naturally is, in a request handler); duplicates are visible in the portal and correctable via `override()`.\n- **`ObjectRegistry.getConfig(X)` needs the class module imported** (decorator side effects) — manifest-only registration doesn't carry api/mcp/conflictColumns; tests asserting surface configs need a side-effect import of the module barrel.\n"
6503
+ "agentDoc": "# @happyvertical/smrt-sales\n\nModular sales: provider-neutral agreement execution, CRM, referral intake, a neutral commissions financial core, and reusable Svelte surfaces. One installable package with distinct subpath exports:\n\n- `@happyvertical/smrt-sales/agreements`\n- `@happyvertical/smrt-sales/crm`\n- `@happyvertical/smrt-sales/referrals`\n- `@happyvertical/smrt-sales/commissions`\n- `@happyvertical/smrt-sales/svelte`\n\nThe root export re-exports every TS module. `agreements` depends on the provider-neutral `@happyvertical/signatures` contract and `smrt-assets`; provider credentials stay in the injected SDK adapter/secret store. `referrals` binds its versioned terms to `agreements`.\n\n## Validation\n\nRun `pnpm --filter @happyvertical/smrt-sales test` and `pnpm --filter @happyvertical/smrt-sales typecheck` for package changes. PostgreSQL-sensitive changes must also run `pnpm --filter @happyvertical/smrt-sales test:postgres`; the command uses the repository's disposable PostgreSQL harness and is registered in the PostgreSQL CI shard.\n\n## Roles vs. money\n\nReferrers and Sales Representatives are **distinct roles** and stay that way. Both connect to money through one neutral financial account:\n\n- **Earner** (commissions): payout identity — method, threshold, currency, status. Referenced by every Commission and CommissionPayout.\n- **SalesRepresentative** (crm) and **Referrer** (referrals): role models, each holding `profileId` (cross-package string ref to smrt-profiles) and `earnerId`.\n\n## Modules\n\n### agreements — verified execution evidence\n\n- **AgreementExecution**: private required-tenant orchestration state keyed by `(tenant_id, idempotency_key)`, with a collision-checked provider request/status, an expiring create-operation lease, source version/hash/size/Asset, intended signer identity/auth method, cancellation/expiry, reconciliation, exact staged artifact metadata, and secret-store references only.\n- **AgreementExecutionEvent**: private append-only evidence for verified webhooks and operator/provider operations. Provider events are deduped by tenant/provider replay key; explicit occurrence/first-receipt timestamps and the exact payload/hash are retained, while signature headers are never stored. A later receipt time for the same verified provider event is an idempotent replay. Stale, regressive, or conflicting terminal events are audited without regressing lifecycle state or creating executed evidence.\n- **ExecutedAgreement**: immutable read-only source version plus signed-document and audit-trail Asset ids/hashes/sizes/filenames/media types, completed signer evidence, acceptance/effective dates, and supersession reference. Amendments create new records.\n- **AgreementExecutionService**: accepts an SDK `SignatureProvider` and shared `AssetRuntimeLike`; enforces ambient tenant equality, expiring remote-attempt fencing, create idempotency, verified webhook ingestion/replay, tenant-fenced lifecycle compare-and-set with monotonic provider-event ordering, partial audit/failure updates that cannot overwrite lifecycle state, cancellation/expiry/reconciliation, exact artifact hashing, and immutable finalization. An expired create lease may be reclaimed only when the provider advertises atomic idempotency; otherwise a create with no confirmed request id must be reconciled/adopted before retry.\n\n### commissions — neutral financial core\n\n- **Earner**: `profileId`, `status` (`pending|active|suspended`), `payoutMethod` (`bank_transfer|check|paypal|credit|other`), `payoutThresholdCents`, `payoutScheduleKey` (open string: `manual`, `monthly`, …), `currency`, `metadata`.\n- **EarnerSourceAttribution** (#1986): indexed external attribution mapping `(sourceKind, sourceId) → earnerId` with `status` (`active|inactive`) — the queryable replacement for stashing associations in `Earner.metadata`. Natural key `(tenant_id, source_kind, source_id)` (one mapping per key per tenant; a re-`create` upserts/re-points; the adapters' null-aware upsert dedups NULL-tenant keys too, but duplicate global rows minted outside the model layer remain representable). The attribution kind space is consumer-defined and need not match earning-event source kinds. `EarnerAttributionService` is the public API: `registerAttribution` (idempotent; reports `created`/`previousEarnerId`; also the documented metadata-migration backfill primitive — loop earners, register each association, verify, drop the metadata key) and `resolveActiveEarnerBySource`/`resolveActiveEarnersBySources` (single/batched; 2 queries bounded by the REQUESTED ids, never a scan of active earners; fail-closed typed reasons `no_mapping|mapping_inactive|ambiguous_mapping|earner_not_found|earner_not_active` — more than one ACTIVE row for a key refuses rather than guessing). In tenant context mappings resolve within that tenant only; without context, lookups span all rows and cross-tenant duplicates surface as `ambiguous_mapping`.\n- **CommissionPlan**: versioned calculation terms. Natural key `(tenant_id, plan_key, version)` (per-tenant keys; the adapters' null-aware upsert dedups NULL-tenant keys through the model layer, though duplicate global rows minted via raw SQL remain representable); `status` (`draft|active|superseded|retired`); `effectiveFrom`. Components are a JSON-string array (`getComponents()`): each component has `key`, `trigger` (earning-event kind or `*`), `basis` (`fixed|gross|net|margin|custom`), `rate` or `fixedAmountCents`, `recurrence` (`one_time` or `recurring` with optional `maxOccurrences`/`windowMonths`). **Immutable once active** — amendments create a new row with `version + 1`; prior versions are never rewritten. `latestActiveByKey(key, at?)` resolves the highest active version **already in effect at `at`** — a future-dated amendment can be activated ahead of time without governing earlier qualifications (same rule on `AttributionPolicyCollection`).\n- **EarningEvent**: immutable commercial-event evidence (`conversion`, `agreement_execution`, `invoice_payment`, `collected_revenue`, `recognized_margin`, or any extensible kind). Carries `sourceKind`/`sourceId` (generic earning source), `grossAmountCents`/`netAmountCents`/`marginCents`, `currency`, `occurredAt`, and a `dedupeKey` natural key for idempotent ingestion. No update/delete surface, plus a save-time immutability guard: hydrated edits, blind id overwrites, and fresh creates onto an existing dedupe key all throw (the upsert would rotate the row id and orphan referencing Commissions) — idempotent ingestion goes through `getOrCreateByDedupeKey()`.\n- **Commission**: one earning record per earner per plan component per event occurrence. Integer-cents amounts (`baseAmountCents`, `amountCents`), decimal `rate`, `basis`, snapshot references (`planKey`/`planVersion`, generic `termsSnapshotKind`/`termsSnapshotId`), a JSON `calculationTrace` sufficient to reproduce the amount, split support (`splitGroupId`, `shareFraction`), and a `dedupeKey` for idempotent creation. Lifecycle `pending → earned → approved → payable → paid`, enforced by a save-time transition guard with an authoritative prior-status re-read (commerce pattern).\n- **CommissionAdjustment**: append-only corrections (`refund|credit|chargeback|dispute|correction`), signed `amountCents`, required `reason`, immutable once created. Earned/paid Commissions are never rewritten — adjustments append to them.\n- **Payable balance**: computed, not stored — `CommissionBalanceService` sums payable Commissions plus unsettled Adjustments per Earner/currency.\n- **CommissionPayout**: settlement batch per Earner. `pending → approved → processing → completed | failed` (`resetFromFailed()` is the only exit from failed), plus the terminal operator decline `rejected` reachable from pending/approved (#1987 — reject through `transitionPayoutForSource`, which also RELEASES the batch's membership so the rows settle through a future batch; `reject()` on the model mutates status only). Settles rows via the collections' **conditional `claimForPayout`** (model-layer get/save/re-read — tenancy- and dialect-safe; a row owned by another batch is never re-claimed) and stores totals recomputed from the VERIFIED claimed membership; idempotent via `idempotencyKey` natural key (default `${earnerId}:${currency}:${YYYY-MM-DD}`) — a clean replay touches nothing, while a pending payout whose totals disagree with its stamped rows (interrupted claim pass) is repaired on replay (the claim pass re-checks the payout is still pending first). Each batch/repair pass also derives the payout's **single-source stamp** (`sourceKind`/`sourceId`, indexed): set when every claimed commission — and every claimed adjustment through its parent commission — shares exactly one non-empty source; empty for mixed/unknown/empty membership. `completePayout` flips member commissions to `paid` BEFORE the terminal transition, so a mid-loop failure stays retryable. Enforces `totalAmountCents = commissionTotalCents + adjustmentTotalCents` at save; retains `paymentReference`/`providerRef`; optional `invoiceId` cross-package string ref to a commerce Invoice. Generated surface is read-only on ALL doors (api/mcp/cli `list`/`get`) — writes go through `CommissionPayoutService`. `createPayoutBatch` settles the whole earner+currency by default, or a **scoped** subset: pass `sourceKind`+`sourceId` to settle just one earning source (e.g. one ad network), or `commissionIds` for an explicit set (each validated payable+unsettled+earner+currency; requires its own `idempotencyKey`) — adjustments are narrowed to the same scope, and the default idempotency key folds the source in. Scoping is how concurrent per-source batches settle safely: batches scoped to different sources (or disjoint `commissionIds`) gather **disjoint** row sets and never contend. The collection layer has no cross-row transaction, so OVERLAPPING concurrent scopes (a source batch + the earner-wide batch, intersecting id sets, or a batch replay racing a lifecycle transition of the same payout) must be serialized by the caller — `claimForPayout` still won't double-own a row, but a shared commission and its adjustment could split across the two batches.\n- **Source-scoped payout history** (#1985): `CommissionPayoutService.getSourcePayoutHistory({ sourceKind, sourceId, limit?, offset? })` — one page of the payouts belonging to ONE earning source, newest first (`created_at DESC, id DESC`), offset contract (`nextOffset` advances by the scanned count; `null` when exhausted). Candidates come from the indexed stamp, so work is bounded by page size (a sparse source never scans the global history); each page is re-verified against actual membership in three batched queries (no per-payout N+1) — every member commission and every adjustment's parent must carry exactly the requested source and agree on earner/currency/tenant; unprovable rows (mixed-source, missing parents, memberless artifacts, released/rejected batches) are excluded fail-closed and reported in `excluded` with reasons. Adjustment-only payouts prove ownership through parent commissions and list normally. Payouts minted before the stamp existed are invisible until backfilled with `restampPayoutSource(payoutId)` (idempotent, any status).\n- **Source-authorized lifecycle transitions** (#1987): `CommissionPayoutService.transitionPayoutForSource({ payoutId, sourceKind, sourceId, action, expectedStatus?, paymentReference?, reason? })` — atomic alternative to load-verify-transition. Actions `approve | mark_processing | complete | fail | reject`. Everything runs on one transaction database: the payout row is locked (PostgreSQL `SELECT … FOR UPDATE`, replica-safe; single-connection engines serialize transitions per database in-process), membership is re-verified under the lock (every commission and every adjustment through its parent must carry exactly the requested source and matching earner/currency/tenant — else typed fail-closed refusals with no payout/member data), and only authorized calls receive status-derived `already_applied` or `status_conflict` outcomes. Totals are then recomputed under the lock (drift refuses the money-forward actions with `totals_drift`; repair = `createPayoutBatch` replay while pending; `fail`/`reject` proceed despite drift — reject IS the remedy), and the transition plus member writes commit or roll back together (`complete` flips members to paid in the same transaction; `reject` releases membership then declines). Outcomes: `transitioned`, `already_applied` (idempotent echo — terminal completion NEVER rewrites `paymentReference`/`providerRef`/`paidAt`), or `refused` with `{ reason, detail }`. Concurrent duplicate calls for actions that retain membership resolve as one `transitioned` + `already_applied` echoes; `reject` releases its membership evidence, so serialized replays fail `membership_empty` rather than expose rejected state. `expectedStatus` adds optimistic-concurrency strictness after authorization. Memberless batch artifacts cannot pass the source-authorized door (`membership_empty`) — they stay operator-level.\n\n### crm\n\n- **SalesRepresentative**: role model (`profileId`, `earnerId`, `status`).\n- **Lead**: identified prospect with owner assignment, generic acquisition source (`sourceKind`/`sourceId`), preserved `acquisitionContext` JSON, and audited merge (`mergedIntoId`; merges preserve activity + acquisition history on both sides).\n- **PipelineDefinition / PipelineStage**: configurable ordered stages with default `new → qualified → discovery → proposal → negotiation → closed_won | closed_lost` (seeded via `ensureDefaultPipeline()`); stages carry `probability` and `isWon`/`isLost` terminal flags.\n- **Opportunity**: qualified engagement — owner, pipeline + stage, `expectedValueCents`, `probability`, `expectedCloseAt`, outcome. Stage movement validated against the pipeline; terminal stages set `won|lost` status.\n- **SalesActivity**: activity/next-action trail for Leads and Opportunities (`subjectKind`/`subjectId`), also the audit trail for assignment, qualification, merges, and stage movement.\n- **OpportunityConversion**: idempotent conversion links (`targetKind`/`targetId` — client, project, contract, subscription, …) with a composite natural key. CRM never creates downstream records itself and never mutates referral or commission state.\n\n### referrals\n\n- **Referrer**: role model (`profileId`, `earnerId`, `status`).\n- **ReferralProgram**: program defaults — default commission plan key, default attribution policy key, eligibility defaults.\n- **AttributionPolicy**: versioned `(tenant_id, policy_key, version)` policy (per-tenant keys) — attribution `windowDays`, credit mode (`first_touch|last_touch|assigned|split`), split shares, self-referral/existing-client eligibility, eligible services/campaigns/regions, conflict behavior. Immutable once active; amendments bump `version`.\n- **ReferralLink**: shareable link/code per Referrer+Program. Codes are crypto-random, uniqueness-checked, with click counting.\n- **ReferralTouch**: immutable attribution evidence (`click|code_entry|manual_assignment|partner_entry`) with subject hints and evidence JSON.\n- **Referral**: the introduction — generic qualifying target (`targetKind`/`targetId`: lead, opportunity, client, project, subscription, …), resolved policy version, credit fraction (splits create sibling Referrals sharing `splitGroupId`), lifecycle `pending → attributed → qualified | disqualified | expired | under_review`.\n- **AttributionException**: conflict review queue. Conclusive policy resolves automatically; ambiguity creates an exception; overrides require a `resolutionReason` and are audited.\n- **ReferralAgreement**: versioned per-Referrer terms binding — pins `commissionPlanKey`/`planVersion`, effective dating, and an immutable `ExecutedAgreement` before activation. Amendments are fresh unsigned drafts and never inherit execution evidence.\n- **ReferralTermSnapshot**: immutable at qualification — freezes agreement/plan/policy version refs plus the calculation inputs needed to reproduce every later earning.\n- **Services**: `ReferralAgreementExecutionService` binds generated Referral Agreement versions to the generic execution service and activates/supersedes them transactionally with future-effective amendment support; `AttributionService`, `ReferralQualificationService`, and `ReferralCommissionService` retain their existing attribution/qualification/earning responsibilities.\n\n### svelte\n\nProps-driven presentational components (no data fetching, no model-class imports, Provider-free smrt-ui primitives, `--smrt-*` tokens only): CRM — `SalesDashboard`, `LeadList`, `OpportunityBoard`, `OpportunityDetail`; referrer portal — `ReferralLinkManager`, `ReferralStatusList`, `ReferrerEarningsSummary`, `CommissionBreakdown` (trace-explained amounts), `PayoutHistoryList`, `ExecutedAgreementsList`; operator — `AttributionConflictQueue` (award editor + required resolution reason), `PayoutBatchReview`, `CommissionExpenseSummary` (explicitly distinct from client invoices). Monetary props stay integer cents; `format.ts` converts at render. View-model prop types are exported interfaces (never inline intersected generics in `$props()`); pure helpers (dashboard math, award validation mirroring the service, payout action gating) are unit-tested while components are svelte-check-gated.\n\n## Currency\n\n**All monetary fields are integer cents** with `*Cents` suffixes; rates are decimal (`0`–`1`). Rounding happens once per calculation step via `roundCents()` (half-away-from-zero) and every Commission stores its `calculationTrace` so amounts are reproducible. Convert at display/commerce boundaries with `centsToAmount()`/`amountToCents()`.\n\n## Tenancy\n\nEvery model is `@TenantScoped({ mode: 'optional' })` with `@tenantId({ nullable: true })`. This deliberately departs from legacy smrt-affiliates (which was cross-tenant by design for ad networks): sales programs, earners, and payouts are tenant-owned; `tenantId: null` remains available for global/operator-level rows.\n\n## Cross-package references (plain strings)\n\n`profileId` → smrt-profiles Profile; `invoiceId` (CommissionPayout) → smrt-commerce Invoice; agreement artifacts → smrt-assets Asset ids. No static imports of unrelated sibling domain packages.\n\n## smrt-affiliates migration\n\n`@happyvertical/smrt-affiliates` is now a deprecated compatibility package that re-exports this package's commissions core under the legacy names (`Partner` → `Earner`, `Payout` → `CommissionPayout`, `Commission`, collections, enums) with `@deprecated` guidance and **no duplicate persistence model**. The data/API mapping lives in `packages/affiliates/MIGRATION.md`.\n\n## Gotchas\n\n- **Cents vs. rates**: `*Cents` fields are INTEGER (`= 0` defaults); `rate`/`probability`/`shareFraction` are DECIMAL (`= 0.0` defaults). Don't mix the two conventions on one field.\n- **Versioned terms are rows, not edits**: CommissionPlan / AttributionPolicy / ReferralAgreement amendments insert `version + 1`; active versions are save-guarded immutable.\n- **Idempotency is dedupeKey-based**: EarningEvent, Commission, and CommissionPayout carry natural keys (`conflictColumns`) — retried ingestion/settlement upserts instead of duplicating.\n- **Adjustments never rewrite**: correcting an earned/paid Commission means appending a CommissionAdjustment, not editing the Commission.\n- **CommissionPayout, not Payout**: avoids the pre-existing global table-name collision between commerce `Payout` and legacy affiliates `Payout` (`payouts`).\n- **Table names are global**: new models were named to avoid collisions across packages (`commission_payouts`, `sales_activities`, `attribution_policies`, …).\n- **Svelte module is svelte-check-gated**: no runtime component tests; `typecheck` runs `svelte-check` via `scripts/svelte-check-a11y.mjs`. Ship raw `.svelte` via `svelte-package`.\n- **Model transition methods don't save**: `markEarned()/approve()/markPayable()/markPaid()` (Commission) and the payout transitions mutate + stamp timestamps only — callers save; the settlement/payout services do both.\n- **`reject()` alone strands rows**: the model method only flips status — always reject through `transitionPayoutForSource`, which releases the batch's membership in the same transaction; stamped rows on a rejected payout would be unsettleable forever.\n- **The payout source stamp is derived data, never authorization**: `sourceKind`/`sourceId` on CommissionPayout index the history listing; both the listing and the lifecycle service re-verify actual membership and fail closed when the stamp cannot be proven.\n- **`sweepClearing` treats `clearingEndsAt: null` as immediately sweepable** (no clearing configured ⇒ nothing to wait for).\n- **Agreement freeze scope**: activating a ReferralAgreement requires and freezes its execution/evidence refs along with its commercial terms. `effectiveTo` remains writable for explicit end-dating; signed bytes, hashes, signer evidence, and audit trail exist only on immutable ExecutedAgreement/Asset records.\n- **Circular FK pair by design**: `Referral.snapshotId ↔ ReferralTermSnapshot.referralId`. Fine on SQLite (and no cross-table DDL constraints are emitted for these string FKs); keep an eye on strict-DDL environments.\n- **Attribution resolution is single-writer per (target, program)**: `resolve()` is idempotent against PERSISTED state, but two workers resolving the same target concurrently can both pass the existence check and create duplicate credit — the collection layer has no cross-row transaction (the same stance as payout batching, which relies on disjoint scopes for concurrency). Run intake resolution serially per target (it naturally is, in a request handler); duplicates are visible in the portal and correctable via `override()`.\n- **`ObjectRegistry.getConfig(X)` needs the class module imported** (decorator side effects) — manifest-only registration doesn't carry api/mcp/conflictColumns; tests asserting surface configs need a side-effect import of the module barrel.\n"
5757
6504
  }