@happyvertical/smrt-approvals 0.0.0-stage → 0.55.10

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 (38) hide show
  1. package/AGENTS.md +45 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +124 -2
  5. package/agents/approvals.md +93 -0
  6. package/dist/__smrt-register__.d.ts +2 -0
  7. package/dist/__smrt-register__.d.ts.map +1 -0
  8. package/dist/collections/ApprovalEventCollection.d.ts +13 -0
  9. package/dist/collections/ApprovalEventCollection.d.ts.map +1 -0
  10. package/dist/collections/ApprovalPolicyCollection.d.ts +13 -0
  11. package/dist/collections/ApprovalPolicyCollection.d.ts.map +1 -0
  12. package/dist/collections/ApprovalRequestCollection.d.ts +13 -0
  13. package/dist/collections/ApprovalRequestCollection.d.ts.map +1 -0
  14. package/dist/index.d.ts +46 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +1307 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/kinds.d.ts +61 -0
  19. package/dist/kinds.d.ts.map +1 -0
  20. package/dist/manifest.json +1531 -0
  21. package/dist/models/ApprovalEvent.d.ts +82 -0
  22. package/dist/models/ApprovalEvent.d.ts.map +1 -0
  23. package/dist/models/ApprovalPolicy.d.ts +47 -0
  24. package/dist/models/ApprovalPolicy.d.ts.map +1 -0
  25. package/dist/models/ApprovalRequest.d.ts +118 -0
  26. package/dist/models/ApprovalRequest.d.ts.map +1 -0
  27. package/dist/permissions.d.ts +20 -0
  28. package/dist/permissions.d.ts.map +1 -0
  29. package/dist/service.d.ts +203 -0
  30. package/dist/service.d.ts.map +1 -0
  31. package/dist/smrt-knowledge.json +751 -0
  32. package/dist/types.d.ts +136 -0
  33. package/dist/types.d.ts.map +1 -0
  34. package/dist/types.js +45 -0
  35. package/dist/types.js.map +1 -0
  36. package/dist/write-capability.d.ts +28 -0
  37. package/dist/write-capability.d.ts.map +1 -0
  38. package/package.json +64 -3
