@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
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public vocabulary of `@happyvertical/smrt-approvals`: statuses, event
|
|
3
|
+
* types, principals, kind definitions, and the typed transition results.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
/** Every status an {@link ApprovalRequest} can hold. */
|
|
8
|
+
export declare const APPROVAL_STATUSES: readonly ["pending", "approved", "rejected", "changes_requested", "cancelled", "expired"];
|
|
9
|
+
/** Lifecycle status of an approval request. */
|
|
10
|
+
export type ApprovalStatus = (typeof APPROVAL_STATUSES)[number];
|
|
11
|
+
/** Every event type the append-only ledger records. */
|
|
12
|
+
export declare const APPROVAL_EVENT_TYPES: readonly ["created", "approved", "rejected", "changes_requested", "cancelled", "expired", "consumed"];
|
|
13
|
+
/** Type of an {@link ApprovalEvent} row. */
|
|
14
|
+
export type ApprovalEventType = (typeof APPROVAL_EVENT_TYPES)[number];
|
|
15
|
+
/** A decider's verdict. */
|
|
16
|
+
export type ApprovalDecision = 'approve' | 'reject' | 'request_changes';
|
|
17
|
+
/**
|
|
18
|
+
* What kind of actor a principal is. Only `human` principals may decide;
|
|
19
|
+
* `agent` (an autonomous principal: persona, assistant, worker) and
|
|
20
|
+
* `service` (a job or integration) may request, consume, and expire.
|
|
21
|
+
*/
|
|
22
|
+
export type ApprovalPrincipalType = 'human' | 'agent' | 'service';
|
|
23
|
+
/** Every principal type, for validation. */
|
|
24
|
+
export declare const APPROVAL_PRINCIPAL_TYPES: readonly ApprovalPrincipalType[];
|
|
25
|
+
/**
|
|
26
|
+
* An actor whose authority is a set of permission slugs resolved for ONE
|
|
27
|
+
* tenant (the `principal.can(slug)` idiom shared with personas and support).
|
|
28
|
+
* Build it from `PermissionResolver.resolvePermissions(userId, tenantId)` in
|
|
29
|
+
* production; tests use {@link approvalPrincipalFromPermissions}.
|
|
30
|
+
*/
|
|
31
|
+
export interface ApprovalPrincipal {
|
|
32
|
+
/** Stable actor id recorded on events (a user or profile id). */
|
|
33
|
+
readonly id: string;
|
|
34
|
+
/** The tenant the permissions were resolved for. Required. */
|
|
35
|
+
readonly tenantId: string;
|
|
36
|
+
/** Actor type. Only `human` may approve, reject, or request changes. */
|
|
37
|
+
readonly type: ApprovalPrincipalType;
|
|
38
|
+
/** Whether this actor holds the permission slug in {@link tenantId}. */
|
|
39
|
+
can(slug: string): boolean;
|
|
40
|
+
}
|
|
41
|
+
/** Build an {@link ApprovalPrincipal} from an explicit set of granted slugs. */
|
|
42
|
+
export declare function approvalPrincipalFromPermissions(permissions: Iterable<string>, options: {
|
|
43
|
+
id: string;
|
|
44
|
+
tenantId: string;
|
|
45
|
+
type: ApprovalPrincipalType;
|
|
46
|
+
}): ApprovalPrincipal;
|
|
47
|
+
/** Package defaults a kind declares; tenant policy may only tighten them. */
|
|
48
|
+
export interface ApprovalKindDefaults {
|
|
49
|
+
/** Distinct approvers needed. Integer, 1-25. Default 1. */
|
|
50
|
+
requiredApprovals?: number;
|
|
51
|
+
/** Lifetime of a request in milliseconds. Default seven days. */
|
|
52
|
+
ttlMs?: number;
|
|
53
|
+
}
|
|
54
|
+
/** Input to {@link defineApprovalKind}. */
|
|
55
|
+
export interface ApprovalKindInput {
|
|
56
|
+
/** Dotted, lower-case key, e.g. `social.post.publish`. Unique per process. */
|
|
57
|
+
key: string;
|
|
58
|
+
/** Qualified subject class, e.g. `@happyvertical/smrt-social:SocialPost`. */
|
|
59
|
+
subject: string;
|
|
60
|
+
/** Permission slug a principal must hold to decide a request of this kind. */
|
|
61
|
+
permission: string;
|
|
62
|
+
/** Package defaults. */
|
|
63
|
+
defaults?: ApprovalKindDefaults;
|
|
64
|
+
}
|
|
65
|
+
/** A registered, normalized approval kind. */
|
|
66
|
+
export interface ApprovalKind {
|
|
67
|
+
readonly key: string;
|
|
68
|
+
readonly subject: string;
|
|
69
|
+
readonly permission: string;
|
|
70
|
+
readonly defaults: Readonly<Required<ApprovalKindDefaults>>;
|
|
71
|
+
}
|
|
72
|
+
/** The effective rules for one request: kind defaults tightened by policy. */
|
|
73
|
+
export interface EffectiveApprovalRules {
|
|
74
|
+
requiredApprovals: number;
|
|
75
|
+
ttlMs: number;
|
|
76
|
+
/** Every slug a decider must hold (the kind's, plus policy additions). */
|
|
77
|
+
requiredPermissions: string[];
|
|
78
|
+
}
|
|
79
|
+
/** Why a transition was refused. */
|
|
80
|
+
export type ApprovalRefusalReason =
|
|
81
|
+
/** No request with that id in the principal's tenant (or malformed id). */
|
|
82
|
+
'not_found'
|
|
83
|
+
/** The principal's tenant does not match the active tenant context. */
|
|
84
|
+
| 'tenant_mismatch'
|
|
85
|
+
/** The request is not in a status this transition accepts. */
|
|
86
|
+
| 'not_pending'
|
|
87
|
+
/** The request's expiry has passed. */
|
|
88
|
+
| 'expired'
|
|
89
|
+
/** The requester tried to decide their own request. */
|
|
90
|
+
| 'self_approval'
|
|
91
|
+
/** A non-human principal tried to decide. */
|
|
92
|
+
| 'not_human'
|
|
93
|
+
/** The principal lacks a required permission. */
|
|
94
|
+
| 'forbidden'
|
|
95
|
+
/** This principal already recorded a decision on the request. */
|
|
96
|
+
| 'already_decided'
|
|
97
|
+
/** `consume` was called with a different subject revision hash. */
|
|
98
|
+
| 'revision_mismatch'
|
|
99
|
+
/** The request row says approved but its event ledger does not. */
|
|
100
|
+
| 'unbacked_approval'
|
|
101
|
+
/** The approval was already consumed. */
|
|
102
|
+
| 'already_consumed'
|
|
103
|
+
/** The request is not approved (consume) or not yet expired (expire). */
|
|
104
|
+
| 'not_approved' | 'not_expired'
|
|
105
|
+
/** A `requestKey` was reused for a different subject or revision. */
|
|
106
|
+
| 'request_key_conflict'
|
|
107
|
+
/** Concurrent transitions kept winning; retry later. */
|
|
108
|
+
| 'contention';
|
|
109
|
+
/** Outcome of a transition. */
|
|
110
|
+
export type ApprovalTransitionOutcome =
|
|
111
|
+
/** This call performed the transition. */
|
|
112
|
+
'transitioned'
|
|
113
|
+
/** The same request already existed (idempotent `requestKey` replay). */
|
|
114
|
+
| 'already_applied'
|
|
115
|
+
/** Fail-closed; see `refusal`. */
|
|
116
|
+
| 'refused';
|
|
117
|
+
/** Result of every {@link ApprovalService} transition. */
|
|
118
|
+
export interface ApprovalTransitionResult<T = unknown> {
|
|
119
|
+
outcome: ApprovalTransitionOutcome;
|
|
120
|
+
/** The request re-read after commit; `null` when refused as not found. */
|
|
121
|
+
request: T | null;
|
|
122
|
+
/** Set exactly when `outcome` is `refused`. */
|
|
123
|
+
refusal?: {
|
|
124
|
+
reason: ApprovalRefusalReason;
|
|
125
|
+
detail: string;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** Typed error for invalid input (programming errors, not refusals). */
|
|
129
|
+
export declare class ApprovalError extends Error {
|
|
130
|
+
/** Machine-readable code. */
|
|
131
|
+
readonly code: ApprovalErrorCode;
|
|
132
|
+
constructor(code: ApprovalErrorCode, message: string);
|
|
133
|
+
}
|
|
134
|
+
/** Codes carried by {@link ApprovalError}. */
|
|
135
|
+
export type ApprovalErrorCode = 'APPROVAL_INVALID' | 'APPROVAL_KIND_UNKNOWN' | 'APPROVAL_KIND_CONFLICT' | 'APPROVAL_POLICY_LOOSENS' | 'APPROVAL_FORBIDDEN' | 'APPROVAL_NO_TRANSACTION';
|
|
136
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/types.ts
|
|
2
|
+
var APPROVAL_STATUSES = [
|
|
3
|
+
"pending",
|
|
4
|
+
"approved",
|
|
5
|
+
"rejected",
|
|
6
|
+
"changes_requested",
|
|
7
|
+
"cancelled",
|
|
8
|
+
"expired"
|
|
9
|
+
];
|
|
10
|
+
var APPROVAL_EVENT_TYPES = [
|
|
11
|
+
"created",
|
|
12
|
+
"approved",
|
|
13
|
+
"rejected",
|
|
14
|
+
"changes_requested",
|
|
15
|
+
"cancelled",
|
|
16
|
+
"expired",
|
|
17
|
+
"consumed"
|
|
18
|
+
];
|
|
19
|
+
var APPROVAL_PRINCIPAL_TYPES = [
|
|
20
|
+
"human",
|
|
21
|
+
"agent",
|
|
22
|
+
"service"
|
|
23
|
+
];
|
|
24
|
+
function approvalPrincipalFromPermissions(permissions, options) {
|
|
25
|
+
const granted = new Set(permissions);
|
|
26
|
+
return {
|
|
27
|
+
id: options.id,
|
|
28
|
+
tenantId: options.tenantId,
|
|
29
|
+
type: options.type,
|
|
30
|
+
can: (slug) => granted.has(slug)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
var ApprovalError = class extends Error {
|
|
34
|
+
/** Machine-readable code. */
|
|
35
|
+
code;
|
|
36
|
+
constructor(code, message) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = "ApprovalError";
|
|
39
|
+
this.code = code;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
//#endregion
|
|
43
|
+
export { APPROVAL_EVENT_TYPES, APPROVAL_PRINCIPAL_TYPES, APPROVAL_STATUSES, ApprovalError, approvalPrincipalFromPermissions };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +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 tried 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;AAyFO,IAAM,gBAAN,cAA4B,MAAM;;CAE9B;CAET,YAAY,MAAyB,SAAiB;EACpD,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service-only write capability for approval requests and events.
|
|
3
|
+
*
|
|
4
|
+
* `ApprovalRequest` and `ApprovalEvent` rows are inserted only by
|
|
5
|
+
* `ApprovalService`. The service passes {@link SERVICE_WRITE} as a
|
|
6
|
+
* constructor option; the models record the instance here and refuse any
|
|
7
|
+
* insert that does not carry it. A symbol key can never arrive from JSON
|
|
8
|
+
* (REST body, MCP tool arguments) and this module is not part of the
|
|
9
|
+
* package's public exports, so `new ApprovalRequest({ status: 'approved' })`,
|
|
10
|
+
* `ApprovalRequestCollection.create(...)`, or a forged `approved` event are
|
|
11
|
+
* all refused at the model layer (the `runtime-report.ts` pattern in
|
|
12
|
+
* smrt-reports). This is an in-process API guard, not a defence against raw
|
|
13
|
+
* SQL; `consume()` additionally re-checks the ledger.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
* @packageDocumentation
|
|
17
|
+
*/
|
|
18
|
+
/** Constructor-option key carrying the capability. Never exported publicly. */
|
|
19
|
+
export declare const SERVICE_WRITE: unique symbol;
|
|
20
|
+
/** Constructor options fragment that grants the capability. */
|
|
21
|
+
export declare function serviceWriteOption(): {
|
|
22
|
+
[SERVICE_WRITE]: object;
|
|
23
|
+
};
|
|
24
|
+
/** Record `instance` as service-minted when its options carry the token. */
|
|
25
|
+
export declare function adoptServiceWrite(instance: object, options: unknown): void;
|
|
26
|
+
/** Whether `instance` was minted by the service for insertion. */
|
|
27
|
+
export declare function hasServiceWrite(instance: object): boolean;
|
|
28
|
+
//# sourceMappingURL=write-capability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-capability.d.ts","sourceRoot":"","sources":["../src/write-capability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,aAAa,eAAwC,CAAC;AAQnE,+DAA+D;AAC/D,wBAAgB,kBAAkB,IAAI;IAAE,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAEhE;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAQ1E;AAED,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-approvals",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.55.11",
|
|
4
|
+
"description": "Approval decision gate for the SMRT framework: revision-bound, single-use approvals with quorum, expiry, and an append-only event ledger",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"CLAUDE.md",
|
|
11
|
+
"AGENTS.md",
|
|
12
|
+
"agents"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./manifest": "./dist/manifest.json",
|
|
20
|
+
"./manifest.json": "./dist/manifest.json"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@happyvertical/smrt-core": "0.55.11",
|
|
24
|
+
"@happyvertical/smrt-tenancy": "0.55.11",
|
|
25
|
+
"@happyvertical/smrt-users": "0.55.11",
|
|
26
|
+
"@happyvertical/sql": "^0.103.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@happyvertical/smrt-vitest": "0.55.11",
|
|
30
|
+
"@types/node": "24.13.6",
|
|
31
|
+
"typescript": "6.0.3",
|
|
32
|
+
"vite": "8.1.4",
|
|
33
|
+
"vitest": "4.1.10"
|
|
34
|
+
},
|
|
35
|
+
"smrtJsdoc": "strict",
|
|
36
|
+
"smrtRawPrimitives": "strict",
|
|
37
|
+
"keywords": [
|
|
38
|
+
"smrt",
|
|
39
|
+
"approvals",
|
|
40
|
+
"workflow",
|
|
41
|
+
"quorum",
|
|
42
|
+
"audit",
|
|
43
|
+
"multi-tenant"
|
|
44
|
+
],
|
|
45
|
+
"author": "HappyVertical",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"registry": "https://registry.npmjs.org",
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "https://github.com/happyvertical/smrt.git",
|
|
54
|
+
"directory": "packages/approvals"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "vite build --mode library",
|
|
58
|
+
"build:watch": "vite build --mode library --watch",
|
|
59
|
+
"clean": "rm -rf dist",
|
|
60
|
+
"dev": "vite build --mode library --watch",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:postgres": "node ../../scripts/run-with-ci-postgres.mjs -- pnpm exec vitest run optional.test.ts",
|
|
63
|
+
"test:watch": "vitest",
|
|
64
|
+
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
|
|
65
|
+
"verify:pack": "node ../../scripts/verify-package-types-exports.js ."
|
|
66
|
+
}
|
|
6
67
|
}
|