@happyvertical/smrt-approvals 0.0.0-stage → 0.55.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/AGENTS.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @happyvertical/smrt-approvals
|
|
2
|
+
|
|
3
|
+
Decision gate plus ledger (slice 1 of #3716): `ApprovalRequest`,
|
|
4
|
+
append-only `ApprovalEvent`, tighten-only `ApprovalPolicy`, and
|
|
5
|
+
`ApprovalService`, the only write path. Not a workflow engine: no stages, no
|
|
6
|
+
executor, no compensation. Read [agents/approvals.md](agents/approvals.md)
|
|
7
|
+
before changing a transition, the guard predicates, or the surface.
|
|
8
|
+
|
|
9
|
+
## Invariants
|
|
10
|
+
|
|
11
|
+
- Every transition is ONE guarded `UPDATE ... WHERE id AND tenant_id AND
|
|
12
|
+
status AND version AND expires_at ... RETURNING version` plus one event
|
|
13
|
+
insert, in one transaction. Zero rows means a concurrent writer won: retry
|
|
14
|
+
from a fresh read, never write anyway. Keep the predicate the arbiter; the
|
|
15
|
+
in-transaction reads only classify refusals.
|
|
16
|
+
- Only `human` principals decide; never the requester; one decision per actor
|
|
17
|
+
(unique `(request_id, vote_key)`); quorum counts distinct approver events.
|
|
18
|
+
Agents and services may request, consume, and expire.
|
|
19
|
+
- Tenant: every statement names `tenant_id`, the work runs in
|
|
20
|
+
`withTenant(principal.tenantId)`, and a conflicting active context is
|
|
21
|
+
refused. Principal tenant ids must be UUIDs.
|
|
22
|
+
- Policy may only tighten (`assertPolicyTightens` on write, `effectiveRules`
|
|
23
|
+
clamps on read). Quorum and expiry are fixed at request time; decide-time
|
|
24
|
+
permissions are the request snapshot plus the current kind and policy slugs.
|
|
25
|
+
- Generated surface is `list`/`get` on REST and MCP, no CLI. A collection's
|
|
26
|
+
`@smrt()` config is applied to its item class by core, so each collection
|
|
27
|
+
repeats its model's surface exactly; changing one without the other opens
|
|
28
|
+
or closes the model. `surface.test.ts` holds this.
|
|
29
|
+
- Models refuse re-save and delete of requests and events. Never add an
|
|
30
|
+
update path for events. Inserts need the service-only capability in
|
|
31
|
+
`src/write-capability.ts`; never export it or mint it outside the service.
|
|
32
|
+
- The event FK is physical on PostgreSQL and SQLite only: DuckDB runs an
|
|
33
|
+
indexed-column UPDATE on a referenced row as delete plus insert.
|
|
34
|
+
- No Node built-ins in `src/` (browser gate); use `globalThis.crypto`.
|
|
35
|
+
|
|
36
|
+
## Validation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm --filter @happyvertical/smrt-approvals test
|
|
40
|
+
pnpm --filter @happyvertical/smrt-approvals typecheck
|
|
41
|
+
pnpm --filter @happyvertical/smrt-approvals test:postgres
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The behaviour suite (`src/__tests__/helpers/approval-suite.ts`) runs on
|
|
45
|
+
SQLite, DuckDB, and, through `test:postgres`, a migrated PostgreSQL database.
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright <2025> <Happy Vertical Corporation>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,125 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @happyvertical/smrt-approvals
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A small decision gate for s-m-r-t applications. A package defines an approval
|
|
4
|
+
kind; a requester opens a request bound to one revision of one subject;
|
|
5
|
+
eligible people decide; the consumer consumes the approval exactly once and
|
|
6
|
+
then performs its own action. It is not a workflow engine: there are no
|
|
7
|
+
stages, no executor, and no compensation.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @happyvertical/smrt-approvals
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Concepts
|
|
16
|
+
|
|
17
|
+
| Model | Purpose |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `ApprovalRequest` | One gate on one subject revision: kind, subject type and id, revision hash, requester, status, quorum, expiry, decision and consumption timestamps, tenant. |
|
|
20
|
+
| `ApprovalEvent` | Append-only ledger: `created`, `approved`, `rejected`, `changes_requested`, `cancelled`, `expired`, `consumed`, with actor and reason. The source of truth. |
|
|
21
|
+
| `ApprovalPolicy` | A tenant's tightening of a kind: a higher quorum, a shorter expiry, or one more required permission. It can never loosen the package defaults. |
|
|
22
|
+
|
|
23
|
+
Status moves from `pending` to `approved`, `rejected`, `changes_requested`,
|
|
24
|
+
`cancelled`, or `expired`. An approved request is consumed once (it keeps the
|
|
25
|
+
`approved` status and gains `consumedAt`) or expires unconsumed. A request
|
|
26
|
+
that needs changes is finished; the requester opens a new request for the new
|
|
27
|
+
revision.
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import {
|
|
33
|
+
ApprovalService,
|
|
34
|
+
defineApprovalKind,
|
|
35
|
+
} from '@happyvertical/smrt-approvals';
|
|
36
|
+
|
|
37
|
+
// In the owning package. Register `social.approve-post` with
|
|
38
|
+
// registerPermissionDefinitions from @happyvertical/smrt-users.
|
|
39
|
+
export const publishPost = defineApprovalKind({
|
|
40
|
+
key: 'social.post.publish',
|
|
41
|
+
subject: '@happyvertical/smrt-social:SocialPost',
|
|
42
|
+
permission: 'social.approve-post',
|
|
43
|
+
defaults: { requiredApprovals: 1, ttlMs: 7 * 24 * 60 * 60 * 1000 },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const approvals = new ApprovalService({ db });
|
|
47
|
+
|
|
48
|
+
// Any principal type may request: human, agent, or service.
|
|
49
|
+
const opened = await approvals.requestApproval(author, {
|
|
50
|
+
kind: publishPost,
|
|
51
|
+
subjectId: post.id,
|
|
52
|
+
subjectRevisionHash: revisionHash(post),
|
|
53
|
+
requestKey: `publish:${post.id}:${revisionHash(post)}`,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Only a human of the same tenant, other than the requester, holding the
|
|
57
|
+
// kind's permission, may decide, once.
|
|
58
|
+
await approvals.decide(editor, opened.request.id, { decision: 'approve' });
|
|
59
|
+
|
|
60
|
+
// The consumer presents the revision it is about to act on.
|
|
61
|
+
const used = await approvals.consume(publisher, opened.request.id, revisionHash(post));
|
|
62
|
+
if (used.outcome === 'transitioned') {
|
|
63
|
+
await publish(post);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
A principal is `{ id, tenantId, type: 'human' | 'agent' | 'service', can(slug) }`,
|
|
68
|
+
built from `PermissionResolver.resolvePermissions(userId, tenantId)` in
|
|
69
|
+
production or `approvalPrincipalFromPermissions()` in tests.
|
|
70
|
+
|
|
71
|
+
Every transition returns `{ outcome, request, refusal? }`. `outcome` is
|
|
72
|
+
`transitioned`, `already_applied` (an idempotent `requestKey` replay), or
|
|
73
|
+
`refused` with a typed reason such as `not_pending`, `expired`,
|
|
74
|
+
`self_approval`, `not_human`, `forbidden`, `already_decided`,
|
|
75
|
+
`revision_mismatch`, `already_consumed`, `unbacked_approval`, or `not_found`. A refusal writes
|
|
76
|
+
nothing.
|
|
77
|
+
|
|
78
|
+
To consume inside your own transaction, so the domain write and the
|
|
79
|
+
consumption commit together, pass `{ transaction: tx }` to `consume`. On
|
|
80
|
+
SQLite and DuckDB open that transaction with `withEmbeddedWriteTransaction`
|
|
81
|
+
from `@happyvertical/smrt-core` (or `SmrtObject.withTransaction()`), not a raw
|
|
82
|
+
`db.transaction()`, so it holds the embedded write queue.
|
|
83
|
+
|
|
84
|
+
Expiry: `expire(principal, id)` and `expireDue(principal)` record expiry for
|
|
85
|
+
requests whose deadline passed; a job can call them. Decisions and
|
|
86
|
+
consumption after the deadline are refused even before the sweep runs.
|
|
87
|
+
|
|
88
|
+
## Guarantees
|
|
89
|
+
|
|
90
|
+
- Every transition is one guarded conditional `UPDATE` (id, tenant, expected
|
|
91
|
+
status, version, expiry) plus one event insert in the same transaction.
|
|
92
|
+
Concurrent double approval or double consumption has exactly one winner;
|
|
93
|
+
the loser is refused cleanly and writes nothing.
|
|
94
|
+
- Quorum counts distinct approvers from the ledger; one actor decides once.
|
|
95
|
+
- Tenant isolation on every read and write: a request id from another tenant
|
|
96
|
+
is `not_found`.
|
|
97
|
+
- The generated REST and MCP surface is `list` and `get` only, and there is no
|
|
98
|
+
generated CLI. Events cannot be updated or deleted.
|
|
99
|
+
- Only `ApprovalService` can create requests and events: a row built
|
|
100
|
+
directly (`new ApprovalRequest(...)`, `ApprovalRequestCollection.create(...)`,
|
|
101
|
+
a hand-written `ApprovalEvent`) is refused at the model layer. `consume` also refuses an approval its event
|
|
102
|
+
ledger does not back (`unbacked_approval`).
|
|
103
|
+
|
|
104
|
+
## Permissions
|
|
105
|
+
|
|
106
|
+
| Slug | Grants |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| per-kind slug | Deciding requests of that kind. |
|
|
109
|
+
| `approvals.cancel-any` | Cancelling someone else's pending request. |
|
|
110
|
+
| `approvals.manage-policy` | Writing tenant policy (humans only). |
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pnpm --filter @happyvertical/smrt-approvals test
|
|
116
|
+
pnpm --filter @happyvertical/smrt-approvals typecheck
|
|
117
|
+
pnpm --filter @happyvertical/smrt-approvals test:postgres
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
See [AGENTS.md](./AGENTS.md) and [agents/approvals.md](./agents/approvals.md)
|
|
121
|
+
for the design invariants.
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Approvals: transitions, concurrency, and scope
|
|
2
|
+
|
|
3
|
+
Design source: the accepted recommendation on smrt#3716. This file records
|
|
4
|
+
the slice-1 contract (#3741) and what later slices add.
|
|
5
|
+
|
|
6
|
+
## States and transitions
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
pending --approve (distinct approvers reach quorum)--> approved
|
|
10
|
+
approved --consume (hash matches, unexpired)---------> approved + consumedAt
|
|
11
|
+
pending --reject | request_changes-------------------> rejected | changes_requested
|
|
12
|
+
pending --cancel (requester or approvals.cancel-any)-> cancelled
|
|
13
|
+
pending | approved(unconsumed) --expire (deadline)---> expired
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`changes_requested` is terminal in slice 1: the requester opens a new request
|
|
17
|
+
for the new revision (a new `subjectRevisionHash`). There is no resubmit.
|
|
18
|
+
|
|
19
|
+
Each transition in `src/service.ts`:
|
|
20
|
+
|
|
21
|
+
1. Validates input and the principal (`id`, UUID `tenantId`, `type`, `can`).
|
|
22
|
+
2. Refuses a principal whose tenant differs from an active tenant context.
|
|
23
|
+
3. Opens a transaction through core's `withEmbeddedWriteTransaction(db,
|
|
24
|
+
isEmbeddedDatabase(db), fn)`. On embedded engines (SQLite, DuckDB, libsql)
|
|
25
|
+
it takes the embedded write queue for the database first, then the
|
|
26
|
+
adapter's connection lock; the event insert and change-feed append inside
|
|
27
|
+
re-enter that hold. The reverse order (a raw `db.transaction()`) deadlocks
|
|
28
|
+
against an unrelated NULL-tenant save, which holds the queue and then
|
|
29
|
+
opens its own write transaction, until the 30 s transaction-queue timeout,
|
|
30
|
+
and lets other root writes land inside the transaction. The queue also
|
|
31
|
+
serializes transitions per database, so no separate chain exists.
|
|
32
|
+
PostgreSQL is not queued: each transaction gets its own pooled connection.
|
|
33
|
+
4. Reads the request through a transaction-bound collection with an explicit
|
|
34
|
+
`tenantId` filter, and classifies refusals from that read.
|
|
35
|
+
5. Runs the guarded UPDATE with the version it read. Zero rows: the
|
|
36
|
+
transaction body returns `retry`, nothing was written, and the loop starts
|
|
37
|
+
again from a fresh read (up to `maxAttempts`, then `contention`).
|
|
38
|
+
6. Inserts the event with `sequence` = the returned version and appends a
|
|
39
|
+
change-feed entry (the raw UPDATE bypasses save hooks).
|
|
40
|
+
7. Re-reads the request after commit on the service connection.
|
|
41
|
+
|
|
42
|
+
On PostgreSQL the UPDATE takes the row lock; under READ COMMITTED a waiting
|
|
43
|
+
UPDATE re-evaluates `version = ?` after the winner commits, so it matches
|
|
44
|
+
nothing. Unique keys `(request_id, sequence)` and `(request_id, vote_key)`
|
|
45
|
+
are a second line: a violation aborts the attempt and it retries.
|
|
46
|
+
|
|
47
|
+
`consume({ transaction })` runs steps 4 to 6 on the caller's open
|
|
48
|
+
transaction, once, without retry or serialization; the caller owns commit.
|
|
49
|
+
On embedded engines that handle must come from `withEmbeddedWriteTransaction`
|
|
50
|
+
or `SmrtObject.withTransaction()`, never a raw `db.transaction()`, for the
|
|
51
|
+
lock-order reason in step 3.
|
|
52
|
+
|
|
53
|
+
## Raw SQL boundaries
|
|
54
|
+
|
|
55
|
+
The guarded UPDATEs are the only raw SQL. They bind every value (no literal
|
|
56
|
+
UUIDs or empty strings), validate the request id as a UUID first, and name
|
|
57
|
+
`tenant_id`. Reads and inserts go through collections so tenancy
|
|
58
|
+
interceptors, UUID columns, and the change feed behave as for any model.
|
|
59
|
+
|
|
60
|
+
## Service-only write capability
|
|
61
|
+
|
|
62
|
+
Only `ApprovalService` inserts `ApprovalRequest` and `ApprovalEvent` rows
|
|
63
|
+
(events only in the same transaction as the guarded UPDATE they record). It passes the
|
|
64
|
+
module-private `SERVICE_WRITE` symbol (`src/write-capability.ts`, not a public
|
|
65
|
+
export) as a constructor option; the model records that instance in a
|
|
66
|
+
`WeakSet` and refuses, in `save()` and `validateBeforeSave()`, any insert
|
|
67
|
+
without it, and any minted insert that does not start pending, undecided,
|
|
68
|
+
and unconsumed. `new ApprovalRequest({ status: 'approved' }).save()`,
|
|
69
|
+
`ApprovalRequestCollection.create(...)`, and a forged `approved` event
|
|
70
|
+
(which would otherwise count toward quorum) are refused and write nothing. A
|
|
71
|
+
symbol never arrives from JSON, so REST and MCP input cannot carry it (the
|
|
72
|
+
smrt-reports `runtime-report.ts` pattern). It guards the in-process API,
|
|
73
|
+
not raw SQL.
|
|
74
|
+
|
|
75
|
+
`consume` re-checks the ledger before consuming (defence in depth against a
|
|
76
|
+
status written around the service): the `created` event at sequence 1, no
|
|
77
|
+
terminal event, and `approved` events from at least `requiredApprovals`
|
|
78
|
+
distinct human actors other than the requester; otherwise it refuses
|
|
79
|
+
`unbacked_approval`. Quorum counting in `decide` applies the same filter.
|
|
80
|
+
|
|
81
|
+
## Out of scope here (later slices of #3716)
|
|
82
|
+
|
|
83
|
+
- Slice 2: Svelte inbox, panel, decision bar; `approvals.requests` recipe.
|
|
84
|
+
- Slice 3: notification adapter (`UserNotificationService.notify`, sourceRef
|
|
85
|
+
`approval:<id>:<event>`), profiles `AuditLog` mirror, expiry and reminder
|
|
86
|
+
job calling `expireDue`.
|
|
87
|
+
- Slice 4: adopters (social, personas, content, subscriptions).
|
|
88
|
+
- Slice 5: agent and MCP request tools (deciding stays human-only).
|
|
89
|
+
- Slice 6: #3667 `pending_approval` hand-off.
|
|
90
|
+
|
|
91
|
+
Known gaps: listing every holder of a permission needs a users-package
|
|
92
|
+
helper (for notifications); a vote stays counted if the approver later loses
|
|
93
|
+
the permission.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"__smrt-register__.d.ts","sourceRoot":"","sources":["../src/__smrt-register__.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection for {@link ApprovalEvent}. Reads only: every write goes
|
|
3
|
+
* through `ApprovalService`.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
import { SmrtCollection } from '@happyvertical/smrt-core';
|
|
8
|
+
import { ApprovalEvent } from '../models/ApprovalEvent.js';
|
|
9
|
+
/** Tenant-scoped reads of {@link ApprovalEvent} rows. */
|
|
10
|
+
export declare class ApprovalEventCollection extends SmrtCollection<ApprovalEvent> {
|
|
11
|
+
static readonly _itemClass: typeof ApprovalEvent;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=ApprovalEventCollection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApprovalEventCollection.d.ts","sourceRoot":"","sources":["../../src/collections/ApprovalEventCollection.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAQ,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D,yDAAyD;AAIzD,qBAKa,uBAAwB,SAAQ,cAAc,CAAC,aAAa,CAAC;IACxE,MAAM,CAAC,QAAQ,CAAC,UAAU,uBAAiB;CAC5C"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection for {@link ApprovalPolicy}. Reads only: every write goes
|
|
3
|
+
* through `ApprovalService`.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
import { SmrtCollection } from '@happyvertical/smrt-core';
|
|
8
|
+
import { ApprovalPolicy } from '../models/ApprovalPolicy.js';
|
|
9
|
+
/** Tenant-scoped reads of {@link ApprovalPolicy} rows. */
|
|
10
|
+
export declare class ApprovalPolicyCollection extends SmrtCollection<ApprovalPolicy> {
|
|
11
|
+
static readonly _itemClass: typeof ApprovalPolicy;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=ApprovalPolicyCollection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApprovalPolicyCollection.d.ts","sourceRoot":"","sources":["../../src/collections/ApprovalPolicyCollection.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAQ,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,0DAA0D;AAI1D,qBAKa,wBAAyB,SAAQ,cAAc,CAAC,cAAc,CAAC;IAC1E,MAAM,CAAC,QAAQ,CAAC,UAAU,wBAAkB;CAC7C"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection for {@link ApprovalRequest}. Reads only: every write goes
|
|
3
|
+
* through `ApprovalService`.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
import { SmrtCollection } from '@happyvertical/smrt-core';
|
|
8
|
+
import { ApprovalRequest } from '../models/ApprovalRequest.js';
|
|
9
|
+
/** Tenant-scoped reads of {@link ApprovalRequest} rows. */
|
|
10
|
+
export declare class ApprovalRequestCollection extends SmrtCollection<ApprovalRequest> {
|
|
11
|
+
static readonly _itemClass: typeof ApprovalRequest;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=ApprovalRequestCollection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApprovalRequestCollection.d.ts","sourceRoot":"","sources":["../../src/collections/ApprovalRequestCollection.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAQ,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,2DAA2D;AAI3D,qBAKa,yBAA0B,SAAQ,cAAc,CAAC,eAAe,CAAC;IAC5E,MAAM,CAAC,QAAQ,CAAC,UAAU,yBAAmB;CAC9C"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @happyvertical/smrt-approvals
|
|
3
|
+
*
|
|
4
|
+
* A small decision gate: a package defines an approval kind, a requester
|
|
5
|
+
* opens a request bound to one subject revision, eligible humans decide,
|
|
6
|
+
* and the consumer consumes the approval exactly once before acting.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import {
|
|
11
|
+
* ApprovalService,
|
|
12
|
+
* defineApprovalKind,
|
|
13
|
+
* } from '@happyvertical/smrt-approvals';
|
|
14
|
+
*
|
|
15
|
+
* export const publishPost = defineApprovalKind({
|
|
16
|
+
* key: 'social.post.publish',
|
|
17
|
+
* subject: '@happyvertical/smrt-social:SocialPost',
|
|
18
|
+
* permission: 'social.approve-post',
|
|
19
|
+
* defaults: { requiredApprovals: 1 },
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* const approvals = new ApprovalService({ db });
|
|
23
|
+
* const { request } = await approvals.requestApproval(author, {
|
|
24
|
+
* kind: publishPost,
|
|
25
|
+
* subjectId: post.id,
|
|
26
|
+
* subjectRevisionHash: hashOf(post),
|
|
27
|
+
* });
|
|
28
|
+
* await approvals.decide(editor, request.id, { decision: 'approve' });
|
|
29
|
+
* const used = await approvals.consume(publisher, request.id, hashOf(post));
|
|
30
|
+
* if (used.outcome === 'transitioned') await publish(post);
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @packageDocumentation
|
|
34
|
+
*/
|
|
35
|
+
import './__smrt-register__.js';
|
|
36
|
+
export { ApprovalEventCollection } from './collections/ApprovalEventCollection.js';
|
|
37
|
+
export { ApprovalPolicyCollection } from './collections/ApprovalPolicyCollection.js';
|
|
38
|
+
export { ApprovalRequestCollection } from './collections/ApprovalRequestCollection.js';
|
|
39
|
+
export { type ApprovalPolicyRules, assertPolicyTightens, DEFAULT_APPROVAL_TTL_MS, defineApprovalKind, effectiveRules, getApprovalKind, listApprovalKinds, MAX_APPROVAL_TTL_MS, MAX_REQUIRED_APPROVALS, MIN_APPROVAL_TTL_MS, requireApprovalKind, unregisterApprovalKind, } from './kinds.js';
|
|
40
|
+
export { ApprovalEvent, type ApprovalEventOptions, } from './models/ApprovalEvent.js';
|
|
41
|
+
export { ApprovalPolicy, type ApprovalPolicyOptions, } from './models/ApprovalPolicy.js';
|
|
42
|
+
export { ApprovalRequest, type ApprovalRequestOptions, } from './models/ApprovalRequest.js';
|
|
43
|
+
export { APPROVAL_PERMISSION_DEFINITIONS, CANCEL_ANY_APPROVAL_PERMISSION, ensureApprovalPermissionsRegistered, MANAGE_APPROVAL_POLICY_PERMISSION, registerApprovalPermissions, } from './permissions.js';
|
|
44
|
+
export { type ApprovalResult, ApprovalService, type ApprovalServiceOptions, type ConsumeOptions, type DecideInput, type RequestApprovalInput, } from './service.js';
|
|
45
|
+
export { APPROVAL_EVENT_TYPES, APPROVAL_PRINCIPAL_TYPES, APPROVAL_STATUSES, type ApprovalDecision, ApprovalError, type ApprovalErrorCode, type ApprovalEventType, type ApprovalKind, type ApprovalKindDefaults, type ApprovalKindInput, type ApprovalPrincipal, type ApprovalPrincipalType, type ApprovalRefusalReason, type ApprovalStatus, type ApprovalTransitionOutcome, type ApprovalTransitionResult, approvalPrincipalFromPermissions, type EffectiveApprovalRules, } from './types.js';
|
|
46
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,OAAO,wBAAwB,CAAC;AAGhC,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AACnF,OAAO,EAAE,wBAAwB,EAAE,MAAM,2CAA2C,CAAC;AACrF,OAAO,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAC;AACvF,OAAO,EACL,KAAK,mBAAmB,EACxB,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,GAC1B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,cAAc,EACd,KAAK,qBAAqB,GAC3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,GAC5B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,EAC9B,mCAAmC,EACnC,iCAAiC,EACjC,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,KAAK,cAAc,EACnB,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,oBAAoB,GAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,aAAa,EACb,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,gCAAgC,EAChC,KAAK,sBAAsB,GAC5B,MAAM,YAAY,CAAC"}
|