@happyvertical/smrt-approvals 0.55.12 → 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 CHANGED
@@ -13,7 +13,8 @@ before changing a transition, the guard predicates, or the surface.
13
13
  insert, in one transaction. Zero rows means a concurrent writer won: retry
14
14
  from a fresh read, never write anyway. Keep the predicate the arbiter; the
15
15
  in-transaction reads only classify refusals.
16
- - Only `human` principals decide; never the requester; one decision per actor
16
+ - Only `human` principals decide; requesters additionally need the kind's
17
+ explicit `selfApprovalPermission`; one decision per actor
17
18
  (unique `(request_id, vote_key)`); quorum counts distinct approver events.
18
19
  Agents and services may request, consume, and expire.
19
20
  - Tenant: every statement names `tenant_id`, the work runs in
package/README.md CHANGED
@@ -53,7 +53,7 @@ const opened = await approvals.requestApproval(author, {
53
53
  requestKey: `publish:${post.id}:${revisionHash(post)}`,
54
54
  });
55
55
 
56
- // Only a human of the same tenant, other than the requester, holding the
56
+ // Only a human of the same tenant holding the
57
57
  // kind's permission, may decide, once.
58
58
  await approvals.decide(editor, opened.request.id, { decision: 'approve' });
59
59
 
@@ -123,3 +123,15 @@ for the design invariants.
123
123
  ## License
124
124
 
125
125
  MIT
126
+
127
+ ### Optional self-approval
128
+
129
+ Set `selfApprovalPermission: 'content.approve-self'` in `defineApprovalKind` to
130
+ allow a human requester holding that slug **and all normal decision permissions**
131
+ to approve, reject, or request changes on their own request. Omit it to retain the
132
+ default `self_approval` refusal. A self-decision records `selfApproved: true` and
133
+ `selfApprovalPermission` on its immutable event. Its approval counts as one vote
134
+ and can be consumed normally; larger quorums still require other humans.
135
+ Deploy manifest migrations for the two new event columns before running the new
136
+ service. Existing unmarked self events do not count. Recorded votes retain their
137
+ authority if permissions subsequently change, just like ordinary approvals.
@@ -75,7 +75,8 @@ not raw SQL.
75
75
  `consume` re-checks the ledger before consuming (defence in depth against a
76
76
  status written around the service): the `created` event at sequence 1, no
77
77
  terminal event, and `approved` events from at least `requiredApprovals`
78
- distinct human actors other than the requester; otherwise it refuses
78
+ distinct human actors; requester events count only with `selfApproved: true`
79
+ and a valid `selfApprovalPermission` audit slug. Otherwise it refuses
79
80
  `unbacked_approval`. Quorum counting in `decide` applies the same filter.
80
81
 
81
82
  ## Out of scope here (later slices of #3716)
@@ -91,3 +92,22 @@ distinct human actors other than the requester; otherwise it refuses
91
92
  Known gaps: listing every holder of a permission needs a users-package
92
93
  helper (for notifications); a vote stays counted if the approver later loses
93
94
  the permission.
