@voltro/protocol 0.85.0 → 0.87.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +5987 -0
  2. package/README.md +14 -0
  3. package/THIRD-PARTY-NOTICES.md +1122 -31
  4. package/dist/apiRoleEndpoints.js +108 -0
  5. package/dist/apiRoleObservation.js +68 -0
  6. package/dist/apikey.d.ts +10 -0
  7. package/dist/approval.js +64 -0
  8. package/dist/approvalDescriptors.js +26 -0
  9. package/dist/auth.js +389 -0
  10. package/dist/boundedRing.js +27 -0
  11. package/dist/businessRule.js +11 -0
  12. package/dist/canonicalJson.js +14 -0
  13. package/dist/connection.js +73 -0
  14. package/dist/devActivity.js +4 -0
  15. package/dist/errorSchema.js +24 -0
  16. package/dist/errorValue.js +21 -0
  17. package/dist/event.js +110 -0
  18. package/dist/formCapture.js +12 -0
  19. package/dist/httpStreamMaxDuration.js +9 -0
  20. package/dist/idempotency.js +121 -0
  21. package/dist/idempotencyInput.js +123 -0
  22. package/dist/identity.js +5 -9
  23. package/dist/index.d.ts +1095 -139
  24. package/dist/index.js +54 -1700
  25. package/dist/inspect.d.ts +1 -1
  26. package/dist/inspect.js +14 -240
  27. package/dist/inspectBackfill.js +52 -0
  28. package/dist/inspectCommandRejection.js +21 -0
  29. package/dist/inspectProjection.js +133 -0
  30. package/dist/inspectStorageProjection.js +86 -0
  31. package/dist/inspectValueIdentity.js +4 -0
  32. package/dist/inspectWorkflowExecution.js +34 -0
  33. package/dist/inspectWorkflowQueuedStart.js +31 -0
  34. package/dist/jsonSchema.js +54 -0
  35. package/dist/jwt.d.ts +10 -0
  36. package/dist/jwt.js +1 -1
  37. package/dist/outbox.js +10 -0
  38. package/dist/pairing.js +75 -0
  39. package/dist/plugin.js +87 -0
  40. package/dist/pluginAccountMerge.js +88 -0
  41. package/dist/pluginNaming.js +32 -0
  42. package/dist/pluginPrivacy.js +166 -0
  43. package/dist/procedure.js +280 -0
  44. package/dist/publicApi.js +325 -0
  45. package/dist/queryOptionalSources.js +14 -0
  46. package/dist/reactivityChannel.js +34 -0
  47. package/dist/rest.d.ts +260 -64
  48. package/dist/rest.js +345 -499
  49. package/dist/restFormat.js +163 -0
  50. package/dist/restReceipt.js +43 -0
  51. package/dist/revalidate.js +13 -0
  52. package/dist/rowEquality.js +31 -0
  53. package/dist/rowPatch.js +182 -0
  54. package/dist/schemaShape.js +67 -0
  55. package/dist/scopes.js +273 -0
  56. package/dist/secretWindows.js +71 -0
  57. package/dist/serverErrorBus.js +15 -0
  58. package/dist/serviceCall.js +14 -0
  59. package/dist/session.d.ts +89 -6
  60. package/dist/session.js +62 -63
  61. package/dist/{sessionHeader-DriTZhmv.js → sessionHeader.js} +1 -1
  62. package/dist/ssrfPolicy.js +30 -0
  63. package/dist/storageAccess.js +14 -0
  64. package/dist/storeErrors.js +66 -0
  65. package/dist/storeScreenshots.js +90 -0
  66. package/dist/strictInput.js +27 -0
  67. package/dist/subscriptionEvent.js +37 -0
  68. package/dist/tenantSelection.js +40 -0
  69. package/dist/time.js +7 -0
  70. package/dist/undo.js +38 -0
  71. package/dist/validation.js +39 -0
  72. package/dist/webSocketGateway.js +16 -0
  73. package/dist/workflow.js +297 -0
  74. package/dist/workflowAccess.js +61 -0
  75. package/dist/workflowRestart.js +45 -0
  76. package/dist/workflowRetry.js +44 -0
  77. package/dist/workflowStartOutcome.js +30 -0
  78. package/package.json +5 -3
  79. package/dist/restFormat-BlgTLxm1.js +0 -298
  80. package/dist/scopes-ClywBij8.js +0 -548
