@voltro/plugin-licensing 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.
@@ -0,0 +1,31 @@
1
+ import { Effect } from 'effect';
2
+ import { VoidIfEmpty } from 'effect/Types';
3
+ import { YieldableError } from 'effect/Cause';
4
+
5
+ /** Authenticated JSON retrieval from the existing Cloud /licenses route.
6
+ * HTTPS, or literal local loopback HTTP for private development/fixtures.
7
+ * Redirects refuse before credential forwarding. No issuer or verification key. */
8
+ export declare const cloudLicenseSnapshot: (options: CloudLicenseSnapshotOptions) => LicenseSnapshotSource;
9
+
10
+ export declare interface CloudLicenseSnapshotOptions {
11
+ readonly url: string;
12
+ /** Read the actual license:read credential at refresh time. Never a signing key. */
13
+ readonly bearerToken: () => Effect.Effect<string, LicenseSnapshotFetchError>;
14
+ readonly timeoutMs?: number;
15
+ }
16
+
17
+ /** Redacted transport refusal. No response body, URL, token or credential cause. */
18
+ export declare class LicenseSnapshotFetchError extends LicenseSnapshotFetchError_base<{
19
+ readonly reason: 'credentials' | 'transport' | 'status' | 'response';
20
+ readonly status?: number;
21
+ }> {
22
+ }
23
+
24
+ declare const LicenseSnapshotFetchError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
25
+ readonly _tag: "LicenseSnapshotFetchError";
26
+ } & Readonly<A>;
27
+
28
+ /** Swappable server transport; verification and installation remain with Licensing. */
29
+ export declare type LicenseSnapshotSource = () => Effect.Effect<string, LicenseSnapshotFetchError>;
30
+
31
+ export { }
package/dist/cloud.js ADDED
@@ -0,0 +1,61 @@
1
+ import { Data as e, Effect as t } from "effect";
2
+ //#region src/cloud.ts
3
+ var n = class extends e.TaggedError("LicenseSnapshotFetchError") {}, r = 131072, i = 65536, a = (e) => {
4
+ let a = new URL(e.url), o = a.hostname === "127.0.0.1" || a.hostname === "[::1]";
5
+ if (a.username !== "" || a.password !== "" || a.hash !== "" || a.protocol !== "https:" && !(a.protocol === "http:" && o)) throw Error("Cloud license retrieval requires HTTPS or literal local loopback HTTP, without URL credentials or fragments");
6
+ let s = a.href, c = e.bearerToken, l = e.timeoutMs ?? 3e4;
7
+ if (typeof c != "function" || !Number.isSafeInteger(l) || l <= 0 || l > 3e5) throw Error("Cloud license retrieval requires an actual credential port and bounded positive timeout");
8
+ return () => t.gen(function* () {
9
+ let e = yield* c();
10
+ return typeof e != "string" || e.length === 0 || e.length > i || !/^[\x21-\x7e]+$/.test(e) ? yield* t.fail(new n({ reason: "credentials" })) : yield* t.tryPromise({
11
+ try: async (t) => {
12
+ let a = new AbortController(), o = () => {
13
+ a.abort();
14
+ };
15
+ t.addEventListener("abort", o, { once: !0 }), t.aborted && a.abort();
16
+ let c = setTimeout(o, l);
17
+ try {
18
+ let t = await fetch(s, {
19
+ headers: { authorization: `Bearer ${e}` },
20
+ redirect: "error",
21
+ signal: a.signal
22
+ });
23
+ if (!t.ok) throw await t.body?.cancel(), new n({
24
+ reason: "status",
25
+ status: t.status
26
+ });
27
+ if (!/^application\/json(?:\s*;|\s*$)/i.test(t.headers.get("content-type") ?? "") || t.body === null) throw await t.body?.cancel(), new n({ reason: "response" });
28
+ let o = t.headers.get("content-length");
29
+ if (o !== null && (!/^\d+$/.test(o) || Number(o) > r)) throw await t.body.cancel(), new n({ reason: "response" });
30
+ let c = t.body.getReader(), l = [], u = 0;
31
+ try {
32
+ for (;;) {
33
+ let e = await c.read();
34
+ if (e.done) break;
35
+ if (u += e.value.byteLength, u > r) throw await c.cancel(), new n({ reason: "response" });
36
+ l.push(e.value);
37
+ }
38
+ } finally {
39
+ c.releaseLock();
40
+ }
41
+ let d = new Uint8Array(u), f = 0;
42
+ for (let e of l) d.set(e, f), f += e.byteLength;
43
+ let p;
44
+ try {
45
+ p = JSON.parse(new TextDecoder("utf-8", { fatal: !0 }).decode(d));
46
+ } catch {
47
+ throw new n({ reason: "response" });
48
+ }
49
+ let m = typeof p == "object" && p && !Array.isArray(p) && "token" in p ? p.token : void 0;
50
+ if (typeof m != "string" || m.length > i || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(m)) throw new n({ reason: "response" });
51
+ return m;
52
+ } finally {
53
+ clearTimeout(c), t.removeEventListener("abort", o);
54
+ }
55
+ },
56
+ catch: (e) => e instanceof n ? e : new n({ reason: "transport" })
57
+ });
58
+ });
59
+ };
60
+ //#endregion
61
+ export { n as LicenseSnapshotFetchError, a as cloudLicenseSnapshot };
@@ -0,0 +1,39 @@
1
+ import { Schema } from 'effect';
2
+
3
+ /** A numeric cap, or `'unlimited'` for no cap. */
4
+ declare type EntitlementLimit = number | 'unlimited';
5
+
6
+ /** License names, not package installation or accepted capability claims. */
7
+ export declare const GAMES_PLUGIN_GRANTS: readonly ["rooms", "matchmaking", "leaderboards", "social", "chat", "guilds", "inventory", "economy", "progression", "liveops", "players", "saves", "sanctions", "gm"];
8
+
9
+ export declare const GamesLicenseEdition: Schema.Literals<readonly ["games", "games-enterprise"]>;
10
+
11
+ export declare type GamesLicenseEdition = typeof GamesLicenseEdition.Type;
12
+
13
+ export declare interface GamesSignLicenseInput extends Omit<SignLicenseInput, 'edition'> {
14
+ readonly edition: GamesLicenseEdition;
15
+ }
16
+
17
+ declare interface SignLicenseInput {
18
+ /** Scope the license to one tenant id; omit for a deployment-wide license. */
19
+ readonly tenant?: string;
20
+ /** Edition label (e.g. `'pro'`, `'enterprise'`). */
21
+ readonly edition: string;
22
+ /** Per-key numeric limits (`'unlimited'` for no cap). */
23
+ readonly entitlements: Readonly<Record<string, EntitlementLimit>>;
24
+ /** Boolean feature flags to grant. */
25
+ readonly features?: ReadonlyArray<string>;
26
+ /** Plugins licensed for production use (names without the
27
+ * `@voltro/plugin-` prefix, e.g. `['storage']`). */
28
+ readonly plugins?: ReadonlyArray<string>;
29
+ /** Seconds from `now` until the license expires. */
30
+ readonly ttlSeconds: number;
31
+ /** Key id stamped into the JWS header (public-key rotation). */
32
+ readonly kid?: string;
33
+ }
34
+
35
+ /** Issuer-side composition. Retains actual caller-owned base grants, limits,
36
+ * seats and TTL. It signs nothing and invents no price, key or entitlement. */
37
+ export declare const withGamesPluginGrants: (input: GamesSignLicenseInput) => SignLicenseInput;
38
+
39
+ export { }
package/dist/games.js ADDED
@@ -0,0 +1,65 @@
1
+ import { Schema as e } from "effect";
2
+ //#region src/games.ts
3
+ var t = e.Literals(["games", "games-enterprise"]), n = Object.freeze([
4
+ "rooms",
5
+ "matchmaking",
6
+ "leaderboards",
7
+ "social",
8
+ "chat",
9
+ "guilds",
10
+ "inventory",
11
+ "economy",
12
+ "progression",
13
+ "liveops",
14
+ "players",
15
+ "saves",
16
+ "sanctions",
17
+ "gm"
18
+ ]), r = (r) => {
19
+ if (!r || Object.getPrototypeOf(r) !== Object.prototype || Object.getOwnPropertySymbols(r).length) throw TypeError("Invalid Games license input");
20
+ let i = Object.getOwnPropertyDescriptors(r);
21
+ if (Object.entries(i).some(([e, t]) => ![
22
+ "tenant",
23
+ "edition",
24
+ "entitlements",
25
+ "features",
26
+ "plugins",
27
+ "ttlSeconds",
28
+ "kid"
29
+ ].includes(e) || !t.enumerable || !("value" in t))) throw TypeError("Invalid Games license input");
30
+ let a = Object.fromEntries(Object.entries(i).map(([e, t]) => [e, t.value]));
31
+ e.decodeUnknownSync(e.toType(t))(a.edition);
32
+ let o = (e) => typeof e == "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/.test(e);
33
+ if (a.tenant !== void 0 && !o(a.tenant) || a.kid !== void 0 && !o(a.kid) || !Number.isSafeInteger(a.ttlSeconds) || a.ttlSeconds <= 0) throw TypeError("Invalid Games license input");
34
+ let s = (e) => {
35
+ if (e === void 0) return Object.freeze([]);
36
+ if (!Array.isArray(e) || e.length > 256 || Reflect.ownKeys(e).length !== e.length + 1) throw TypeError("Invalid Games license grants");
37
+ let t = [];
38
+ for (let n = 0; n < e.length; n++) {
39
+ let r = Object.getOwnPropertyDescriptor(e, String(n));
40
+ if (!r?.enumerable || !("value" in r) || !o(r.value)) throw TypeError("Invalid Games license grants");
41
+ t.push(r.value);
42
+ }
43
+ return Object.freeze([...new Set(t)]);
44
+ }, c = a.entitlements;
45
+ if (!c || Object.getPrototypeOf(c) !== Object.prototype || Reflect.ownKeys(c).length > 256) throw TypeError("Invalid Games license limits");
46
+ let l = {};
47
+ for (let e of Reflect.ownKeys(c)) {
48
+ let t = Object.getOwnPropertyDescriptor(c, e);
49
+ if (!o(e) || !t?.enumerable || !("value" in t)) throw TypeError("Invalid Games license limits");
50
+ let n = t.value;
51
+ if (n !== "unlimited" && (typeof n != "number" || !Number.isFinite(n) || n < 0)) throw TypeError("Invalid Games license limits");
52
+ Object.defineProperty(l, e, {
53
+ value: n,
54
+ enumerable: !0
55
+ });
56
+ }
57
+ return Object.freeze({
58
+ ...a,
59
+ entitlements: Object.freeze(l),
60
+ features: s(a.features),
61
+ plugins: Object.freeze([.../* @__PURE__ */ new Set([...s(a.plugins), ...n])])
62
+ });
63
+ };
64
+ //#endregion
65
+ export { n as GAMES_PLUGIN_GRANTS, t as GamesLicenseEdition, r as withGamesPluginGrants };
package/dist/gate.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { VoltroPlugin } from '@voltro/protocol';
2
+
3
+ /** Server-only factory admission. The ordinary lifecycle still owns order,
4
+ * hook execution and cleanup; no descriptor/transport/store/tick contribution. */
5
+ export declare const defineLicensedGamePlugin: (descriptor: VoltroPlugin) => VoltroPlugin;
6
+
7
+ export { }
package/dist/gate.js ADDED
@@ -0,0 +1,28 @@
1
+ import { GAMES_PLUGIN_GRANTS as e } from "./games.js";
2
+ import { PluginNotLicensed as t, licenseStatus as n, requireLicensedPlugin as r } from "./index.js";
3
+ import { Effect as i } from "effect";
4
+ import { definePlugin as a } from "@voltro/protocol";
5
+ //#region src/gate.ts
6
+ var o = (o) => {
7
+ let s = o.baseName ?? o.name, c = e.find((e) => s === `@voltro/plugin-${e}`);
8
+ if (c === void 0) throw TypeError("Game license admission requires an actual canonical Games plugin identity");
9
+ let l = o.onActivate;
10
+ return a({
11
+ ...o,
12
+ onActivate: (e) => i.gen(function* () {
13
+ let a = (e.env.NODE_ENV ?? process.env.NODE_ENV ?? "production").toLowerCase();
14
+ if (a !== "development" && a !== "test") {
15
+ let e = n();
16
+ if (e.state !== "active" && e.state !== "grace" || e.tenant !== void 0) return yield* i.fail(new t({ plugin: c }));
17
+ yield* r(c);
18
+ }
19
+ let o = l?.(e);
20
+ i.isEffect(o) ? yield* o : o !== void 0 && (yield* i.tryPromise({
21
+ try: () => Promise.resolve(o),
22
+ catch: (e) => e
23
+ }));
24
+ })
25
+ });
26
+ };
27
+ //#endregion
28
+ export { o as defineLicensedGamePlugin };
package/dist/index.d.ts CHANGED
@@ -67,6 +67,20 @@ export declare interface LicenseSnapshot {
67
67
  readonly state: () => 'active' | 'grace' | 'expired';
68
68
  }