@@ -0,0 +1,751 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "sensitiveFieldsExcluded": true,
4
+ "generatedAt": "1970-01-01T00:00:00.000Z",
5
+ "packageName": "@happyvertical/smrt-approvals",
6
+ "packageVersion": "0.55.10",
7
+ "sourceManifestPath": "dist/manifest.json",
8
+ "agentDocPath": "AGENTS.md",
9
+ "sourceHashes": {
10
+ "manifest": "ba2e1a9554601128ca9027f1502d3a226c7803cb8a3a00d4c2bac857a5f16b95",
11
+ "packageJson": "964faf5a4547cdfe20bb134652a70ac18148d74e2b391190c83c7b970fa0ac97",
12
+ "agents": "d79f62b8c882c0d39aa6d1e2b9c56b4d58bc9270ce4bc656f7ead5df37a275fc",
13
+ "moduleDoc:agents/approvals.md": "9f5a3a2332617e4e9acd7014c9ffa30d3a5391f4dcf0e0cb8b9df4f824576961"
14
+ },
15
+ "exports": [
16
+ ".",
17
+ "./manifest",
18
+ "./manifest.json"
19
+ ],
20
+ "dependencies": {
21
+ "@happyvertical/smrt-core": "workspace:*",
22
+ "@happyvertical/smrt-tenancy": "workspace:*",
23
+ "@happyvertical/smrt-users": "workspace:*",
24
+ "@happyvertical/sql": "catalog:",
25
+ "@happyvertical/smrt-vitest": "workspace:*",
26
+ "@types/node": "24.13.6",
27
+ "typescript": "6.0.3",
28
+ "vite": "8.1.4",
29
+ "vitest": "4.1.10"
30
+ },
31
+ "smrtDependencies": [
32
+ "@happyvertical/smrt-core",
33
+ "@happyvertical/smrt-tenancy",
34
+ "@happyvertical/smrt-users",
35
+ "@happyvertical/smrt-vitest"
36
+ ],
37
+ "sdkDependencies": [
38
+ "@happyvertical/sql"
39
+ ],
40
+ "tags": [
41
+ "approvals",
42
+ "audit",
43
+ "cross-package",
44
+ "multi-tenant",
45
+ "quorum",
46
+ "smrt",
47
+ "workflow"
48
+ ],
49
+ "risks": [
50
+ "sensitive-fields-excluded"
51
+ ],
52
+ "objects": [
53
+ {
54
+ "name": "ApprovalEventCollection",
55
+ "qualifiedName": "@happyvertical/smrt-approvals:ApprovalEventCollection",
56
+ "collection": "approvalevents",
57
+ "tableName": "approval_events",
58
+ "packageName": "@happyvertical/smrt-approvals",
59
+ "extends": "SmrtCollection",
60
+ "fields": [],
61
+ "relationships": [],
62
+ "methods": [],
63
+ "surfaces": [],
64
+ "relationshipFeatures": [
65
+ "uuidColumns"
66
+ ],
67
+ "tags": [],
68
+ "risks": []
69
+ },
70
+ {
71
+ "name": "ApprovalPolicyCollection",
72
+ "qualifiedName": "@happyvertical/smrt-approvals:ApprovalPolicyCollection",
73
+ "collection": "approvalpolicies",
74
+ "tableName": "approval_policies",
75
+ "packageName": "@happyvertical/smrt-approvals",
76
+ "extends": "SmrtCollection",
77
+ "fields": [],
78
+ "relationships": [],
79
+ "methods": [],
80
+ "surfaces": [],
81
+ "relationshipFeatures": [
82
+ "uuidColumns"
83
+ ],
84
+ "tags": [],
85
+ "risks": []
86
+ },
87
+ {
88
+ "name": "ApprovalRequestCollection",
89
+ "qualifiedName": "@happyvertical/smrt-approvals:ApprovalRequestCollection",
90
+ "collection": "approvalrequests",
91
+ "tableName": "approval_requests",
92
+ "packageName": "@happyvertical/smrt-approvals",
93
+ "extends": "SmrtCollection",
94
+ "fields": [],
95
+ "relationships": [],
96
+ "methods": [],
97
+ "surfaces": [],
98
+ "relationshipFeatures": [
99
+ "uuidColumns"
100
+ ],
101
+ "tags": [],
102
+ "risks": []
103
+ },
104
+ {
105
+ "name": "ApprovalEvent",
106
+ "qualifiedName": "@happyvertical/smrt-approvals:ApprovalEvent",
107
+ "collection": "approvalevents",
108
+ "description": "One immutable entry in an approval request's history.",
109
+ "tableName": "approval_events",
110
+ "packageName": "@happyvertical/smrt-approvals",
111
+ "extends": "SmrtObject",
112
+ "fields": [
113
+ {
114
+ "name": "tenantId",
115
+ "type": "text",
116
+ "required": true,
117
+ "columnType": "UUID"
118
+ },
119
+ {
120
+ "name": "requestId",
121
+ "type": "foreignKey",
122
+ "required": true,
123
+ "related": "ApprovalRequest",
124
+ "columnType": "UUID"
125
+ },
126
+ {
127
+ "name": "type",
128
+ "type": "text",
129
+ "required": true,
130
+ "columnType": "TEXT",
131
+ "readonly": true
132
+ },
133
+ {
134
+ "name": "actorId",
135
+ "type": "text",
136
+ "required": true,
137
+ "columnType": "TEXT",
138
+ "readonly": true
139
+ },
140
+ {
141
+ "name": "actorType",
142
+ "type": "text",
143
+ "required": false,
144
+ "columnType": "TEXT",
145
+ "readonly": true
146
+ },
147
+ {
148
+ "name": "reason",
149
+ "type": "text",
150
+ "required": false,
151
+ "columnType": "TEXT",
152
+ "readonly": true
153
+ },
154
+ {
155
+ "name": "subjectRevisionHash",
156
+ "type": "text",
157
+ "required": false,
158
+ "columnType": "TEXT",
159
+ "readonly": true
160
+ },
161
+ {
162
+ "name": "sequence",
163
+ "type": "integer",
164
+ "required": false,
165
+ "columnType": "INTEGER",
166
+ "readonly": true
167
+ },
168
+ {
169
+ "name": "voteKey",
170
+ "type": "text",
171
+ "required": false,
172
+ "columnType": "TEXT",
173
+ "readonly": true
174
+ },
175
+ {
176
+ "name": "occurredAt",
177
+ "type": "datetime",
178
+ "required": false,
179
+ "columnType": "TIMESTAMP",
180
+ "readonly": true
181
+ }
182
+ ],
183
+ "relationships": [
184
+ {
185
+ "name": "requestId",
186
+ "type": "foreignKey",
187
+ "required": true,
188
+ "related": "ApprovalRequest",
189
+ "columnType": "UUID"
190
+ }
191
+ ],
192
+ "methods": [
193
+ "delete",
194
+ "save"
195
+ ],
196
+ "methodSignatures": [
197
+ {
198
+ "name": "delete",
199
+ "async": true,
200
+ "returns": "Promise<void>"
201
+ },
202
+ {
203
+ "name": "save",
204
+ "async": true,
205
+ "returns": "Promise<this>"
206
+ }
207
+ ],
208
+ "tenant": {
209
+ "scoped": true,
210
+ "mode": "required",
211
+ "field": "tenantId"
212
+ },
213
+ "conflictColumns": [
214
+ "tenant_id",
215
+ "slug",
216
+ "context"
217
+ ],
218
+ "surfaces": [
219
+ {
220
+ "kind": "api",
221
+ "name": "approvalevents.list",
222
+ "operation": "list",
223
+ "objectName": "@happyvertical/smrt-approvals:ApprovalEvent",
224
+ "path": "/approvalevents",
225
+ "method": "GET"
226
+ },
227
+ {
228
+ "kind": "api",
229
+ "name": "approvalevents.get",
230
+ "operation": "get",
231
+ "objectName": "@happyvertical/smrt-approvals:ApprovalEvent",
232
+ "path": "/approvalevents/[id]",
233
+ "method": "GET"
234
+ },
235
+ {
236
+ "kind": "mcp",
237
+ "name": "approvalevent_list",
238
+ "operation": "list",
239
+ "objectName": "@happyvertical/smrt-approvals:ApprovalEvent"
240
+ },
241
+ {
242
+ "kind": "mcp",
243
+ "name": "approvalevent_get",
244
+ "operation": "get",
245
+ "objectName": "@happyvertical/smrt-approvals:ApprovalEvent"
246
+ }
247
+ ],
248
+ "withheldSurfaces": [
249
+ {
250
+ "kind": "api",
251
+ "operation": "save",
252
+ "code": "lifecycle-method",
253
+ "reason": "`save` is a framework lifecycle method, not a distinct operation",
254
+ "objectName": "@happyvertical/smrt-approvals:ApprovalEvent"
255
+ }
256
+ ],
257
+ "relationshipFeatures": [
258
+ "foreignKey",
259
+ "uuidColumns"
260
+ ],
261
+ "tags": [],
262
+ "risks": []
263
+ },
264
+ {
265
+ "name": "ApprovalPolicy",
266
+ "qualifiedName": "@happyvertical/smrt-approvals:ApprovalPolicy",
267
+ "collection": "approvalpolicies",
268
+ "description": "Tenant-level tightening of an approval kind. One row per tenant and kind.",
269
+ "tableName": "approval_policies",
270
+ "packageName": "@happyvertical/smrt-approvals",
271
+ "extends": "SmrtObject",
272
+ "fields": [
273
+ {
274
+ "name": "tenantId",
275
+ "type": "text",
276
+ "required": true,
277
+ "columnType": "UUID"
278
+ },
279
+ {
280
+ "name": "kind",
281
+ "type": "text",
282
+ "required": true,
283
+ "columnType": "TEXT"
284
+ },
285
+ {
286
+ "name": "requiredApprovals",
287
+ "type": "integer",
288
+ "required": false,
289
+ "columnType": "INTEGER"
290
+ },
291
+ {
292
+ "name": "ttlMs",
293
+ "type": "integer",
294
+ "required": false,
295
+ "columnType": "INTEGER"
296
+ },
297
+ {
298
+ "name": "requiredPermission",
299
+ "type": "text",
300
+ "required": false,
301
+ "columnType": "TEXT"
302
+ },
303
+ {
304
+ "name": "updatedBy",
305
+ "type": "text",
306
+ "required": false,
307
+ "columnType": "TEXT"
308
+ }
309
+ ],
310
+ "relationships": [],
311
+ "methods": [
312
+ "save"
313
+ ],
314
+ "methodSignatures": [
315
+ {
316
+ "name": "save",
317
+ "async": true,
318
+ "returns": "Promise<this>"
319
+ }
320
+ ],
321
+ "tenant": {
322
+ "scoped": true,
323
+ "mode": "required",
324
+ "field": "tenantId"
325
+ },
326
+ "conflictColumns": [
327
+ "tenant_id",
328
+ "slug",
329
+ "context"
330
+ ],
331
+ "surfaces": [
332
+ {
333
+ "kind": "api",
334
+ "name": "approvalpolicies.list",
335
+ "operation": "list",
336
+ "objectName": "@happyvertical/smrt-approvals:ApprovalPolicy",
337
+ "path": "/approvalpolicies",
338
+ "method": "GET"
339
+ },
340
+ {
341
+ "kind": "api",
342
+ "name": "approvalpolicies.get",
343
+ "operation": "get",
344
+ "objectName": "@happyvertical/smrt-approvals:ApprovalPolicy",
345
+ "path": "/approvalpolicies/[id]",
346
+ "method": "GET"
347
+ },
348
+ {
349
+ "kind": "mcp",
350
+ "name": "approvalpolicy_list",
351
+ "operation": "list",
352
+ "objectName": "@happyvertical/smrt-approvals:ApprovalPolicy"
353
+ },
354
+ {
355
+ "kind": "mcp",
356
+ "name": "approvalpolicy_get",
357
+ "operation": "get",
358
+ "objectName": "@happyvertical/smrt-approvals:ApprovalPolicy"
359
+ }
360
+ ],
361
+ "withheldSurfaces": [
362
+ {
363
+ "kind": "api",
364
+ "operation": "save",
365
+ "code": "lifecycle-method",
366
+ "reason": "`save` is a framework lifecycle method, not a distinct operation",
367
+ "objectName": "@happyvertical/smrt-approvals:ApprovalPolicy"
368
+ }
369
+ ],
370
+ "relationshipFeatures": [
371
+ "uuidColumns"
372
+ ],
373
+ "tags": [],
374
+ "risks": []
375
+ },
376
+ {
377
+ "name": "ApprovalRequest",
378
+ "qualifiedName": "@happyvertical/smrt-approvals:ApprovalRequest",
379
+ "collection": "approvalrequests",
380
+ "description": "A request for approval of one subject revision.",
381
+ "tableName": "approval_requests",
382
+ "packageName": "@happyvertical/smrt-approvals",
383
+ "extends": "SmrtObject",
384
+ "fields": [
385
+ {
386
+ "name": "tenantId",
387
+ "type": "text",
388
+ "required": true,
389
+ "columnType": "UUID"
390
+ },
391
+ {
392
+ "name": "kind",
393
+ "type": "text",
394
+ "required": true,
395
+ "columnType": "TEXT",
396
+ "readonly": true
397
+ },
398
+ {
399
+ "name": "subjectType",
400
+ "type": "text",
401
+ "required": true,
402
+ "columnType": "TEXT",
403
+ "readonly": true
404
+ },
405
+ {
406
+ "name": "subjectId",
407
+ "type": "text",
408
+ "required": true,
409
+ "columnType": "TEXT",
410
+ "readonly": true
411
+ },
412
+ {
413
+ "name": "subjectRevisionHash",
414
+ "type": "text",
415
+ "required": true,
416
+ "columnType": "TEXT",
417
+ "readonly": true
418
+ },
419
+ {
420
+ "name": "requestKey",
421
+ "type": "text",
422
+ "required": true,
423
+ "columnType": "TEXT",
424
+ "readonly": true
425
+ },
426
+ {
427
+ "name": "requestedBy",
428
+ "type": "text",
429
+ "required": true,
430
+ "columnType": "TEXT",
431
+ "readonly": true
432
+ },
433
+ {
434
+ "name": "requestedByType",
435
+ "type": "text",
436
+ "required": false,
437
+ "columnType": "TEXT",
438
+ "readonly": true
439
+ },
440
+ {
441
+ "name": "summary",
442
+ "type": "text",
443
+ "required": false,
444
+ "columnType": "TEXT",
445
+ "readonly": true
446
+ },
447
+ {
448
+ "name": "status",
449
+ "type": "text",
450
+ "required": false,
451
+ "columnType": "TEXT",
452
+ "readonly": true
453
+ },
454
+ {
455
+ "name": "requiredApprovals",
456
+ "type": "integer",
457
+ "required": false,
458
+ "columnType": "INTEGER",
459
+ "readonly": true
460
+ },
461
+ {
462
+ "name": "approvalCount",
463
+ "type": "integer",
464
+ "required": false,
465
+ "columnType": "INTEGER",
466
+ "readonly": true
467
+ },
468
+ {
469
+ "name": "requiredPermissions",
470
+ "type": "text",
471
+ "required": false,
472
+ "columnType": "TEXT",
473
+ "readonly": true
474
+ },
475
+ {
476
+ "name": "version",
477
+ "type": "integer",
478
+ "required": false,
479
+ "columnType": "INTEGER",
480
+ "readonly": true
481
+ },
482
+ {
483
+ "name": "expiresAt",
484
+ "type": "datetime",
485
+ "required": false,
486
+ "columnType": "TIMESTAMP",
487
+ "readonly": true
488
+ },
489
+ {
490
+ "name": "decidedAt",
491
+ "type": "datetime",
492
+ "required": false,
493
+ "columnType": "TIMESTAMP",
494
+ "readonly": true
495
+ },
496
+ {
497
+ "name": "consumedAt",
498
+ "type": "datetime",
499
+ "required": false,
500
+ "columnType": "TIMESTAMP",
501
+ "readonly": true
502
+ },
503
+ {
504
+ "name": "consumedBy",
505
+ "type": "text",
506
+ "required": false,
507
+ "columnType": "TEXT",
508
+ "readonly": true
509
+ }
510
+ ],
511
+ "relationships": [],
512
+ "methods": [
513
+ "delete",
514
+ "expiresAtMs",
515
+ "getRequiredPermissions",
516
+ "isConsumed",
517
+ "save",
518
+ "setRequiredPermissions"
519
+ ],
520
+ "methodSignatures": [
521
+ {
522
+ "name": "delete",
523
+ "async": true,
524
+ "returns": "Promise<void>"
525
+ },
526
+ {
527
+ "name": "expiresAtMs",
528
+ "returns": "number"
529
+ },
530
+ {
531
+ "name": "getRequiredPermissions",
532
+ "returns": "string[]"
533
+ },
534
+ {
535
+ "name": "isConsumed",
536
+ "returns": "boolean"
537
+ },
538
+ {
539
+ "name": "save",
540
+ "async": true,
541
+ "returns": "Promise<this>"
542
+ },
543
+ {
544
+ "name": "setRequiredPermissions",
545
+ "params": [
546
+ "slugs: any"
547
+ ],
548
+ "returns": "void"
549
+ }
550
+ ],
551
+ "tenant": {
552
+ "scoped": true,
553
+ "mode": "required",
554
+ "field": "tenantId"
555
+ },
556
+ "conflictColumns": [
557
+ "tenant_id",
558
+ "slug",
559
+ "context"
560
+ ],
561
+ "surfaces": [
562
+ {
563
+ "kind": "api",
564
+ "name": "approvalrequests.list",
565
+ "operation": "list",
566
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest",
567
+ "path": "/approvalrequests",
568
+ "method": "GET"
569
+ },
570
+ {
571
+ "kind": "api",
572
+ "name": "approvalrequests.get",
573
+ "operation": "get",
574
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest",
575
+ "path": "/approvalrequests/[id]",
576
+ "method": "GET"
577
+ },
578
+ {
579
+ "kind": "mcp",
580
+ "name": "approvalrequest_list",
581
+ "operation": "list",
582
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest"
583
+ },
584
+ {
585
+ "kind": "mcp",
586
+ "name": "approvalrequest_get",
587
+ "operation": "get",
588
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest"
589
+ }
590
+ ],
591
+ "withheldSurfaces": [
592
+ {
593
+ "kind": "api",
594
+ "operation": "expiresAtMs",
595
+ "code": "not-included",
596
+ "reason": "not listed in api.include",
597
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest"
598
+ },
599
+ {
600
+ "kind": "api",
601
+ "operation": "getRequiredPermissions",
602
+ "code": "not-included",
603
+ "reason": "not listed in api.include",
604
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest"
605
+ },
606
+ {
607
+ "kind": "api",
608
+ "operation": "isConsumed",
609
+ "code": "not-included",
610
+ "reason": "not listed in api.include",
611
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest"
612
+ },
613
+ {
614
+ "kind": "api",
615
+ "operation": "save",
616
+ "code": "lifecycle-method",
617
+ "reason": "`save` is a framework lifecycle method, not a distinct operation",
618
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest"
619
+ },
620
+ {
621
+ "kind": "api",
622
+ "operation": "setRequiredPermissions",
623
+ "code": "not-included",
624
+ "reason": "not listed in api.include",
625
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest"
626
+ }
627
+ ],
628
+ "relationshipFeatures": [
629
+ "uuidColumns"
630
+ ],
631
+ "tags": [],
632
+ "risks": []
633
+ }
634
+ ],
635
+ "surfaces": [
636
+ {
637
+ "kind": "api",
638
+ "name": "approvalevents.list",
639
+ "operation": "list",
640
+ "objectName": "@happyvertical/smrt-approvals:ApprovalEvent",
641
+ "path": "/approvalevents",
642
+ "method": "GET"
643
+ },
644
+ {
645
+ "kind": "api",
646
+ "name": "approvalevents.get",
647
+ "operation": "get",
648
+ "objectName": "@happyvertical/smrt-approvals:ApprovalEvent",
649
+ "path": "/approvalevents/[id]",
650
+ "method": "GET"
651
+ },
652
+ {
653
+ "kind": "mcp",
654
+ "name": "approvalevent_list",
655
+ "operation": "list",
656
+ "objectName": "@happyvertical/smrt-approvals:ApprovalEvent"
657
+ },
658
+ {
659
+ "kind": "mcp",
660
+ "name": "approvalevent_get",
661
+ "operation": "get",
662
+ "objectName": "@happyvertical/smrt-approvals:ApprovalEvent"
663
+ },
664
+ {
665
+ "kind": "api",
666
+ "name": "approvalpolicies.list",
667
+ "operation": "list",
668
+ "objectName": "@happyvertical/smrt-approvals:ApprovalPolicy",
669
+ "path": "/approvalpolicies",
670
+ "method": "GET"
671
+ },
672
+ {
673
+ "kind": "api",
674
+ "name": "approvalpolicies.get",
675
+ "operation": "get",
676
+ "objectName": "@happyvertical/smrt-approvals:ApprovalPolicy",
677
+ "path": "/approvalpolicies/[id]",
678
+ "method": "GET"
679
+ },
680
+ {
681
+ "kind": "mcp",
682
+ "name": "approvalpolicy_list",
683
+ "operation": "list",
684
+ "objectName": "@happyvertical/smrt-approvals:ApprovalPolicy"
685
+ },
686
+ {
687
+ "kind": "mcp",
688
+ "name": "approvalpolicy_get",
689
+ "operation": "get",
690
+ "objectName": "@happyvertical/smrt-approvals:ApprovalPolicy"
691
+ },
692
+ {
693
+ "kind": "api",
694
+ "name": "approvalrequests.list",
695
+ "operation": "list",
696
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest",
697
+ "path": "/approvalrequests",
698
+ "method": "GET"
699
+ },
700
+ {
701
+ "kind": "api",
702
+ "name": "approvalrequests.get",
703
+ "operation": "get",
704
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest",
705
+ "path": "/approvalrequests/[id]",
706
+ "method": "GET"
707
+ },
708
+ {
709
+ "kind": "mcp",
710
+ "name": "approvalrequest_list",
711
+ "operation": "list",
712
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest"
713
+ },
714
+ {
715
+ "kind": "mcp",
716
+ "name": "approvalrequest_get",
717
+ "operation": "get",
718
+ "objectName": "@happyvertical/smrt-approvals:ApprovalRequest"
719
+ }
720
+ ],
721
+ "prompts": [],
722
+ "relationshipsV2": {
723
+ "foreignKeyFields": 1,
724
+ "crossPackageRefFields": 0,
725
+ "junctionCollections": 0,
726
+ "hierarchicalObjects": 0,
727
+ "polymorphicAssociations": 0,
728
+ "uuidColumns": 14
729
+ },
730
+ "agentDoc": "# @happyvertical/smrt-approvals\n\nDecision gate plus ledger (slice 1 of #3716): `ApprovalRequest`,\nappend-only `ApprovalEvent`, tighten-only `ApprovalPolicy`, and\n`ApprovalService`, the only write path. Not a workflow engine: no stages, no\nexecutor, no compensation. Read [agents/approvals.md](agents/approvals.md)\nbefore changing a transition, the guard predicates, or the surface.\n\n## Invariants\n\n- Every transition is ONE guarded `UPDATE ... WHERE id AND tenant_id AND\n status AND version AND expires_at ... RETURNING version` plus one event\n insert, in one transaction. Zero rows means a concurrent writer won: retry\n from a fresh read, never write anyway. Keep the predicate the arbiter; the\n in-transaction reads only classify refusals.\n- Only `human` principals decide; never the requester; one decision per actor\n (unique `(request_id, vote_key)`); quorum counts distinct approver events.\n Agents and services may request, consume, and expire.\n- Tenant: every statement names `tenant_id`, the work runs in\n `withTenant(principal.tenantId)`, and a conflicting active context is\n refused. Principal tenant ids must be UUIDs.\n- Policy may only tighten (`assertPolicyTightens` on write, `effectiveRules`\n clamps on read). Quorum and expiry are fixed at request time; decide-time\n permissions are the request snapshot plus the current kind and policy slugs.\n- Generated surface is `list`/`get` on REST and MCP, no CLI. A collection's\n `@smrt()` config is applied to its item class by core, so each collection\n repeats its model's surface exactly; changing one without the other opens\n or closes the model. `surface.test.ts` holds this.\n- Models refuse re-save and delete of requests and events. Never add an\n update path for events. Inserts need the service-only capability in\n `src/write-capability.ts`; never export it or mint it outside the service.\n- The event FK is physical on PostgreSQL and SQLite only: DuckDB runs an\n indexed-column UPDATE on a referenced row as delete plus insert.\n- No Node built-ins in `src/` (browser gate); use `globalThis.crypto`.\n\n## Validation\n\n```bash\npnpm --filter @happyvertical/smrt-approvals test\npnpm --filter @happyvertical/smrt-approvals typecheck\npnpm --filter @happyvertical/smrt-approvals test:postgres\n```\n\nThe behaviour suite (`src/__tests__/helpers/approval-suite.ts`) runs on\nSQLite, DuckDB, and, through `test:postgres`, a migrated PostgreSQL database.\n",
731
+ "moduleDocs": [
732
+ {
733
+ "path": "agents/approvals.md",
734
+ "module": "approvals",
735
+ "content": "# Approvals: transitions, concurrency, and scope\n\nDesign source: the accepted recommendation on smrt#3716. This file records\nthe slice-1 contract (#3741) and what later slices add.\n\n## States and transitions\n\n```\npending --approve (distinct approvers reach quorum)--> approved\napproved --consume (hash matches, unexpired)---------> approved + consumedAt\npending --reject | request_changes-------------------> rejected | changes_requested\npending --cancel (requester or approvals.cancel-any)-> cancelled\npending | approved(unconsumed) --expire (deadline)---> expired\n```\n\n`changes_requested` is terminal in slice 1: the requester opens a new request\nfor the new revision (a new `subjectRevisionHash`). There is no resubmit.\n\nEach transition in `src/service.ts`:\n\n1. Validates input and the principal (`id`, UUID `tenantId`, `type`, `can`).\n2. Refuses a principal whose tenant differs from an active tenant context.\n3. Opens a transaction through core's `withEmbeddedWriteTransaction(db,\n isEmbeddedDatabase(db), fn)`. On embedded engines (SQLite, DuckDB, libsql)\n it takes the embedded write queue for the database first, then the\n adapter's connection lock; the event insert and change-feed append inside\n re-enter that hold. The reverse order (a raw `db.transaction()`) deadlocks\n against an unrelated NULL-tenant save, which holds the queue and then\n opens its own write transaction, until the 30 s transaction-queue timeout,\n and lets other root writes land inside the transaction. The queue also\n serializes transitions per database, so no separate chain exists.\n PostgreSQL is not queued: each transaction gets its own pooled connection.\n4. Reads the request through a transaction-bound collection with an explicit\n `tenantId` filter, and classifies refusals from that read.\n5. Runs the guarded UPDATE with the version it read. Zero rows: the\n transaction body returns `retry`, nothing was written, and the loop starts\n again from a fresh read (up to `maxAttempts`, then `contention`).\n6. Inserts the event with `sequence` = the returned version and appends a\n change-feed entry (the raw UPDATE bypasses save hooks).\n7. Re-reads the request after commit on the service connection.\n\nOn PostgreSQL the UPDATE takes the row lock; under READ COMMITTED a waiting\nUPDATE re-evaluates `version = ?` after the winner commits, so it matches\nnothing. Unique keys `(request_id, sequence)` and `(request_id, vote_key)`\nare a second line: a violation aborts the attempt and it retries.\n\n`consume({ transaction })` runs steps 4 to 6 on the caller's open\ntransaction, once, without retry or serialization; the caller owns commit.\nOn embedded engines that handle must come from `withEmbeddedWriteTransaction`\nor `SmrtObject.withTransaction()`, never a raw `db.transaction()`, for the\nlock-order reason in step 3.\n\n## Raw SQL boundaries\n\nThe guarded UPDATEs are the only raw SQL. They bind every value (no literal\nUUIDs or empty strings), validate the request id as a UUID first, and name\n`tenant_id`. Reads and inserts go through collections so tenancy\ninterceptors, UUID columns, and the change feed behave as for any model.\n\n## Service-only write capability\n\nOnly `ApprovalService` inserts `ApprovalRequest` and `ApprovalEvent` rows\n(events only in the same transaction as the guarded UPDATE they record). It passes the\nmodule-private `SERVICE_WRITE` symbol (`src/write-capability.ts`, not a public\nexport) as a constructor option; the model records that instance in a\n`WeakSet` and refuses, in `save()` and `validateBeforeSave()`, any insert\nwithout it, and any minted insert that does not start pending, undecided,\nand unconsumed. `new ApprovalRequest({ status: 'approved' }).save()`,\n`ApprovalRequestCollection.create(...)`, and a forged `approved` event\n(which would otherwise count toward quorum) are refused and write nothing. A\nsymbol never arrives from JSON, so REST and MCP input cannot carry it (the\nsmrt-reports `runtime-report.ts` pattern). It guards the in-process API,\nnot raw SQL.\n\n`consume` re-checks the ledger before consuming (defence in depth against a\nstatus written around the service): the `created` event at sequence 1, no\nterminal event, and `approved` events from at least `requiredApprovals`\ndistinct human actors other than the requester; otherwise it refuses\n`unbacked_approval`. Quorum counting in `decide` applies the same filter.\n\n## Out of scope here (later slices of #3716)\n\n- Slice 2: Svelte inbox, panel, decision bar; `approvals.requests` recipe.\n- Slice 3: notification adapter (`UserNotificationService.notify`, sourceRef\n `approval:<id>:<event>`), profiles `AuditLog` mirror, expiry and reminder\n job calling `expireDue`.\n- Slice 4: adopters (social, personas, content, subscriptions).\n- Slice 5: agent and MCP request tools (deciding stays human-only).\n- Slice 6: #3667 `pending_approval` hand-off.\n\nKnown gaps: listing every holder of a permission needs a users-package\nhelper (for notifications); a vote stays counted if the approver later loses\nthe permission.\n"
736
+ }
737
+ ],
738
+ "browser": {
739
+ "status": "server-only",
740
+ "issues": [
741
+ "#3617",
742
+ "#3624",
743
+ "#3626"
744
+ ],
745
+ "via": [
746
+ "@happyvertical/smrt-profiles",
747
+ "@happyvertical/smrt-tenancy",
748
+ "@happyvertical/smrt-users"
749
+ ]
750
+ }
751
+ }