@voltro/plugin-gm 0.0.0-stage → 0.88.0

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/dist/rpc.d.ts ADDED
@@ -0,0 +1,103 @@
1
+ import { ActionProcedureDescriptor } from '@voltro/protocol';
2
+ import { PluginRpcClientDescriptor } from '@voltro/protocol';
3
+ import { QueryProcedureDescriptor } from '@voltro/protocol';
4
+ import { Schema } from 'effect';
5
+ import { YieldableError } from 'effect/Cause';
6
+
7
+ export declare const actionsDescriptor: QueryProcedureDescriptor<"gm.actions", Schema.Struct<{}>, Schema.$Array<Schema.Struct<{
8
+ readonly id: Schema.String;
9
+ readonly title: Schema.String;
10
+ readonly risk: Schema.Literals<readonly ["routine", "sensitive"]>;
11
+ readonly input: Schema.Unknown;
12
+ }>>, typeof GmError>;
13
+
14
+ export declare const approveDescriptor: ActionProcedureDescriptor<"gm.approve", Schema.Struct<{
15
+ readonly id: Schema.NonEmptyString;
16
+ }>, Schema.Struct<{
17
+ readonly id: Schema.String;
18
+ readonly actionId: Schema.String;
19
+ readonly state: Schema.Literals<readonly ["pending", "approved", "executed", "rejected"]>;
20
+ readonly targetId: Schema.NullOr<Schema.String>;
21
+ readonly input: Schema.Unknown;
22
+ readonly output: Schema.NullOr<Schema.Unknown>;
23
+ readonly reason: Schema.String;
24
+ readonly ticket: Schema.NullOr<Schema.String>;
25
+ readonly requesterId: Schema.NullOr<Schema.String>;
26
+ readonly approverId: Schema.NullOr<Schema.String>;
27
+ readonly createdAt: Schema.Number;
28
+ readonly decidedAt: Schema.NullOr<Schema.Number>;
29
+ readonly executedAt: Schema.NullOr<Schema.Number>;
30
+ }>, typeof GmError>;
31
+
32
+ export declare const BASE_NAME = "@voltro/plugin-gm";
33
+
34
+ export declare const executeDescriptor: ActionProcedureDescriptor<"gm.execute", Schema.Struct<{
35
+ readonly requestId: Schema.NonEmptyString;
36
+ readonly actionId: Schema.NonEmptyString;
37
+ readonly input: Schema.Unknown;
38
+ readonly reason: Schema.NonEmptyString;
39
+ readonly ticket: Schema.optional<Schema.NonEmptyString>;
40
+ }>, Schema.Struct<{
41
+ readonly id: Schema.String;
42
+ readonly actionId: Schema.String;
43
+ readonly state: Schema.Literals<readonly ["pending", "approved", "executed", "rejected"]>;
44
+ readonly targetId: Schema.NullOr<Schema.String>;
45
+ readonly input: Schema.Unknown;
46
+ readonly output: Schema.NullOr<Schema.Unknown>;
47
+ readonly reason: Schema.String;
48
+ readonly ticket: Schema.NullOr<Schema.String>;
49
+ readonly requesterId: Schema.NullOr<Schema.String>;
50
+ readonly approverId: Schema.NullOr<Schema.String>;
51
+ readonly createdAt: Schema.Number;
52
+ readonly decidedAt: Schema.NullOr<Schema.Number>;
53
+ readonly executedAt: Schema.NullOr<Schema.Number>;
54
+ }>, typeof GmError>;
55
+
56
+ /** `action_failed` carries the owning plugin's own error code (`insufficient_funds`, `conflict`, …) in `actionCode`. */
57
+ declare class GmError extends GmError_base {
58
+ }
59
+
60
+ declare const GmError_base: Schema.Class<GmError, Schema.TaggedStruct<"GmError", {
61
+ readonly code: Schema.Literals<readonly ["identity_required", "invalid_input", "forbidden", "not_found", "conflict", "unavailable", "action_failed"]>;
62
+ readonly actionCode: Schema.optional<Schema.String>;
63
+ }>, YieldableError>;
64
+
65
+ export declare const gmRpcClientImports: ReadonlyArray<PluginRpcClientDescriptor>;
66
+
67
+ export declare const rejectDescriptor: ActionProcedureDescriptor<"gm.reject", Schema.Struct<{
68
+ readonly id: Schema.NonEmptyString;
69
+ }>, Schema.Struct<{
70
+ readonly id: Schema.String;
71
+ readonly actionId: Schema.String;
72
+ readonly state: Schema.Literals<readonly ["pending", "approved", "executed", "rejected"]>;
73
+ readonly targetId: Schema.NullOr<Schema.String>;
74
+ readonly input: Schema.Unknown;
75
+ readonly output: Schema.NullOr<Schema.Unknown>;
76
+ readonly reason: Schema.String;
77
+ readonly ticket: Schema.NullOr<Schema.String>;
78
+ readonly requesterId: Schema.NullOr<Schema.String>;
79
+ readonly approverId: Schema.NullOr<Schema.String>;
80
+ readonly createdAt: Schema.Number;
81
+ readonly decidedAt: Schema.NullOr<Schema.Number>;
82
+ readonly executedAt: Schema.NullOr<Schema.Number>;
83
+ }>, typeof GmError>;
84
+
85
+ export declare const requestsDescriptor: QueryProcedureDescriptor<"gm.requests", Schema.Struct<{
86
+ readonly state: Schema.optional<Schema.Literals<readonly ["pending", "approved", "executed", "rejected"]>>;
87
+ }>, Schema.$Array<Schema.Struct<{
88
+ readonly id: Schema.String;
89
+ readonly actionId: Schema.String;
90
+ readonly state: Schema.Literals<readonly ["pending", "approved", "executed", "rejected"]>;
91
+ readonly targetId: Schema.NullOr<Schema.String>;
92
+ readonly input: Schema.Unknown;
93
+ readonly output: Schema.NullOr<Schema.Unknown>;
94
+ readonly reason: Schema.String;
95
+ readonly ticket: Schema.NullOr<Schema.String>;
96
+ readonly requesterId: Schema.NullOr<Schema.String>;
97
+ readonly approverId: Schema.NullOr<Schema.String>;
98
+ readonly createdAt: Schema.Number;
99
+ readonly decidedAt: Schema.NullOr<Schema.Number>;
100
+ readonly executedAt: Schema.NullOr<Schema.Number>;
101
+ }>>, typeof GmError>;
102
+
103
+ export { }
package/dist/rpc.js ADDED
@@ -0,0 +1,62 @@
1
+ import { GmError as e } from "./errors.js";
2
+ import { GmActionInfoSchema as t, GmRequestSchema as n, REQUESTS_TABLE as r, REQUEST_STATES as i } from "./contract.js";
3
+ import { Schema as a } from "effect";
4
+ import { defineAction as o, defineQuery as s } from "@voltro/protocol";
5
+ // src/rpc.ts
6
+ var c = "@voltro/plugin-gm", l = "Staff user or service account in an exact tenant. Each action requires its gm scope (or the configured authorize policy); approvals require the approver scope and a different person.", u = a.NonEmptyString, d = s({
7
+ name: "gm.actions",
8
+ input: a.Struct({}),
9
+ output: a.Array(t),
10
+ error: e,
11
+ openAccess: l
12
+ }), f = s({
13
+ name: "gm.requests",
14
+ input: a.Struct({ state: a.optional(a.Literals(i)) }),
15
+ output: a.Array(n),
16
+ source: [r],
17
+ error: e,
18
+ openAccess: l
19
+ }), p = o({
20
+ name: "gm.execute",
21
+ input: a.Struct({
22
+ requestId: u,
23
+ actionId: u,
24
+ input: a.Unknown,
25
+ reason: u,
26
+ ticket: a.optional(u)
27
+ }),
28
+ output: n,
29
+ error: e,
30
+ openAccess: l
31
+ }), m = o({
32
+ name: "gm.approve",
33
+ input: a.Struct({ id: u }),
34
+ output: n,
35
+ error: e,
36
+ openAccess: l
37
+ }), h = o({
38
+ name: "gm.reject",
39
+ input: a.Struct({ id: u }),
40
+ output: n,
41
+ error: e,
42
+ openAccess: l
43
+ }), g = [...[
44
+ "execute",
45
+ "approve",
46
+ "reject"
47
+ ].map((e) => ({
48
+ tag: "gm." + e,
49
+ kind: "action",
50
+ import: {
51
+ module: "@voltro/plugin-gm/rpc",
52
+ name: e + "Descriptor"
53
+ }
54
+ })), ...["actions", "requests"].map((e) => ({
55
+ tag: "gm." + e,
56
+ kind: "query",
57
+ import: {
58
+ module: "@voltro/plugin-gm/rpc",
59
+ name: e + "Descriptor"
60
+ }
61
+ }))];
62
+ export { c as BASE_NAME, d as actionsDescriptor, m as approveDescriptor, p as executeDescriptor, g as gmRpcClientImports, h as rejectDescriptor, f as requestsDescriptor };
@@ -0,0 +1,257 @@
1
+ import { GmError as e, gmError as t } from "./errors.js";
2
+ import { ACTION_RISKS as n, GmRequestSchema as r, REQUEST_STATES as i } from "./contract.js";
3
+ import { gmRequestsTable as a } from "./entities.js";
4
+ import { Effect as o, Result as s, Schema as c, SchemaAST as l } from "effect";
5
+ import { and as u, eq as d, or as f, queryFor as p } from "@voltro/database";
6
+ import { createHash as m } from "node:crypto";
7
+ import { effectiveScopes as h, jsonSchemaOf as g } from "@voltro/protocol";
8
+ // src/service.ts
9
+ var _ = /^[a-z][A-Za-z0-9]*(?:\.[a-z][A-Za-z0-9]*)*$/, v = /^[A-Za-z0-9][A-Za-z0-9:._*-]{0,127}$/, y = (e, t, n) => {
10
+ let r = e === void 0 ? t : e;
11
+ if (typeof r != "number" || !Number.isSafeInteger(r) || r < 1 || r > n) throw TypeError("Invalid GM option");
12
+ return r;
13
+ }, b = (e, t) => e.scopes.some((e) => e === "admin:full" || e === "gm:*" || e === "gm:" + t.id || e.startsWith("gm:") && e.endsWith(".*") && e.length > 5 && t.id.startsWith(e.slice(3, -1))), x = ({ action: e }) => e.risk === "sensitive", S = (e) => {
14
+ if (!e || !Array.isArray(e.actions)) throw TypeError("gmPlugin requires an actions list");
15
+ let t = /* @__PURE__ */ new Map();
16
+ for (let r of e.actions) {
17
+ if (!r || typeof r.id != "string" || !_.test(r.id) || r.id.length > 128) throw TypeError("Invalid GM action id");
18
+ if (t.has(r.id)) throw TypeError("Duplicate GM action " + r.id);
19
+ if (typeof r.title != "string" || !r.title.trim() || !n.includes(r.risk) || typeof r.execute != "function" || !c.isSchema(r.input) || !c.isSchema(r.output) || r.target !== void 0 && typeof r.target != "function") throw TypeError("Invalid GM action " + r.id);
20
+ let e = c.toEncoded(r.input);
21
+ if (!l.isObjects(e.ast)) throw TypeError("GM action " + r.id + " input must encode to an object");
22
+ let i = g(e);
23
+ t.set(r.id, Object.freeze({
24
+ action: r,
25
+ summary: Object.freeze({
26
+ id: r.id,
27
+ title: r.title,
28
+ risk: r.risk
29
+ }),
30
+ schema: i
31
+ }));
32
+ }
33
+ let r = e.approval === void 0 ? null : {
34
+ when: e.approval.when ?? x,
35
+ approverScope: e.approval.approverScope ?? "gm:approve"
36
+ };
37
+ if (r && (typeof r.when != "function" || !v.test(r.approverScope))) throw TypeError("Invalid GM approval policy");
38
+ if (e.authorize !== void 0 && typeof e.authorize != "function") throw TypeError("Invalid GM authorize policy");
39
+ return Object.freeze({
40
+ actions: t,
41
+ authorize: e.authorize ?? b,
42
+ approval: r,
43
+ now: e.now ?? Date.now,
44
+ pageSize: y(e.pageSize, 100, 1e3),
45
+ maxExportRows: y(e.maxExportRows, 1e4, 1e5)
46
+ });
47
+ }, C = (e) => m("sha256").update(JSON.stringify(e)).digest("hex"), w = (n) => n instanceof e ? n : t("unavailable"), T = (e) => o.tryPromise({
48
+ try: e,
49
+ catch: w
50
+ }), E = (e, n = 256) => {
51
+ if (typeof e != "string" || !e.trim() || e.length > n || e.includes("\0")) throw t("invalid_input");
52
+ return e;
53
+ }, D = (e, n) => {
54
+ if (typeof e != "string" || !e.trim() || e.length > n || e.includes("\0")) throw t("invalid_input");
55
+ return e;
56
+ }, O = (e) => {
57
+ let n = e instanceof Date ? e.getTime() : typeof e == "string" ? Date.parse(e) : typeof e == "number" ? e : NaN;
58
+ if (!Number.isSafeInteger(n) || n < 0) throw t("unavailable");
59
+ return n;
60
+ }, k = (e) => e == null ? null : O(e), A = (e) => typeof e == "string" ? e : null, j = (e) => typeof e == "object" && !!e && !Array.isArray(e), M = (e) => typeof e == "string" && /^[A-Z][A-Za-z0-9]{0,62}$/.test(e) ? e.replace(/(?<!^)([A-Z])/g, "_$1").toLowerCase() : void 0, N = (e) => {
61
+ let n = typeof e == "object" && e ? e : {}, r = typeof n.code == "string" && /^[a-z][a-z0-9_]{0,63}$/.test(n.code) ? n.code : M(n._tag);
62
+ return t("action_failed", r);
63
+ }, P = (e) => {
64
+ if (e.type === "anonymous" || e.tenantId === null) throw t("identity_required");
65
+ if (e.type !== "user" && e.type !== "serviceAccount" || e.type === "user" && e.kind === "guest") throw t("forbidden");
66
+ return Object.freeze({
67
+ type: e.type,
68
+ id: E(e.id),
69
+ tenantId: E(e.tenantId),
70
+ scopes: Object.freeze([...h(e) ?? []].filter((e) => typeof e == "string"))
71
+ });
72
+ }, F = (e) => {
73
+ let t = e.output, n = e.input;
74
+ return c.decodeUnknownSync(r)({
75
+ id: e.id,
76
+ actionId: e.actionId,
77
+ state: e.state,
78
+ targetId: A(e.targetId),
79
+ input: j(n) ? n : {},
80
+ output: j(t) && "value" in t ? t.value : null,
81
+ reason: e.reason ?? "",
82
+ ticket: A(e.ticket),
83
+ requesterId: A(e.requesterId),
84
+ approverId: A(e.approverId),
85
+ createdAt: O(e.createdAt),
86
+ decidedAt: k(e.decidedAt),
87
+ executedAt: k(e.executedAt)
88
+ });
89
+ }, I = (e, n) => {
90
+ let r = a.tableName, l = () => {
91
+ let e = n.now();
92
+ if (!Number.isSafeInteger(e) || e < 0) throw t("unavailable");
93
+ return e;
94
+ }, m = (e) => {
95
+ try {
96
+ return e() === !0;
97
+ } catch {
98
+ throw t("unavailable");
99
+ }
100
+ }, h = (e, t) => m(() => n.authorize(e, t.summary)), g = (e) => n.approval !== null && e.scopes.some((e) => e === "admin:full" || e === n.approval.approverScope), _ = (e, r = "not_found") => {
101
+ let i = typeof e == "string" ? n.actions.get(e) : void 0;
102
+ if (!i) throw t(r);
103
+ return i;
104
+ }, v = (t, n) => e.query(p(a).where(t).orderBy("createdAt", "desc").orderBy("id", "asc").limit(n).descriptor), y = async (n, r) => {
105
+ let i = (await e.query(p(a).where(d("id", E(r))).limit(1).descriptor))[0];
106
+ if (!i || i.tenantId !== n) throw t("not_found");
107
+ return i;
108
+ }, b = async (n) => {
109
+ let i = _(n.actionId, "unavailable"), a = c.decodeUnknownResult(i.action.input)(n.input);
110
+ if (s.isFailure(a) || typeof n.requesterId != "string" || typeof n.id != "string") throw t("unavailable");
111
+ let f = Object.freeze({
112
+ tenantId: String(n.tenantId),
113
+ requestId: "gm-" + n.id.slice(6),
114
+ requesterId: n.requesterId,
115
+ approverId: A(n.approverId),
116
+ reason: String(n.reason),
117
+ ticket: A(n.ticket)
118
+ }), p = await o.runPromise(o.result(o.suspend(() => i.action.execute(a.success, f)).pipe(o.mapError(N), o.catchDefect(() => o.fail(t("unavailable"))))));
119
+ if (s.isFailure(p)) throw p.failure;
120
+ let m = c.encodeResult(i.action.output)(p.success);
121
+ if (s.isFailure(m)) throw t("unavailable");
122
+ return await e.updateMany(r, {
123
+ state: "executed",
124
+ output: { value: m.success },
125
+ executedAt: new Date(l())
126
+ }, { where: u(d("id", n.id), d("state", "approved")) }), F(await y(String(n.tenantId), n.id));
127
+ }, x = {
128
+ actions: (e) => T(async () => {
129
+ let t = P(e);
130
+ return [...n.actions.values()].filter((e) => h(t, e)).map((e) => ({
131
+ ...e.summary,
132
+ input: e.schema
133
+ }));
134
+ }),
135
+ requests: (e, r) => T(async () => {
136
+ let a = P(e), o = r.state;
137
+ if (o !== void 0 && !i.includes(o)) throw t("invalid_input");
138
+ return (await v(o === void 0 ? d("tenantId", a.tenantId) : u(d("tenantId", a.tenantId), d("state", o)), n.pageSize)).filter((e) => e.tenantId === a.tenantId && (e.requesterId === a.id || g(a) || n.actions.has(String(e.actionId)) && h(a, _(e.actionId)))).map(F);
139
+ }),
140
+ execute: (i, a) => T(async () => {
141
+ let o = P(i), u = structuredClone(a);
142
+ if (!j(u)) throw t("invalid_input");
143
+ let d = E(u.requestId, 128), f = _(u.actionId);
144
+ if (!h(o, f)) throw t("forbidden");
145
+ let p = D(u.reason, 2e3), g = u.ticket === void 0 ? null : E(u.ticket, 256), v = c.decodeUnknownResult(f.action.input)(u.input);
146
+ if (s.isFailure(v)) throw t("invalid_input");
147
+ let x = c.encodeResult(f.action.input)(v.success);
148
+ if (s.isFailure(x) || !j(x.success)) throw t("invalid_input");
149
+ let S = JSON.parse(JSON.stringify(x.success)), w = f.action.target === void 0 ? null : (() => {
150
+ try {
151
+ let e = f.action.target(v.success);
152
+ return e === null ? null : E(e);
153
+ } catch {
154
+ throw t("invalid_input");
155
+ }
156
+ })(), T = n.approval !== null && m(() => n.approval.when({
157
+ action: f.summary,
158
+ input: v.success,
159
+ actor: o
160
+ })), O = "gmreq_" + C([
161
+ o.tenantId,
162
+ o.id,
163
+ d
164
+ ]).slice(0, 54), k = C([
165
+ f.action.id,
166
+ S,
167
+ p,
168
+ g
169
+ ]);
170
+ await e.insertIgnore(r, {
171
+ id: O,
172
+ tenantId: o.tenantId,
173
+ actionId: f.action.id,
174
+ state: T ? "pending" : "approved",
175
+ targetId: w,
176
+ input: S,
177
+ output: null,
178
+ reason: p,
179
+ ticket: g,
180
+ requesterId: o.id,
181
+ approverId: null,
182
+ digest: k,
183
+ createdAt: new Date(l()),
184
+ decidedAt: null,
185
+ executedAt: null
186
+ }, { conflictColumns: ["id"] });
187
+ let A = await y(o.tenantId, O);
188
+ if (A.requesterId !== o.id || A.digest !== k) throw t("conflict");
189
+ return A.state === "approved" ? b(A) : F(A);
190
+ }),
191
+ approve: (n, i) => T(async () => {
192
+ let a = P(n);
193
+ if (!g(a)) throw t("forbidden");
194
+ let o = await y(a.tenantId, i);
195
+ if (o.requesterId === a.id) throw t("forbidden");
196
+ if (o.state === "pending" && (await e.updateMany(r, {
197
+ state: "approved",
198
+ approverId: a.id,
199
+ decidedAt: new Date(l())
200
+ }, { where: u(d("id", o.id), d("state", "pending")) }), o = await y(a.tenantId, i)), o.state === "approved") return b(o);
201
+ if (o.state === "executed") return F(o);
202
+ throw t("conflict");
203
+ }),
204
+ reject: (n, i) => T(async () => {
205
+ let a = P(n), o = await y(a.tenantId, i);
206
+ if (!g(a) && o.requesterId !== a.id) throw t("forbidden");
207
+ o.state === "pending" && await e.updateMany(r, {
208
+ state: "rejected",
209
+ approverId: a.id,
210
+ decidedAt: new Date(l())
211
+ }, { where: u(d("id", o.id), d("state", "pending")) });
212
+ let s = await y(a.tenantId, i);
213
+ if (s.state !== "rejected") throw t("conflict");
214
+ return F(s);
215
+ })
216
+ }, S = (e, r) => {
217
+ if (r.tenantId === null) throw t("identity_required");
218
+ let i = E(r.tenantId), o = E(r.subjectId);
219
+ return e.query(p(a).where(u(d("tenantId", i), f(d("requesterId", o), d("approverId", o), d("targetId", o)))).orderBy("id", "asc").limit(n.maxExportRows + 1).descriptor);
220
+ };
221
+ return {
222
+ api: x,
223
+ exportSubject: (r) => T(async () => {
224
+ let i = await S(e, r);
225
+ if (i.length > n.maxExportRows) throw t("unavailable");
226
+ return i.map(F);
227
+ }),
228
+ eraseSubject: (n) => T(async () => {
229
+ for (let t = 0; t < 100; t++) if (await e.transactional(async (e) => {
230
+ let t = await S(e, n);
231
+ if (t.length === 0) return !0;
232
+ for (let i of t) {
233
+ let t = i.targetId === n.subjectId, a = i.requesterId === n.subjectId, o = i.state === "pending" || i.state === "approved";
234
+ await e.update(r, String(i.id), {
235
+ ...t ? {
236
+ targetId: null,
237
+ input: {},
238
+ output: null,
239
+ reason: "",
240
+ ticket: null,
241
+ digest: ""
242
+ } : {},
243
+ ...a ? { requesterId: null } : {},
244
+ ...i.approverId === n.subjectId ? { approverId: null } : {},
245
+ ...o && (t || a) ? {
246
+ state: "rejected",
247
+ decidedAt: new Date(l())
248
+ } : {}
249
+ });
250
+ }
251
+ return !1;
252
+ })) return;
253
+ throw t("unavailable");
254
+ })
255
+ };
256
+ };
257
+ export { I as dataStoreGm, x as defaultGmApprovalWhen, b as defaultGmAuthorize, S as resolveGmOptions, P as staffActor };
@@ -0,0 +1,206 @@
1
+ import { Effect } from 'effect';
2
+ import { InMemoryDataStore } from '@voltro/runtime';
3
+ import { PluginPrivacyTarget } from '@voltro/protocol';
4
+ import { Schema } from 'effect';
5
+ import { Subject } from '@voltro/protocol';
6
+ import { YieldableError } from 'effect/Cause';
7
+
8
+ declare const ACTION_RISKS: readonly ["routine", "sensitive"];
9
+
10
+ declare namespace C {
11
+ export {
12
+ REQUESTS_TABLE,
13
+ REQUEST_STATES,
14
+ GmRequestState,
15
+ ACTION_RISKS,
16
+ GmActionRisk,
17
+ GmRequestSchema,
18
+ GmRequest,
19
+ GmActionInfoSchema,
20
+ GmActionInfo,
21
+ GmExecuteInput
22
+ }
23
+ }
24
+
25
+ /** The erased shape the plugin stores; build one with `defineGmAction`. */
26
+ declare interface GmAction {
27
+ readonly id: string;
28
+ readonly title: string;
29
+ readonly input: Schema.Codec<unknown, unknown>;
30
+ readonly output: Schema.Codec<unknown, unknown>;
31
+ readonly risk: GmActionRisk;
32
+ readonly target?: ((input: never) => string | null) | undefined;
33
+ readonly execute: (input: never, ctx: GmActionContext) => Effect.Effect<unknown, unknown>;
34
+ }
35
+
36
+ /** What an action receives besides its decoded input. `requestId` is stable for the request
37
+ * (`[A-Za-z0-9-]`, at most 64 characters): pass it to the owning plugin as its idempotency key. */
38
+ declare interface GmActionContext {
39
+ readonly tenantId: string;
40
+ readonly requestId: string;
41
+ readonly requesterId: string;
42
+ readonly approverId: string | null;
43
+ readonly reason: string;
44
+ readonly ticket: string | null;
45
+ }
46
+
47
+ declare type GmActionInfo = typeof GmActionInfoSchema.Type;
48
+
49
+ /** One action the caller may run; `input` is the JSON Schema of the encoded input a console renders its form from. */
50
+ declare const GmActionInfoSchema: Schema.Struct<{
51
+ readonly id: Schema.String;
52
+ readonly title: Schema.String;
53
+ readonly risk: Schema.Literals<readonly ["routine", "sensitive"]>;
54
+ readonly input: Schema.Unknown;
55
+ }>;
56
+
57
+ declare type GmActionRisk = typeof ACTION_RISKS[number];
58
+
59
+ declare interface GmActionSummary {
60
+ readonly id: string;
61
+ readonly title: string;
62
+ readonly risk: C.GmActionRisk;
63
+ }
64
+
65
+ /** The staff caller as the policies see it: resolved scopes, never a credential claim. */
66
+ declare interface GmActor {
67
+ readonly type: 'user' | 'serviceAccount';
68
+ readonly id: string;
69
+ readonly tenantId: string;
70
+ readonly scopes: ReadonlyArray<string>;
71
+ }
72
+
73
+ declare interface GmApi {
74
+ readonly actions: (subject: Subject) => Effect.Effect<ReadonlyArray<C.GmActionInfo>, GmError>;
75
+ readonly requests: (subject: Subject, input: {
76
+ readonly state?: C.GmRequestState | undefined;
77
+ }) => Effect.Effect<ReadonlyArray<C.GmRequest>, GmError>;
78
+ readonly execute: (subject: Subject, input: C.GmExecuteInput) => Effect.Effect<C.GmRequest, GmError>;
79
+ readonly approve: (subject: Subject, id: string) => Effect.Effect<C.GmRequest, GmError>;
80
+ readonly reject: (subject: Subject, id: string) => Effect.Effect<C.GmRequest, GmError>;
81
+ }
82
+
83
+ declare interface GmApprovalPolicy {
84
+ /** Which requests wait for a second person. Default: actions whose risk is `sensitive`. */
85
+ readonly when?: (request: {
86
+ readonly action: GmActionSummary;
87
+ readonly input: unknown;
88
+ readonly actor: GmActor;
89
+ }) => boolean;
90
+ /** Scope an approver holds (`admin:full` also approves). Default `gm:approve`. */
91
+ readonly approverScope?: string;
92
+ }
93
+
94
+ /** Default `sensitive` rule versus a configured `approval.when`. */
95
+ export declare const gmApprovalSlotFixture: {
96
+ kind: "slot";
97
+ defaultImplementation: ({ action }: {
98
+ readonly action: GmActionSummary;
99
+ }) => boolean;
100
+ defaultProbe: {
101
+ run: () => Effect.Effect<"rejected" | "pending" | "approved" | "executed", GmError, never>;
102
+ expected: string;
103
+ };
104
+ overrideProbe: {
105
+ run: () => Effect.Effect<"rejected" | "pending" | "approved" | "executed", GmError, never>;
106
+ expected: string;
107
+ };
108
+ };
109
+
110
+ /** Default scope rule versus a configured `authorize`. */
111
+ export declare const gmAuthorizeSlotFixture: {
112
+ kind: "slot";
113
+ defaultImplementation: (actor: GmActor, action: GmActionSummary) => boolean;
114
+ defaultProbe: {
115
+ run: () => Effect.Effect<string[], GmError, never>;
116
+ expected: string[];
117
+ };
118
+ overrideProbe: {
119
+ run: () => Effect.Effect<string[], GmError, never>;
120
+ expected: never[];
121
+ };
122
+ };
123
+
124
+ /** `action_failed` carries the owning plugin's own error code (`insufficient_funds`, `conflict`, …) in `actionCode`. */
125
+ declare class GmError extends GmError_base {
126
+ }
127
+
128
+ declare const GmError_base: Schema.Class<GmError, Schema.TaggedStruct<"GmError", {
129
+ readonly code: Schema.Literals<readonly ["identity_required", "invalid_input", "forbidden", "not_found", "conflict", "unavailable", "action_failed"]>;
130
+ readonly actionCode: Schema.optional<Schema.String>;
131
+ }>, YieldableError>;
132
+
133
+ declare interface GmExecuteInput {
134
+ readonly requestId: string;
135
+ readonly actionId: string;
136
+ readonly input: unknown;
137
+ readonly reason: string;
138
+ readonly ticket?: string | undefined;
139
+ }
140
+
141
+ /** A fresh in-memory GM adapter over the actual request table. */
142
+ export declare const gmFixture: (options: Omit<GmOptions, "now"> & {
143
+ readonly now?: () => number;
144
+ }) => {
145
+ store: InMemoryDataStore;
146
+ gm: {
147
+ api: GmApi;
148
+ exportSubject: (target: PluginPrivacyTarget) => Effect.Effect<{
149
+ readonly id: string;
150
+ readonly actionId: string;
151
+ readonly state: "rejected" | "pending" | "approved" | "executed";
152
+ readonly targetId: string | null;
153
+ readonly input: unknown;
154
+ readonly output: unknown;
155
+ readonly reason: string;
156
+ readonly ticket: string | null;
157
+ readonly requesterId: string | null;
158
+ readonly approverId: string | null;
159
+ readonly createdAt: number;
160
+ readonly decidedAt: number | null;
161
+ readonly executedAt: number | null;
162
+ }[], GmError, never>;
163
+ eraseSubject: (target: PluginPrivacyTarget) => Effect.Effect<void, GmError, never>;
164
+ };
165
+ };
166
+
167
+ declare interface GmOptions {
168
+ readonly actions: ReadonlyArray<GmAction>;
169
+ /** Replaces the default rule: `gm:<id>`, `gm:<namespace>.*`, `gm:*` or `admin:full`. */
170
+ readonly authorize?: (actor: GmActor, action: GmActionSummary) => boolean;
171
+ /** Omitted: every authorized request runs at once. */
172
+ readonly approval?: GmApprovalPolicy;
173
+ readonly now?: () => number;
174
+ readonly pageSize?: number;
175
+ readonly maxExportRows?: number;
176
+ }
177
+
178
+ /** A side-effect-free action for fixtures and probes. */
179
+ export declare const gmProbeAction: (risk?: "routine" | "sensitive") => GmAction;
180
+
181
+ declare type GmRequest = typeof GmRequestSchema.Type;
182
+
183
+ declare const GmRequestSchema: Schema.Struct<{
184
+ readonly id: Schema.String;
185
+ readonly actionId: Schema.String;
186
+ readonly state: Schema.Literals<readonly ["pending", "approved", "executed", "rejected"]>;
187
+ readonly targetId: Schema.NullOr<Schema.String>;
188
+ readonly input: Schema.Unknown;
189
+ readonly output: Schema.NullOr<Schema.Unknown>;
190
+ readonly reason: Schema.String;
191
+ readonly ticket: Schema.NullOr<Schema.String>;
192
+ readonly requesterId: Schema.NullOr<Schema.String>;
193
+ readonly approverId: Schema.NullOr<Schema.String>;
194
+ readonly createdAt: Schema.Number;
195
+ readonly decidedAt: Schema.NullOr<Schema.Number>;
196
+ readonly executedAt: Schema.NullOr<Schema.Number>;
197
+ }>;
198
+
199
+ declare type GmRequestState = typeof REQUEST_STATES[number];
200
+
201
+ /** pending: waits for a second person. approved: may run (retries re-run the idempotent action). executed and rejected are terminal. */
202
+ declare const REQUEST_STATES: readonly ["pending", "approved", "executed", "rejected"];
203
+
204
+ declare const REQUESTS_TABLE = "_voltro_gm_requests";
205
+
206
+ export { }