69
69
 
70
+ /** Redacted transport refusal. No response body, URL, token or credential cause. */
71
+ export declare class LicenseSnapshotFetchError extends LicenseSnapshotFetchError_base<{
72
+ readonly reason: 'credentials' | 'transport' | 'status' | 'response';
73
+ readonly status?: number;
74
+ }> {
75
+ }
76
+
77
+ declare const LicenseSnapshotFetchError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
78
+ readonly _tag: "LicenseSnapshotFetchError";
79
+ } & Readonly<A>;
80
+
81
+ /** Swappable server transport; verification and installation remain with Licensing. */
82
+ export declare type LicenseSnapshotSource = () => Effect.Effect<string, LicenseSnapshotFetchError>;
83
+
70
84
  /** The service handlers can `yield*` to read the current license directly (for
71
85
  * feature checks beyond billing's metered entitlements). */
72
86
  /**
@@ -111,6 +125,13 @@ declare const LicenseVerifyError_base: new <A extends Record<string, any> = {}>(
111
125
  readonly _tag: "LicenseVerifyError";
112
126
  } & Readonly<A>;
113
127
 
128
+ export declare interface LicenseVerifyOptions {
129
+ /** Explicit expiry-only grace. Signature, not-before and claim checks remain strict. */
130
+ readonly graceSeconds?: number;
131
+ /** Trusted server/test clock in whole Unix seconds. Defaults to the actual clock. */
132
+ readonly nowSeconds?: number;
133
+ }
134
+
114
135
  export declare const licensingPlugin: (options?: LicensingPluginOptions) => VoltroPlugin;
