@voltro/protocol 0.69.1 → 0.71.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.
@@ -0,0 +1,287 @@
1
+ import { Schema as e } from "effect";
2
+ import * as t from "effect/SchemaAST";
3
+ import * as n from "effect/Option";
4
+ //#region src/idempotency.ts
5
+ var r = (e, t, n) => `${e ?? "(global)"}|${t.toUpperCase()}|${n}`, i = (e, t, n, r) => r === void 0 ? `${e ?? "(global)"}|${t ?? "(anon)"}|WS|${n}` : `WS-connection:${JSON.stringify([
6
+ e ?? null,
7
+ t ?? null,
8
+ n,
9
+ r
10
+ ])}`, a = class extends e.TaggedError()("MutationConnectionRequired", { traceId: e.String }) {
11
+ get message() {
12
+ return "this mutation requires a live native WebSocket connection";
13
+ }
14
+ }, o = async (e, t, n, r, i) => {
15
+ let a = await e.claim(t, n, i, r);
16
+ return a === "claimed" ? { kind: "fresh" } : a.status === "completed" && a.response ? {
17
+ kind: "replay",
18
+ response: a.response
19
+ } : { kind: "conflict" };
20
+ }, s = (e, t, n, r, i) => e.complete(t, n, r, i), c = (e, t, n) => e.release(t, n), l = () => {
21
+ let e = /* @__PURE__ */ new Map(), t = (e, t) => `${e}\u0000${t}`;
22
+ return {
23
+ get: async (n, r) => e.get(t(n, r)) ?? null,
24
+ claim: async (n, r, i, a) => {
25
+ let o = e.get(t(n, r));
26
+ return o && i - o.createdAt < a ? o : (e.set(t(n, r), {
27
+ scope: n,
28
+ key: r,
29
+ status: "in_flight",
30
+ response: null,
31
+ createdAt: i
32
+ }), "claimed");
33
+ },
34
+ complete: async (n, r, i, a) => {
35
+ e.set(t(n, r), {
36
+ scope: n,
37
+ key: r,
38
+ status: "completed",
39
+ response: i,
40
+ createdAt: a
41
+ });
42
+ },
43
+ release: async (n, r) => {
44
+ e.delete(t(n, r));
45
+ }
46
+ };
47
+ }, u = class extends e.TaggedError()("MutationInFlight", {
48
+ traceId: e.String,
49
+ retryAfterMs: e.Number
50
+ }) {
51
+ get message() {
52
+ return `a request with this idempotency key is still in flight — replay the same key after ${this.retryAfterMs}ms`;
53
+ }
54
+ }, d = 1e3, f = (e) => typeof e == "object" && !!e && typeof e._tag == "string", p = 64, m = (e, t = 0) => {
55
+ if (!f(e)) return String(e);
56
+ if (t >= p) return "…";
57
+ let n = t + 1, r = (t) => m(e[t], n), i = (t) => Array.isArray(e[t]) ? e[t] : [];
58
+ switch (e._tag) {
59
+ case "Transformation": return `T(${r("from")}->${r("to")})`;
60
+ case "Refinement": return `R(${r("from")})`;
61
+ case "Suspend": return "S(…)";
62
+ case "TypeLiteral": {
63
+ let e = i("propertySignatures").map((e) => {
64
+ let t = e;
65
+ return `${String(t.name)}${t.isOptional === !0 ? "?" : ""}:${m(t.type, n)}`;
66
+ }).join(","), t = i("indexSignatures").map((e) => {
67
+ let t = e;
68
+ return `[${m(t.parameter, n)}]:${m(t.type, n)}`;
69
+ }).join(",");
70
+ return t === "" ? `{${e}}` : `{${e};${t}}`;
71
+ }
72
+ case "Union": return `U(${i("types").map((e) => m(e, n)).join("|")})`;
73
+ case "TupleType": {
74
+ let e = i("elements").map((e) => {
75
+ let t = e;
76
+ return `${m(t.type, n)}${t.isOptional === !0 ? "?" : ""}`;
77
+ }).join(","), t = i("rest").map((e) => m(e.type, n)).join(",");
78
+ return `A(${e}${t === "" ? "" : `...${t}`})`;
79
+ }
80
+ case "Declaration": return `D(${i("typeParameters").map((e) => m(e, n)).join(",")})`;
81
+ default: return String(e);
82
+ }
83
+ }, h = (e) => {
84
+ let t = 2166136261;
85
+ for (let n = 0; n < e.length; n++) t ^= e.charCodeAt(n), t = Math.imul(t, 16777619) >>> 0;
86
+ return t.toString(16).padStart(8, "0");
87
+ }, g = (e) => h([
88
+ m(e.payloadSchema?.ast),
89
+ m(e.successSchema?.ast),
90
+ m(e.errorSchema?.ast)
91
+ ].join("\0")), _ = (e) => {
92
+ let t = {}, n = e.requests, r = n instanceof Map ? n.entries() : Array.isArray(n) ? n.map((e, t) => [t, e]) : [];
93
+ for (let [, e] of r) {
94
+ if (typeof e != "object" && typeof e != "function" || e === null) continue;
95
+ let n = e;
96
+ typeof n._tag == "string" && (t[n._tag] = g(n));
97
+ }
98
+ return t;
99
+ }, v = (e, t) => {
100
+ let n = [], r = [];
101
+ for (let [i, a] of Object.entries(t)) {
102
+ let t = e[i];
103
+ t === void 0 ? r.push(i) : t !== a && n.push(i);
104
+ }
105
+ return {
106
+ differing: n.sort(),
107
+ missingHere: r.sort()
108
+ };
109
+ }, y = /* @__PURE__ */ new Set(), b = (e) => {
110
+ for (let t of y) try {
111
+ t(e);
112
+ } catch {}
113
+ }, x = (e) => (y.add(e), () => {
114
+ y.delete(e);
115
+ }), S = {
116
+ rpc: {
117
+ name: "rpc",
118
+ dates: "epoch-ms",
119
+ errors: "tagged",
120
+ envelope: "none",
121
+ pagination: "body-cursor",
122
+ cursorField: "nextCursor",
123
+ cursorParam: "cursor",
124
+ naming: "camelCase",
125
+ etag: !1
126
+ },
127
+ standard: {
128
+ name: "standard",
129
+ dates: "iso",
130
+ errors: "problem-details",
131
+ envelope: "none",
132
+ pagination: "link-header",
133
+ cursorField: "nextCursor",
134
+ cursorParam: "cursor",
135
+ naming: "camelCase",
136
+ etag: !0
137
+ },
138
+ jsonapi: {
139
+ name: "jsonapi",
140
+ dates: "iso",
141
+ errors: "problem-details",
142
+ envelope: "jsonapi",
143
+ pagination: "link-header",
144
+ cursorField: "nextCursor",
145
+ cursorParam: "cursor",
146
+ naming: "camelCase",
147
+ etag: !0
148
+ }
149
+ }, C = [
150
+ "rpc",
151
+ "standard",
152
+ "jsonapi"
153
+ ], w = (e, t = void 0) => {
154
+ let n = (e) => {
155
+ if (e?.profile !== void 0) {
156
+ if (typeof e.profile == "string") {
157
+ let t = S[e.profile];
158
+ if (t === void 0) throw Error(`publicApi.profile: unknown profile '${String(e.profile)}' — one of ${C.join(", ")}, or a RestFormat object`);
159
+ return t;
160
+ }
161
+ return e.profile;
162
+ }
163
+ }, r = (e, t) => t === void 0 ? e : {
164
+ ...e,
165
+ ...t.dates === void 0 ? {} : { dates: t.dates },
166
+ ...t.errors === void 0 ? {} : { errors: t.errors },
167
+ ...t.envelope === void 0 ? {} : { envelope: t.envelope },
168
+ ...t.pagination === void 0 ? {} : { pagination: t.pagination },
169
+ ...t.cursorField === void 0 ? {} : { cursorField: t.cursorField },
170
+ ...t.cursorParam === void 0 ? {} : { cursorParam: t.cursorParam },
171
+ ...t.naming === void 0 ? {} : { naming: t.naming },
172
+ ...t.etag === void 0 ? {} : { etag: t.etag }
173
+ }, i = r(n(e) ?? S.rpc, e);
174
+ return r(n(t) ?? i, t);
175
+ }, T = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toLowerCase(), E = (e, t) => {
176
+ if (typeof e != "object" || !e || Array.isArray(e)) return e;
177
+ let n = {};
178
+ for (let [r, i] of Object.entries(e)) n[t(r)] = i;
179
+ return n;
180
+ }, D = (e) => n.getOrUndefined(t.getIdentifierAnnotation(e)), O = (n, r) => {
181
+ if (r.dates === "epoch-ms" && r.naming === "camelCase") return n;
182
+ let i = /* @__PURE__ */ new Map(), a = (e) => {
183
+ let t = i.get(e);
184
+ if (t !== void 0) return t;
185
+ let n = o(e);
186
+ return i.set(e, n), n;
187
+ }, o = (n) => {
188
+ switch (n._tag) {
189
+ case "Transformation":
190
+ if (r.dates === "iso" && D(n) === "DateFromNumber") {
191
+ let r = e.DateFromString.ast;
192
+ return new t.Transformation(r.from, r.to, r.transformation, {
193
+ ...r.annotations,
194
+ ...n.annotations,
195
+ [t.IdentifierAnnotationId]: "DateFromString",
196
+ [t.JSONSchemaAnnotationId]: {
197
+ type: "string",
198
+ format: "date-time"
199
+ }
200
+ });
201
+ }
202
+ return n;
203
+ case "Refinement": return new t.Refinement(a(n.from), n.filter, n.annotations);
204
+ case "Union": return t.Union.make(n.types.map(a), n.annotations);
205
+ case "TupleType": return new t.TupleType(n.elements.map((e) => new t.OptionalType(a(e.type), e.isOptional, e.annotations)), n.rest.map((e) => new t.Type(a(e.type), e.annotations)), n.isReadonly, n.annotations);
206
+ case "Suspend": return new t.Suspend(() => a(n.f()), n.annotations);
207
+ case "TypeLiteral": {
208
+ let i = n.propertySignatures.map((e) => new t.PropertySignature(e.name, a(e.type), e.isOptional, e.isReadonly, e.annotations)), o = n.indexSignatures.map((e) => new t.IndexSignature(e.parameter, a(e.type), e.isReadonly)), s = new t.TypeLiteral(i, o, n.annotations);
209
+ if (r.naming !== "snake_case") return s;
210
+ let c = i.map((e) => typeof e.name == "string" ? T(e.name) : e.name);
211
+ if (c.every((e, t) => e === i[t].name)) return s;
212
+ let l = new t.TypeLiteral(i.map((e, n) => new t.PropertySignature(c[n], t.encodedAST(e.type), e.isOptional, e.isReadonly, e.annotations)), o.map((e) => new t.IndexSignature(e.parameter, t.encodedAST(e.type), e.isReadonly)), n.annotations), u = new Map(i.map((e, t) => [String(c[t]), String(e.name)])), d = new Map(i.map((e, t) => [String(e.name), String(c[t])]));
213
+ return e.transform(e.make(l), e.make(s), {
214
+ strict: !1,
215
+ decode: (e) => E(e, (e) => u.get(e) ?? e),
216
+ encode: (e) => E(e, (e) => d.get(e) ?? e)
217
+ }).ast;
218
+ }
219
+ default: return n;
220
+ }
221
+ };
222
+ return e.make(a(n.ast));
223
+ }, k = (e) => `urn:voltro:error:${e}`, A = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2"), j = (e) => ({
224
+ type: k(e.tag),
225
+ title: A(e.tag),
226
+ status: e.status,
227
+ ...e.message.length > 0 ? { detail: e.message } : {},
228
+ ...e.fields
229
+ }), M = "application/problem+json; charset=utf-8", N = "application/vnd.api+json; charset=utf-8", P = (e, t, n) => e.encodeError === void 0 ? e.errors === "problem-details" ? e.envelope === "jsonapi" ? {
230
+ body: { errors: [{
231
+ status: String(t.status),
232
+ code: t.tag,
233
+ title: A(t.tag),
234
+ ...t.message.length > 0 ? { detail: t.message } : {},
235
+ ...Object.keys(t.fields).length > 0 ? { meta: t.fields } : {}
236
+ }] },
237
+ contentType: N
238
+ } : {
239
+ body: j(t),
240
+ contentType: M
241
+ } : { body: {
242
+ error: t.tag === "InvalidRequest" || t.tag === "InternalServerError" || t.tag === "Unauthorized" || t.tag === "Forbidden" || t.tag === "Gone" || t.tag === "MethodNotAllowed" || t.tag === "Conflict" ? t.message : t.tag,
243
+ ...t.fields
244
+ } } : e.encodeError(t, n), F = (e, t) => {
245
+ if (typeof e != "object" || !e || Array.isArray(e)) return {
246
+ cursor: void 0,
247
+ rest: e
248
+ };
249
+ let n = e[t];
250
+ return typeof n != "string" && typeof n != "number" ? {
251
+ cursor: void 0,
252
+ rest: e
253
+ } : {
254
+ cursor: String(n),
255
+ rest: e
256
+ };
257
+ }, I = (e, t, n) => {
258
+ let r = new URLSearchParams(e.query);
259
+ return r.set(n, t), `<${e.path}?${r.toString()}>; rel="next"`;
260
+ }, L = (e, t) => {
261
+ if (typeof e != "object" || !e || Array.isArray(e)) return e;
262
+ let { id: n, ...r } = e;
263
+ return {
264
+ type: t,
265
+ ...n === void 0 ? {} : { id: String(n) },
266
+ attributes: r
267
+ };
268
+ }, R = (e, t, n) => {
269
+ if (e.encodeSuccess !== void 0) return e.encodeSuccess(t, n);
270
+ let { cursor: r } = F(t, e.cursorField), i = e.pagination === "link-header" && r !== void 0 ? { Link: I(n, r, e.cursorParam) } : void 0, a = (e, t) => ({
271
+ body: e,
272
+ ...i ? { headers: i } : {},
273
+ ...t ? { contentType: t } : {}
274
+ });
275
+ if (e.envelope === "none") return a(t);
276
+ if (e.envelope === "data") return a({
277
+ data: t,
278
+ ...r !== void 0 && e.pagination === "link-header" ? { next: r } : {}
279
+ });
280
+ let o = n.source ?? n.tag.split(".")[0] ?? "resource", s = Array.isArray(t) ? t : typeof t == "object" && t && Array.isArray(t.items) ? t.items : void 0;
281
+ return a({
282
+ data: s === void 0 ? L(t, o) : s.map((e) => L(e, o)),
283
+ ...r === void 0 ? {} : { links: { next: I(n, r, e.cursorParam).replace(/^<|>; rel="next"$/g, "") } }
284
+ }, N);
285
+ };
286
+ //#endregion
287
+ export { c as C, i as D, l as E, o as S, r as T, m as _, R as a, a as b, k as c, T as d, b as f, g, _ as h, P as i, w as l, v as m, M as n, I as o, x as p, C as r, j as s, N as t, O as u, h as v, s as w, u as x, d as y };
@@ -20,6 +20,7 @@ var i = n.Record({
20
20
  type: n.Literal("serviceAccount"),
21
21
  id: n.String,
22
22
  tenantId: n.String,
23
+ auditActorId: n.optionalWith(n.NullOr(n.String), { exact: !0 }),
23
24
  scopes: a,
24
25
  metadata: n.optional(i)
25
26
  }), l = n.Struct({
@@ -42,24 +43,31 @@ var i = n.Record({
42
43
  id: null,
43
44
  tenantId: e,
44
45
  ...t === void 0 ? {} : { credentialRejected: t }
45
- }), ee = (e, t = ["admin:full"]) => ({
46
+ }), g = (e, t = ["admin:full"]) => ({
46
47
  type: "system",
47
48
  id: e,
48
49
  tenantId: null,
49
50
  scopes: t
50
- }), te = (e, t) => {
51
+ }), _ = (e) => {
52
+ if (e.type === "system") return null;
53
+ if (e.type === "serviceAccount" && e.auditActorId !== void 0) return e.auditActorId;
54
+ if (e.type !== "apiKey") return e.id;
55
+ let t = e.metadata?.userId;
56
+ return typeof t == "string" && t !== "" ? t : e.id;
57
+ }, ee = (e, t) => {
51
58
  let n = e;
52
59
  return {
53
60
  type: "serviceAccount",
54
61
  id: e.id ?? "system",
55
62
  tenantId: t,
56
- scopes: n.scopes ?? ["admin:full"],
63
+ auditActorId: _(e),
64
+ scopes: m(e),
57
65
  ...n.metadata === void 0 ? {} : { metadata: n.metadata }
58
66
  };
59
- }, g = (e) => e.type === "system", _ = "impersonation", v = (e) => e.metadata?.impersonation ?? null, y = class extends e.Tag("@voltro/Subject")() {}, b = class extends e.Tag("@voltro/ConnectionInfo")() {}, x = class extends r.Tag()("@voltro/AuthMiddleware", { provides: y }) {}, S = class extends r.Tag()("@voltro/ConnectionInfoMiddleware", { provides: b }) {}, ne = (e) => typeof e.mountRoutes == "function", C = (e) => Array.isArray(e) ? {
67
+ }, te = (e) => e.type === "system", ne = "impersonation", v = (e) => e.metadata?.impersonation ?? null, y = class extends e.Tag("@voltro/Subject")() {}, b = class extends e.Tag("@voltro/ConnectionInfo")() {}, x = class extends r.Tag()("@voltro/AuthMiddleware", { provides: y }) {}, S = class extends r.Tag()("@voltro/ConnectionInfoMiddleware", { provides: b }) {}, C = (e) => typeof e.mountRoutes == "function", w = (e) => Array.isArray(e) ? {
60
68
  kind: "grant",
61
69
  scopes: e
62
- } : e, w = (e, t) => {
70
+ } : e, T = (e, t) => {
63
71
  if (t.kind === "vetoed") throw new k({ reason: `${t.code}: ${t.message}` });
64
72
  if (e.type === "anonymous") return {
65
73
  subject: e,
@@ -88,13 +96,13 @@ var i = n.Record({
88
96
  },
89
97
  removed: i
90
98
  };
91
- }, re = 3e4, ie = 1e4, T = (e) => `${e.type}\u0000${e.tenantId ?? ""}\u0000${e.id ?? ""}`, ae = () => {
99
+ }, re = 3e4, ie = 1e4, E = (e) => `${e.type}\u0000${e.tenantId ?? ""}\u0000${e.id ?? ""}`, ae = () => {
92
100
  if (typeof process > "u") return;
93
101
  let e = process.env.VOLTRO_AUTH_SCOPE_CACHE_TTL_MS;
94
102
  if (e === void 0 || e === "") return;
95
103
  let t = Number(e);
96
104
  return Number.isFinite(t) && t >= 0 ? t : void 0;
97
- }, E = (e = {}) => {
105
+ }, D = (e = {}) => {
98
106
  let t = e.ttlMs ?? ae() ?? 3e4, n = e.maxEntries ?? 1e4, r = e.now ?? Date.now, i = /* @__PURE__ */ new Map(), a = () => {
99
107
  if (i.size <= n) return;
100
108
  let e = r();
@@ -134,21 +142,21 @@ var i = n.Record({
134
142
  }, oe = (e) => typeof e.resolve == "function", se = (e) => {
135
143
  let t = e.authorization ?? e.Authorization;
136
144
  if (t !== void 0) return /^Bearer\s+(.+)$/i.exec(t.trim())?.[1]?.trim();
137
- }, D = (e, t) => {
145
+ }, O = (e, t) => {
138
146
  let n = se(t);
139
147
  if (n === void 0) return e;
140
148
  let r = e.filter((e) => e.claimsBearerPrefix !== void 0 && e.claimsBearerPrefix !== "" && n.startsWith(e.claimsBearerPrefix));
141
149
  if (r.length === 0) return e;
142
150
  let i = new Set(r);
143
151
  return [...r, ...e.filter((e) => !i.has(e))];
144
- }, O = (e, t) => {
145
- let n = t?.resolveScopes, r = t?.subjectGuards ?? [], i = n !== void 0 || r.length > 0, a = !i || t?.scopeCache === !1 ? null : t?.scopeCache !== void 0 && oe(t.scopeCache) ? t.scopeCache : E(t?.scopeCache ?? {});
152
+ }, ce = (e, t) => {
153
+ let n = t?.resolveScopes, r = t?.subjectGuards ?? [], i = n !== void 0 || r.length > 0, a = !i || t?.scopeCache === !1 ? null : t?.scopeCache !== void 0 && oe(t.scopeCache) ? t.scopeCache : D(t?.scopeCache ?? {});
146
154
  return async (o) => {
147
155
  let s = t?.getStore?.(), c = s === void 0 ? o : {
148
156
  ...o,
149
157
  store: s
150
158
  }, l;
151
- for (let o of D(e, c.headers)) {
159
+ for (let o of O(e, c.headers)) {
152
160
  let e = await o.resolve(c);
153
161
  if (e.kind === "matched") {
154
162
  let s = e.subject;
@@ -157,9 +165,9 @@ var i = n.Record({
157
165
  let t = n === void 0 ? {
158
166
  kind: "grant",
159
167
  scopes: []
160
- } : C(await n(e.subject, c));
168
+ } : w(await n(e.subject, c));
161
169
  if (t.kind === "unavailable" || r.length === 0) return t;
162
- let i = w(e.subject, t).subject, a = {
170
+ let i = T(e.subject, t).subject, a = {
163
171
  origin: "request",
164
172
  headers: c.headers,
165
173
  clientId: c.clientId,
@@ -177,7 +185,7 @@ var i = n.Record({
177
185
  }, { decision: l, fresh: u } = a === null ? {
178
186
  decision: await i(),
179
187
  fresh: !0
180
- } : await a.resolve(T(e.subject), i);
188
+ } : await a.resolve(E(e.subject), i);
181
189
  if (l.kind === "vetoed") {
182
190
  let e = `${l.code}: ${l.message}`;
183
191
  throw t?.onStrategyFailed?.({
@@ -192,7 +200,7 @@ var i = n.Record({
192
200
  reason: e
193
201
  }), new k({ reason: e });
194
202
  }
195
- let d = w(e.subject, l);
203
+ let d = T(e.subject, l);
196
204
  u && d.removed.length > 0 && t?.onScopesNarrowed?.({
197
205
  strategyId: o.id,
198
206
  subjectType: e.subject.type,
@@ -245,9 +253,24 @@ var i = n.Record({
245
253
  }, H = (e, n) => B(e, n) ? t.void : t.fail(new F({
246
254
  required: n,
247
255
  message: `missing required scope '${n}'`
248
- })), U = (e) => typeof e.action == "string" && typeof e.resourceType == "string", W = (e) => typeof e.open == "string", G = (e) => e.authenticated === !0, K = (e) => ({ open: e }), q = (e) => typeof e == "string" ? [e] : e, J, ce = (e) => {
256
+ })), U = (e, t) => {
257
+ let n;
258
+ try {
259
+ n = typeof e.action == "function" ? e.action(t) : e.action;
260
+ } catch {
261
+ throw new F({
262
+ required: e.resourceType,
263
+ message: "policy action selector failed"
264
+ });
265
+ }
266
+ if (typeof n != "string" || n.trim() === "") throw new F({
267
+ required: e.resourceType,
268
+ message: "policy action selector returned no action"
269
+ });
270
+ return n;
271
+ }, W = (e) => (typeof e.action == "string" || typeof e.action == "function") && typeof e.resourceType == "string", G = (e) => typeof e.open == "string", K = (e) => e.authenticated === !0, le = (e) => ({ open: e }), q = (e) => typeof e == "string" ? [e] : e, J, ue = (e) => {
249
272
  J = e;
250
- }, le = (e) => J === void 0 ? e.filter((e) => (e.guards ?? []).some((e) => !U(e) && e.resource !== void 0)).map((e) => e.tag) : [], ue = (e) => [
273
+ }, Y = (e) => J === void 0 ? e.filter((e) => (e.guards ?? []).some((e) => !W(e) && e.resource !== void 0)).map((e) => e.tag) : [], de = (e) => [
251
274
  `${e.length} procedure${e.length === 1 ? "" : "s"} declare a per-resource guard that nothing enforces:`,
252
275
  ...e.slice(0, 10).map((e) => ` ${e}`),
253
276
  ...e.length > 10 ? [` … and ${e.length - 10} more`] : [],
@@ -261,9 +284,9 @@ var i = n.Record({
261
284
  " • setResourceScopeResolver((req) => Effect<boolean>) — answers \"does this",
262
285
  " subject hold `req.scope` on `req.resource`\" from your own tables.",
263
286
  " • Drop the `resource` extractor if a global scope check is what you meant."
264
- ].join("\n"), de = () => J, Y, fe = (e) => {
265
- Y = e;
266
- }, X = () => Y, Z = (e) => new F({
287
+ ].join("\n"), fe = () => J, X, pe = (e) => {
288
+ X = e;
289
+ }, me = () => X, Z = (e) => new F({
267
290
  required: "access-decision",
268
291
  message: `${e ?? "this procedure"} declares no access decision: it has neither \`guards:\` nor \`openAccess:\`, and the app runs with \`security.defaultDeny\`. Add the scope(s) a caller must hold (\`guards: [{ scope: '…' }]\`), or declare it open on purpose with \`openAccess: '<why anyone may call this>'\`.`
269
292
  }), Q = (e, t) => {
@@ -272,15 +295,15 @@ var i = n.Record({
272
295
  }, $ = (e, t, n) => {
273
296
  if (!t || t.length === 0) return n?.defaultDeny === !0 ? Z(n.procedure) : null;
274
297
  for (let n of t) {
275
- if (W(n)) continue;
276
- if (G(n)) {
298
+ if (G(n)) continue;
299
+ if (K(n)) {
277
300
  if (e.id !== null && e.id !== void 0) continue;
278
301
  return Q(e, new F({
279
302
  required: "authenticated",
280
303
  message: "this procedure requires an authenticated caller"
281
304
  }));
282
305
  }
283
- if (U(n)) {
306
+ if (W(n)) {
284
307
  if (B(e, "admin:full")) continue;
285
308
  return Q(e, new F({
286
309
  required: `${n.resourceType}:${n.action}`,
@@ -299,36 +322,42 @@ var i = n.Record({
299
322
  }
300
323
  }
301
324
  return null;
302
- }, pe = (e, n, r) => {
303
- let i = (e) => new F({
304
- required: `${n.resourceType}:${n.action}`,
325
+ }, he = (e, n, r) => {
326
+ let i;
327
+ try {
328
+ i = U(n, r);
329
+ } catch (e) {
330
+ return t.succeed(e);
331
+ }
332
+ let a = (e) => new F({
333
+ required: `${n.resourceType}:${i}`,
305
334
  message: e
306
335
  });
307
336
  if (B(e, "admin:full")) return t.succeed(null);
308
- let a = Y;
309
- if (a === void 0) return t.succeed(i(`no policy resolver registered — cannot authorize '${n.action}' on '${n.resourceType}'`));
310
- let o = n.resource(r);
311
- return o === void 0 || o === "" ? t.succeed(i(`input does not identify a '${n.resourceType}' to authorize`)) : t.suspend(() => a({
337
+ let o = X;
338
+ if (o === void 0) return t.succeed(a(`no policy resolver registered — cannot authorize '${i}' on '${n.resourceType}'`));
339
+ let s = n.resource(r);
340
+ return s === void 0 || s === "" ? t.succeed(a(`input does not identify a '${n.resourceType}' to authorize`)) : t.suspend(() => o({
312
341
  subject: e,
313
- action: n.action,
342
+ action: i,
314
343
  resourceType: n.resourceType,
315
- resourceId: o
316
- })).pipe(t.catchAllCause(() => t.succeed(!1)), t.map((e) => e ? null : i(`no '${n.action}' relation on ${n.resourceType}:${o}`)));
317
- }, me = (e, n, r, i) => {
344
+ resourceId: s
345
+ })).pipe(t.catchAllCause(() => t.succeed(!1)), t.map((e) => e ? null : a(`no '${i}' relation on ${n.resourceType}:${s}`)));
346
+ }, ge = (e, n, r, i) => {
318
347
  if (!n || n.length === 0) return t.succeed($(e, n, i));
319
348
  let a = J;
320
- return !n.some(U) && (!a || !n.some((e) => e.resource !== void 0)) ? t.succeed($(e, n, i)) : t.gen(function* () {
349
+ return !n.some(W) && (!a || !n.some((e) => e.resource !== void 0)) ? t.succeed($(e, n, i)) : t.gen(function* () {
321
350
  for (let i of n) {
322
- if (W(i)) continue;
323
- if (G(i)) {
351
+ if (G(i)) continue;
352
+ if (K(i)) {
324
353
  if (e.id !== null && e.id !== void 0) continue;
325
354
  return Q(e, new F({
326
355
  required: "authenticated",
327
356
  message: "this procedure requires an authenticated caller"
328
357
  }));
329
358
  }
330
- if (U(i)) {
331
- let t = yield* pe(e, i, r);
359
+ if (W(i)) {
360
+ let t = yield* he(e, i, r);
332
361
  if (t) return t;
333
362
  continue;
334
363
  }
@@ -362,4 +391,4 @@ var i = n.Record({
362
391
  });
363
392
  };
364
393
  //#endregion
365
- export { _ as A, g as B, ce as C, S as D, b as E, h as F, p as G, D as H, w as I, ee as K, A as L, f as M, y as N, ie as O, k as P, O as R, fe as S, x as T, v as U, E as V, T as W, U as _, F as a, H as b, me as c, X as d, de as f, W as g, G as h, M as i, d as j, re as k, z as l, V as m, N as n, ue as o, B as p, te as q, P as r, $ as s, j as t, le as u, Z as v, I as w, R as x, K as y, ne as z };
394
+ export { re as A, C as B, pe as C, b as D, x as E, k as F, E as G, D as H, h as I, g as J, _ as K, T as L, d as M, f as N, S as O, y as P, A as R, R as S, I as T, O as U, te as V, v as W, ee as Y, W as _, F as a, H as b, ge as c, me as d, fe as f, G as g, K as h, M as i, ne as j, ie as k, z as l, V as m, N as n, de as o, B as p, p as q, P as r, $ as s, j as t, Y as u, Z as v, ue as w, U as x, le as y, ce as z };
package/dist/session.d.ts CHANGED
@@ -162,28 +162,31 @@ declare const SessionPayload_2: Schema.Struct<{
162
162
  v: Schema.Literal<[2]>;
163
163
  /** WHO, never WHAT-THEY-MAY-DO. `SubjectIdentity` has no `scopes` field. */
164
164
  subject: Schema.Union<[Schema.Struct<{
165
- id: typeof Schema.String;
166
165
  type: Schema.Literal<["user"]>;
166
+ id: typeof Schema.String;
167
167
  tenantId: typeof Schema.String;
168
168
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
169
169
  }>, Schema.Struct<{
170
- id: typeof Schema.String;
171
170
  type: Schema.Literal<["apiKey"]>;
171
+ id: typeof Schema.String;
172
172
  tenantId: typeof Schema.String;
173
173
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
174
174
  }>, Schema.Struct<{
175
- id: typeof Schema.String;
176
175
  type: Schema.Literal<["serviceAccount"]>;
176
+ id: typeof Schema.String;
177
177
  tenantId: typeof Schema.String;
178
178
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
179
+ auditActorId: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
180
+ exact: true;
181
+ }>;
179
182
  }>, Schema.Struct<{
180
183
  type: Schema.Literal<["anonymous"]>;
181
184
  id: typeof Schema.Null;
182
185
  tenantId: Schema.NullOr<typeof Schema.String>;
183
186
  credentialRejected: Schema.optional<typeof Schema.String>;
184
187
  }>, Schema.Struct<{
185
- id: typeof Schema.String;
186
188
  type: Schema.Literal<["system"]>;
189
+ id: typeof Schema.String;
187
190
  tenantId: typeof Schema.Null;
188
191
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
189
192
  }>]>;
@@ -274,6 +277,11 @@ declare const Subject: Schema.Union<[Schema.Struct<{
274
277
  type: Schema.Literal<["serviceAccount"]>;
275
278
  id: typeof Schema.String;
276
279
  tenantId: typeof Schema.String;
280
+ /** Application actor used for audit columns; null means machine work with
281
+ * no application actor. Identity only, never a permission grant. */
282
+ auditActorId: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
283
+ exact: true;
284
+ }>;
277
285
  scopes: Schema.optional<Schema.Array$<typeof Schema.String>>;
278
286
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
279
287
  }>, Schema.Struct<{
@@ -308,20 +316,23 @@ declare const Subject: Schema.Union<[Schema.Struct<{
308
316
  declare type Subject = typeof Subject.Type;
309
317
 
310
318
  declare const SubjectIdentity: Schema.Union<[Schema.Struct<{
311
- id: typeof Schema.String;
312
319
  type: Schema.Literal<["user"]>;
320
+ id: typeof Schema.String;
313
321
  tenantId: typeof Schema.String;
314
322
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
315
323
  }>, Schema.Struct<{
316
- id: typeof Schema.String;
317
324
  type: Schema.Literal<["apiKey"]>;
325
+ id: typeof Schema.String;
318
326
  tenantId: typeof Schema.String;
319
327
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
320
328
  }>, Schema.Struct<{
321
- id: typeof Schema.String;
322
329
  type: Schema.Literal<["serviceAccount"]>;
330
+ id: typeof Schema.String;
323
331
  tenantId: typeof Schema.String;
324
332
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
333
+ auditActorId: Schema.optionalWith<Schema.NullOr<typeof Schema.String>, {
334
+ exact: true;
335
+ }>;
325
336
  }>, Schema.Struct<{
326
337
  type: Schema.Literal<["anonymous"]>;
327
338
  id: typeof Schema.Null;
@@ -344,8 +355,8 @@ declare const SubjectIdentity: Schema.Union<[Schema.Struct<{
344
355
  */
345
356
  credentialRejected: Schema.optional<typeof Schema.String>;
346
357
  }>, Schema.Struct<{
347
- id: typeof Schema.String;
348
358
  type: Schema.Literal<["system"]>;
359
+ id: typeof Schema.String;
349
360
  tenantId: typeof Schema.Null;
350
361
  metadata: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
351
362
  }>]>;
package/dist/session.js CHANGED
@@ -1,11 +1,11 @@
1
- import { G as e, M as t, w as n } from "./scopes-DVtiKKsm.js";
1
+ import { N as e, T as t, q as n } from "./scopes-CW9_LbDO.js";
2
2
  import { Schema as r } from "effect";
3
3
  import { createHmac as i, timingSafeEqual as a } from "node:crypto";
4
4
  import { createLogger as o } from "@voltro/logger";
5
5
  //#region src/session.ts
6
6
  var s = o({ scope: "@voltro/protocol:session" }), c = 2, l = r.Struct({
7
7
  v: r.Literal(2),
8
- subject: t,
8
+ subject: e,
9
9
  exp: r.Number,
10
10
  iat: r.Number,
11
11
  kid: r.optional(r.String)
@@ -38,14 +38,14 @@ var s = o({ scope: "@voltro/protocol:session" }), c = 2, l = r.Struct({
38
38
  }, C = 604800, w = .7, T = (e, t) => x(i("sha256", t).update(e).digest()), E = (e) => typeof e == "string" ? {
39
39
  kid: "k0",
40
40
  secret: e
41
- } : e, D = (t, r, i = {}) => {
41
+ } : e, D = (e, r, i = {}) => {
42
42
  let a = E(r);
43
43
  if (!a.secret) throw Error("signSession: secret must be a non-empty string");
44
- let o = n(t);
45
- if (o.length > 0) throw Error(`signSession: refusing to mint a session cookie for a ${t.type} subject carrying ${o.length} scope(s) (${o.slice(0, 5).join(", ")}). A session cookie carries IDENTITY only — authority is resolved per request, so that removing a role takes effect on the session that already exists instead of in up to 7 days. Mint the session from the identity and move the scopes into the auth.resolveScopes resolver, which runs on every request and can be invalidated the moment a role changes.`);
44
+ let o = t(e);
45
+ if (o.length > 0) throw Error(`signSession: refusing to mint a session cookie for a ${e.type} subject carrying ${o.length} scope(s) (${o.slice(0, 5).join(", ")}). A session cookie carries IDENTITY only — authority is resolved per request, so that removing a role takes effect on the session that already exists instead of in up to 7 days. Mint the session from the identity and move the scopes into the auth.resolveScopes resolver, which runs on every request and can be invalidated the moment a role changes.`);
46
46
  let s = i.ttlSeconds ?? C, c = Math.floor((i.now ?? Date.now)() / 1e3), l = {
47
47
  v: 2,
48
- subject: e(t),
48
+ subject: n(e),
49
49
  exp: c + s,
50
50
  iat: c,
51
51
  ...typeof r == "string" ? {} : { kid: a.kid }