@happyvertical/smrt-approvals 0.0.0-stage → 0.55.11
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 +45 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +124 -2
- package/agents/approvals.md +93 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/collections/ApprovalEventCollection.d.ts +13 -0
- package/dist/collections/ApprovalEventCollection.d.ts.map +1 -0
- package/dist/collections/ApprovalPolicyCollection.d.ts +13 -0
- package/dist/collections/ApprovalPolicyCollection.d.ts.map +1 -0
- package/dist/collections/ApprovalRequestCollection.d.ts +13 -0
- package/dist/collections/ApprovalRequestCollection.d.ts.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1307 -0
- package/dist/index.js.map +1 -0
- package/dist/kinds.d.ts +61 -0
- package/dist/kinds.d.ts.map +1 -0
- package/dist/manifest.json +1531 -0
- package/dist/models/ApprovalEvent.d.ts +82 -0
- package/dist/models/ApprovalEvent.d.ts.map +1 -0
- package/dist/models/ApprovalPolicy.d.ts +47 -0
- package/dist/models/ApprovalPolicy.d.ts.map +1 -0
- package/dist/models/ApprovalRequest.d.ts +118 -0
- package/dist/models/ApprovalRequest.d.ts.map +1 -0
- package/dist/permissions.d.ts +20 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/service.d.ts +203 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +751 -0
- package/dist/types.d.ts +136 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +45 -0
- package/dist/types.js.map +1 -0
- package/dist/write-capability.d.ts +28 -0
- package/dist/write-capability.d.ts.map +1 -0
- package/package.json +64 -3
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ApprovalEvent: the append-only ledger of an approval request.
|
|
3
|
+
*
|
|
4
|
+
* Events are the source of truth for who did what: every transition inserts
|
|
5
|
+
* exactly one event in the same transaction as its guarded UPDATE. The
|
|
6
|
+
* generated surface is read-only, `save()` only ever inserts, and `delete()`
|
|
7
|
+
* is refused. `(requestId, sequence)` is unique (one event per request
|
|
8
|
+
* version) and `(requestId, voteKey)` is unique (one decision per actor).
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
import { SmrtObject, type SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
13
|
+
import { type ApprovalEventType, type ApprovalPrincipalType } from '../types.js';
|
|
14
|
+
/** Constructor options for {@link ApprovalEvent}. */
|
|
15
|
+
export interface ApprovalEventOptions extends SmrtObjectOptions {
|
|
16
|
+
tenantId?: string;
|
|
17
|
+
requestId?: string;
|
|
18
|
+
type?: ApprovalEventType;
|
|
19
|
+
actorId?: string;
|
|
20
|
+
actorType?: ApprovalPrincipalType;
|
|
21
|
+
reason?: string;
|
|
22
|
+
subjectRevisionHash?: string;
|
|
23
|
+
sequence?: number;
|
|
24
|
+
voteKey?: string | null;
|
|
25
|
+
occurredAt?: Date | null;
|
|
26
|
+
}
|
|
27
|
+
/** One immutable entry in an approval request's history. */
|
|
28
|
+
export declare class ApprovalEvent extends SmrtObject {
|
|
29
|
+
/** Owning tenant; always the request's tenant. */
|
|
30
|
+
tenantId: string;
|
|
31
|
+
/**
|
|
32
|
+
* The request this event belongs to. DuckDB executes an UPDATE of an
|
|
33
|
+
* indexed column on a referenced row as delete plus insert, so a physical
|
|
34
|
+
* FK there would refuse every transition of a request that has events;
|
|
35
|
+
* the constraint is physical on PostgreSQL and SQLite only. Requests are
|
|
36
|
+
* never deleted, so no engine can orphan an event.
|
|
37
|
+
*/
|
|
38
|
+
requestId: string;
|
|
39
|
+
/** What happened. */
|
|
40
|
+
type: ApprovalEventType;
|
|
41
|
+
/** Principal id of the actor. */
|
|
42
|
+
actorId: string;
|
|
43
|
+
/** Principal type of the actor. */
|
|
44
|
+
actorType: ApprovalPrincipalType;
|
|
45
|
+
/** Free-text reason (rejection note, cancellation reason, and so on). */
|
|
46
|
+
reason: string;
|
|
47
|
+
/** The subject revision the request was bound to when this happened. */
|
|
48
|
+
subjectRevisionHash: string;
|
|
49
|
+
/** The request `version` this event produced; unique per request. */
|
|
50
|
+
sequence: number;
|
|
51
|
+
/**
|
|
52
|
+
* The deciding actor's id on decision events, `null` otherwise; unique per
|
|
53
|
+
* request so one actor decides at most once.
|
|
54
|
+
*/
|
|
55
|
+
voteKey: string | null;
|
|
56
|
+
/** When the event happened. */
|
|
57
|
+
occurredAt: Date | null;
|
|
58
|
+
constructor(options?: ApprovalEventOptions);
|
|
59
|
+
/**
|
|
60
|
+
* Append the event. An event is written once and never updated.
|
|
61
|
+
*
|
|
62
|
+
* @throws {ApprovalError} `APPROVAL_FORBIDDEN` for a persisted event.
|
|
63
|
+
*/
|
|
64
|
+
save(): Promise<this>;
|
|
65
|
+
/**
|
|
66
|
+
* Refuse any insert `ApprovalService` did not mint in the same transaction
|
|
67
|
+
* as its guarded UPDATE (see `write-capability.ts`): a forged `approved`
|
|
68
|
+
* event would otherwise count toward quorum. Runs for ordinary saves and
|
|
69
|
+
* bulk creates alike.
|
|
70
|
+
*
|
|
71
|
+
* @throws {ApprovalError} `APPROVAL_FORBIDDEN`.
|
|
72
|
+
*/
|
|
73
|
+
protected validateBeforeSave(): Promise<void>;
|
|
74
|
+
private assertInsertAllowed;
|
|
75
|
+
/**
|
|
76
|
+
* Events are append-only.
|
|
77
|
+
*
|
|
78
|
+
* @throws {ApprovalError} always.
|
|
79
|
+
*/
|
|
80
|
+
delete(): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=ApprovalEvent.d.ts.map
|
|
@@ -0,0 +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;gBAEnB,OAAO,GAAE,oBAAyB;IAgB9C;;;;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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ApprovalPolicy: a tenant's tightening of one approval kind.
|
|
3
|
+
*
|
|
4
|
+
* A row may raise the quorum, shorten the expiry, or add one required
|
|
5
|
+
* permission; it can never loosen the kind's package defaults. Writes go
|
|
6
|
+
* through `ApprovalService.setPolicy`, which refuses loosening, and
|
|
7
|
+
* `effectiveRules` clamps any stored row when a request is made. A policy
|
|
8
|
+
* applies to requests made after it is written; existing requests keep the
|
|
9
|
+
* rules fixed at request time.
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
import { SmrtObject, type SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
14
|
+
/** Constructor options for {@link ApprovalPolicy}. */
|
|
15
|
+
export interface ApprovalPolicyOptions extends SmrtObjectOptions {
|
|
16
|
+
tenantId?: string;
|
|
17
|
+
kind?: string;
|
|
18
|
+
requiredApprovals?: number;
|
|
19
|
+
ttlMs?: number;
|
|
20
|
+
requiredPermission?: string;
|
|
21
|
+
updatedBy?: string;
|
|
22
|
+
}
|
|
23
|
+
/** Tenant-level tightening of an approval kind. One row per tenant and kind. */
|
|
24
|
+
export declare class ApprovalPolicy extends SmrtObject {
|
|
25
|
+
/** Owning tenant. */
|
|
26
|
+
tenantId: string;
|
|
27
|
+
/** Approval kind key this row tightens. */
|
|
28
|
+
kind: string;
|
|
29
|
+
/** Raised quorum; `0` inherits the kind default. */
|
|
30
|
+
requiredApprovals: number;
|
|
31
|
+
/** Shortened lifetime in milliseconds; `0` inherits the kind default. */
|
|
32
|
+
ttlMs: number;
|
|
33
|
+
/** Extra permission slug a decider must hold; empty for none. */
|
|
34
|
+
requiredPermission: string;
|
|
35
|
+
/** Principal id that last wrote the row. */
|
|
36
|
+
updatedBy: string;
|
|
37
|
+
constructor(options?: ApprovalPolicyOptions);
|
|
38
|
+
/**
|
|
39
|
+
* Validate against the registered kind, then persist. A row for an unknown
|
|
40
|
+
* kind, or one that would loosen the kind, is refused.
|
|
41
|
+
*
|
|
42
|
+
* @throws {ApprovalError} `APPROVAL_KIND_UNKNOWN`,
|
|
43
|
+
* `APPROVAL_POLICY_LOOSENS`, or `APPROVAL_INVALID`.
|
|
44
|
+
*/
|
|
45
|
+
save(): Promise<this>;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=ApprovalPolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApprovalPolicy.d.ts","sourceRoot":"","sources":["../../src/models/ApprovalPolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAEL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAIlC,sDAAsD;AACtD,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,gFAAgF;AAChF,qBAca,cAAe,SAAQ,UAAU;IAC5C,qBAAqB;IAErB,QAAQ,EAAE,MAAM,CAAM;IAEtB,2CAA2C;IAE3C,IAAI,EAAE,MAAM,CAAM;IAElB,oDAAoD;IAEpD,iBAAiB,EAAE,MAAM,CAAK;IAE9B,yEAAyE;IAEzE,KAAK,EAAE,MAAM,CAAK;IAElB,iEAAiE;IAEjE,kBAAkB,EAAE,MAAM,CAAM;IAEhC,4CAA4C;IAE5C,SAAS,EAAE,MAAM,CAAM;gBAEX,OAAO,GAAE,qBAA0B;IAY/C;;;;;;OAMG;IACY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAWrC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ApprovalRequest: one decision gate on one revision of one subject.
|
|
3
|
+
*
|
|
4
|
+
* The row is the authority for the decision; a consumer's own status column
|
|
5
|
+
* is a projection it writes in the same transaction as `consume`. Every
|
|
6
|
+
* lifecycle field is changed only by `ApprovalService` through one guarded
|
|
7
|
+
* conditional UPDATE, so the generated surface is read-only and a plain
|
|
8
|
+
* `save()` of a loaded request is refused.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
import { SmrtObject, type SmrtObjectOptions } from '@happyvertical/smrt-core';
|
|
13
|
+
import { type ApprovalPrincipalType, type ApprovalStatus } from '../types.js';
|
|
14
|
+
/** Constructor options for {@link ApprovalRequest}. */
|
|
15
|
+
export interface ApprovalRequestOptions extends SmrtObjectOptions {
|
|
16
|
+
tenantId?: string;
|
|
17
|
+
kind?: string;
|
|
18
|
+
subjectType?: string;
|
|
19
|
+
subjectId?: string;
|
|
20
|
+
subjectRevisionHash?: string;
|
|
21
|
+
requestKey?: string;
|
|
22
|
+
requestedBy?: string;
|
|
23
|
+
requestedByType?: ApprovalPrincipalType;
|
|
24
|
+
summary?: string;
|
|
25
|
+
status?: ApprovalStatus;
|
|
26
|
+
requiredApprovals?: number;
|
|
27
|
+
approvalCount?: number;
|
|
28
|
+
requiredPermissions?: string;
|
|
29
|
+
version?: number;
|
|
30
|
+
expiresAt?: Date | null;
|
|
31
|
+
decidedAt?: Date | null;
|
|
32
|
+
consumedAt?: Date | null;
|
|
33
|
+
consumedBy?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A request for approval of one subject revision.
|
|
37
|
+
*
|
|
38
|
+
* Status moves `pending` to `approved`, `rejected`, `changes_requested`,
|
|
39
|
+
* `cancelled`, or `expired`; an approved request is consumed once
|
|
40
|
+
* (`consumedAt` set, status stays `approved`) or expires unconsumed.
|
|
41
|
+
*/
|
|
42
|
+
export declare class ApprovalRequest extends SmrtObject {
|
|
43
|
+
/** Owning tenant. Every read and transition is pinned to it. */
|
|
44
|
+
tenantId: string;
|
|
45
|
+
/** Approval kind key, e.g. `social.post.publish`. */
|
|
46
|
+
kind: string;
|
|
47
|
+
/** Qualified subject class, e.g. `@happyvertical/smrt-social:SocialPost`. */
|
|
48
|
+
subjectType: string;
|
|
49
|
+
/** Subject id. Bare string: the subject table is polymorphic. */
|
|
50
|
+
subjectId: string;
|
|
51
|
+
/**
|
|
52
|
+
* Hash of the exact subject revision being approved. `consume` must
|
|
53
|
+
* present the same hash, so an approval never transfers to changed content.
|
|
54
|
+
*/
|
|
55
|
+
subjectRevisionHash: string;
|
|
56
|
+
/** Idempotency key, unique per tenant. A replay returns the same request. */
|
|
57
|
+
requestKey: string;
|
|
58
|
+
/** Principal id of the requester. The requester can never decide. */
|
|
59
|
+
requestedBy: string;
|
|
60
|
+
/** Principal type of the requester (`human`, `agent`, or `service`). */
|
|
61
|
+
requestedByType: ApprovalPrincipalType;
|
|
62
|
+
/** Short human-readable description shown to deciders. */
|
|
63
|
+
summary: string;
|
|
64
|
+
/** Lifecycle status. Changed only by guarded transitions. */
|
|
65
|
+
status: ApprovalStatus;
|
|
66
|
+
/** Distinct approvers needed, fixed when the request is made. */
|
|
67
|
+
requiredApprovals: number;
|
|
68
|
+
/** Distinct approvals recorded so far. */
|
|
69
|
+
approvalCount: number;
|
|
70
|
+
/**
|
|
71
|
+
* JSON array of permission slugs a decider must hold, fixed at request
|
|
72
|
+
* time. Read it through {@link getRequiredPermissions}.
|
|
73
|
+
*/
|
|
74
|
+
requiredPermissions: string;
|
|
75
|
+
/** Transition counter; every guarded UPDATE requires and bumps it. */
|
|
76
|
+
version: number;
|
|
77
|
+
/** After this instant the request can no longer be decided or consumed. */
|
|
78
|
+
expiresAt: Date | null;
|
|
79
|
+
/** When the request reached a terminal decision. */
|
|
80
|
+
decidedAt: Date | null;
|
|
81
|
+
/** When the approval was consumed (single use). */
|
|
82
|
+
consumedAt: Date | null;
|
|
83
|
+
/** Principal id that consumed the approval. */
|
|
84
|
+
consumedBy: string;
|
|
85
|
+
constructor(options?: ApprovalRequestOptions);
|
|
86
|
+
/** The permission slugs a decider must hold (never throws; bad JSON = none valid). */
|
|
87
|
+
getRequiredPermissions(): string[];
|
|
88
|
+
/** Store the permission slugs a decider must hold. */
|
|
89
|
+
setRequiredPermissions(slugs: readonly string[]): void;
|
|
90
|
+
/** The expiry as epoch milliseconds (`NaN` when unset or unparseable). */
|
|
91
|
+
expiresAtMs(): number;
|
|
92
|
+
/** True when the approval was consumed. */
|
|
93
|
+
isConsumed(): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Refuse any insert `ApprovalService` did not mint (see
|
|
96
|
+
* `write-capability.ts`), and any minted insert that does not start in the
|
|
97
|
+
* initial state. Runs for ordinary saves and bulk creates alike (core
|
|
98
|
+
* wraps an error thrown here; `save()` checks first so callers see it).
|
|
99
|
+
*
|
|
100
|
+
* @throws {ApprovalError} `APPROVAL_FORBIDDEN`.
|
|
101
|
+
*/
|
|
102
|
+
protected validateBeforeSave(): Promise<void>;
|
|
103
|
+
private assertInsertAllowed;
|
|
104
|
+
/**
|
|
105
|
+
* Insert a new request. A loaded request is never re-saved: every later
|
|
106
|
+
* change is a guarded transition in `ApprovalService`.
|
|
107
|
+
*
|
|
108
|
+
* @throws {ApprovalError} `APPROVAL_FORBIDDEN` for a persisted request.
|
|
109
|
+
*/
|
|
110
|
+
save(): Promise<this>;
|
|
111
|
+
/**
|
|
112
|
+
* Requests are durable records of a decision.
|
|
113
|
+
*
|
|
114
|
+
* @throws {ApprovalError} always.
|
|
115
|
+
*/
|
|
116
|
+
delete(): Promise<void>;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=ApprovalRequest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApprovalRequest.d.ts","sourceRoot":"","sources":["../../src/models/ApprovalRequest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAEL,UAAU,EACV,KAAK,iBAAiB,EAEvB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAGrB,uDAAuD;AACvD,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,qBAsBa,eAAgB,SAAQ,UAAU;IAC7C,gEAAgE;IAEhE,QAAQ,EAAE,MAAM,CAAM;IAEtB,qDAAqD;IAErD,IAAI,EAAE,MAAM,CAAM;IAElB,6EAA6E;IAE7E,WAAW,EAAE,MAAM,CAAM;IAEzB,iEAAiE;IAEjE,SAAS,EAAE,MAAM,CAAM;IAEvB;;;OAGG;IAEH,mBAAmB,EAAE,MAAM,CAAM;IAEjC,6EAA6E;IAE7E,UAAU,EAAE,MAAM,CAAM;IAExB,qEAAqE;IAErE,WAAW,EAAE,MAAM,CAAM;IAEzB,wEAAwE;IAExE,eAAe,EAAE,qBAAqB,CAAW;IAEjD,0DAA0D;IAE1D,OAAO,EAAE,MAAM,CAAM;IAErB,6DAA6D;IAE7D,MAAM,EAAE,cAAc,CAAa;IAEnC,iEAAiE;IAEjE,iBAAiB,EAAE,MAAM,CAAK;IAE9B,0CAA0C;IAE1C,aAAa,EAAE,MAAM,CAAK;IAE1B;;;OAGG;IAEH,mBAAmB,EAAE,MAAM,CAAQ;IAEnC,sEAAsE;IAEtE,OAAO,EAAE,MAAM,CAAK;IAEpB,2EAA2E;IAE3E,SAAS,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE9B,oDAAoD;IAEpD,SAAS,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE9B,mDAAmD;IAEnD,UAAU,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE/B,+CAA+C;IAE/C,UAAU,EAAE,MAAM,CAAM;gBAEZ,OAAO,GAAE,sBAA2B;IA8BhD,sFAAsF;IACtF,sBAAsB,IAAI,MAAM,EAAE;IAWlC,sDAAsD;IACtD,sBAAsB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAItD,0EAA0E;IAC1E,WAAW,IAAI,MAAM;IASrB,2CAA2C;IAC3C,UAAU,IAAI,OAAO;IAIrB;;;;;;;OAOG;cACsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5D,OAAO,CAAC,mBAAmB;IAuB3B;;;;;OAKG;IACY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAYpC;;;;OAIG;IACY,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAMvC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission slugs contributed by `@happyvertical/smrt-approvals` to the
|
|
3
|
+
* runtime permission catalog. Deciding a request is gated by the per-kind
|
|
4
|
+
* slug a package passes to `defineApprovalKind`; these slugs cover the
|
|
5
|
+
* package-wide operations.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
import { type PermissionDefinition } from '@happyvertical/smrt-users';
|
|
10
|
+
/** Cancel a pending request someone else made. */
|
|
11
|
+
export declare const CANCEL_ANY_APPROVAL_PERMISSION = "approvals.cancel-any";
|
|
12
|
+
/** Write tenant approval policy rows (tighten-only). */
|
|
13
|
+
export declare const MANAGE_APPROVAL_POLICY_PERMISSION = "approvals.manage-policy";
|
|
14
|
+
/** Catalog definitions for the package-wide slugs. */
|
|
15
|
+
export declare const APPROVAL_PERMISSION_DEFINITIONS: PermissionDefinition[];
|
|
16
|
+
/** Register the package slugs (returns an unregister function for tests). */
|
|
17
|
+
export declare function registerApprovalPermissions(): () => void;
|
|
18
|
+
/** Register the package slugs once per process (package entry side effect). */
|
|
19
|
+
export declare function ensureApprovalPermissionsRegistered(): void;
|
|
20
|
+
//# sourceMappingURL=permissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,2BAA2B,CAAC;AAEnC,kDAAkD;AAClD,eAAO,MAAM,8BAA8B,yBAAyB,CAAC;AAErE,wDAAwD;AACxD,eAAO,MAAM,iCAAiC,4BAA4B,CAAC;AAE3E,sDAAsD;AACtD,eAAO,MAAM,+BAA+B,EAAE,oBAAoB,EAejE,CAAC;AAIF,6EAA6E;AAC7E,wBAAgB,2BAA2B,IAAI,MAAM,IAAI,CAExD;AAED,+EAA+E;AAC/E,wBAAgB,mCAAmC,IAAI,IAAI,CAI1D"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ApprovalService: the only write path for approval requests.
|
|
3
|
+
*
|
|
4
|
+
* Every transition is ONE guarded conditional `UPDATE ... WHERE id AND
|
|
5
|
+
* tenant_id AND status AND version AND expiry ... RETURNING version` plus
|
|
6
|
+
* one {@link ApprovalEvent} insert, in the same database transaction (the
|
|
7
|
+
* `CliAuthRequestCollection.approvePendingRequest` and
|
|
8
|
+
* `CommissionPayoutService.transitionPayoutForSource` shape):
|
|
9
|
+
*
|
|
10
|
+
* - The guarded UPDATE is the arbiter. Two concurrent transitions of one
|
|
11
|
+
* request both read version `v`; only one UPDATE can match `version = v`.
|
|
12
|
+
* The loser matches zero rows, writes nothing, and retries from a fresh
|
|
13
|
+
* read, where it sees the winner's state and is refused cleanly
|
|
14
|
+
* (`not_pending`, `already_decided`, `already_consumed`).
|
|
15
|
+
* - PostgreSQL runs each transaction on its own pooled connection; the
|
|
16
|
+
* UPDATE's row lock serializes writers and READ COMMITTED re-evaluates the
|
|
17
|
+
* predicate after the winner commits. On embedded engines (SQLite,
|
|
18
|
+
* DuckDB) each transaction runs through core's embedded write queue
|
|
19
|
+
* (`withEmbeddedWriteTransaction`), which takes the queue before the
|
|
20
|
+
* connection lock and serializes writers per database; the predicate
|
|
21
|
+
* still decides.
|
|
22
|
+
* - Every statement names the tenant explicitly and the work runs inside
|
|
23
|
+
* `withTenant(principal.tenantId)`, so tenancy interceptors and the raw
|
|
24
|
+
* predicate both pin the row; a cross-tenant id is `not_found`.
|
|
25
|
+
* - Events are inserted through a collection bound to the transaction
|
|
26
|
+
* database (the tx-bound collection pattern), so a failed insert rolls
|
|
27
|
+
* the UPDATE back.
|
|
28
|
+
*
|
|
29
|
+
* @packageDocumentation
|
|
30
|
+
*/
|
|
31
|
+
import type { DatabaseInterface } from '@happyvertical/sql';
|
|
32
|
+
import { type ApprovalPolicyRules } from './kinds.js';
|
|
33
|
+
import type { ApprovalEvent } from './models/ApprovalEvent.js';
|
|
34
|
+
import type { ApprovalPolicy } from './models/ApprovalPolicy.js';
|
|
35
|
+
import type { ApprovalRequest } from './models/ApprovalRequest.js';
|
|
36
|
+
import { type ApprovalDecision, type ApprovalKind, type ApprovalPrincipal, type ApprovalTransitionResult } from './types.js';
|
|
37
|
+
/** Options for {@link ApprovalService}. */
|
|
38
|
+
export interface ApprovalServiceOptions {
|
|
39
|
+
/** The database every transition runs against. Must support transactions. */
|
|
40
|
+
db: DatabaseInterface;
|
|
41
|
+
/** Clock override for deterministic tests. */
|
|
42
|
+
now?: () => Date;
|
|
43
|
+
/** Attempts before a contended transition is refused. Default 8. */
|
|
44
|
+
maxAttempts?: number;
|
|
45
|
+
}
|
|
46
|
+
/** Input for {@link ApprovalService.requestApproval}. */
|
|
47
|
+
export interface RequestApprovalInput {
|
|
48
|
+
/** A kind registered with `defineApprovalKind` (or its key). */
|
|
49
|
+
kind: ApprovalKind | string;
|
|
50
|
+
/** The subject's id. */
|
|
51
|
+
subjectId: string;
|
|
52
|
+
/** Hash of the exact subject revision to approve. */
|
|
53
|
+
subjectRevisionHash: string;
|
|
54
|
+
/**
|
|
55
|
+
* Idempotency key, unique per tenant. A replay with the same subject and
|
|
56
|
+
* revision returns the existing request as `already_applied`; a replay with
|
|
57
|
+
* different content is refused `request_key_conflict`. Default: random.
|
|
58
|
+
*/
|
|
59
|
+
requestKey?: string;
|
|
60
|
+
/** Short description shown to deciders. */
|
|
61
|
+
summary?: string;
|
|
62
|
+
}
|
|
63
|
+
/** Input for {@link ApprovalService.decide}. */
|
|
64
|
+
export interface DecideInput {
|
|
65
|
+
decision: ApprovalDecision;
|
|
66
|
+
/** Required for `reject` and `request_changes`. */
|
|
67
|
+
reason?: string;
|
|
68
|
+
}
|
|
69
|
+
/** Options for {@link ApprovalService.consume}. */
|
|
70
|
+
export interface ConsumeOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Run inside the caller's open transaction (a `db.transaction()` callback
|
|
73
|
+
* handle) so the consumer's own write commits or rolls back together with
|
|
74
|
+
* the consumption. The caller owns the transaction. On embedded engines
|
|
75
|
+
* (SQLite, DuckDB, libsql) the handle must come from
|
|
76
|
+
* `withEmbeddedWriteTransaction(db, isEmbeddedDatabase(db), fn)` or
|
|
77
|
+
* `SmrtObject.withTransaction()` so it holds core's embedded write queue;
|
|
78
|
+
* a raw `db.transaction()` handle can deadlock against concurrent saves.
|
|
79
|
+
*/
|
|
80
|
+
transaction?: DatabaseInterface;
|
|
81
|
+
}
|
|
82
|
+
/** Result type of every transition. */
|
|
83
|
+
export type ApprovalResult = ApprovalTransitionResult<ApprovalRequest>;
|
|
84
|
+
/**
|
|
85
|
+
* The decision gate. Construct it over the application's database; it binds
|
|
86
|
+
* its own collections per call and per transaction.
|
|
87
|
+
*/
|
|
88
|
+
export declare class ApprovalService {
|
|
89
|
+
private readonly db;
|
|
90
|
+
private readonly clock;
|
|
91
|
+
private readonly maxAttempts;
|
|
92
|
+
constructor(options: ApprovalServiceOptions);
|
|
93
|
+
/** One request in the principal's tenant, or `null`. */
|
|
94
|
+
getRequest(principal: ApprovalPrincipal, requestId: string): Promise<ApprovalRequest | null>;
|
|
95
|
+
/** A request's events in order, in the principal's tenant. */
|
|
96
|
+
listEvents(principal: ApprovalPrincipal, requestId: string): Promise<ApprovalEvent[]>;
|
|
97
|
+
/**
|
|
98
|
+
* Open a request for approval of one subject revision. Any principal type
|
|
99
|
+
* may request. The quorum, expiry, and required permissions are fixed now
|
|
100
|
+
* from the kind's defaults tightened by the tenant's policy.
|
|
101
|
+
*/
|
|
102
|
+
requestApproval(principal: ApprovalPrincipal, input: RequestApprovalInput): Promise<ApprovalResult>;
|
|
103
|
+
/**
|
|
104
|
+
* Record a decision. Only a `human` principal of the request's tenant who
|
|
105
|
+
* is not the requester and holds every required permission may decide,
|
|
106
|
+
* once, while the request is pending and unexpired. `approve` counts
|
|
107
|
+
* toward the quorum of distinct approvers; `reject` and `request_changes`
|
|
108
|
+
* end the request.
|
|
109
|
+
*/
|
|
110
|
+
decide(principal: ApprovalPrincipal, requestId: string, input: DecideInput): Promise<ApprovalResult>;
|
|
111
|
+
/**
|
|
112
|
+
* Cancel a pending request. The requester may cancel their own request;
|
|
113
|
+
* anyone else needs `approvals.cancel-any`.
|
|
114
|
+
*/
|
|
115
|
+
cancel(principal: ApprovalPrincipal, requestId: string, input?: {
|
|
116
|
+
reason?: string;
|
|
117
|
+
}): Promise<ApprovalResult>;
|
|
118
|
+
/**
|
|
119
|
+
* Mark one request expired: a pending request, or an approved one that
|
|
120
|
+
* was never consumed, whose expiry has passed. Needs no permission (it
|
|
121
|
+
* only records what time already decided), so a job may call it.
|
|
122
|
+
*/
|
|
123
|
+
expire(principal: ApprovalPrincipal, requestId: string): Promise<ApprovalResult>;
|
|
124
|
+
/**
|
|
125
|
+
* Expire every due request in the principal's tenant: pending requests
|
|
126
|
+
* and unconsumed approvals past their deadline, oldest expiry first, at
|
|
127
|
+
* most `limit`. Returns the ids this call expired.
|
|
128
|
+
*/
|
|
129
|
+
expireDue(principal: ApprovalPrincipal, options?: {
|
|
130
|
+
limit?: number;
|
|
131
|
+
}): Promise<string[]>;
|
|
132
|
+
/**
|
|
133
|
+
* Consume an approval exactly once. The presented hash must equal the
|
|
134
|
+
* revision that was approved; the request must be approved, unconsumed,
|
|
135
|
+
* and unexpired. Any principal type of the tenant may consume (the
|
|
136
|
+
* consumer's own service is the executor). Pass `options.transaction` to
|
|
137
|
+
* make the consumption part of the consumer's own transaction.
|
|
138
|
+
*/
|
|
139
|
+
consume(principal: ApprovalPrincipal, requestId: string, subjectRevisionHash: string, options?: ConsumeOptions): Promise<ApprovalResult>;
|
|
140
|
+
/**
|
|
141
|
+
* Write the tenant's tightening of a kind. Needs
|
|
142
|
+
* `approvals.manage-policy`; a rule that would loosen the kind's defaults
|
|
143
|
+
* throws `APPROVAL_POLICY_LOOSENS`. `0`/`''` clear a rule back to the
|
|
144
|
+
* default. Applies to requests made afterwards.
|
|
145
|
+
*/
|
|
146
|
+
setPolicy(principal: ApprovalPrincipal, kindKey: ApprovalKind | string, rules: ApprovalPolicyRules): Promise<ApprovalPolicy>;
|
|
147
|
+
/** The effective rules a new request of `kind` would get in the tenant. */
|
|
148
|
+
rulesFor(principal: ApprovalPrincipal, kindKey: ApprovalKind | string): Promise<import("./types.js").EffectiveApprovalRules>;
|
|
149
|
+
/**
|
|
150
|
+
* Run a guarded transition with retry. `body` runs inside a transaction
|
|
151
|
+
* on a fresh read of the request and returns `retry` when its guarded
|
|
152
|
+
* UPDATE matched no row (a concurrent transition won).
|
|
153
|
+
*/
|
|
154
|
+
private transition;
|
|
155
|
+
/** True when an active tenant context names a different tenant. */
|
|
156
|
+
private contextMismatch;
|
|
157
|
+
/** The refusal returned when {@link contextMismatch} holds. */
|
|
158
|
+
private static mismatchResult;
|
|
159
|
+
/** Pin the work to the principal's tenant; refuse a conflicting context. */
|
|
160
|
+
private inTenant;
|
|
161
|
+
/**
|
|
162
|
+
* Run `fn` in a transaction through core's embedded write queue.
|
|
163
|
+
*
|
|
164
|
+
* On embedded engines (SQLite, DuckDB, libsql) the transaction takes the
|
|
165
|
+
* queue for its database BEFORE the adapter's connection lock, and the
|
|
166
|
+
* model writes inside it (event inserts, change-feed appends) re-enter that
|
|
167
|
+
* hold. Taking the connection lock first deadlocks against an unrelated
|
|
168
|
+
* save that holds the queue and then opens its own write transaction, and
|
|
169
|
+
* lets other root writes land inside this BEGIN..COMMIT. The queue also
|
|
170
|
+
* serializes these transactions per database in-process, which is what
|
|
171
|
+
* the former per-handle promise chain did, so that chain is gone.
|
|
172
|
+
* PostgreSQL is not queued: each transaction has its own pooled
|
|
173
|
+
* connection and the guarded UPDATE's row lock arbitrates.
|
|
174
|
+
*/
|
|
175
|
+
private runTransaction;
|
|
176
|
+
/** A collection bound to a transaction database. */
|
|
177
|
+
private txCollection;
|
|
178
|
+
private loadRequest;
|
|
179
|
+
private loadPolicy;
|
|
180
|
+
/**
|
|
181
|
+
* Every slug a decider must hold now: the slugs fixed at request time
|
|
182
|
+
* plus the kind's and the tenant policy's current ones (tightening only:
|
|
183
|
+
* a later policy can add a slug, never remove one).
|
|
184
|
+
*/
|
|
185
|
+
private requiredPermissionsFor;
|
|
186
|
+
private appendEvent;
|
|
187
|
+
/**
|
|
188
|
+
* Defence in depth for `consume`: the request row says `approved`, so the
|
|
189
|
+
* ledger must agree. It needs the `created` event at sequence 1, no
|
|
190
|
+
* terminal event, and approvals from at least `requiredApprovals` distinct
|
|
191
|
+
* human actors other than the requester. Catches a status written around
|
|
192
|
+
* the service (raw SQL); model-layer inserts are already capability-gated.
|
|
193
|
+
*/
|
|
194
|
+
private ledgerBacksApproval;
|
|
195
|
+
/**
|
|
196
|
+
* The guarded UPDATE bypasses save hooks, so record the change-feed entry
|
|
197
|
+
* the save path would have written. Best effort, like the save path.
|
|
198
|
+
*/
|
|
199
|
+
private recordChange;
|
|
200
|
+
/** Re-read the request after commit (ids only cross the boundary). */
|
|
201
|
+
private toResult;
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAaH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAI5D,OAAO,EACL,KAAK,mBAAmB,EAKzB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAKnE,OAAO,EAEL,KAAK,gBAAgB,EAGrB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EAGtB,KAAK,wBAAwB,EAC9B,MAAM,YAAY,CAAC;AAmCpB,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB;IACrC,6EAA6E;IAC7E,EAAE,EAAE,iBAAiB,CAAC;IACtB,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,IAAI,EAAE,YAAY,GAAG,MAAM,CAAC;IAC5B,wBAAwB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC7B;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACjC;AAED,uCAAuC;AACvC,MAAM,MAAM,cAAc,GAAG,wBAAwB,CAAC,eAAe,CAAC,CAAC;AAmGvE;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAA6B;IAChD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,OAAO,EAAE,sBAAsB;IAyB3C,wDAAwD;IAClD,UAAU,CACd,SAAS,EAAE,iBAAiB,EAC5B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAQlC,8DAA8D;IACxD,UAAU,CACd,SAAS,EAAE,iBAAiB,EAC5B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,EAAE,CAAC;IAiB3B;;;;OAIG;IACG,eAAe,CACnB,SAAS,EAAE,iBAAiB,EAC5B,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,cAAc,CAAC;IA4G1B;;;;;;OAMG;IACG,MAAM,CACV,SAAS,EAAE,iBAAiB,EAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,cAAc,CAAC;IA+I1B;;;OAGG;IACG,MAAM,CACV,SAAS,EAAE,iBAAiB,EAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAC9B,OAAO,CAAC,cAAc,CAAC;IAgE1B;;;;OAIG;IACG,MAAM,CACV,SAAS,EAAE,iBAAiB,EAC5B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC;IAgE1B;;;;OAIG;IACG,SAAS,CACb,SAAS,EAAE,iBAAiB,EAC5B,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAC/B,OAAO,CAAC,MAAM,EAAE,CAAC;IA4BpB;;;;;;OAMG;IACG,OAAO,CACX,SAAS,EAAE,iBAAiB,EAC5B,SAAS,EAAE,MAAM,EACjB,mBAAmB,EAAE,MAAM,EAC3B,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,cAAc,CAAC;IAmF1B;;;;;OAKG;IACG,SAAS,CACb,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE,YAAY,GAAG,MAAM,EAC9B,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,cAAc,CAAC;IAoE1B,2EAA2E;IACrE,QAAQ,CAAC,SAAS,EAAE,iBAAiB,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM;IAiB3E;;;;OAIG;YACW,UAAU;IAgExB,mEAAmE;IACnE,OAAO,CAAC,eAAe;IASvB,+DAA+D;IAC/D,OAAO,CAAC,MAAM,CAAC,cAAc;IAY7B,4EAA4E;YAC9D,QAAQ;IAatB;;;;;;;;;;;;;OAaG;YACW,cAAc;IAU5B,oDAAoD;YACtC,YAAY;YAWZ,WAAW;YAiBX,UAAU;IAgBxB;;;;OAIG;YACW,sBAAsB;YActB,WAAW;IA+BzB;;;;;;OAMG;YACW,mBAAmB;IAgCjC;;;OAGG;YACW,YAAY;IAkB1B,sEAAsE;YACxD,QAAQ;CAmBvB"}
|