package/dist/scopes.js ADDED
@@ -0,0 +1,273 @@
1
+ import { RequestedTenantRefused as e } from "./tenantSelection.js";
2
+ import { Unauthenticated as t, currentSubjectAuthority as n, reauthorizeSubject as r } from "./auth.js";
3
+ import { Effect as i, Schema as a } from "effect";
4
+ //#region src/scopes.ts
5
+ var o = "admin:full", s = "apikeys:issue:self", c = "apikeys:issue:org", l = "apikeys:issue:other", u = "devices:manage", d = class extends a.TaggedError()("ScopeError", {
6
+ required: a.String,
7
+ message: a.String
8
+ }) {}, f = class extends a.TaggedError()("NoSuchResource", {
9
+ resourceType: a.NullOr(a.String),
10
+ resourceId: a.String,
11
+ message: a.String
12
+ }) {}, p = (e) => "scopes" in e && e.scopes ? e.scopes : [], m = /* @__PURE__ */ new WeakMap(), h = /* @__PURE__ */ new WeakMap(), g = (e, t) => {
13
+ h.set(e, t);
14
+ }, _ = async (e, t) => {
15
+ let n = h.get(e);
16
+ n !== void 0 && (await n(t), h.set(t, n));
17
+ }, v = (e, t) => {
18
+ typeof e == "object" && e && m.set(e, t);
19
+ }, y = (e) => {
20
+ if (e = n(e), typeof e == "object" && e) {
21
+ let t = m.get(e);
22
+ if (t) return t;
23
+ }
24
+ return p(e);
25
+ }, b = (e, t) => {
26
+ let n = y(e);
27
+ return n.includes("admin:full") || n.includes(t);
28
+ }, x = (e, t) => {
29
+ let n = p(e);
30
+ return n.includes("admin:full") || n.includes(t);
31
+ }, S = (e, t) => b(e, t) ? i.void : i.fail(new d({
32
+ required: t,
33
+ message: `missing required scope '${t}'`
34
+ })), C = (e, t) => {
35
+ let n;
36
+ try {
37
+ n = typeof e.action == "function" ? e.action(t) : e.action;
38
+ } catch {
39
+ throw new d({
40
+ required: e.resourceType,
41
+ message: "policy action selector failed"
42
+ });
43
+ }
44
+ if (typeof n != "string" || n.trim() === "") throw new d({
45
+ required: e.resourceType,
46
+ message: "policy action selector returned no action"
47
+ });
48
+ return n;
49
+ }, w = (e) => (typeof e.action == "string" || typeof e.action == "function") && typeof e.resourceType == "string", T = (e) => e !== void 0 && e.some((e) => w(e) || typeof e.resource == "function"), E = (e) => typeof e.open == "string", D = (e) => Array.isArray(e.service), O = (e) => ({ service: [...e] }), k = (e) => e?.find(D)?.service, A = (e, t) => {
50
+ for (let n of t ?? []) {
51
+ let t = N(n);
52
+ if (t) return K(e, t);
53
+ }
54
+ if (t?.some(M)) {
55
+ let t = P(e);
56
+ if (t) return t;
57
+ }
58
+ let n = k(t);
59
+ if (e.type === "service") return n !== void 0 && n.includes(e.service) ? null : new d({
60
+ required: "service-procedure",
61
+ message: n === void 0 ? `service '${e.service}' may only call procedures that declare it in \`service: { callers }\`` : `service '${e.service}' is not among this procedure's callers [${n.join(", ")}]`
62
+ });
63
+ if (n !== void 0) return K(e, new d({
64
+ required: `service:${n.join("|")}`,
65
+ message: `this procedure is callable only by the service${n.length === 1 ? "" : "s"} [${n.join(", ")}]`
66
+ }));
67
+ }, j = (e) => e.authenticated === !0, M = (e) => e.upgraded === !0, N = (e) => typeof e != "object" || !e || !("upgraded" in e) || e.upgraded !== !0 ? null : [
68
+ "scope",
69
+ "mode",
70
+ "resource",
71
+ "action",
72
+ "resourceType",
73
+ "authenticated",
74
+ "open",
75
+ "service"
76
+ ].some((t) => t in e) ? new d({
77
+ required: "guard-shape",
78
+ message: "An upgraded guard must be a separate entry; put each additional scope, policy or identity requirement in its own guard entry."
79
+ }) : null, P = (e) => e.type === "user" && e.kind !== "guest" ? null : K(e, new d({
80
+ required: "upgraded",
81
+ message: "this procedure requires a registered user account"
82
+ })), F = (e) => ({ open: e }), I = (e) => typeof e == "string" ? [e] : e, L, R = (e) => {
83
+ L = e;
84
+ }, z = (e) => L === void 0 ? e.filter((e) => (e.guards ?? []).some((e) => !w(e) && e.resource !== void 0)).map((e) => e.tag) : [], B = (e) => [
85
+ `${e.length} procedure${e.length === 1 ? "" : "s"} declare a per-resource guard that nothing enforces:`,
86
+ ...e.slice(0, 10).map((e) => ` ${e}`),
87
+ ...e.length > 10 ? [` … and ${e.length - 10} more`] : [],
88
+ "",
89
+ " `guards: [{ scope, resource }]` reads as \"may you do this to THIS resource\",",
90
+ " but with no resource-scope resolver registered the extractor is advisory and",
91
+ " the check runs against the caller's GLOBAL scopes. For an app whose",
92
+ " authorization is per-resource, that guard passes callers it should refuse.",
93
+ "",
94
+ " Fix it one of two ways:",
95
+ " • setResourceScopeResolver((req) => Effect<boolean>) — answers \"does this",
96
+ " subject hold `req.scope` on `req.resource`\" from your own tables.",
97
+ " • Drop the `resource` extractor if a global scope check is what you meant."
98
+ ].join("\n"), V = () => L, H, U = (e) => {
99
+ H = e;
100
+ }, W = () => H, G = (e) => new d({
101
+ required: "access-decision",
102
+ 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>'\`.`
103
+ }), K = (e, n) => {
104
+ let r = e.credentialRejected;
105
+ return r === void 0 ? n : new t({ reason: `${r} — the request carried a credential and it was REJECTED, so this caller resolved to anonymous. The guard then refused (${n.message}); the scope is not the problem. Re-authenticate and retry.` });
106
+ }, q = (e) => {
107
+ let t = Array.isArray(e), n = Object.getPrototypeOf(e);
108
+ if (t ? n !== Array.prototype : n !== Object.prototype && n !== null) return !1;
109
+ for (let n of Reflect.ownKeys(e)) if (t && (typeof n != "string" || n !== "length" && !/^(0|[1-9][0-9]*)$/.test(n)) || !("value" in Object.getOwnPropertyDescriptor(e, n))) return !1;
110
+ return !0;
111
+ }, J = (e, t) => {
112
+ let n, r = 0, i, a = 0, o;
113
+ return (s) => {
114
+ let c = y(e);
115
+ return (c !== n || c.length !== r) && (n = c, r = c.length, i = void 0, a = 0, o = void 0), i === void 0 && c.length >= 32 && ++a > 1 && (o ??= q(c) && q(e) && q(t) && t.every((e) => q(e) && (!("scope" in e) || typeof e.scope == "string" || q(e.scope))), o && (i = new Set(c))), i === void 0 ? c.includes("admin:full") || c.includes(s) : i.has("admin:full") || i.has(s);
116
+ };
117
+ }, Y = (e, t, n) => {
118
+ let r = A(e, t);
119
+ if (r !== void 0) return r;
120
+ if (!t || t.length === 0) return n?.defaultDeny === !0 ? G(n.procedure) : null;
121
+ let i = 0, a;
122
+ for (let n of t) {
123
+ if (E(n) || D(n)) continue;
124
+ if (M(n)) {
125
+ let t = P(e);
126
+ if (t) return t;
127
+ continue;
128
+ }
129
+ if (j(n)) {
130
+ if (e.id !== null && e.id !== void 0) continue;
131
+ return K(e, new d({
132
+ required: "authenticated",
133
+ message: "this procedure requires an authenticated caller"
134
+ }));
135
+ }
136
+ if (w(n)) {
137
+ if (i++, a === void 0 && (t.length >= 512 || i >= 512) && (a = J(e, t)), a === void 0 ? b(e, "admin:full") : a("admin:full")) continue;
138
+ return K(e, new d({
139
+ required: `${n.resourceType}:${n.action}`,
140
+ message: `relationship guard '${n.action}' on '${n.resourceType}' cannot be checked synchronously`
141
+ }));
142
+ }
143
+ let r = I(n.scope);
144
+ if (n.resourceOnly === !0) return K(e, new d({
145
+ required: r[0] ?? "resource",
146
+ message: "resource-only guard cannot be checked synchronously"
147
+ }));
148
+ if (r.length === 0) continue;
149
+ i += r.length, a === void 0 && (t.length >= 512 || i >= 512) && (a = J(e, t));
150
+ let o = n.mode ?? "all", s;
151
+ if (o === "any") {
152
+ if (a === void 0 ? r.some((t) => b(e, t)) : r.some(a)) continue;
153
+ s = r[0];
154
+ } else {
155
+ let t = a === void 0 ? r.find((t) => !b(e, t)) : r.find((e) => !a(e));
156
+ if (t === void 0) continue;
157
+ s = t;
158
+ }
159
+ return K(e, new d({
160
+ required: s,
161
+ message: o === "any" ? `missing all of required scopes [${r.join(", ")}]` : `missing required scope '${s}'`
162
+ }));
163
+ }
164
+ return null;
165
+ }, X = (e, t, n) => {
166
+ let r;
167
+ try {
168
+ r = C(t, n);
169
+ } catch (e) {
170
+ return i.succeed(e);
171
+ }
172
+ let a = (e) => new d({
173
+ required: `${t.resourceType}:${r}`,
174
+ message: e
175
+ });
176
+ if (b(e, "admin:full")) return i.succeed(null);
177
+ let o = H;
178
+ if (o === void 0) return i.succeed(a(`no policy resolver registered — cannot authorize '${r}' on '${t.resourceType}'`));
179
+ let s = t.resource(n);
180
+ return s === void 0 || s === "" ? i.succeed(a(`input does not identify a '${t.resourceType}' to authorize`)) : i.suspend(() => o({
181
+ subject: e,
182
+ action: r,
183
+ resourceType: t.resourceType,
184
+ resourceId: s
185
+ })).pipe(i.catchCause(() => i.succeed(!1)), i.map((n) => Z(e, n, t.resourceType, s, () => a(`no '${r}' relation on ${t.resourceType}:${s}`))));
186
+ }, Z = (e, t, n, r, i) => t === !0 ? null : t === "missing" && e.id !== null && e.id !== void 0 ? new f({
187
+ resourceType: n,
188
+ resourceId: r,
189
+ message: n === null ? `no such resource '${r}'` : `no such ${n} '${r}'`
190
+ }) : i(), Q = (e, t, n, r) => {
191
+ let a = A(e, t);
192
+ if (a !== void 0) return i.succeed(a);
193
+ if (!t || t.length === 0) return i.succeed(Y(e, t, r));
194
+ let o = L, s = t.some(w), c = t.some((e) => "scope" in e && e.resourceOnly === !0);
195
+ return !s && !c && (!o || !t.some((e) => e.resource !== void 0)) ? i.succeed(Y(e, t, r)) : i.gen(function* () {
196
+ for (let r of t) {
197
+ if (E(r) || D(r)) continue;
198
+ if (M(r)) {
199
+ let t = P(e);
200
+ if (t) return t;
201
+ continue;
202
+ }
203
+ if (j(r)) {
204
+ if (e.id !== null && e.id !== void 0) continue;
205
+ return K(e, new d({
206
+ required: "authenticated",
207
+ message: "this procedure requires an authenticated caller"
208
+ }));
209
+ }
210
+ if (w(r)) {
211
+ let t = yield* X(e, r, n);
212
+ if (t) return t;
213
+ continue;
214
+ }
215
+ let t = I(r.scope);
216
+ if (t.length === 0) {
217
+ if (r.resourceOnly === !0) return K(e, new d({
218
+ required: "resource",
219
+ message: "resource-only guard requires a non-empty scope list"
220
+ }));
221
+ continue;
222
+ }
223
+ let a;
224
+ try {
225
+ a = r.resource ? r.resource(n) : void 0;
226
+ } catch (n) {
227
+ if (r.resourceOnly !== !0) throw n;
228
+ return K(e, new d({
229
+ required: t[0],
230
+ message: "resource selector failed"
231
+ }));
232
+ }
233
+ if (r.resourceOnly === !0 && (o === void 0 || typeof a != "string" || a === "")) return K(e, new d({
234
+ required: t[0],
235
+ message: "resource-only guard requires a resolver and a non-empty resource id"
236
+ }));
237
+ let s = [], c = !1;
238
+ for (let n of t) if (r.resourceOnly !== !0 && b(e, n)) s.push(!0);
239
+ else if (a !== void 0 && o !== void 0) {
240
+ let t = yield* i.suspend(() => o({
241
+ subject: e,
242
+ scope: n,
243
+ resource: a
244
+ })).pipe(i.catchCause(() => i.succeed(!1)));
245
+ t === "missing" && (c = !0), s.push(t === !0);
246
+ } else s.push(!1);
247
+ let l = r.mode ?? "all";
248
+ if (!(l === "any" ? s.some(Boolean) : s.every(Boolean))) {
249
+ let n = l === "any" ? t[0] : t[t.findIndex((e, t) => !s[t])];
250
+ if (c && a !== void 0) {
251
+ let t = Z(e, "missing", null, a, () => new d({
252
+ required: n,
253
+ message: `missing required scope '${n}' on resource '${a}'`
254
+ }));
255
+ if (t !== null) return t;
256
+ }
257
+ return new d({
258
+ required: n,
259
+ message: l === "any" ? `missing all of required scopes [${t.join(", ")}]` : `missing required scope '${n}'` + (a === void 0 ? "" : ` on resource '${a}'`)
260
+ });
261
+ }
262
+ }
263
+ return null;
264
+ });
265
+ }, $ = (n, o, s, c) => i.tryPromise({
266
+ try: async () => r(n, (e) => _(n, e)),
267
+ catch: (e) => e
268
+ }).pipe(i.matchEffect({
269
+ onFailure: (n) => a.is(t)(n) || a.is(e)(n) ? i.succeed(n) : i.die(n),
270
+ onSuccess: (e) => Q(e, o, s, c)
271
+ }));
272
+ //#endregion
273
+ export { o as ADMIN_SCOPE, c as APIKEY_ISSUE_ORG_SCOPE, l as APIKEY_ISSUE_OTHER_SCOPE, s as APIKEY_ISSUE_SELF_SCOPE, u as DEVICES_MANAGE_SCOPE, f as NoSuchResource, d as ScopeError, B as advisoryResourceGuardWarning, Y as checkGuards, Q as checkGuardsEffect, T as declaresResourceGuard, y as effectiveScopes, z as findAdvisoryResourceGuards, W as getPolicyGuardResolver, V as getResourceScopeResolver, b as hasEffectiveScope, x as hasScope, j as isAuthenticatedCheck, E as isOpenAccess, w as isPolicyCheck, D as isServiceAccess, M as isUpgradedCheck, G as missingAccessDecision, F as openAccessSpec, $ as recheckGuardsEffect, _ as refreshEffectiveScopes, S as requireScope, C as resolvePolicyAction, O as serviceAccessSpec, k as serviceCallersOf, v as setEffectiveScopes, g as setEffectiveScopesRefresher, U as setPolicyGuardResolver, R as setResourceScopeResolver, p as subjectScopes, N as upgradedGuardShapeError };
@@ -0,0 +1,71 @@
1
+ import { createHash as e, createHmac as t, timingSafeEqual as n } from "node:crypto";
2
+ //#region src/secretWindows.ts
3
+ var r = (e) => t("sha256", e).update("voltro:secret-fingerprint:v1").digest("hex").slice(0, 16), i = Symbol.for("voltro.secretWindows"), a = () => {
4
+ let e = globalThis;
5
+ return e[i] ??= {
6
+ signed: /* @__PURE__ */ new Map(),
7
+ configured: /* @__PURE__ */ new Map(),
8
+ presented: /* @__PURE__ */ new Map(),
9
+ declared: /* @__PURE__ */ new Map(),
10
+ windows: /* @__PURE__ */ new Map()
11
+ }, e[i];
12
+ }, o = (e, t) => {
13
+ let n = e.get(t);
14
+ if (n !== void 0) return n;
15
+ let r = /* @__PURE__ */ new Map();
16
+ return e.set(t, r), r;
17
+ }, s = (e, t, n, i = Date.now()) => {
18
+ let s = o(a().signed, e), c = r(t), l = n ?? Infinity, u = s.get(c), d = Math.max(u?.longestMs ?? 0, l - i);
19
+ s.set(c, {
20
+ until: Math.max(u?.until ?? -Infinity, l),
21
+ longestMs: d
22
+ });
23
+ }, c = (e, t) => {
24
+ let n = a().declared;
25
+ n.set(e, Math.max(n.get(e) ?? 0, t));
26
+ }, l = (e, t, n) => {
27
+ a().configured.set(e, {
28
+ current: r(t),
29
+ previous: n === void 0 || n === "" || n === t ? null : r(n)
30
+ });
31
+ }, u = () => {
32
+ let e = a(), t = [...e.signed].flatMap(([e, t]) => [...t].map(([t, n]) => ({
33
+ purpose: e,
34
+ fingerprint: t,
35
+ signedUntil: n.until,
36
+ longestMs: n.longestMs
37
+ }))), n = [...e.presented].flatMap(([e, t]) => [...t].map(([t, n]) => ({
38
+ purpose: e,
39
+ fingerprint: t,
40
+ at: n
41
+ }))), r = [...e.configured].map(([e, t]) => ({
42
+ purpose: e,
43
+ ...t
44
+ })), i = [...e.declared].map(([e, t]) => ({
45
+ purpose: e,
46
+ lifetimeMs: t
47
+ }));
48
+ return e.signed.clear(), e.presented.clear(), {
49
+ signed: t,
50
+ configured: r,
51
+ presented: n,
52
+ declared: i
53
+ };
54
+ }, d = (e) => {
55
+ let t = /* @__PURE__ */ new Map();
56
+ for (let n of e) o(t, n.purpose).set(n.fingerprint, n.closesAt);
57
+ a().windows = t;
58
+ }, f = (e, t, n) => {
59
+ o(a().windows, e).set(r(t), n);
60
+ }, p = () => {
61
+ let e = a();
62
+ e.windows = /* @__PURE__ */ new Map(), e.signed.clear(), e.configured.clear(), e.presented.clear(), e.declared.clear();
63
+ }, m = (e, t, n = Date.now()) => {
64
+ let i = a().windows.get(e)?.get(r(t));
65
+ return i == null || n <= i;
66
+ }, h = (t) => e("sha256").update(t).digest(), g = (e, t, i, s) => {
67
+ let c = h(t), l = i !== "" && n(c, h(i)), u = s !== void 0 && s !== "" && s !== i && n(c, h(s));
68
+ return l ? !0 : !u || !m(e, s) ? !1 : (o(a().presented, e).set(r(s), Date.now()), !0);
69
+ };
70
+ //#endregion
71
+ export { d as applySecretWindows, g as bearerMatches, p as clearSecretWindows, u as drainSecretActivity, l as noteConfigured, s as noteSigned, c as noteSigningLifetime, m as previousAccepted, r as secretFingerprint, f as setSecretWindow };
@@ -0,0 +1,15 @@
1
+ //#region src/serverErrorBus.ts
2
+ var e = (e) => {
3
+ let t = e.error, n = t instanceof Error ? t.name : typeof t?._tag == "string" ? String(t._tag) : "Error", r = [e.name === void 0 ? e.source : `${e.source} '${e.name}'`, ...typeof e.fields?.runId == "string" ? [`run ${e.fields.runId}`] : []].join(", "), i = /* @__PURE__ */ Error(`[message withheld: ${r} is declared encrypted — read it where the run is shown]`);
4
+ i.name = n;
5
+ let a = t instanceof Error && typeof t.stack == "string" ? t.stack : "", o = a.search(/^\s+at /m);
6
+ return i.stack = `${n}: ${i.message}${o === -1 ? "" : `\n${a.slice(o)}`}`, i;
7
+ }, t = /* @__PURE__ */ new Set(), n = (e) => {
8
+ for (let n of t) try {
9
+ n(e);
10
+ } catch {}
11
+ }, r = (e) => (t.add(e), () => {
12
+ t.delete(e);
13
+ });
14
+ //#endregion
15
+ export { n as publishServerError, r as subscribeServerErrors, e as withheldServerError };
@@ -0,0 +1,14 @@
1
+ import { Schema as e } from "effect";
2
+ //#region src/serviceCall.ts
3
+ var t = class extends e.TaggedError()("ApiUnavailable", {
4
+ api: e.String,
5
+ tag: e.String,
6
+ reason: e.Literals([
7
+ "timeout",
8
+ "transport",
9
+ "closed"
10
+ ]),
11
+ message: e.String
12
+ }) {}, n = (e) => typeof e == "object" && !!e && e._tag === "ApiUnavailable";
13
+ //#endregion
14
+ export { t as ApiUnavailable, n as isApiUnavailable };
package/dist/session.d.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import { Schema } from 'effect';
2
2
 
