@happyvertical/smrt-approvals 0.55.11 → 0.55.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -1
- package/README.md +13 -1
- package/agents/approvals.md +21 -1
- package/dist/index.js +33 -9
- package/dist/index.js.map +1 -1
- package/dist/kinds.d.ts.map +1 -1
- package/dist/manifest.json +49 -5
- package/dist/models/ApprovalEvent.d.ts +6 -0
- package/dist/models/ApprovalEvent.d.ts.map +1 -1
- package/dist/service.d.ts +5 -3
- package/dist/service.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +23 -7
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +6 -6
package/dist/kinds.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kinds.d.ts","sourceRoot":"","sources":["../src/kinds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC5B,MAAM,YAAY,CAAC;AAEpB,mDAAmD;AACnD,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,iDAAiD;AACjD,eAAO,MAAM,uBAAuB,QAA0B,CAAC;AAE/D,qDAAqD;AACrD,eAAO,MAAM,mBAAmB,QAA4B,CAAC;AAE7D,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,QAAY,CAAC;AAS7C,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEhE;AA4BD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"kinds.d.ts","sourceRoot":"","sources":["../src/kinds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC5B,MAAM,YAAY,CAAC;AAEpB,mDAAmD;AACnD,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,iDAAiD;AACjD,eAAO,MAAM,uBAAuB,QAA0B,CAAC;AAE/D,qDAAqD;AACrD,eAAO,MAAM,mBAAmB,QAA4B,CAAC;AAE7D,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,QAAY,CAAC;AAS7C,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEhE;AA4BD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CA6DzE;AAED,qDAAqD;AACrD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAErE;AAED,4CAA4C;AAC5C,wBAAgB,iBAAiB,IAAI,YAAY,EAAE,CAElD;AAED,gEAAgE;AAChE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAS7D;AAED,mCAAmC;AACnC,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE,mBAAmB,GACzB,IAAI,CA4BN;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,YAAY,EAClB,MAAM,CAAC,EAAE,mBAAmB,GAAG,IAAI,GAClC,sBAAsB,CAoBxB"}
|
package/dist/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": "1.0.0",
|
|
3
3
|
"timestamp": 0,
|
|
4
4
|
"packageName": "@happyvertical/smrt-approvals",
|
|
5
|
-
"packageVersion": "0.55.
|
|
5
|
+
"packageVersion": "0.55.13",
|
|
6
6
|
"objects": {
|
|
7
7
|
"@happyvertical/smrt-approvals:ApprovalEventCollection": {
|
|
8
8
|
"name": "approvaleventcollection",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"collectionExportName": "ApprovalEventCollectionCollection",
|
|
36
36
|
"schema": {
|
|
37
37
|
"tableName": "approval_events",
|
|
38
|
-
"ddl": "CREATE TABLE IF NOT EXISTS \"approval_events\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"tenant_id\" UUID NOT NULL,\n \"request_id\" UUID NOT NULL,\n \"type\" TEXT NOT NULL,\n \"actor_id\" TEXT NOT NULL,\n \"actor_type\" TEXT,\n \"reason\" TEXT,\n \"subject_revision_hash\" TEXT,\n \"sequence\" INTEGER,\n \"vote_key\" TEXT,\n \"occurred_at\" TIMESTAMP\n);",
|
|
38
|
+
"ddl": "CREATE TABLE IF NOT EXISTS \"approval_events\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"tenant_id\" UUID NOT NULL,\n \"request_id\" UUID NOT NULL,\n \"type\" TEXT NOT NULL,\n \"actor_id\" TEXT NOT NULL,\n \"actor_type\" TEXT,\n \"reason\" TEXT,\n \"subject_revision_hash\" TEXT,\n \"sequence\" INTEGER,\n \"vote_key\" TEXT,\n \"occurred_at\" TIMESTAMP,\n \"self_approved\" BOOLEAN DEFAULT FALSE,\n \"self_approval_permission\" TEXT DEFAULT ''\n);",
|
|
39
39
|
"columns": {
|
|
40
40
|
"id": {
|
|
41
41
|
"type": "UUID",
|
|
@@ -123,6 +123,18 @@
|
|
|
123
123
|
"type": "TIMESTAMP",
|
|
124
124
|
"notNull": false,
|
|
125
125
|
"unique": false
|
|
126
|
+
},
|
|
127
|
+
"self_approved": {
|
|
128
|
+
"type": "BOOLEAN",
|
|
129
|
+
"notNull": false,
|
|
130
|
+
"unique": false,
|
|
131
|
+
"default": false
|
|
132
|
+
},
|
|
133
|
+
"self_approval_permission": {
|
|
134
|
+
"type": "TEXT",
|
|
135
|
+
"notNull": false,
|
|
136
|
+
"unique": false,
|
|
137
|
+
"default": ""
|
|
126
138
|
}
|
|
127
139
|
},
|
|
128
140
|
"indexes": [
|
|
@@ -166,7 +178,7 @@
|
|
|
166
178
|
]
|
|
167
179
|
}
|
|
168
180
|
],
|
|
169
|
-
"version": "
|
|
181
|
+
"version": "998756b0"
|
|
170
182
|
},
|
|
171
183
|
"importPath": "@happyvertical/smrt-approvals"
|
|
172
184
|
},
|
|
@@ -599,6 +611,26 @@
|
|
|
599
611
|
"readonly": true
|
|
600
612
|
},
|
|
601
613
|
"readonly": true
|
|
614
|
+
},
|
|
615
|
+
"selfApproved": {
|
|
616
|
+
"type": "boolean",
|
|
617
|
+
"required": false,
|
|
618
|
+
"default": false,
|
|
619
|
+
"_meta": {
|
|
620
|
+
"readonly": true,
|
|
621
|
+
"default": false
|
|
622
|
+
},
|
|
623
|
+
"readonly": true
|
|
624
|
+
},
|
|
625
|
+
"selfApprovalPermission": {
|
|
626
|
+
"type": "text",
|
|
627
|
+
"required": false,
|
|
628
|
+
"default": "",
|
|
629
|
+
"_meta": {
|
|
630
|
+
"readonly": true,
|
|
631
|
+
"default": ""
|
|
632
|
+
},
|
|
633
|
+
"readonly": true
|
|
602
634
|
}
|
|
603
635
|
},
|
|
604
636
|
"methods": {
|
|
@@ -690,7 +722,7 @@
|
|
|
690
722
|
],
|
|
691
723
|
"schema": {
|
|
692
724
|
"tableName": "approval_events",
|
|
693
|
-
"ddl": "CREATE TABLE IF NOT EXISTS \"approval_events\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"tenant_id\" UUID NOT NULL,\n \"request_id\" UUID NOT NULL,\n \"type\" TEXT NOT NULL,\n \"actor_id\" TEXT NOT NULL,\n \"actor_type\" TEXT,\n \"reason\" TEXT,\n \"subject_revision_hash\" TEXT,\n \"sequence\" INTEGER,\n \"vote_key\" TEXT,\n \"occurred_at\" TIMESTAMP\n);",
|
|
725
|
+
"ddl": "CREATE TABLE IF NOT EXISTS \"approval_events\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"tenant_id\" UUID NOT NULL,\n \"request_id\" UUID NOT NULL,\n \"type\" TEXT NOT NULL,\n \"actor_id\" TEXT NOT NULL,\n \"actor_type\" TEXT,\n \"reason\" TEXT,\n \"subject_revision_hash\" TEXT,\n \"sequence\" INTEGER,\n \"vote_key\" TEXT,\n \"occurred_at\" TIMESTAMP,\n \"self_approved\" BOOLEAN DEFAULT FALSE,\n \"self_approval_permission\" TEXT DEFAULT ''\n);",
|
|
694
726
|
"columns": {
|
|
695
727
|
"id": {
|
|
696
728
|
"type": "UUID",
|
|
@@ -778,6 +810,18 @@
|
|
|
778
810
|
"type": "TIMESTAMP",
|
|
779
811
|
"notNull": false,
|
|
780
812
|
"unique": false
|
|
813
|
+
},
|
|
814
|
+
"self_approved": {
|
|
815
|
+
"type": "BOOLEAN",
|
|
816
|
+
"notNull": false,
|
|
817
|
+
"unique": false,
|
|
818
|
+
"default": false
|
|
819
|
+
},
|
|
820
|
+
"self_approval_permission": {
|
|
821
|
+
"type": "TEXT",
|
|
822
|
+
"notNull": false,
|
|
823
|
+
"unique": false,
|
|
824
|
+
"default": ""
|
|
781
825
|
}
|
|
782
826
|
},
|
|
783
827
|
"indexes": [
|
|
@@ -821,7 +865,7 @@
|
|
|
821
865
|
]
|
|
822
866
|
}
|
|
823
867
|
],
|
|
824
|
-
"version": "
|
|
868
|
+
"version": "998756b0"
|
|
825
869
|
},
|
|
826
870
|
"importPath": "@happyvertical/smrt-approvals"
|
|
827
871
|
},
|
|
@@ -19,6 +19,8 @@ export interface ApprovalEventOptions extends SmrtObjectOptions {
|
|
|
19
19
|
actorId?: string;
|
|
20
20
|
actorType?: ApprovalPrincipalType;
|
|
21
21
|
reason?: string;
|
|
22
|
+
selfApproved?: boolean;
|
|
23
|
+
selfApprovalPermission?: string;
|
|
22
24
|
subjectRevisionHash?: string;
|
|
23
25
|
sequence?: number;
|
|
24
26
|
voteKey?: string | null;
|
|
@@ -55,6 +57,10 @@ export declare class ApprovalEvent extends SmrtObject {
|
|
|
55
57
|
voteKey: string | null;
|
|
56
58
|
/** When the event happened. */
|
|
57
59
|
occurredAt: Date | null;
|
|
60
|
+
/** Whether the requester used the explicit self-decision allowance. */
|
|
61
|
+
selfApproved: boolean;
|
|
62
|
+
/** Permission checked when this self-decision was recorded. Empty otherwise. */
|
|
63
|
+
selfApprovalPermission: string;
|
|
58
64
|
constructor(options?: ApprovalEventOptions);
|
|
59
65
|
/**
|
|
60
66
|
* Append the event. An event is written once and never updated.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApprovalEvent.d.ts","sourceRoot":"","sources":["../../src/models/ApprovalEvent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAGL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC3B,MAAM,aAAa,CAAC;AAIrB,qDAAqD;AACrD,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CAC1B;AAED,4DAA4D;AAC5D,qBAuBa,aAAc,SAAQ,UAAU;IAC3C,kDAAkD;IAElD,QAAQ,EAAE,MAAM,CAAM;IAEtB;;;;;;OAMG;IAQH,SAAS,EAAE,MAAM,CAAM;IAEvB,qBAAqB;IAErB,IAAI,EAAE,iBAAiB,CAAa;IAEpC,iCAAiC;IAEjC,OAAO,EAAE,MAAM,CAAM;IAErB,mCAAmC;IAEnC,SAAS,EAAE,qBAAqB,CAAW;IAE3C,yEAAyE;IAEzE,MAAM,EAAE,MAAM,CAAM;IAEpB,wEAAwE;IAExE,mBAAmB,EAAE,MAAM,CAAM;IAEjC,qEAAqE;IAErE,QAAQ,EAAE,MAAM,CAAK;IAErB;;;OAGG;IAEH,OAAO,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE9B,+BAA+B;IAE/B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAQ;
|
|
1
|
+
{"version":3,"file":"ApprovalEvent.d.ts","sourceRoot":"","sources":["../../src/models/ApprovalEvent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAGL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC3B,MAAM,aAAa,CAAC;AAIrB,qDAAqD;AACrD,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CAC1B;AAED,4DAA4D;AAC5D,qBAuBa,aAAc,SAAQ,UAAU;IAC3C,kDAAkD;IAElD,QAAQ,EAAE,MAAM,CAAM;IAEtB;;;;;;OAMG;IAQH,SAAS,EAAE,MAAM,CAAM;IAEvB,qBAAqB;IAErB,IAAI,EAAE,iBAAiB,CAAa;IAEpC,iCAAiC;IAEjC,OAAO,EAAE,MAAM,CAAM;IAErB,mCAAmC;IAEnC,SAAS,EAAE,qBAAqB,CAAW;IAE3C,yEAAyE;IAEzE,MAAM,EAAE,MAAM,CAAM;IAEpB,wEAAwE;IAExE,mBAAmB,EAAE,MAAM,CAAM;IAEjC,qEAAqE;IAErE,QAAQ,EAAE,MAAM,CAAK;IAErB;;;OAGG;IAEH,OAAO,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE9B,+BAA+B;IAE/B,UAAU,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE/B,uEAAuE;IAEvE,YAAY,EAAE,OAAO,CAAS;IAE9B,gFAAgF;IAEhF,sBAAsB,EAAE,MAAM,CAAM;gBAExB,OAAO,GAAE,oBAAyB;IAoB9C;;;;OAIG;IACY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAapC;;;;;;;OAOG;cACsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5D,OAAO,CAAC,mBAAmB;IAQ3B;;;;OAIG;IACY,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAMvC"}
|
package/dist/service.d.ts
CHANGED
|
@@ -102,7 +102,8 @@ export declare class ApprovalService {
|
|
|
102
102
|
requestApproval(principal: ApprovalPrincipal, input: RequestApprovalInput): Promise<ApprovalResult>;
|
|
103
103
|
/**
|
|
104
104
|
* Record a decision. Only a `human` principal of the request's tenant who
|
|
105
|
-
*
|
|
105
|
+
* holds every required permission may decide (requesters also need the
|
|
106
|
+
* kind's explicit selfApprovalPermission),
|
|
106
107
|
* once, while the request is pending and unexpired. `approve` counts
|
|
107
108
|
* toward the quorum of distinct approvers; `reject` and `request_changes`
|
|
108
109
|
* end the request.
|
|
@@ -188,8 +189,9 @@ export declare class ApprovalService {
|
|
|
188
189
|
* Defence in depth for `consume`: the request row says `approved`, so the
|
|
189
190
|
* ledger must agree. It needs the `created` event at sequence 1, no
|
|
190
191
|
* terminal event, and approvals from at least `requiredApprovals` distinct
|
|
191
|
-
* human actors
|
|
192
|
-
* the service (raw SQL); model-layer inserts are
|
|
192
|
+
* human actors, with explicit audit markers for self-decisions. Catches a
|
|
193
|
+
* status written around the service (raw SQL); model-layer inserts are
|
|
194
|
+
* already capability-gated.
|
|
193
195
|
*/
|
|
194
196
|
private ledgerBacksApproval;
|
|
195
197
|
/**
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-approvals",
|
|
6
|
-
"packageVersion": "0.55.
|
|
6
|
+
"packageVersion": "0.55.13",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
12
|
-
"agents": "
|
|
13
|
-
"moduleDoc:agents/approvals.md": "
|
|
10
|
+
"manifest": "8ded4580ad30aa55911fbdb4ab6eaba037dc115815054ffa00d0826e97de8f6b",
|
|
11
|
+
"packageJson": "bee72b59eedb630041ca2b3e86f349af0aec9a7f8536238210723d21a011dae1",
|
|
12
|
+
"agents": "d97f97c724891f846420dadc8abfc9b73af5e2d453a56ae748816cf1b039d452",
|
|
13
|
+
"moduleDoc:agents/approvals.md": "3e2b9ef6441798041de30d39b295886c758053185085174bd430d2f156b81e5a"
|
|
14
14
|
},
|
|
15
15
|
"exports": [
|
|
16
16
|
".",
|
|
@@ -178,6 +178,22 @@
|
|
|
178
178
|
"required": false,
|
|
179
179
|
"columnType": "TIMESTAMP",
|
|
180
180
|
"readonly": true
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "selfApproved",
|
|
184
|
+
"type": "boolean",
|
|
185
|
+
"required": false,
|
|
186
|
+
"columnType": "BOOLEAN",
|
|
187
|
+
"default": false,
|
|
188
|
+
"readonly": true
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"name": "selfApprovalPermission",
|
|
192
|
+
"type": "text",
|
|
193
|
+
"required": false,
|
|
194
|
+
"columnType": "TEXT",
|
|
195
|
+
"default": "",
|
|
196
|
+
"readonly": true
|
|
181
197
|
}
|
|
182
198
|
],
|
|
183
199
|
"relationships": [
|
|
@@ -727,12 +743,12 @@
|
|
|
727
743
|
"polymorphicAssociations": 0,
|
|
728
744
|
"uuidColumns": 14
|
|
729
745
|
},
|
|
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;
|
|
746
|
+
"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; requesters additionally need the kind's\n explicit `selfApprovalPermission`; 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
747
|
"moduleDocs": [
|
|
732
748
|
{
|
|
733
749
|
"path": "agents/approvals.md",
|
|
734
750
|
"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
|
|
751
|
+
"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; requester events count only with `selfApproved: true`\nand a valid `selfApprovalPermission` audit slug. 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\n## Permission-gated self-decisions\n\nA kind may opt in with `selfApprovalPermission: 'content.approve-self'`.\nThe requester must be human and hold that permission plus every normal snapshot,\ncurrent kind and tenant-policy decision permission. All three verdicts use this\nallowance. Without the opt-in or grant, `self_approval` remains the refusal.\nSelf-decisions record `selfApproved` and `selfApprovalPermission` on the event;\nordinary events default to false and an empty slug. These fields are service-only,\nnot caller input. Deploy the generated event-column migration before upgrading\nruntime code; existing events default to false/empty and cannot gain self authority.\nLike other votes, an authorized self vote remains counted after permission or kind\nconfiguration changes. It counts once, never bypasses quorum, expiry or tenancy,\nand remains atomic with the guarded transition.\n\nDuckDB deployments on `@happyvertical/sql` 0.103.1 may repeatedly recreate the\nunchanged request/sequence index during migration because index introspection\nretains SQL identifier quotes; see [SDK #1395](https://github.com/happyvertical/sdk/issues/1395).\nThe new audit columns converge and existing ordinary approvals remain consumable.\n"
|
|
736
752
|
}
|
|
737
753
|
],
|
|
738
754
|
"browser": {
|
package/dist/types.d.ts
CHANGED
|
@@ -59,6 +59,8 @@ export interface ApprovalKindInput {
|
|
|
59
59
|
subject: string;
|
|
60
60
|
/** Permission slug a principal must hold to decide a request of this kind. */
|
|
61
61
|
permission: string;
|
|
62
|
+
/** Additional permission allowing a human requester to decide their own request. */
|
|
63
|
+
selfApprovalPermission?: string;
|
|
62
64
|
/** Package defaults. */
|
|
63
65
|
defaults?: ApprovalKindDefaults;
|
|
64
66
|
}
|
|
@@ -67,6 +69,8 @@ export interface ApprovalKind {
|
|
|
67
69
|
readonly key: string;
|
|
68
70
|
readonly subject: string;
|
|
69
71
|
readonly permission: string;
|
|
72
|
+
/** Optional opt-in; supplements every normal decision permission. */
|
|
73
|
+
readonly selfApprovalPermission?: string;
|
|
70
74
|
readonly defaults: Readonly<Required<ApprovalKindDefaults>>;
|
|
71
75
|
}
|
|
72
76
|
/** The effective rules for one request: kind defaults tightened by policy. */
|
|
@@ -86,7 +90,7 @@ export type ApprovalRefusalReason =
|
|
|
86
90
|
| 'not_pending'
|
|
87
91
|
/** The request's expiry has passed. */
|
|
88
92
|
| 'expired'
|
|
89
|
-
/** The requester
|
|
93
|
+
/** The requester lacks the explicit kind allowance to decide their own request. */
|
|
90
94
|
| 'self_approval'
|
|
91
95
|
/** A non-human principal tried to decide. */
|
|
92
96
|
| 'not_human'
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,2FAOpB,CAAC;AAEX,+CAA+C;AAC/C,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,uGAQvB,CAAC;AAEX,4CAA4C;AAC5C,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,2BAA2B;AAC3B,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,iBAAiB,CAAC;AAExE;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;AAElE,4CAA4C;AAC5C,eAAO,MAAM,wBAAwB,EAAE,SAAS,qBAAqB,EAIpE,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,wEAAwE;IACxE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAED,gFAAgF;AAChF,wBAAgB,gCAAgC,CAC9C,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC7B,OAAO,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,qBAAqB,CAAA;CAAE,GACrE,iBAAiB,CAQnB;AAED,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CACjC;AAED,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAC;CAC7D;AAED,8EAA8E;AAC9E,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,oCAAoC;AACpC,MAAM,MAAM,qBAAqB;AAC/B,2EAA2E;AACzE,WAAW;AACb,uEAAuE;GACrE,iBAAiB;AACnB,8DAA8D;GAC5D,aAAa;AACf,uCAAuC;GACrC,SAAS;AACX,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * Public vocabulary of `@happyvertical/smrt-approvals`: statuses, event\n * types, principals, kind definitions, and the typed transition results.\n *\n * @packageDocumentation\n */\n\n/** Every status an {@link ApprovalRequest} can hold. */\nexport const APPROVAL_STATUSES = [\n 'pending',\n 'approved',\n 'rejected',\n 'changes_requested',\n 'cancelled',\n 'expired',\n] as const;\n\n/** Lifecycle status of an approval request. */\nexport type ApprovalStatus = (typeof APPROVAL_STATUSES)[number];\n\n/** Every event type the append-only ledger records. */\nexport const APPROVAL_EVENT_TYPES = [\n 'created',\n 'approved',\n 'rejected',\n 'changes_requested',\n 'cancelled',\n 'expired',\n 'consumed',\n] as const;\n\n/** Type of an {@link ApprovalEvent} row. */\nexport type ApprovalEventType = (typeof APPROVAL_EVENT_TYPES)[number];\n\n/** A decider's verdict. */\nexport type ApprovalDecision = 'approve' | 'reject' | 'request_changes';\n\n/**\n * What kind of actor a principal is. Only `human` principals may decide;\n * `agent` (an autonomous principal: persona, assistant, worker) and\n * `service` (a job or integration) may request, consume, and expire.\n */\nexport type ApprovalPrincipalType = 'human' | 'agent' | 'service';\n\n/** Every principal type, for validation. */\nexport const APPROVAL_PRINCIPAL_TYPES: readonly ApprovalPrincipalType[] = [\n 'human',\n 'agent',\n 'service',\n];\n\n/**\n * An actor whose authority is a set of permission slugs resolved for ONE\n * tenant (the `principal.can(slug)` idiom shared with personas and support).\n * Build it from `PermissionResolver.resolvePermissions(userId, tenantId)` in\n * production; tests use {@link approvalPrincipalFromPermissions}.\n */\nexport interface ApprovalPrincipal {\n /** Stable actor id recorded on events (a user or profile id). */\n readonly id: string;\n /** The tenant the permissions were resolved for. Required. */\n readonly tenantId: string;\n /** Actor type. Only `human` may approve, reject, or request changes. */\n readonly type: ApprovalPrincipalType;\n /** Whether this actor holds the permission slug in {@link tenantId}. */\n can(slug: string): boolean;\n}\n\n/** Build an {@link ApprovalPrincipal} from an explicit set of granted slugs. */\nexport function approvalPrincipalFromPermissions(\n permissions: Iterable<string>,\n options: { id: string; tenantId: string; type: ApprovalPrincipalType },\n): ApprovalPrincipal {\n const granted = new Set(permissions);\n return {\n id: options.id,\n tenantId: options.tenantId,\n type: options.type,\n can: (slug: string) => granted.has(slug),\n };\n}\n\n/** Package defaults a kind declares; tenant policy may only tighten them. */\nexport interface ApprovalKindDefaults {\n /** Distinct approvers needed. Integer, 1-25. Default 1. */\n requiredApprovals?: number;\n /** Lifetime of a request in milliseconds. Default seven days. */\n ttlMs?: number;\n}\n\n/** Input to {@link defineApprovalKind}. */\nexport interface ApprovalKindInput {\n /** Dotted, lower-case key, e.g. `social.post.publish`. Unique per process. */\n key: string;\n /** Qualified subject class, e.g. `@happyvertical/smrt-social:SocialPost`. */\n subject: string;\n /** Permission slug a principal must hold to decide a request of this kind. */\n permission: string;\n /** Package defaults. */\n defaults?: ApprovalKindDefaults;\n}\n\n/** A registered, normalized approval kind. */\nexport interface ApprovalKind {\n readonly key: string;\n readonly subject: string;\n readonly permission: string;\n readonly defaults: Readonly<Required<ApprovalKindDefaults>>;\n}\n\n/** The effective rules for one request: kind defaults tightened by policy. */\nexport interface EffectiveApprovalRules {\n requiredApprovals: number;\n ttlMs: number;\n /** Every slug a decider must hold (the kind's, plus policy additions). */\n requiredPermissions: string[];\n}\n\n/** Why a transition was refused. */\nexport type ApprovalRefusalReason =\n /** No request with that id in the principal's tenant (or malformed id). */\n | 'not_found'\n /** The principal's tenant does not match the active tenant context. */\n | 'tenant_mismatch'\n /** The request is not in a status this transition accepts. */\n | 'not_pending'\n /** The request's expiry has passed. */\n | 'expired'\n /** The requester
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["/**\n * Public vocabulary of `@happyvertical/smrt-approvals`: statuses, event\n * types, principals, kind definitions, and the typed transition results.\n *\n * @packageDocumentation\n */\n\n/** Every status an {@link ApprovalRequest} can hold. */\nexport const APPROVAL_STATUSES = [\n 'pending',\n 'approved',\n 'rejected',\n 'changes_requested',\n 'cancelled',\n 'expired',\n] as const;\n\n/** Lifecycle status of an approval request. */\nexport type ApprovalStatus = (typeof APPROVAL_STATUSES)[number];\n\n/** Every event type the append-only ledger records. */\nexport const APPROVAL_EVENT_TYPES = [\n 'created',\n 'approved',\n 'rejected',\n 'changes_requested',\n 'cancelled',\n 'expired',\n 'consumed',\n] as const;\n\n/** Type of an {@link ApprovalEvent} row. */\nexport type ApprovalEventType = (typeof APPROVAL_EVENT_TYPES)[number];\n\n/** A decider's verdict. */\nexport type ApprovalDecision = 'approve' | 'reject' | 'request_changes';\n\n/**\n * What kind of actor a principal is. Only `human` principals may decide;\n * `agent` (an autonomous principal: persona, assistant, worker) and\n * `service` (a job or integration) may request, consume, and expire.\n */\nexport type ApprovalPrincipalType = 'human' | 'agent' | 'service';\n\n/** Every principal type, for validation. */\nexport const APPROVAL_PRINCIPAL_TYPES: readonly ApprovalPrincipalType[] = [\n 'human',\n 'agent',\n 'service',\n];\n\n/**\n * An actor whose authority is a set of permission slugs resolved for ONE\n * tenant (the `principal.can(slug)` idiom shared with personas and support).\n * Build it from `PermissionResolver.resolvePermissions(userId, tenantId)` in\n * production; tests use {@link approvalPrincipalFromPermissions}.\n */\nexport interface ApprovalPrincipal {\n /** Stable actor id recorded on events (a user or profile id). */\n readonly id: string;\n /** The tenant the permissions were resolved for. Required. */\n readonly tenantId: string;\n /** Actor type. Only `human` may approve, reject, or request changes. */\n readonly type: ApprovalPrincipalType;\n /** Whether this actor holds the permission slug in {@link tenantId}. */\n can(slug: string): boolean;\n}\n\n/** Build an {@link ApprovalPrincipal} from an explicit set of granted slugs. */\nexport function approvalPrincipalFromPermissions(\n permissions: Iterable<string>,\n options: { id: string; tenantId: string; type: ApprovalPrincipalType },\n): ApprovalPrincipal {\n const granted = new Set(permissions);\n return {\n id: options.id,\n tenantId: options.tenantId,\n type: options.type,\n can: (slug: string) => granted.has(slug),\n };\n}\n\n/** Package defaults a kind declares; tenant policy may only tighten them. */\nexport interface ApprovalKindDefaults {\n /** Distinct approvers needed. Integer, 1-25. Default 1. */\n requiredApprovals?: number;\n /** Lifetime of a request in milliseconds. Default seven days. */\n ttlMs?: number;\n}\n\n/** Input to {@link defineApprovalKind}. */\nexport interface ApprovalKindInput {\n /** Dotted, lower-case key, e.g. `social.post.publish`. Unique per process. */\n key: string;\n /** Qualified subject class, e.g. `@happyvertical/smrt-social:SocialPost`. */\n subject: string;\n /** Permission slug a principal must hold to decide a request of this kind. */\n permission: string;\n /** Additional permission allowing a human requester to decide their own request. */\n selfApprovalPermission?: string;\n /** Package defaults. */\n defaults?: ApprovalKindDefaults;\n}\n\n/** A registered, normalized approval kind. */\nexport interface ApprovalKind {\n readonly key: string;\n readonly subject: string;\n readonly permission: string;\n /** Optional opt-in; supplements every normal decision permission. */\n readonly selfApprovalPermission?: string;\n readonly defaults: Readonly<Required<ApprovalKindDefaults>>;\n}\n\n/** The effective rules for one request: kind defaults tightened by policy. */\nexport interface EffectiveApprovalRules {\n requiredApprovals: number;\n ttlMs: number;\n /** Every slug a decider must hold (the kind's, plus policy additions). */\n requiredPermissions: string[];\n}\n\n/** Why a transition was refused. */\nexport type ApprovalRefusalReason =\n /** No request with that id in the principal's tenant (or malformed id). */\n | 'not_found'\n /** The principal's tenant does not match the active tenant context. */\n | 'tenant_mismatch'\n /** The request is not in a status this transition accepts. */\n | 'not_pending'\n /** The request's expiry has passed. */\n | 'expired'\n /** The requester lacks the explicit kind allowance to decide their own request. */\n | 'self_approval'\n /** A non-human principal tried to decide. */\n | 'not_human'\n /** The principal lacks a required permission. */\n | 'forbidden'\n /** This principal already recorded a decision on the request. */\n | 'already_decided'\n /** `consume` was called with a different subject revision hash. */\n | 'revision_mismatch'\n /** The request row says approved but its event ledger does not. */\n | 'unbacked_approval'\n /** The approval was already consumed. */\n | 'already_consumed'\n /** The request is not approved (consume) or not yet expired (expire). */\n | 'not_approved'\n | 'not_expired'\n /** A `requestKey` was reused for a different subject or revision. */\n | 'request_key_conflict'\n /** Concurrent transitions kept winning; retry later. */\n | 'contention';\n\n/** Outcome of a transition. */\nexport type ApprovalTransitionOutcome =\n /** This call performed the transition. */\n | 'transitioned'\n /** The same request already existed (idempotent `requestKey` replay). */\n | 'already_applied'\n /** Fail-closed; see `refusal`. */\n | 'refused';\n\n/** Result of every {@link ApprovalService} transition. */\nexport interface ApprovalTransitionResult<T = unknown> {\n outcome: ApprovalTransitionOutcome;\n /** The request re-read after commit; `null` when refused as not found. */\n request: T | null;\n /** Set exactly when `outcome` is `refused`. */\n refusal?: { reason: ApprovalRefusalReason; detail: string };\n}\n\n/** Typed error for invalid input (programming errors, not refusals). */\nexport class ApprovalError extends Error {\n /** Machine-readable code. */\n readonly code: ApprovalErrorCode;\n\n constructor(code: ApprovalErrorCode, message: string) {\n super(message);\n this.name = 'ApprovalError';\n this.code = code;\n }\n}\n\n/** Codes carried by {@link ApprovalError}. */\nexport type ApprovalErrorCode =\n | 'APPROVAL_INVALID'\n | 'APPROVAL_KIND_UNKNOWN'\n | 'APPROVAL_KIND_CONFLICT'\n | 'APPROVAL_POLICY_LOOSENS'\n | 'APPROVAL_FORBIDDEN'\n | 'APPROVAL_NO_TRANSACTION';\n"],"mappings":";AAQO,IAAM,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;AACF;AAMO,IAAM,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAgBO,IAAM,2BAA6D;CACxE;CACA;CACA;AACF;AAoBO,SAAS,iCACd,aACA,SACmB;CACnB,MAAM,UAAU,IAAI,IAAI,WAAW;CACnC,OAAO;EACL,IAAI,QAAQ;EACZ,UAAU,QAAQ;EAClB,MAAM,QAAQ;EACd,MAAM,SAAiB,QAAQ,IAAI,IAAI;CACzC;AACF;AA6FO,IAAM,gBAAN,cAA4B,MAAM;;CAE9B;CAET,YAAY,MAAyB,SAAiB;EACpD,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-approvals",
|
|
3
|
-
"version": "0.55.
|
|
3
|
+
"version": "0.55.13",
|
|
4
4
|
"description": "Approval decision gate for the SMRT framework: revision-bound, single-use approvals with quorum, expiry, and an append-only event ledger",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
"./manifest.json": "./dist/manifest.json"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@happyvertical/smrt-core": "0.55.
|
|
24
|
-
"@happyvertical/smrt-tenancy": "0.55.
|
|
25
|
-
"@happyvertical/smrt-users": "0.55.
|
|
26
|
-
"@happyvertical/sql": "^0.103.
|
|
23
|
+
"@happyvertical/smrt-core": "0.55.13",
|
|
24
|
+
"@happyvertical/smrt-tenancy": "0.55.13",
|
|
25
|
+
"@happyvertical/smrt-users": "0.55.13",
|
|
26
|
+
"@happyvertical/sql": "^0.103.2"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@happyvertical/smrt-vitest": "0.55.
|
|
29
|
+
"@happyvertical/smrt-vitest": "0.55.13",
|
|
30
30
|
"@types/node": "24.13.6",
|
|
31
31
|
"typescript": "6.0.3",
|
|
32
32
|
"vite": "8.1.4",
|