95
+
96
+ ## Permission-gated self-decisions
97
+
98
+ A kind may opt in with `selfApprovalPermission: 'content.approve-self'`.
99
+ The requester must be human and hold that permission plus every normal snapshot,
100
+ current kind and tenant-policy decision permission. All three verdicts use this
101
+ allowance. Without the opt-in or grant, `self_approval` remains the refusal.
102
+ Self-decisions record `selfApproved` and `selfApprovalPermission` on the event;
103
+ ordinary events default to false and an empty slug. These fields are service-only,
104
+ not caller input. Deploy the generated event-column migration before upgrading
105
+ runtime code; existing events default to false/empty and cannot gain self authority.
106
+ Like other votes, an authorized self vote remains counted after permission or kind
107
+ configuration changes. It counts once, never bypasses quorum, expiry or tenancy,
108
+ and remains atomic with the guarded transition.
109
+
110
+ DuckDB deployments on `@happyvertical/sql` 0.103.1 may repeatedly recreate the
111
+ unchanged request/sequence index during migration because index introspection
112
+ retains SQL identifier quotes; see [SDK #1395](https://github.com/happyvertical/sdk/issues/1395).
113
+ The new audit columns converge and existing ordinary approvals remain consumable.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { APPROVAL_EVENT_TYPES, APPROVAL_PRINCIPAL_TYPES, APPROVAL_STATUSES, ApprovalError, approvalPrincipalFromPermissions } from "./types.js";
2
- import { ObjectRegistry, SmrtCollection, SmrtObject, appendChange, field, foreignKey, isEmbeddedDatabase, isUniqueViolationError, smrt, withEmbeddedWriteTransaction } from "@happyvertical/smrt-core";
2
+ import { ObjectRegistry, SmrtCollection, SmrtObject, appendChange, field, foreignKey, isEmbeddedDatabase, isUniqueViolationError, smrt, withEmbeddedWriteQueue, withEmbeddedWriteTransaction } from "@happyvertical/smrt-core";
3
3
  import { registerPermissionDefinitions } from "@happyvertical/smrt-users";
4
4
  import { TenantScoped, getTenantId, isSystemContext, tenantId, withTenant } from "@happyvertical/smrt-tenancy";
5
5
  //#region src/__smrt-register__.ts
6
- ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-approvals","packageVersion":"0.55.12","objects":{"@happyvertical/smrt-approvals:ApprovalEventCollection":{"name":"approvaleventcollection","className":"ApprovalEventCollection","qualifiedName":"@happyvertical/smrt-approvals:ApprovalEventCollection","collection":"approvalevents","filePath":"/home/runner/work/smrt/smrt/packages/approvals/src/collections/ApprovalEventCollection.ts","packageName":"@happyvertical/smrt-approvals","fields":{},"methods":{},"decoratorConfig":{"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":false,"tableName":"approval_events"},"extends":"SmrtCollection","extendsTypeArg":"ApprovalEvent","exportName":"ApprovalEventCollection","collectionExportName":"ApprovalEventCollectionCollection","schema":{"tableName":"approval_events","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);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"request_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"approval_requests","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT","engines":["postgres","sqlite"]}},"type":{"type":"TEXT","notNull":true,"unique":false},"actor_id":{"type":"TEXT","notNull":true,"unique":false},"actor_type":{"type":"TEXT","notNull":false,"unique":false},"reason":{"type":"TEXT","notNull":false,"unique":false},"subject_revision_hash":{"type":"TEXT","notNull":false,"unique":false},"sequence":{"type":"INTEGER","notNull":false,"unique":false},"vote_key":{"type":"TEXT","notNull":false,"unique":false},"occurred_at":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"approval_events_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"approval_events_request_sequence_key","columns":["request_id","sequence"],"unique":true},{"name":"approval_events_request_vote_key","columns":["request_id","vote_key"],"unique":true},{"name":"approval_events_tenant_occurred_idx","columns":["tenant_id","occurred_at"]},{"name":"approval_events_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"4963053a"},"importPath":"@happyvertical/smrt-approvals"},"@happyvertical/smrt-approvals:ApprovalPolicyCollection":{"name":"approvalpolicycollection","className":"ApprovalPolicyCollection","qualifiedName":"@happyvertical/smrt-approvals:ApprovalPolicyCollection","collection":"approvalpolicies","filePath":"/home/runner/work/smrt/smrt/packages/approvals/src/collections/ApprovalPolicyCollection.ts","packageName":"@happyvertical/smrt-approvals","fields":{},"methods":{},"decoratorConfig":{"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":false,"tableName":"approval_policies"},"extends":"SmrtCollection","extendsTypeArg":"ApprovalPolicy","exportName":"ApprovalPolicyCollection","collectionExportName":"ApprovalPolicyCollectionCollection","schema":{"tableName":"approval_policies","ddl":"CREATE TABLE IF NOT EXISTS \\"approval_policies\\" (\\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 \\"kind\\" TEXT NOT NULL,\\n \\"required_approvals\\" INTEGER,\\n \\"ttl_ms\\" INTEGER,\\n \\"required_permission\\" TEXT,\\n \\"updated_by\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"kind":{"type":"TEXT","notNull":true,"unique":false},"required_approvals":{"type":"INTEGER","notNull":false,"unique":false},"ttl_ms":{"type":"INTEGER","notNull":false,"unique":false},"required_permission":{"type":"TEXT","notNull":false,"unique":false},"updated_by":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"approval_policies_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"approval_policies_tenant_kind_key","columns":["tenant_id","kind"],"unique":true},{"name":"approval_policies_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"80929cc8"},"importPath":"@happyvertical/smrt-approvals"},"@happyvertical/smrt-approvals:ApprovalRequestCollection":{"name":"approvalrequestcollection","className":"ApprovalRequestCollection","qualifiedName":"@happyvertical/smrt-approvals:ApprovalRequestCollection","collection":"approvalrequests","filePath":"/home/runner/work/smrt/smrt/packages/approvals/src/collections/ApprovalRequestCollection.ts","packageName":"@happyvertical/smrt-approvals","fields":{},"methods":{},"decoratorConfig":{"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":false,"tableName":"approval_requests"},"extends":"SmrtCollection","extendsTypeArg":"ApprovalRequest","exportName":"ApprovalRequestCollection","collectionExportName":"ApprovalRequestCollectionCollection","schema":{"tableName":"approval_requests","ddl":"CREATE TABLE IF NOT EXISTS \\"approval_requests\\" (\\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 \\"kind\\" TEXT NOT NULL,\\n \\"subject_type\\" TEXT NOT NULL,\\n \\"subject_id\\" TEXT NOT NULL,\\n \\"subject_revision_hash\\" TEXT NOT NULL,\\n \\"request_key\\" TEXT NOT NULL,\\n \\"requested_by\\" TEXT NOT NULL,\\n \\"requested_by_type\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"status\\" TEXT,\\n \\"required_approvals\\" INTEGER,\\n \\"approval_count\\" INTEGER,\\n \\"required_permissions\\" TEXT,\\n \\"version\\" INTEGER,\\n \\"expires_at\\" TIMESTAMP,\\n \\"decided_at\\" TIMESTAMP,\\n \\"consumed_at\\" TIMESTAMP,\\n \\"consumed_by\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"kind":{"type":"TEXT","notNull":true,"unique":false},"subject_type":{"type":"TEXT","notNull":true,"unique":false},"subject_id":{"type":"TEXT","notNull":true,"unique":false},"subject_revision_hash":{"type":"TEXT","notNull":true,"unique":false},"request_key":{"type":"TEXT","notNull":true,"unique":false},"requested_by":{"type":"TEXT","notNull":true,"unique":false},"requested_by_type":{"type":"TEXT","notNull":false,"unique":false},"summary":{"type":"TEXT","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false},"required_approvals":{"type":"INTEGER","notNull":false,"unique":false},"approval_count":{"type":"INTEGER","notNull":false,"unique":false},"required_permissions":{"type":"TEXT","notNull":false,"unique":false},"version":{"type":"INTEGER","notNull":false,"unique":false},"expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"decided_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"consumed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"consumed_by":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"approval_requests_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"approval_requests_tenant_request_key","columns":["tenant_id","request_key"],"unique":true},{"name":"approval_requests_status_expiry_idx","columns":["tenant_id","status","expires_at"]},{"name":"approval_requests_subject_idx","columns":["tenant_id","subject_type","subject_id"]},{"name":"approval_requests_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"a5800bdd"},"importPath":"@happyvertical/smrt-approvals"},"@happyvertical/smrt-approvals:ApprovalEvent":{"name":"approvalevent","className":"ApprovalEvent","qualifiedName":"@happyvertical/smrt-approvals:ApprovalEvent","collection":"approvalevents","description":"One immutable entry in an approval request's history.","filePath":"/home/runner/work/smrt/smrt/packages/approvals/src/models/ApprovalEvent.ts","packageName":"@happyvertical/smrt-approvals","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"requestId":{"type":"foreignKey","required":true,"related":"ApprovalRequest","_meta":{"required":true,"constraint":{"engines":["postgres","sqlite"]},"onDelete":"RESTRICT","onUpdate":"RESTRICT"}},"type":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"actorId":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"actorType":{"type":"text","required":false,"_meta":{"readonly":true},"enum":["human","agent","service"],"readonly":true},"reason":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"subjectRevisionHash":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"sequence":{"type":"integer","required":false,"_meta":{"readonly":true},"readonly":true},"voteKey":{"type":"text","required":false,"_meta":{"nullable":true,"readonly":true},"readonly":true},"occurredAt":{"type":"datetime","required":false,"_meta":{"readonly":true},"readonly":true}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"approval_events","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":false,"indexes":[{"name":"approval_events_request_sequence_key","columns":["requestId","sequence"],"unique":true},{"name":"approval_events_request_vote_key","columns":["requestId","voteKey"],"unique":true},{"name":"approval_events_tenant_occurred_idx","columns":["tenantId","occurredAt"]}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"ApprovalEvent","collectionExportName":"ApprovalEventCollection","validationRules":[{"field":"requestId","rule":"required","fieldType":"foreignKey"},{"field":"type","rule":"required","fieldType":"text"},{"field":"actorId","rule":"required","fieldType":"text"}],"schema":{"tableName":"approval_events","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);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"request_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"approval_requests","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT","engines":["postgres","sqlite"]}},"type":{"type":"TEXT","notNull":true,"unique":false},"actor_id":{"type":"TEXT","notNull":true,"unique":false},"actor_type":{"type":"TEXT","notNull":false,"unique":false},"reason":{"type":"TEXT","notNull":false,"unique":false},"subject_revision_hash":{"type":"TEXT","notNull":false,"unique":false},"sequence":{"type":"INTEGER","notNull":false,"unique":false},"vote_key":{"type":"TEXT","notNull":false,"unique":false},"occurred_at":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"approval_events_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"approval_events_request_sequence_key","columns":["request_id","sequence"],"unique":true},{"name":"approval_events_request_vote_key","columns":["request_id","vote_key"],"unique":true},{"name":"approval_events_tenant_occurred_idx","columns":["tenant_id","occurred_at"]},{"name":"approval_events_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"4963053a"},"importPath":"@happyvertical/smrt-approvals"},"@happyvertical/smrt-approvals:ApprovalPolicy":{"name":"approvalpolicy","className":"ApprovalPolicy","qualifiedName":"@happyvertical/smrt-approvals:ApprovalPolicy","collection":"approvalpolicies","description":"Tenant-level tightening of an approval kind. One row per tenant and kind.","filePath":"/home/runner/work/smrt/smrt/packages/approvals/src/models/ApprovalPolicy.ts","packageName":"@happyvertical/smrt-approvals","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"kind":{"type":"text","required":true,"_meta":{"required":true}},"requiredApprovals":{"type":"integer","required":false,"_meta":{}},"ttlMs":{"type":"integer","required":false,"_meta":{}},"requiredPermission":{"type":"text","required":false,"_meta":{}},"updatedBy":{"type":"text","required":false,"_meta":{}}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"approval_policies","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":false,"indexes":[{"name":"approval_policies_tenant_kind_key","columns":["tenantId","kind"],"unique":true}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"ApprovalPolicy","collectionExportName":"ApprovalPolicyCollection","validationRules":[{"field":"kind","rule":"required","fieldType":"text"}],"schema":{"tableName":"approval_policies","ddl":"CREATE TABLE IF NOT EXISTS \\"approval_policies\\" (\\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 \\"kind\\" TEXT NOT NULL,\\n \\"required_approvals\\" INTEGER,\\n \\"ttl_ms\\" INTEGER,\\n \\"required_permission\\" TEXT,\\n \\"updated_by\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"kind":{"type":"TEXT","notNull":true,"unique":false},"required_approvals":{"type":"INTEGER","notNull":false,"unique":false},"ttl_ms":{"type":"INTEGER","notNull":false,"unique":false},"required_permission":{"type":"TEXT","notNull":false,"unique":false},"updated_by":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"approval_policies_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"approval_policies_tenant_kind_key","columns":["tenant_id","kind"],"unique":true},{"name":"approval_policies_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"80929cc8"},"importPath":"@happyvertical/smrt-approvals"},"@happyvertical/smrt-approvals:ApprovalRequest":{"name":"approvalrequest","className":"ApprovalRequest","qualifiedName":"@happyvertical/smrt-approvals:ApprovalRequest","collection":"approvalrequests","description":"A request for approval of one subject revision.","filePath":"/home/runner/work/smrt/smrt/packages/approvals/src/models/ApprovalRequest.ts","packageName":"@happyvertical/smrt-approvals","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"kind":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"subjectType":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"subjectId":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"subjectRevisionHash":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"requestKey":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"requestedBy":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"requestedByType":{"type":"text","required":false,"_meta":{"readonly":true},"enum":["human","agent","service"],"readonly":true},"summary":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"status":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"requiredApprovals":{"type":"integer","required":false,"_meta":{"readonly":true},"readonly":true},"approvalCount":{"type":"integer","required":false,"_meta":{"readonly":true},"readonly":true},"requiredPermissions":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"version":{"type":"integer","required":false,"_meta":{"readonly":true},"readonly":true},"expiresAt":{"type":"datetime","required":false,"_meta":{"readonly":true},"readonly":true},"decidedAt":{"type":"datetime","required":false,"_meta":{"readonly":true},"readonly":true},"consumedAt":{"type":"datetime","required":false,"_meta":{"readonly":true},"readonly":true},"consumedBy":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true}},"methods":{"getRequiredPermissions":{"name":"getRequiredPermissions","async":false,"parameters":[],"returnType":"string[]","isStatic":false,"isPublic":true},"setRequiredPermissions":{"name":"setRequiredPermissions","async":false,"parameters":[{"name":"slugs","type":"any","optional":false,"typeUnresolved":true}],"returnType":"void","isStatic":false,"isPublic":true},"expiresAtMs":{"name":"expiresAtMs","async":false,"parameters":[],"returnType":"number","isStatic":false,"isPublic":true},"isConsumed":{"name":"isConsumed","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"approval_requests","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":false,"indexes":[{"name":"approval_requests_tenant_request_key","columns":["tenantId","requestKey"],"unique":true},{"name":"approval_requests_status_expiry_idx","columns":["tenantId","status","expiresAt"]},{"name":"approval_requests_subject_idx","columns":["tenantId","subjectType","subjectId"]}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"ApprovalRequest","collectionExportName":"ApprovalRequestCollection","validationRules":[{"field":"kind","rule":"required","fieldType":"text"},{"field":"subjectType","rule":"required","fieldType":"text"},{"field":"subjectId","rule":"required","fieldType":"text"},{"field":"subjectRevisionHash","rule":"required","fieldType":"text"},{"field":"requestKey","rule":"required","fieldType":"text"},{"field":"requestedBy","rule":"required","fieldType":"text"}],"schema":{"tableName":"approval_requests","ddl":"CREATE TABLE IF NOT EXISTS \\"approval_requests\\" (\\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 \\"kind\\" TEXT NOT NULL,\\n \\"subject_type\\" TEXT NOT NULL,\\n \\"subject_id\\" TEXT NOT NULL,\\n \\"subject_revision_hash\\" TEXT NOT NULL,\\n \\"request_key\\" TEXT NOT NULL,\\n \\"requested_by\\" TEXT NOT NULL,\\n \\"requested_by_type\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"status\\" TEXT,\\n \\"required_approvals\\" INTEGER,\\n \\"approval_count\\" INTEGER,\\n \\"required_permissions\\" TEXT,\\n \\"version\\" INTEGER,\\n \\"expires_at\\" TIMESTAMP,\\n \\"decided_at\\" TIMESTAMP,\\n \\"consumed_at\\" TIMESTAMP,\\n \\"consumed_by\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"kind":{"type":"TEXT","notNull":true,"unique":false},"subject_type":{"type":"TEXT","notNull":true,"unique":false},"subject_id":{"type":"TEXT","notNull":true,"unique":false},"subject_revision_hash":{"type":"TEXT","notNull":true,"unique":false},"request_key":{"type":"TEXT","notNull":true,"unique":false},"requested_by":{"type":"TEXT","notNull":true,"unique":false},"requested_by_type":{"type":"TEXT","notNull":false,"unique":false},"summary":{"type":"TEXT","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false},"required_approvals":{"type":"INTEGER","notNull":false,"unique":false},"approval_count":{"type":"INTEGER","notNull":false,"unique":false},"required_permissions":{"type":"TEXT","notNull":false,"unique":false},"version":{"type":"INTEGER","notNull":false,"unique":false},"expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"decided_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"consumed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"consumed_by":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"approval_requests_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"approval_requests_tenant_request_key","columns":["tenant_id","request_key"],"unique":true},{"name":"approval_requests_status_expiry_idx","columns":["tenant_id","status","expires_at"]},{"name":"approval_requests_subject_idx","columns":["tenant_id","subject_type","subject_id"]},{"name":"approval_requests_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"a5800bdd"},"importPath":"@happyvertical/smrt-approvals"}},"moduleType":"smrt","smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"],"browser":{"status":"server-only","issues":["#3617","#3624","#3626"],"via":["@happyvertical/smrt-profiles","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"]},"stampsConstructors":true}`));
6
+ ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-approvals","packageVersion":"0.55.13","objects":{"@happyvertical/smrt-approvals:ApprovalEventCollection":{"name":"approvaleventcollection","className":"ApprovalEventCollection","qualifiedName":"@happyvertical/smrt-approvals:ApprovalEventCollection","collection":"approvalevents","filePath":"/home/runner/work/smrt/smrt/packages/approvals/src/collections/ApprovalEventCollection.ts","packageName":"@happyvertical/smrt-approvals","fields":{},"methods":{},"decoratorConfig":{"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":false,"tableName":"approval_events"},"extends":"SmrtCollection","extendsTypeArg":"ApprovalEvent","exportName":"ApprovalEventCollection","collectionExportName":"ApprovalEventCollectionCollection","schema":{"tableName":"approval_events","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);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"request_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"approval_requests","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT","engines":["postgres","sqlite"]}},"type":{"type":"TEXT","notNull":true,"unique":false},"actor_id":{"type":"TEXT","notNull":true,"unique":false},"actor_type":{"type":"TEXT","notNull":false,"unique":false},"reason":{"type":"TEXT","notNull":false,"unique":false},"subject_revision_hash":{"type":"TEXT","notNull":false,"unique":false},"sequence":{"type":"INTEGER","notNull":false,"unique":false},"vote_key":{"type":"TEXT","notNull":false,"unique":false},"occurred_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"self_approved":{"type":"BOOLEAN","notNull":false,"unique":false,"default":false},"self_approval_permission":{"type":"TEXT","notNull":false,"unique":false,"default":""}},"indexes":[{"name":"approval_events_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"approval_events_request_sequence_key","columns":["request_id","sequence"],"unique":true},{"name":"approval_events_request_vote_key","columns":["request_id","vote_key"],"unique":true},{"name":"approval_events_tenant_occurred_idx","columns":["tenant_id","occurred_at"]},{"name":"approval_events_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"998756b0"},"importPath":"@happyvertical/smrt-approvals"},"@happyvertical/smrt-approvals:ApprovalPolicyCollection":{"name":"approvalpolicycollection","className":"ApprovalPolicyCollection","qualifiedName":"@happyvertical/smrt-approvals:ApprovalPolicyCollection","collection":"approvalpolicies","filePath":"/home/runner/work/smrt/smrt/packages/approvals/src/collections/ApprovalPolicyCollection.ts","packageName":"@happyvertical/smrt-approvals","fields":{},"methods":{},"decoratorConfig":{"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":false,"tableName":"approval_policies"},"extends":"SmrtCollection","extendsTypeArg":"ApprovalPolicy","exportName":"ApprovalPolicyCollection","collectionExportName":"ApprovalPolicyCollectionCollection","schema":{"tableName":"approval_policies","ddl":"CREATE TABLE IF NOT EXISTS \\"approval_policies\\" (\\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 \\"kind\\" TEXT NOT NULL,\\n \\"required_approvals\\" INTEGER,\\n \\"ttl_ms\\" INTEGER,\\n \\"required_permission\\" TEXT,\\n \\"updated_by\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"kind":{"type":"TEXT","notNull":true,"unique":false},"required_approvals":{"type":"INTEGER","notNull":false,"unique":false},"ttl_ms":{"type":"INTEGER","notNull":false,"unique":false},"required_permission":{"type":"TEXT","notNull":false,"unique":false},"updated_by":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"approval_policies_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"approval_policies_tenant_kind_key","columns":["tenant_id","kind"],"unique":true},{"name":"approval_policies_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"80929cc8"},"importPath":"@happyvertical/smrt-approvals"},"@happyvertical/smrt-approvals:ApprovalRequestCollection":{"name":"approvalrequestcollection","className":"ApprovalRequestCollection","qualifiedName":"@happyvertical/smrt-approvals:ApprovalRequestCollection","collection":"approvalrequests","filePath":"/home/runner/work/smrt/smrt/packages/approvals/src/collections/ApprovalRequestCollection.ts","packageName":"@happyvertical/smrt-approvals","fields":{},"methods":{},"decoratorConfig":{"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":false,"tableName":"approval_requests"},"extends":"SmrtCollection","extendsTypeArg":"ApprovalRequest","exportName":"ApprovalRequestCollection","collectionExportName":"ApprovalRequestCollectionCollection","schema":{"tableName":"approval_requests","ddl":"CREATE TABLE IF NOT EXISTS \\"approval_requests\\" (\\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 \\"kind\\" TEXT NOT NULL,\\n \\"subject_type\\" TEXT NOT NULL,\\n \\"subject_id\\" TEXT NOT NULL,\\n \\"subject_revision_hash\\" TEXT NOT NULL,\\n \\"request_key\\" TEXT NOT NULL,\\n \\"requested_by\\" TEXT NOT NULL,\\n \\"requested_by_type\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"status\\" TEXT,\\n \\"required_approvals\\" INTEGER,\\n \\"approval_count\\" INTEGER,\\n \\"required_permissions\\" TEXT,\\n \\"version\\" INTEGER,\\n \\"expires_at\\" TIMESTAMP,\\n \\"decided_at\\" TIMESTAMP,\\n \\"consumed_at\\" TIMESTAMP,\\n \\"consumed_by\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"kind":{"type":"TEXT","notNull":true,"unique":false},"subject_type":{"type":"TEXT","notNull":true,"unique":false},"subject_id":{"type":"TEXT","notNull":true,"unique":false},"subject_revision_hash":{"type":"TEXT","notNull":true,"unique":false},"request_key":{"type":"TEXT","notNull":true,"unique":false},"requested_by":{"type":"TEXT","notNull":true,"unique":false},"requested_by_type":{"type":"TEXT","notNull":false,"unique":false},"summary":{"type":"TEXT","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false},"required_approvals":{"type":"INTEGER","notNull":false,"unique":false},"approval_count":{"type":"INTEGER","notNull":false,"unique":false},"required_permissions":{"type":"TEXT","notNull":false,"unique":false},"version":{"type":"INTEGER","notNull":false,"unique":false},"expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"decided_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"consumed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"consumed_by":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"approval_requests_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"approval_requests_tenant_request_key","columns":["tenant_id","request_key"],"unique":true},{"name":"approval_requests_status_expiry_idx","columns":["tenant_id","status","expires_at"]},{"name":"approval_requests_subject_idx","columns":["tenant_id","subject_type","subject_id"]},{"name":"approval_requests_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"a5800bdd"},"importPath":"@happyvertical/smrt-approvals"},"@happyvertical/smrt-approvals:ApprovalEvent":{"name":"approvalevent","className":"ApprovalEvent","qualifiedName":"@happyvertical/smrt-approvals:ApprovalEvent","collection":"approvalevents","description":"One immutable entry in an approval request's history.","filePath":"/home/runner/work/smrt/smrt/packages/approvals/src/models/ApprovalEvent.ts","packageName":"@happyvertical/smrt-approvals","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"requestId":{"type":"foreignKey","required":true,"related":"ApprovalRequest","_meta":{"required":true,"constraint":{"engines":["postgres","sqlite"]},"onDelete":"RESTRICT","onUpdate":"RESTRICT"}},"type":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"actorId":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"actorType":{"type":"text","required":false,"_meta":{"readonly":true},"enum":["human","agent","service"],"readonly":true},"reason":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"subjectRevisionHash":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"sequence":{"type":"integer","required":false,"_meta":{"readonly":true},"readonly":true},"voteKey":{"type":"text","required":false,"_meta":{"nullable":true,"readonly":true},"readonly":true},"occurredAt":{"type":"datetime","required":false,"_meta":{"readonly":true},"readonly":true},"selfApproved":{"type":"boolean","required":false,"default":false,"_meta":{"readonly":true,"default":false},"readonly":true},"selfApprovalPermission":{"type":"text","required":false,"default":"","_meta":{"readonly":true,"default":""},"readonly":true}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"approval_events","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":false,"indexes":[{"name":"approval_events_request_sequence_key","columns":["requestId","sequence"],"unique":true},{"name":"approval_events_request_vote_key","columns":["requestId","voteKey"],"unique":true},{"name":"approval_events_tenant_occurred_idx","columns":["tenantId","occurredAt"]}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"ApprovalEvent","collectionExportName":"ApprovalEventCollection","validationRules":[{"field":"requestId","rule":"required","fieldType":"foreignKey"},{"field":"type","rule":"required","fieldType":"text"},{"field":"actorId","rule":"required","fieldType":"text"}],"schema":{"tableName":"approval_events","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);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"request_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"approval_requests","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT","engines":["postgres","sqlite"]}},"type":{"type":"TEXT","notNull":true,"unique":false},"actor_id":{"type":"TEXT","notNull":true,"unique":false},"actor_type":{"type":"TEXT","notNull":false,"unique":false},"reason":{"type":"TEXT","notNull":false,"unique":false},"subject_revision_hash":{"type":"TEXT","notNull":false,"unique":false},"sequence":{"type":"INTEGER","notNull":false,"unique":false},"vote_key":{"type":"TEXT","notNull":false,"unique":false},"occurred_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"self_approved":{"type":"BOOLEAN","notNull":false,"unique":false,"default":false},"self_approval_permission":{"type":"TEXT","notNull":false,"unique":false,"default":""}},"indexes":[{"name":"approval_events_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"approval_events_request_sequence_key","columns":["request_id","sequence"],"unique":true},{"name":"approval_events_request_vote_key","columns":["request_id","vote_key"],"unique":true},{"name":"approval_events_tenant_occurred_idx","columns":["tenant_id","occurred_at"]},{"name":"approval_events_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"998756b0"},"importPath":"@happyvertical/smrt-approvals"},"@happyvertical/smrt-approvals:ApprovalPolicy":{"name":"approvalpolicy","className":"ApprovalPolicy","qualifiedName":"@happyvertical/smrt-approvals:ApprovalPolicy","collection":"approvalpolicies","description":"Tenant-level tightening of an approval kind. One row per tenant and kind.","filePath":"/home/runner/work/smrt/smrt/packages/approvals/src/models/ApprovalPolicy.ts","packageName":"@happyvertical/smrt-approvals","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"kind":{"type":"text","required":true,"_meta":{"required":true}},"requiredApprovals":{"type":"integer","required":false,"_meta":{}},"ttlMs":{"type":"integer","required":false,"_meta":{}},"requiredPermission":{"type":"text","required":false,"_meta":{}},"updatedBy":{"type":"text","required":false,"_meta":{}}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"approval_policies","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":false,"indexes":[{"name":"approval_policies_tenant_kind_key","columns":["tenantId","kind"],"unique":true}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"ApprovalPolicy","collectionExportName":"ApprovalPolicyCollection","validationRules":[{"field":"kind","rule":"required","fieldType":"text"}],"schema":{"tableName":"approval_policies","ddl":"CREATE TABLE IF NOT EXISTS \\"approval_policies\\" (\\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 \\"kind\\" TEXT NOT NULL,\\n \\"required_approvals\\" INTEGER,\\n \\"ttl_ms\\" INTEGER,\\n \\"required_permission\\" TEXT,\\n \\"updated_by\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"kind":{"type":"TEXT","notNull":true,"unique":false},"required_approvals":{"type":"INTEGER","notNull":false,"unique":false},"ttl_ms":{"type":"INTEGER","notNull":false,"unique":false},"required_permission":{"type":"TEXT","notNull":false,"unique":false},"updated_by":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"approval_policies_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"approval_policies_tenant_kind_key","columns":["tenant_id","kind"],"unique":true},{"name":"approval_policies_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"80929cc8"},"importPath":"@happyvertical/smrt-approvals"},"@happyvertical/smrt-approvals:ApprovalRequest":{"name":"approvalrequest","className":"ApprovalRequest","qualifiedName":"@happyvertical/smrt-approvals:ApprovalRequest","collection":"approvalrequests","description":"A request for approval of one subject revision.","filePath":"/home/runner/work/smrt/smrt/packages/approvals/src/models/ApprovalRequest.ts","packageName":"@happyvertical/smrt-approvals","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"kind":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"subjectType":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"subjectId":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"subjectRevisionHash":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"requestKey":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"requestedBy":{"type":"text","required":true,"_meta":{"required":true,"readonly":true},"readonly":true},"requestedByType":{"type":"text","required":false,"_meta":{"readonly":true},"enum":["human","agent","service"],"readonly":true},"summary":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"status":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"requiredApprovals":{"type":"integer","required":false,"_meta":{"readonly":true},"readonly":true},"approvalCount":{"type":"integer","required":false,"_meta":{"readonly":true},"readonly":true},"requiredPermissions":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true},"version":{"type":"integer","required":false,"_meta":{"readonly":true},"readonly":true},"expiresAt":{"type":"datetime","required":false,"_meta":{"readonly":true},"readonly":true},"decidedAt":{"type":"datetime","required":false,"_meta":{"readonly":true},"readonly":true},"consumedAt":{"type":"datetime","required":false,"_meta":{"readonly":true},"readonly":true},"consumedBy":{"type":"text","required":false,"_meta":{"readonly":true},"readonly":true}},"methods":{"getRequiredPermissions":{"name":"getRequiredPermissions","async":false,"parameters":[],"returnType":"string[]","isStatic":false,"isPublic":true},"setRequiredPermissions":{"name":"setRequiredPermissions","async":false,"parameters":[{"name":"slugs","type":"any","optional":false,"typeUnresolved":true}],"returnType":"void","isStatic":false,"isPublic":true},"expiresAtMs":{"name":"expiresAtMs","async":false,"parameters":[],"returnType":"number","isStatic":false,"isPublic":true},"isConsumed":{"name":"isConsumed","async":false,"parameters":[],"returnType":"boolean","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"approval_requests","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":false,"indexes":[{"name":"approval_requests_tenant_request_key","columns":["tenantId","requestKey"],"unique":true},{"name":"approval_requests_status_expiry_idx","columns":["tenantId","status","expiresAt"]},{"name":"approval_requests_subject_idx","columns":["tenantId","subjectType","subjectId"]}],"tenantScoped":{"mode":"required"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"ApprovalRequest","collectionExportName":"ApprovalRequestCollection","validationRules":[{"field":"kind","rule":"required","fieldType":"text"},{"field":"subjectType","rule":"required","fieldType":"text"},{"field":"subjectId","rule":"required","fieldType":"text"},{"field":"subjectRevisionHash","rule":"required","fieldType":"text"},{"field":"requestKey","rule":"required","fieldType":"text"},{"field":"requestedBy","rule":"required","fieldType":"text"}],"schema":{"tableName":"approval_requests","ddl":"CREATE TABLE IF NOT EXISTS \\"approval_requests\\" (\\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 \\"kind\\" TEXT NOT NULL,\\n \\"subject_type\\" TEXT NOT NULL,\\n \\"subject_id\\" TEXT NOT NULL,\\n \\"subject_revision_hash\\" TEXT NOT NULL,\\n \\"request_key\\" TEXT NOT NULL,\\n \\"requested_by\\" TEXT NOT NULL,\\n \\"requested_by_type\\" TEXT,\\n \\"summary\\" TEXT,\\n \\"status\\" TEXT,\\n \\"required_approvals\\" INTEGER,\\n \\"approval_count\\" INTEGER,\\n \\"required_permissions\\" TEXT,\\n \\"version\\" INTEGER,\\n \\"expires_at\\" TIMESTAMP,\\n \\"decided_at\\" TIMESTAMP,\\n \\"consumed_at\\" TIMESTAMP,\\n \\"consumed_by\\" TEXT\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"kind":{"type":"TEXT","notNull":true,"unique":false},"subject_type":{"type":"TEXT","notNull":true,"unique":false},"subject_id":{"type":"TEXT","notNull":true,"unique":false},"subject_revision_hash":{"type":"TEXT","notNull":true,"unique":false},"request_key":{"type":"TEXT","notNull":true,"unique":false},"requested_by":{"type":"TEXT","notNull":true,"unique":false},"requested_by_type":{"type":"TEXT","notNull":false,"unique":false},"summary":{"type":"TEXT","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false},"required_approvals":{"type":"INTEGER","notNull":false,"unique":false},"approval_count":{"type":"INTEGER","notNull":false,"unique":false},"required_permissions":{"type":"TEXT","notNull":false,"unique":false},"version":{"type":"INTEGER","notNull":false,"unique":false},"expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"decided_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"consumed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"consumed_by":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"approval_requests_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true},{"name":"approval_requests_tenant_request_key","columns":["tenant_id","request_key"],"unique":true},{"name":"approval_requests_status_expiry_idx","columns":["tenant_id","status","expires_at"]},{"name":"approval_requests_subject_idx","columns":["tenant_id","subject_type","subject_id"]},{"name":"approval_requests_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"a5800bdd"},"importPath":"@happyvertical/smrt-approvals"}},"moduleType":"smrt","smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"],"browser":{"status":"server-only","issues":["#3617","#3624","#3626"],"via":["@happyvertical/smrt-profiles","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"]},"stampsConstructors":true}`));
7
7
  //#endregion
8
8
  //#region src/permissions.ts
9
9
  var CANCEL_ANY_APPROVAL_PERMISSION = "approvals.cancel-any";
@@ -295,6 +295,8 @@ var ApprovalEvent = class extends SmrtObject {
295
295
  sequence = 0;
296
296
  voteKey = null;
297
297
  occurredAt = null;
298
+ selfApproved = false;
299
+ selfApprovalPermission = "";
298
300
  constructor(options = {}) {
299
301
  super(options);
300
302
  adoptServiceWrite(this, options);
@@ -304,6 +306,8 @@ var ApprovalEvent = class extends SmrtObject {
304
306
  if (options.actorId !== void 0) this.actorId = options.actorId;
305
307
  if (options.actorType !== void 0) this.actorType = options.actorType;
306
308
  if (options.reason !== void 0) this.reason = options.reason;
309
+ if (options.selfApproved !== void 0) this.selfApproved = options.selfApproved;
310
+ if (options.selfApprovalPermission !== void 0) this.selfApprovalPermission = options.selfApprovalPermission;
307
311
  if (options.subjectRevisionHash !== void 0) this.subjectRevisionHash = options.subjectRevisionHash;
308
312
  if (options.sequence !== void 0) this.sequence = options.sequence;
309
313
  if (options.voteKey !== void 0) this.voteKey = options.voteKey;
@@ -390,6 +394,16 @@ __decorateClass$4([field({
390
394
  type: "datetime",
391
395
  readonly: true
392
396
  })], ApprovalEvent.prototype, "occurredAt", 2);
397
+ __decorateClass$4([field({
398
+ type: "boolean",
399
+ readonly: true,
400
+ default: false
401
+ })], ApprovalEvent.prototype, "selfApproved", 2);
402
+ __decorateClass$4([field({
403
+ type: "text",
404
+ readonly: true,
405
+ default: ""
406
+ })], ApprovalEvent.prototype, "selfApprovalPermission", 2);
393
407
  ApprovalEvent = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
394
408
  tableName: "approval_events",
395
409
  api: { include: ["list", "get"] },
@@ -460,6 +474,7 @@ function defineApprovalKind(input) {
460
474
  if (!KIND_KEY.test(input.key ?? "")) throw new ApprovalError("APPROVAL_INVALID", `Approval kind key must be dotted lower case such as 'social.post.publish', got '${String(input.key)}'.`);
461
475
  if (!QUALIFIED_CLASS.test(input.subject ?? "")) throw new ApprovalError("APPROVAL_INVALID", `Approval kind '${input.key}' subject must be a qualified class name such as '@happyvertical/smrt-social:SocialPost', got '${String(input.subject)}'.`);
462
476
  if (!isPermissionSlug(input.permission)) throw new ApprovalError("APPROVAL_INVALID", `Approval kind '${input.key}' permission must be a slug such as 'social.approve-post', got '${String(input.permission)}'.`);
477
+ if (input.selfApprovalPermission !== void 0 && !isPermissionSlug(input.selfApprovalPermission)) throw new ApprovalError("APPROVAL_INVALID", "selfApprovalPermission must be a permission slug.");
463
478
  const requiredApprovals = input.defaults?.requiredApprovals ?? 1;
464
479
  const ttlMs = input.defaults?.ttlMs ?? 6048e5;
465
480
  assertQuorum(requiredApprovals, `Approval kind '${input.key}' requiredApprovals`);
@@ -468,6 +483,7 @@ function defineApprovalKind(input) {
468
483
  key: input.key,
469
484
  subject: input.subject,
470
485
  permission: input.permission,
486
+ selfApprovalPermission: input.selfApprovalPermission,
471
487
  defaults: Object.freeze({
472
488
  requiredApprovals,
473
489
  ttlMs
@@ -475,7 +491,7 @@ function defineApprovalKind(input) {
475
491
  });
476
492
  const existing = kinds.get(kind.key);
477
493
  if (existing) {
478
- if (existing.subject === kind.subject && existing.permission === kind.permission && existing.defaults.requiredApprovals === requiredApprovals && existing.defaults.ttlMs === ttlMs) return existing;
494
+ if (existing.subject === kind.subject && existing.permission === kind.permission && existing.selfApprovalPermission === kind.selfApprovalPermission && existing.defaults.requiredApprovals === requiredApprovals && existing.defaults.ttlMs === ttlMs) return existing;
479
495
  throw new ApprovalError("APPROVAL_KIND_CONFLICT", `Approval kind '${kind.key}' is already defined with different rules.`);
480
496
  }
481
497
  kinds.set(kind.key, kind);
@@ -686,7 +702,7 @@ function assertPrincipal(principal) {
686
702
  if (!APPROVAL_PRINCIPAL_TYPES.includes(principal.type)) throw new ApprovalError("APPROVAL_INVALID", `Approval principal type must be one of ${APPROVAL_PRINCIPAL_TYPES.join(", ")}.`);
687
703
  }
688
704
  function countedApprovers(approvals, request) {
689
- return new Set(approvals.filter((event) => event.type === "approved" && event.actorType === "human" && event.actorId !== request.requestedBy).map((event) => event.actorId));
705
+ return new Set(approvals.filter((event) => event.type === "approved" && event.actorType === "human" && (event.actorId !== request.requestedBy || event.selfApproved === true && isPermissionSlug(event.selfApprovalPermission))).map((event) => event.actorId));
690
706
  }
691
707
  function rowVersion(rows) {
692
708
  const row = rows?.[0];
@@ -817,7 +833,8 @@ var ApprovalService = class ApprovalService {
817
833
  }
818
834
  /**
819
835
  * Record a decision. Only a `human` principal of the request's tenant who
820
- * is not the requester and holds every required permission may decide,
836
+ * holds every required permission may decide (requesters also need the
837
+ * kind's explicit selfApprovalPermission),
821
838
  * once, while the request is pending and unexpired. `approve` counts
822
839
  * toward the quorum of distinct approvers; `reject` and `request_changes`
823
840
  * end the request.
@@ -831,7 +848,9 @@ var ApprovalService = class ApprovalService {
831
848
  const tenantId = principal.tenantId;
832
849
  return await this.transition(principal, requestId, async (tx, request, now) => {
833
850
  if (principal.type !== "human") return refusal("not_human", `a ${principal.type} principal cannot decide`, request.id);
834
- if (principal.id === request.requestedBy) return refusal("self_approval", "the requester cannot decide their own request", request.id);
851
+ const selfApproved = principal.id === request.requestedBy;
852
+ const selfApprovalPermission = selfApproved ? requireApprovalKind(request.kind).selfApprovalPermission : void 0;
853
+ if (selfApproved && (!selfApprovalPermission || !principal.can(selfApprovalPermission))) return refusal("self_approval", "the requester lacks the kind self-approval allowance", request.id);
835
854
  const missing = (await this.requiredPermissionsFor(tx, request)).filter((slug) => !principal.can(slug));
836
855
  if (missing.length > 0) return refusal("forbidden", `missing permission: ${missing.join(", ")}`, request.id);
837
856
  if (request.status !== "pending") return refusal("not_pending", `request is ${request.status}`, request.id);
@@ -894,6 +913,8 @@ var ApprovalService = class ApprovalService {
894
913
  subjectRevisionHash: request.subjectRevisionHash,
895
914
  sequence: next,
896
915
  voteKey: principal.id,
916
+ selfApproved,
917
+ selfApprovalPermission,
897
918
  now
898
919
  });
899
920
  await this.recordChange(tx, String(request.id), tenantId);
@@ -1165,7 +1186,7 @@ var ApprovalService = class ApprovalService {
1165
1186
  /** Pin the work to the principal's tenant; refuse a conflicting context. */
1166
1187
  async inTenant(principal, fn) {
1167
1188
  if (this.contextMismatch(principal)) throw new ApprovalError("APPROVAL_FORBIDDEN", `The principal's tenant does not match the active tenant context.`);
1168
- return await withTenant({ tenantId: principal.tenantId }, fn);
1189
+ return await withEmbeddedWriteQueue(this.db, isEmbeddedDatabase(this.db), () => withTenant({ tenantId: principal.tenantId }, fn));
1169
1190
  }
1170
1191
  /**
1171
1192
  * Run `fn` in a transaction through core's embedded write queue.
@@ -1235,6 +1256,8 @@ var ApprovalService = class ApprovalService {
1235
1256
  subjectRevisionHash: input.subjectRevisionHash,
1236
1257
  sequence: input.sequence,
1237
1258
  voteKey: input.voteKey ?? null,
1259
+ selfApproved: input.selfApproved ?? false,
1260
+ selfApprovalPermission: input.selfApprovalPermission ?? "",
1238
1261
  occurredAt: input.now,
1239
1262
  ...serviceWriteOption()
1240
1263
  });
@@ -1243,8 +1266,9 @@ var ApprovalService = class ApprovalService {
1243
1266
  * Defence in depth for `consume`: the request row says `approved`, so the
1244
1267
  * ledger must agree. It needs the `created` event at sequence 1, no
1245
1268
  * terminal event, and approvals from at least `requiredApprovals` distinct
1246
- * human actors other than the requester. Catches a status written around
1247
- * the service (raw SQL); model-layer inserts are already capability-gated.
1269
+ * human actors, with explicit audit markers for self-decisions. Catches a
1270
+ * status written around the service (raw SQL); model-layer inserts are
1271
+ * already capability-gated.
1248
1272
  */
1249
1273
  async ledgerBacksApproval(tx, request) {
1250
1274
  const ledger = await (await this.txCollection(ApprovalEventCollection, tx)).list({