3
+ /** Replace every recorded window with the boot's computed ones. */
4
+ export declare const applySecretWindows: (windows: ReadonlyArray<{
5
+ readonly purpose: string;
6
+ readonly fingerprint: string;
7
+ readonly closesAt: number | null;
8
+ }>) => void;
9
+
3
10
  /**
4
11
  * Fail-closed check that a real session secret is configured. Throws a plain
5
12
  * `Error` (a fatal boot condition, not a wire error) when
@@ -8,6 +15,14 @@ import { Schema } from 'effect';
8
15
  */
9
16
  export declare const assertProductionSessionSecret: (env?: NodeJS.ProcessEnv) => void;
10
17
 
18
+ /**
19
+ * Does a presented bearer token match the current value or — during a
20
+ * rotation, while its window is open — the previous one? Both comparisons run,
21
+ * in constant time over digests, so neither which one matched nor a length
22
+ * leaks through timing. A match on the previous value is recorded.
23
+ */
24
+ export declare const bearerMatches: (purpose: string, presented: string, current: string, previous?: string) => boolean;
25
+
11
26
  /**
12
27
  * Build a `Set-Cookie` header value. Pure helper — the caller adds it
13
28
  * to the actual HTTP response. Used by the framework's login route
@@ -46,6 +61,9 @@ export declare const checkBearer: (headers: Readonly<Record<string, string | und
46
61
  readonly openWhenUnset?: boolean;
47
62
  }) => boolean;
48
63
 
64
+ /** Forget every record — a test seam, and a process that stops verifying. */
65
+ export declare const clearSecretWindows: () => void;
66
+
49
67
  /** Key id assigned to `VOLTRO_SESSION_SECRET_PREVIOUS` when no explicit
50
68
  * `VOLTRO_SESSION_KID_PREVIOUS` is set. The kid is a non-secret label —
51
69
  * rotation works with just the `_PREVIOUS` secret var. */