115
136
 
116
137
  export declare interface LicensingPluginOptions {
@@ -119,11 +140,10 @@ export declare interface LicensingPluginOptions {
119
140
  readonly publicKey?: string;
120
141
  /** The signed license token. Falls back to `VOLTRO_LICENSE_KEY`. */
121
142
  readonly licenseKey?: string;
122
- /** URL to periodically re-fetch a fresh signed license (entitlement sync).
123
- * With `refreshMs`, a cluster-coordinated refresh re-verifies + re-installs
124
- * the snapshot. (The cloud endpoint that serves it ships in a later milestone.) */
125
- readonly snapshotUrl?: string;
126
- /** Refresh interval (ms) for `snapshotUrl`. Default 15 minutes. */
143
+ /** Actual server transport port for periodically retrieving a signed token.
144
+ * Verification remains offline; use ./cloud for authenticated JSON retrieval. */
145
+ readonly fetchSnapshot?: LicenseSnapshotSource;
146
+ /** Refresh interval (ms) for `fetchSnapshot`. Default 15 minutes. */
127
147
  readonly refreshMs?: number;
128
148
  /** Seconds an expired license is still honored (grace) before it goes dark.
129
149
  * Default 0. */
@@ -150,8 +170,9 @@ export declare const requireLicensedPlugin: (plugin: string) => Effect.Effect<vo
150
170
  /**
151
171
  * Verify a license token against an Ed25519 public key (SPKI/PEM), OFFLINE.
152
172
  * Returns the decoded {@link LicenseClaims} or a typed {@link LicenseVerifyError}
153
- * (expiry is enforced by `jose`). No network access.
173
+ * (expiry is enforced by `jose`, with explicit authenticated expiry-only grace).
174
+ * No network access. Grace never changes the clock tolerance for other claims.
154
175
  */
155
- export declare const verifyLicense: (token: string, publicKeyPem: string) => Effect.Effect<LicenseClaims, LicenseVerifyError>;
176
+ export declare const verifyLicense: (token: string, publicKeyPem: string, options?: LicenseVerifyOptions) => Effect.Effect<LicenseClaims, LicenseVerifyError>;
156
177
 
157
178
  export { }
package/dist/index.js CHANGED
@@ -1,84 +1,20 @@
1
- import { Data as e, Effect as t } from "effect";
2
- import { definePlugin as n, definePluginService as r, pluginInstanceName as i } from "@voltro/protocol";
3
- import { pluginEnv as a } from "@voltro/env";
4
- import { importSPKI as o, jwtVerify as s } from "jose";
5
- //#region src/verify.ts
6
- var c = class extends e.TaggedError("LicenseVerifyError") {}, l = (e) => typeof e == "object" && e && "code" in e ? String(e.code) : "", u = (e) => {
7
- let t = e instanceof Error ? e.message : String(e);
8
- return l(e) === "ERR_JWT_EXPIRED" ? new c({
9
- reason: "expired",
10
- message: t
11
- }) : new c({
12
- reason: "invalid",
13
- message: t
14
- });
15
- }, d = (e) => e === "unlimited" || typeof e == "number" && Number.isFinite(e) && e >= 0, f = (e, n) => {
16
- let r = e.edition, i = e.entitlements, a = e.features;
17
- if (typeof r != "string" || typeof i != "object" || !i) return t.fail(new c({
18
- reason: "malformed",
19
- message: "missing edition/entitlements"
20
- }));
21
- let o = {};
22
- for (let [e, n] of Object.entries(i)) {
23
- if (!d(n)) return t.fail(new c({
24
- reason: "malformed",
25
- message: `bad entitlement '${e}'`
26
- }));
27
- o[e] = n;
28
- }
29
- let s = Array.isArray(a) ? a.filter((e) => typeof e == "string") : [], l = e.plugins, u = Array.isArray(l) ? l.filter((e) => typeof e == "string") : [];
30
- return t.succeed({
31
- ...typeof e.tenant == "string" ? { tenant: e.tenant } : {},
32
- edition: r,
33
- entitlements: o,
34
- features: s,
35
- plugins: u,
36
- exp: typeof e.exp == "number" ? e.exp : 0,
37
- ...typeof e.iat == "number" ? { iat: e.iat } : {},
38
- ...n === void 0 ? {} : { kid: n }
39
- });
40
- }, p = (e, n) => t.tryPromise({
41
- try: () => o(n, "EdDSA"),
42
- catch: (e) => new c({
43
- reason: "key",
44
- message: e instanceof Error ? e.message : String(e)
45
- })
46
- }).pipe(t.flatMap((n) => t.tryPromise({
47
- try: async () => {
48
- let { payload: t, protectedHeader: r } = await s(e, n, { algorithms: ["EdDSA"] });
49
- return {
50
- payload: t,
51
- kid: r.kid
52
- };
53
- },
54
- catch: u
55
- })), t.flatMap(({ payload: e, kid: t }) => f(e, t))), m = (e, t = {}) => {
56
- let n = new Set(e.features), r = new Set(e.plugins), i = t.graceSeconds ?? 0, a = t.now ?? (() => Date.now() / 1e3), o = () => e.exp <= 0 || a() <= e.exp + i;
57
- return {
58
- claims: e,
59
- state: () => e.exp <= 0 || a() <= e.exp ? "active" : o() ? "grace" : "expired",
60
- limitFor: (t, n) => {
61
- if (!o() || e.tenant !== void 0 && e.tenant !== t) return;
62
- let r = e.entitlements[n];
63
- if (r !== void 0) return r === "unlimited" ? Infinity : r;
64
- },
65
- hasFeature: (e) => o() && n.has(e),
66
- allowsPlugin: (e) => o() && r.has(e)
67
- };
68
- }, h, g = (e) => {
69
- h = e;
70
- }, _ = () => h, v, y = (e) => {
71
- v = e;
72
- }, b = () => v, x = (e, n) => t.sync(() => {
73
- let t = _()?.limitFor(e, n);
74
- return t === void 0 ? null : t;
75
- }), S = () => {
76
- let e = _(), t = e === void 0 ? void 0 : {
1
+ import { GAMES_PLUGIN_GRANTS as e } from "./games.js";
2
+ import { LicensingStatusSchema as t } from "./inspect.js";
3
+ import { LicenseVerifyError as n, verifyLicense as r } from "./verify.js";
4
+ import { getRejection as i, getSnapshot as a, makeSnapshot as o, setRejection as s, setSnapshot as c } from "./snapshot.js";
5
+ import { entitlementResolver as l } from "./resolver.js";
6
+ import { LicenseSnapshotFetchError as u } from "./cloud.js";
7
+ import { Data as d, Effect as f, Result as p, Schema as m } from "effect";
8
+ import { definePlugin as h, definePluginService as g, pluginInstanceName as _ } from "@voltro/protocol";
9
+ import { pluginEnv as v } from "@voltro/env";
10
+ //#region src/index.ts
11
+ var y = () => {
12
+ let e = a(), t = e === void 0 ? void 0 : {
77
13
  state: e.state(),
78
14
  edition: e.claims.edition,
79
15
  expiresAt: e.claims.exp,
80
16
  ...e.claims.tenant === void 0 ? {} : { tenant: e.claims.tenant }
81
- }, n = b();
17
+ }, n = i();
82
18
  return n === void 0 ? t ?? { state: "none" } : {
83
19
  state: "rejected",
84
20
  reason: n.reason,
@@ -89,7 +25,7 @@ var c = class extends e.TaggedError("LicenseVerifyError") {}, l = (e) => typeof
89
25
  expiresAt: t.expiresAt
90
26
  } }
91
27
  };
92
- }, C = a([{
28
+ }, b = v([{
93
29
  name: "VOLTRO_LICENSE_PUBLIC_KEY",
94
30
  required: !1,
95
31
  secret: !1,
@@ -97,68 +33,97 @@ var c = class extends e.TaggedError("LicenseVerifyError") {}, l = (e) => typeof
97
33
  }, {
98
34
  name: "VOLTRO_LICENSE_KEY",
99
35
  required: !1,
100
- secret: !1,
101
- description: "The signed EdDSA license token, verified offline at boot."
102
- }]), { Tag: w, Live: T } = r("@voltro/plugin-licensing/LicenseService", {
103
- snapshot: () => _(),
104
- status: () => S(),
105
- limitFor: (e, t) => _()?.limitFor(e, t),
106
- hasFeature: (e) => _()?.hasFeature(e) ?? !1,
107
- allowsPlugin: (e) => _()?.allowsPlugin(e) ?? !1,
108
- edition: () => _()?.claims.edition
109
- }), E = class extends e.TaggedError("PluginNotLicensed") {}, D = (e) => _()?.allowsPlugin(e) ?? !1, O = (e) => D(e) ? t.void : t.fail(new E({ plugin: e })), k = (e = {}) => {
110
- let r = C.read("VOLTRO_LICENSE_PUBLIC_KEY", e.publicKey), a = C.read("VOLTRO_LICENSE_KEY", e.licenseKey), o = e.graceSeconds ?? 0, s = e.refreshMs ?? 9e5, c, l = (e, n) => p(e, n).pipe(t.match({
36
+ secret: !0,
37
+ description: "The signed EdDSA license token, verified offline at boot; private local custody."
38
+ }]), { Tag: x, Live: S } = g("@voltro/plugin-licensing/LicenseService", {
39
+ snapshot: () => a(),
40
+ status: () => y(),
41
+ limitFor: (e, t) => a()?.limitFor(e, t),
42
+ hasFeature: (e) => a()?.hasFeature(e) ?? !1,
43
+ allowsPlugin: (e) => a()?.allowsPlugin(e) ?? !1,
44
+ edition: () => a()?.claims.edition
45
+ }), C = class extends d.TaggedError("PluginNotLicensed") {}, w = (e) => a()?.allowsPlugin(e) ?? !1, T = (e) => w(e) ? f.void : f.fail(new C({ plugin: e })), E = (n = {}) => {
46
+ let l = b.read("VOLTRO_LICENSE_PUBLIC_KEY", n.publicKey), u = b.read("VOLTRO_LICENSE_KEY", n.licenseKey), d = n.graceSeconds ?? 0, g = n.refreshMs ?? 9e5, v = n.fetchSnapshot;
47
+ if (!Number.isSafeInteger(g) || g <= 0 || !Number.isSafeInteger(d) || d < 0 || v !== void 0 && typeof v != "function") throw Error("Licensing requires positive whole-millisecond refresh, nonnegative whole-second grace and a callable snapshot port");
48
+ let y, x = 0, C = !1, w = (e, t, n) => r(e, t, { graceSeconds: d }).pipe(f.match({
111
49
  onSuccess: (e) => {
112
- g(m(e, { graceSeconds: o })), y(void 0), c?.info("license active", {
50
+ C && x === n && (c(o(e, { graceSeconds: d })), s(void 0), y?.info("license active", {
113
51
  edition: e.edition,
114
52
  entitlements: Object.keys(e.entitlements).length,
115
53
  exp: e.exp
116
- });
54
+ }));
117
55
  },
118
56
  onFailure: (e) => {
119
- y({
57
+ C && x === n && (s({
120
58
  reason: e.reason,
121
59
  message: e.message
122
- }), c?.warn("license verification failed — entitlements fall back to static plan", {
60
+ }), y?.warn("license verification failed — entitlements fall back to static plan", {
123
61
  reason: e.reason,
124
62
  message: e.message
125
- });
63
+ }));
126
64
  }
127
65
  }));
128
- return n({
129
- name: i({
66
+ return h({
67
+ name: _({
130
68
  base: "@voltro/plugin-licensing",
131
- instance: e.name
69
+ instance: n.name
132
70
  }),
71
+ baseName: "@voltro/plugin-licensing",
72
+ permissions: ["inspect:read"],
73
+ inspectEndpoints: [{
74
+ method: "GET",
75
+ path: "/status",
76
+ scope: "process",
77
+ description: "Redacted current-process license status; no credentials, tenant IDs or entitlement values.",
78
+ handler: () => f.try({
79
+ try: () => {
80
+ let n = a(), r = i(), o = n?.state(), s = n === void 0 ? null : {
81
+ state: o,
82
+ edition: n.claims.edition,
83
+ expiresAt: n.claims.exp,
84
+ scope: n.claims.tenant === void 0 ? "deployment" : "tenant",
85
+ gamesGrants: o === "expired" ? [] : e.filter((e) => n.allowsPlugin(e))
86
+ };
87
+ return {
88
+ kind: "json",
89
+ data: m.decodeUnknownSync(t)({
90
+ observedAt: Date.now(),
91
+ activated: C,
92
+ state: r === void 0 ? o ?? "none" : "rejected",
93
+ installed: s,
94
+ rejection: r?.reason ?? null,
95
+ graceSeconds: d,
96
+ refreshIntervalMs: v === void 0 || l === void 0 ? null : g
97
+ })
98
+ };
99
+ },
100
+ catch: () => /* @__PURE__ */ Error("License inspection unavailable")
101
+ })
102
+ }],
133
103
  description: "Offline-verified EdDSA license keys + entitlement snapshots; feeds @voltro/plugin-billing via entitlementResolver.",
134
- declaredEnv: C.declared,
135
- services: T,
136
- onActivate: (e) => t.gen(function* () {
137
- if (c = e.logger, r === void 0 || a === void 0) {
138
- e.logger.debug("licensing: no license configured — static plan entitlements apply");
139
- return;
140
- }
141
- yield* l(a, r);
104
+ declaredEnv: b.declared,
105
+ services: S,
106
+ onActivate: (e) => f.gen(function* () {
107
+ y = e.logger, C = !0;
108
+ let t = ++x;
109
+ c(void 0), s(void 0), l === void 0 || u === void 0 ? e.logger.debug("licensing: no license configured — static plan entitlements apply") : yield* w(u, l, t);
142
110
  }),
143
- bindDataStore: (n, i) => {
144
- let a = e.snapshotUrl;
145
- if (a === void 0 || r === void 0 || i?.scheduleCoordinated === void 0) return;
146
- let o = r;
147
- i.scheduleCoordinated("licensing.refresh", s, async () => {
148
- try {
149
- let e = await fetch(a);
150
- if (!e.ok) return;
151
- let n = (await e.text()).trim();
152
- n.length > 0 && await t.runPromise(l(n, o));
153
- } catch (e) {
154
- c?.warn("license refresh failed", { message: e instanceof Error ? e.message : String(e) });
155
- }
111
+ bindDataStore: (e, t) => {
112
+ if (v === void 0 || l === void 0 || t?.scheduleCoordinated === void 0) return;
113
+ let n = l;
114
+ t.scheduleCoordinated("licensing.refresh", g, async () => {
115
+ if (!C) return;
116
+ let e = x, t = await f.runPromise(f.result(v()));
117
+ C && x === e && (p.isFailure(t) ? y?.warn("license refresh failed — retaining the last verified snapshot", {
118
+ reason: t.failure.reason,
119
+ ...t.failure.status === void 0 ? {} : { status: t.failure.status }
120
+ }) : await f.runPromise(w(t.success, n, e)));
156
121
  });
157
122
  },
158
- onDeactivate: () => t.sync(() => {
159
- g(void 0), y(void 0);
123
+ onDeactivate: () => f.sync(() => {
124
+ C = !1, x++, c(void 0), s(void 0);
160
125
  })
161
126
  });
162
127
  };
163
128
  //#endregion
164
- export { w as LicenseService, c as LicenseVerifyError, E as PluginNotLicensed, x as entitlementResolver, D as licenseAllowsPlugin, S as licenseStatus, k as licensingPlugin, O as requireLicensedPlugin, p as verifyLicense };
129
+ export { x as LicenseService, u as LicenseSnapshotFetchError, n as LicenseVerifyError, C as PluginNotLicensed, l as entitlementResolver, w as licenseAllowsPlugin, y as licenseStatus, E as licensingPlugin, T as requireLicensedPlugin, r as verifyLicense };
@@ -0,0 +1,56 @@
1
+ import { Schema } from 'effect';
2
+
3
+ declare const base: Schema.Struct<{
4
+ observedAt: Schema.Number;
5
+ activated: Schema.Boolean;
6
+ state: Schema.Literals<readonly ["none", "active", "grace", "expired", "rejected"]>;
7
+ installed: Schema.NullOr<Schema.Struct<{
8
+ readonly state: Schema.Literals<readonly ["active", "grace", "expired"]>;
9
+ readonly edition: Schema.String;
10
+ readonly expiresAt: Schema.Number;
11
+ readonly scope: Schema.Literals<readonly ["deployment", "tenant"]>;
12
+ readonly gamesGrants: Schema.$Array<Schema.Literals<readonly ["rooms", "matchmaking", "leaderboards", "social", "chat", "guilds", "inventory", "economy", "progression", "liveops", "players", "saves", "sanctions", "gm"]>>;
13
+ }>>;
14
+ rejection: Schema.NullOr<Schema.Literals<readonly ["expired", "invalid", "key", "malformed"]>>;
15
+ graceSeconds: Schema.Number;
16
+ refreshIntervalMs: Schema.NullOr<Schema.Number>;
17
+ }>;
18
+
19
+ export declare const isLicensingStatusConsistent: (value: typeof base.Type) => boolean;
20
+
21
+ export declare type LicensingStatus = typeof LicensingStatusSchema.Type;
22
+
23
+ export declare const LicensingStatusFields: {
24
+ observedAt: Schema.Number;
25
+ activated: Schema.Boolean;
26
+ state: Schema.Literals<readonly ["none", "active", "grace", "expired", "rejected"]>;
27
+ installed: Schema.NullOr<Schema.Struct<{
28
+ readonly state: Schema.Literals<readonly ["active", "grace", "expired"]>;
29
+ readonly edition: Schema.String;
30
+ readonly expiresAt: Schema.Number;
31
+ readonly scope: Schema.Literals<readonly ["deployment", "tenant"]>;
32
+ readonly gamesGrants: Schema.$Array<Schema.Literals<readonly ["rooms", "matchmaking", "leaderboards", "social", "chat", "guilds", "inventory", "economy", "progression", "liveops", "players", "saves", "sanctions", "gm"]>>;
33
+ }>>;
34
+ rejection: Schema.NullOr<Schema.Literals<readonly ["expired", "invalid", "key", "malformed"]>>;
35
+ graceSeconds: Schema.Number;
36
+ refreshIntervalMs: Schema.NullOr<Schema.Number>;
37
+ };
38
+
39
+ /** Browser-safe redacted process observation, never a token or grant authority. */
40
+ export declare const LicensingStatusSchema: Schema.Struct<{
41
+ observedAt: Schema.Number;
42
+ activated: Schema.Boolean;
43
+ state: Schema.Literals<readonly ["none", "active", "grace", "expired", "rejected"]>;
44
+ installed: Schema.NullOr<Schema.Struct<{
45
+ readonly state: Schema.Literals<readonly ["active", "grace", "expired"]>;
46
+ readonly edition: Schema.String;
47
+ readonly expiresAt: Schema.Number;
48
+ readonly scope: Schema.Literals<readonly ["deployment", "tenant"]>;
49
+ readonly gamesGrants: Schema.$Array<Schema.Literals<readonly ["rooms", "matchmaking", "leaderboards", "social", "chat", "guilds", "inventory", "economy", "progression", "liveops", "players", "saves", "sanctions", "gm"]>>;
50
+ }>>;
51
+ rejection: Schema.NullOr<Schema.Literals<readonly ["expired", "invalid", "key", "malformed"]>>;
52
+ graceSeconds: Schema.Number;
53
+ refreshIntervalMs: Schema.NullOr<Schema.Number>;
54
+ }>;
55
+
56
+ export { }
@@ -0,0 +1,48 @@
1
+ import { GAMES_PLUGIN_GRANTS as e } from "./games.js";
2
+ import { Schema as t } from "effect";
3
+ //#region src/inspect.ts
4
+ var n = t.Number.pipe(t.check(t.isInt()), t.check(t.isBetween({
5
+ minimum: 0,
6
+ maximum: 2 ** 53 - 1
7
+ }))), r = t.Literals([
8
+ "active",
9
+ "grace",
10
+ "expired"
11
+ ]), i = {
12
+ observedAt: t.Number.pipe(t.check(t.isInt()), t.check(t.isBetween({
13
+ minimum: 0,
14
+ maximum: 864e13
15
+ }))),
16
+ activated: t.Boolean,
17
+ state: t.Literals([
18
+ "none",
19
+ "active",
20
+ "grace",
21
+ "expired",
22
+ "rejected"
23
+ ]),
24
+ installed: t.NullOr(t.Struct({
25
+ state: r,
26
+ edition: t.String.pipe(t.check(t.isMinLength(1)), t.check(t.isMaxLength(256))),
27
+ expiresAt: t.Number.pipe(t.check(t.isInt()), t.check(t.isBetween({
28
+ minimum: 0,
29
+ maximum: 864e10
30
+ }))),
31
+ scope: t.Literals(["deployment", "tenant"]),
32
+ gamesGrants: t.Array(t.Literals(e))
33
+ })),
34
+ rejection: t.NullOr(t.Literals([
35
+ "expired",
36
+ "invalid",
37
+ "key",
38
+ "malformed"
39
+ ])),
40
+ graceSeconds: n,
41
+ refreshIntervalMs: t.NullOr(n.pipe(t.check(t.isGreaterThan(0))))
42
+ }, a = t.Struct(i), o = (e) => {
43
+ if (e.state === "rejected" != (e.rejection !== null) || e.state === "none" && e.installed !== null || e.state !== "none" && e.state !== "rejected" && e.installed?.state !== e.state) return !1;
44
+ let t = e.installed;
45
+ return !(t !== null && (new Set(t.gamesGrants).size !== t.gamesGrants.length || t.state === "expired" && t.gamesGrants.length > 0 || t.state === "grace" && (e.graceSeconds === 0 || t.expiresAt === 0)));
46
+ }, s = a.pipe(t.check(t.makeFilter(o)));
47
+ //#endregion
48
+ export { i as LicensingStatusFields, s as LicensingStatusSchema, o as isLicensingStatusConsistent };