@@ -56,6 +74,9 @@ export declare const DEFAULT_PREVIOUS_SESSION_KID: "k-previous";
56
74
  * string produce the same label the resolver would. */
57
75
  export declare const DEFAULT_SESSION_KID: "k0";
58
76
 
77
+ /** What this process signed, holds and was presented since the last drain — for the boot to persist. */
78
+ export declare const drainSecretActivity: () => SecretActivity;
79
+
59
80
  /** A keyed signing secret. `kid` is stamped into the payload on sign and
60
81
  * matched on verify; it's an opaque, non-secret label (e.g. `'2026-06'`). */
61
82
  export declare interface KeyedSecret {
@@ -68,6 +89,31 @@ export declare interface KeyedSecret {
68
89
  * placeholders, not real secrets. */
69
90
  export declare const MIN_SESSION_SECRET_LENGTH: 32;
70
91
 
92
+ /** A resolver read `current` (and, during a rotation, `previous`) for `purpose`. */
93
+ export declare const noteConfigured: (purpose: string, current: string, previous: string | undefined) => void;
94
+
95
+ /**
96
+ * A signer signed something under `secret` that is valid until `expiresAtMs`
97
+ * (undefined: no expiry). The lifetime it signed with is recorded too: it is
98
+ * what bounds the tokens signed before anything was recorded.
99
+ */
100
+ export declare const noteSigned: (purpose: string, secret: string, expiresAtMs: number | undefined, now?: number) => void;
101
+
102
+ /**
103
+ * A configured signer can sign `purpose` tokens valid for up to `lifetimeMs` —
104
+ * declared when it is built, before it signs anything. The first rotation after
105
+ * an upgrade needs it: the previous value signed tokens nothing recorded, and
106
+ * only the configuration says how long they can live.
107
+ */
108
+ export declare const noteSigningLifetime: (purpose: string, lifetimeMs: number) => void;
109
+
110
+ /**
111
+ * Is this previous value still accepted? Yes while its window is open, and
112
+ * when no window is recorded for it (nothing has decided otherwise). No once
113
+ * the latest expiry it signed has passed, or a compromise closed it.
114
+ */
115
+ export declare const previousAccepted: (purpose: string, previous: string, now?: number) => boolean;
116
+
71
117
  /**
72
118
  * Read a cookie value by name from a `Cookie:` header string.
73
119
  * Returns undefined if the cookie isn't present.
@@ -123,6 +169,37 @@ export declare const resolveSessionSecret: () => string;
123
169
  */
124
170
  export declare const resolveSessionSecrets: () => SessionSecrets;
125
171
 
172
+ /** What a process recorded since the last drain: signatures, configured values, previous bearer values that answered. */
173
+ export declare interface SecretActivity {
174
+ /** Per purpose and fingerprint: the latest expiry signed and the longest lifetime signed with. */
175
+ readonly signed: ReadonlyArray<{
176
+ readonly purpose: string;
177
+ readonly fingerprint: string;
178
+ readonly signedUntil: number;
179
+ readonly longestMs: number;
180
+ }>;
181
+ /** Per purpose: the fingerprints of the current and the previous value this process holds. */
182
+ readonly configured: ReadonlyArray<{
183
+ readonly purpose: string;
184
+ readonly current: string;
185
+ readonly previous: string | null;
186
+ }>;
187
+ /** Per purpose: when a previous bearer value last answered. */
188
+ readonly presented: ReadonlyArray<{
189
+ readonly purpose: string;
190
+ readonly fingerprint: string;
191
+ readonly at: number;
192
+ }>;
193
+ /** Per purpose: the longest lifetime a configured signer declared. */
194
+ readonly declared: ReadonlyArray<{
195
+ readonly purpose: string;
196
+ readonly lifetimeMs: number;
197
+ }>;
198
+ }
199
+
200
+ /** A non-reversible name for a secret value: 16 hex of an HMAC under a fixed label. */
201
+ export declare const secretFingerprint: (value: string) => string;
202
+
126
203
  /**
127
204
  * The cookie the framework's own session strategy writes and reads.
128
205
  *
@@ -166,15 +243,16 @@ export declare const SESSION_PAYLOAD_VERSION: 2;
166
243
  */
167
244
  export declare const sessionExpiryFromHeaders: (headers: Record<string, string | undefined>) => number | undefined;
168
245
 
169
- export declare type SessionPayload = typeof SessionPayload_2.Type;
246
+ export declare type SessionPayload = typeof SessionPayloadSchema.Type;
170
247
 
171
- declare const SessionPayload_2: Schema.Struct<{
248
+ declare const SessionPayloadSchema: Schema.Struct<{
172
249
  /** Payload format version. Required and pinned — see the header. */
173
250
  readonly v: Schema.Literal<2>;
174
251
  /** WHO, never WHAT-THEY-MAY-DO. `SubjectIdentity` has no `scopes` field. */
175
252
  readonly subject: Schema.Union<readonly [Schema.Struct<{
176
253
  readonly type: Schema.Literal<"user">;
177
254
  readonly id: Schema.String;
255
+ readonly kind: Schema.optional<Schema.Literal<"guest">>;
178
256
  readonly tenantId: Schema.String;
179
257
  readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
180
258
  }>, Schema.Struct<{
@@ -201,9 +279,9 @@ declare const SessionPayload_2: Schema.Struct<{
201
279
  }>, Schema.Struct<{
202
280
  readonly type: Schema.Literal<"service">;
203
281
  readonly id: Schema.String;
282
+ readonly service: Schema.String;
204
283
  readonly tenantId: Schema.Null;
205
284
  readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
206
- readonly service: Schema.String;
207
285
  }>]>;
208
286
  /** Unix seconds when this session expires. Verify rejects past expiry. */
209
287
  readonly exp: Schema.Number;
@@ -217,8 +295,6 @@ declare const SessionPayload_2: Schema.Struct<{
217
295
  readonly kid: Schema.optional<Schema.String>;
218
296
  }>;
219
297
 
220
- declare type SessionPayload = typeof SessionPayload_2.Type;
221
-
222
298
  /** The resolved secret set: `current` always signs; `previous` is accepted
223
299
  * on verify during a rotation window. */
224
300
  export declare interface SessionSecrets {
@@ -242,6 +318,9 @@ export declare interface SetCookieOptions {
242
318
  readonly domain?: string;
243
319
  }
244
320
 
321
+ /** Record one window by value — a test seam and a single-process embedder's. */
322
+ export declare const setSecretWindow: (purpose: string, previous: string, closesAt: number | null) => void;
323
+
245
324
  export declare interface SignOptions {
246
325
  /** TTL in seconds. Defaults to 7 days. */
247
326
  readonly ttlSeconds?: number;
@@ -278,6 +357,9 @@ export declare const signSession: (subject: Subject | SubjectIdentity, secret: s
278
357
 
279
358
  declare const Subject: Schema.Union<readonly [Schema.Struct<{
280
359
  readonly type: Schema.Literal<"user">;
360
+ /** A guest has a stable identity but has not linked a registered account.
361
+ * This identity mark survives session signing; it never grants authority. */
362
+ readonly kind: Schema.optional<Schema.Literal<"guest">>;
281
363
  readonly id: Schema.String;
282
364
  readonly tenantId: Schema.String;
283
365
  readonly scopes: Schema.optional<Schema.$Array<Schema.String>>;
@@ -339,6 +421,7 @@ declare type Subject = typeof Subject.Type;
339
421
  declare const SubjectIdentity: Schema.Union<readonly [Schema.Struct<{
340
422
  readonly type: Schema.Literal<"user">;
341
423
  readonly id: Schema.String;
424
+ readonly kind: Schema.optional<Schema.Literal<"guest">>;
342
425
  readonly tenantId: Schema.String;
343
426
  readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
344
427
  }>, Schema.Struct<{
@@ -381,9 +464,9 @@ declare const SubjectIdentity: Schema.Union<readonly [Schema.Struct<{
381
464
  }>, Schema.Struct<{
382
465
  readonly type: Schema.Literal<"service">;
383
466
  readonly id: Schema.String;
467
+ readonly service: Schema.String;
384
468
  readonly tenantId: Schema.Null;
385
469
  readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
386
- readonly service: Schema.String;
387
470
  }>]>;
388
471
 
389
472
  declare type SubjectIdentity = typeof SubjectIdentity.Type;