@nexus-mindgarden/plugin-license-foundation 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexander Pajor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @nexus-mindgarden/plugin-license-foundation
2
+
3
+ NEXUS plugin-entitlement **LicenseGate** for the host's `PluginManager.activate` seam — offline-first, default-deny, last-known-good grace, plus the entitle-on-activation call.
4
+
5
+ ## Model
6
+
7
+ NEXUS issues plugin entitlements as **signed claims on the agent-JWT** (`plugins: string[]`, `ent_ver: int`), verifiable **offline via JWKS**. The host already verifies this JWT for app-licensing; this package adds the per-plugin gate. Revocation = `ent_ver` bump + SSE → host reconcile. (Wire: chatbus `#plugin-licensing`, nexus #5374 / agent #5377.)
8
+
9
+ ## Quick use
10
+
11
+ ```ts
12
+ import {
13
+ createLicenseClient,
14
+ verifyEntitlementJwt,
15
+ remoteJwks,
16
+ entitlePlugin,
17
+ } from '@nexus-mindgarden/plugin-license-foundation'
18
+
19
+ // Cache the JWKS getter once (don't re-fetch per verify).
20
+ const jwks = remoteJwks('https://nexus.example/api/licenses/jwks.json')
21
+
22
+ const { gate } = createLicenseClient({
23
+ // Yield the current entitlement — from the host's already-verified agent-JWT
24
+ // claims, or by verifying the raw JWT here:
25
+ resolveEntitlement: async () => {
26
+ const jwt = await host.currentAgentJwt() // host-supplied
27
+ if (!jwt) return null
28
+ return verifyEntitlementJwt(jwt, {
29
+ jwks,
30
+ issuer: 'nexus.digitaleprojekte.at',
31
+ audience: 'nexus',
32
+ })
33
+ },
34
+ graceMs: 7 * 24 * 60 * 60 * 1000, // last-known-good offline window (host policy)
35
+ // failOpen: true, // ONLY for dev / enforce_entitlements=false
36
+ })
37
+
38
+ // Drops into PluginManager.activate's LicenseGate.check seam:
39
+ const result = await gate.check({ pluginId: 'apex-2d', userId })
40
+ // → { ok: true } | { ok: false, reason: 'not_entitled' | 'no_entitlement' | 'stale_entitlement' | … }
41
+ ```
42
+
43
+ ### Entitle on activation (free → grant, paid → 402)
44
+
45
+ ```ts
46
+ const r = await entitlePlugin({
47
+ endpoint: 'https://nexus.example/api/licenses/entitlements/plugin',
48
+ slug: 'apex-2d',
49
+ activationToken, // device credential (Bearer)
50
+ })
51
+ // free → { ok: true }
52
+ // paid → { ok: false, reason: 'purchase_required' }
53
+ ```
54
+
55
+ ## Security
56
+
57
+ - **Default-deny:** no resolvable entitlement and no usable cache → `{ ok: false }`. `failOpen` is opt-in (dev / issuance-before-enforcement).
58
+ - **Alg-pinned:** `verifyEntitlementJwt` accepts **only** `EdDSA` — never `HS*`/`none`. Any verify failure throws (no permissive path).
59
+ - **Offline grace** is bounded by the cached `exp` + `graceMs` (host policy); past it → `stale_entitlement`, not a silent allow. `verifyEntitlementJwt` **requires** `exp` (an exp-less token → `invalid_claims`), so a verified entitlement's grace is always anchored on a signed freshness boundary. (A custom `resolveEntitlement` that yields an `exp`-less object anchors grace on last-resolved-online instead.)
60
+
61
+ ## License
62
+
63
+ MIT
@@ -0,0 +1,31 @@
1
+ import type { Entitlement, LicenseGate } from './types.js';
2
+ export interface CreateLicenseClientOptions {
3
+ /**
4
+ * Yields the current entitlement, or `null` when none is available (e.g.
5
+ * offline and no token yet). May be sync or async. On `null`/throw the gate
6
+ * falls back to last-known-good within the grace window.
7
+ */
8
+ resolveEntitlement: () => Promise<Entitlement | null> | Entitlement | null;
9
+ /**
10
+ * Last-known-good offline grace (ms past the cached entitlement's `exp`). When
11
+ * `resolveEntitlement` can't produce a fresh entitlement, a cached one is
12
+ * trusted until `exp + graceMs`. Default `0` (no grace → only fresh claims pass).
13
+ * Host-policy — NEXUS sends `exp` (short TTL), not a grace window.
14
+ */
15
+ graceMs?: number;
16
+ /**
17
+ * When true, a gate that cannot resolve an entitlement AND has no usable cache
18
+ * returns `{ ok: true }` instead of denying. Default `false` (DEFAULT-DENY).
19
+ * Use only for dev / `enforce_entitlements=false` (issuance-before-enforcement).
20
+ */
21
+ failOpen?: boolean;
22
+ /** Clock (ms epoch). Default `Date.now`. Injectable for tests. */
23
+ now?: () => number;
24
+ }
25
+ export interface LicenseClient {
26
+ gate: LicenseGate;
27
+ /** The cached last-known-good entitlement (for diagnostics / ent_ver reconcile). */
28
+ lastKnownGood(): Entitlement | null;
29
+ }
30
+ export declare function createLicenseClient(opts: CreateLicenseClientOptions): LicenseClient;
31
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAqB,MAAM,YAAY,CAAA;AAE7E,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,kBAAkB,EAAE,MAAM,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,WAAW,GAAG,IAAI,CAAA;IAC1E;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAA;IACjB,oFAAoF;IACpF,aAAa,IAAI,WAAW,GAAG,IAAI,CAAA;CACpC;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,0BAA0B,GAAG,aAAa,CA8DnF"}
package/dist/client.js ADDED
@@ -0,0 +1,67 @@
1
+ // createLicenseClient — produces a LicenseGate for the host's PluginManager.activate
2
+ // seam. Offline-first (consumes verified entitlement claims; no online call on the
3
+ // hot path), default-deny, with a host-policy last-known-good grace window.
4
+ //
5
+ // The host wires `resolveEntitlement` to yield the current entitlement — either
6
+ // from its already-verified NEXUS agent-JWT claims, or via verifyEntitlementJwt
7
+ // (this package) against JWKS. Revocation (ent_ver bump + SSE) is the host's
8
+ // reconcile job; this gate just answers "is this plugin entitled, right now?".
9
+ export function createLicenseClient(opts) {
10
+ const graceMs = opts.graceMs ?? 0;
11
+ const failOpen = opts.failOpen ?? false;
12
+ const now = opts.now ?? Date.now;
13
+ let cache = null;
14
+ /** End of the trust window for a cached entitlement (ms epoch). */
15
+ function trustUntil(c) {
16
+ const base = c.entitlement.exp !== undefined ? c.entitlement.exp * 1000 : c.cachedAt;
17
+ return base + graceMs;
18
+ }
19
+ function decide(entitlement, pluginId) {
20
+ return entitlement.plugins.includes(pluginId)
21
+ ? { ok: true }
22
+ : { ok: false, reason: 'not_entitled', message: `plugin '${pluginId}' not in entitlement` };
23
+ }
24
+ const gate = {
25
+ async check(input) {
26
+ let fresh = null;
27
+ let threw = false;
28
+ try {
29
+ fresh = await opts.resolveEntitlement();
30
+ }
31
+ catch {
32
+ threw = true;
33
+ }
34
+ // Online path: a freshly-resolved entitlement is authoritative + cached.
35
+ if (fresh) {
36
+ cache = { entitlement: fresh, cachedAt: now() };
37
+ return decide(fresh, input.pluginId);
38
+ }
39
+ // Offline path: fall back to last-known-good within the grace window.
40
+ if (cache) {
41
+ if (now() <= trustUntil(cache)) {
42
+ return decide(cache.entitlement, input.pluginId);
43
+ }
44
+ // cache exists but is past exp + grace
45
+ if (failOpen)
46
+ return { ok: true };
47
+ return {
48
+ ok: false,
49
+ reason: 'stale_entitlement',
50
+ message: 'cached entitlement past exp + grace window',
51
+ };
52
+ }
53
+ // No fresh entitlement and no cache at all.
54
+ if (failOpen)
55
+ return { ok: true };
56
+ return {
57
+ ok: false,
58
+ reason: threw ? 'entitlement_error' : 'no_entitlement',
59
+ message: threw
60
+ ? 'resolveEntitlement threw and no cache available'
61
+ : 'no entitlement available',
62
+ };
63
+ },
64
+ };
65
+ return { gate, lastKnownGood: () => cache?.entitlement ?? null };
66
+ }
67
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,mFAAmF;AACnF,4EAA4E;AAC5E,EAAE;AACF,gFAAgF;AAChF,gFAAgF;AAChF,6EAA6E;AAC7E,+EAA+E;AAkC/E,MAAM,UAAU,mBAAmB,CAAC,IAAgC;IAClE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,CAAC,CAAA;IACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAA;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAA;IAEhC,IAAI,KAAK,GAA0D,IAAI,CAAA;IAEvE,mEAAmE;IACnE,SAAS,UAAU,CAAC,CAAiD;QACnE,MAAM,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;QACpF,OAAO,IAAI,GAAG,OAAO,CAAA;IACvB,CAAC;IAED,SAAS,MAAM,CAAC,WAAwB,EAAE,QAAgB;QACxD,OAAO,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAC3C,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE;YACd,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,QAAQ,sBAAsB,EAAE,CAAA;IAC/F,CAAC;IAED,MAAM,IAAI,GAAgB;QACxB,KAAK,CAAC,KAAK,CAAC,KAAK;YACf,IAAI,KAAK,GAAuB,IAAI,CAAA;YACpC,IAAI,KAAK,GAAG,KAAK,CAAA;YACjB,IAAI,CAAC;gBACH,KAAK,GAAG,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAA;YACzC,CAAC;YAAC,MAAM,CAAC;gBACP,KAAK,GAAG,IAAI,CAAA;YACd,CAAC;YAED,yEAAyE;YACzE,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,CAAA;gBAC/C,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;YACtC,CAAC;YAED,sEAAsE;YACtE,IAAI,KAAK,EAAE,CAAC;gBACV,IAAI,GAAG,EAAE,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC/B,OAAO,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;gBAClD,CAAC;gBACD,uCAAuC;gBACvC,IAAI,QAAQ;oBAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;gBACjC,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,MAAM,EAAE,mBAAmB;oBAC3B,OAAO,EAAE,4CAA4C;iBACtD,CAAA;YACH,CAAC;YAED,4CAA4C;YAC5C,IAAI,QAAQ;gBAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;YACjC,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,gBAAgB;gBACtD,OAAO,EAAE,KAAK;oBACZ,CAAC,CAAC,iDAAiD;oBACnD,CAAC,CAAC,0BAA0B;aAC/B,CAAA;QACH,CAAC;KACF,CAAA;IAED,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,WAAW,IAAI,IAAI,EAAE,CAAA;AAClE,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { LicenseGateResult } from './types.js';
2
+ export interface EntitlePluginOptions {
3
+ /** Full entitle endpoint, e.g. `https://nexus.example/api/licenses/entitlements/plugin`. */
4
+ endpoint: string;
5
+ /** Plugin slug to entitle (the NEXUS-canonical slug, e.g. `apex-2d`). */
6
+ slug: string;
7
+ /** Device activation-token (Bearer) — the same credential used for getEntitlements. */
8
+ activationToken: string;
9
+ /** Optional fetch override (tests / custom agent). Default global fetch. */
10
+ fetchImpl?: typeof fetch;
11
+ }
12
+ /**
13
+ * Entitle a plugin on activation. Returns:
14
+ * - `{ ok: true }` on grant (free auto-grant, 2xx).
15
+ * - `{ ok: false, reason: 'purchase_required' }` on 402 (paid, not purchased) —
16
+ * deterministically mapped from `detail.reason === 'purchase_required'`.
17
+ * - `{ ok: false, reason: 'entitlement_error', message }` on any other failure.
18
+ *
19
+ * Never throws — always returns a LicenseGateResult (the activation seam wants a
20
+ * decision, not an exception).
21
+ */
22
+ export declare function entitlePlugin(opts: EntitlePluginOptions): Promise<LicenseGateResult>;
23
+ //# sourceMappingURL=entitle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entitle.d.ts","sourceRoot":"","sources":["../src/entitle.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,MAAM,WAAW,oBAAoB;IACnC,4FAA4F;IAC5F,QAAQ,EAAE,MAAM,CAAA;IAChB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAA;IACZ,uFAAuF;IACvF,eAAe,EAAE,MAAM,CAAA;IACvB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CACzB;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA6C1F"}
@@ -0,0 +1,64 @@
1
+ // Entitle-on-activation — the optional online step at plugin activation.
2
+ //
3
+ // `POST /api/licenses/entitlements/plugin { plugin_slug }` (Bearer=activation_token),
4
+ // idempotent. Free plugins → auto-grant (2xx). Paid plugins → 402 with
5
+ // `{ detail: { reason: 'purchase_required', … } }` (until shop-checkout exists).
6
+ // After a successful grant, the host refreshes entitlements (cache + SSE handle
7
+ // the rest) so the LicenseGate sees the new slug.
8
+ /**
9
+ * Entitle a plugin on activation. Returns:
10
+ * - `{ ok: true }` on grant (free auto-grant, 2xx).
11
+ * - `{ ok: false, reason: 'purchase_required' }` on 402 (paid, not purchased) —
12
+ * deterministically mapped from `detail.reason === 'purchase_required'`.
13
+ * - `{ ok: false, reason: 'entitlement_error', message }` on any other failure.
14
+ *
15
+ * Never throws — always returns a LicenseGateResult (the activation seam wants a
16
+ * decision, not an exception).
17
+ */
18
+ export async function entitlePlugin(opts) {
19
+ const fetchFn = opts.fetchImpl ?? fetch;
20
+ let res;
21
+ try {
22
+ res = await fetchFn(opts.endpoint, {
23
+ method: 'POST',
24
+ headers: {
25
+ Authorization: `Bearer ${opts.activationToken}`,
26
+ 'Content-Type': 'application/json',
27
+ Accept: 'application/json',
28
+ },
29
+ body: JSON.stringify({ plugin_slug: opts.slug }),
30
+ });
31
+ }
32
+ catch (err) {
33
+ return {
34
+ ok: false,
35
+ reason: 'entitlement_error',
36
+ message: `entitle fetch failed: ${err instanceof Error ? err.message : String(err)}`,
37
+ };
38
+ }
39
+ if (res.ok)
40
+ return { ok: true };
41
+ if (res.status === 402) {
42
+ // Deterministic shape (nexus): { detail: { reason: 'purchase_required', message } }
43
+ let reasonField;
44
+ let message;
45
+ try {
46
+ const body = (await res.json());
47
+ reasonField = body?.detail?.reason;
48
+ message = typeof body?.detail?.message === 'string' ? body.detail.message : undefined;
49
+ }
50
+ catch {
51
+ // ignore parse error — treat as purchase_required by status anyway
52
+ }
53
+ if (reasonField === 'purchase_required' || reasonField === undefined) {
54
+ return { ok: false, reason: 'purchase_required', ...(message ? { message } : {}) };
55
+ }
56
+ return {
57
+ ok: false,
58
+ reason: 'entitlement_error',
59
+ message: `402 with reason '${String(reasonField)}'`,
60
+ };
61
+ }
62
+ return { ok: false, reason: 'entitlement_error', message: `entitle returned HTTP ${res.status}` };
63
+ }
64
+ //# sourceMappingURL=entitle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entitle.js","sourceRoot":"","sources":["../src/entitle.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,EAAE;AACF,sFAAsF;AACtF,uEAAuE;AACvE,iFAAiF;AACjF,gFAAgF;AAChF,kDAAkD;AAelD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAA0B;IAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAA;IACvC,IAAI,GAAa,CAAA;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE;YACjC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,eAAe,EAAE;gBAC/C,cAAc,EAAE,kBAAkB;gBAClC,MAAM,EAAE,kBAAkB;aAC3B;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;SACjD,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,mBAAmB;YAC3B,OAAO,EAAE,yBAAyB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;SACrF,CAAA;IACH,CAAC;IAED,IAAI,GAAG,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;IAE/B,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACvB,oFAAoF;QACpF,IAAI,WAAoB,CAAA;QACxB,IAAI,OAA2B,CAAA;QAC/B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAyD,CAAA;YACvF,WAAW,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,CAAA;YAClC,OAAO,GAAG,OAAO,IAAI,EAAE,MAAM,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;QACvF,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;QACrE,CAAC;QACD,IAAI,WAAW,KAAK,mBAAmB,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YACrE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;QACpF,CAAC;QACD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,mBAAmB;YAC3B,OAAO,EAAE,oBAAoB,MAAM,CAAC,WAAW,CAAC,GAAG;SACpD,CAAA;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,OAAO,EAAE,yBAAyB,GAAG,CAAC,MAAM,EAAE,EAAE,CAAA;AACnG,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { createLicenseClient, type CreateLicenseClientOptions, type LicenseClient, } from './client.js';
2
+ export { verifyEntitlementJwt, remoteJwks, LicenseVerifyError, type VerifyEntitlementJwtOptions, } from './jwks.js';
3
+ export { entitlePlugin, type EntitlePluginOptions } from './entitle.js';
4
+ export { EntitlementSchema, type Entitlement, type LicenseGate, type LicenseGateResult, type LicenseDenyReason, } from './types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,KAAK,aAAa,GACnB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,oBAAoB,EACpB,UAAU,EACV,kBAAkB,EAClB,KAAK,2BAA2B,GACjC,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACvE,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // @nexus-mindgarden/plugin-license-foundation — NEXUS entitlement LicenseGate.
2
+ //
3
+ // Drops into the host's PluginManager.activate seam (`LicenseGate.check`). The
4
+ // host already verifies the NEXUS agent-JWT for app-licensing; this package adds
5
+ // the per-plugin gate (offline, default-deny, last-known-good grace) + the
6
+ // entitle-on-activation call. Wire-spec: chatbus #plugin-licensing (nexus #5374,
7
+ // agent #5377). The `checkLicense` stub in HOST-INTEGRATION-GUIDE §2.3 becomes real.
8
+ export { createLicenseClient, } from './client.js';
9
+ export { verifyEntitlementJwt, remoteJwks, LicenseVerifyError, } from './jwks.js';
10
+ export { entitlePlugin } from './entitle.js';
11
+ export { EntitlementSchema, } from './types.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,+EAA+E;AAC/E,iFAAiF;AACjF,2EAA2E;AAC3E,iFAAiF;AACjF,qFAAqF;AAErF,OAAO,EACL,mBAAmB,GAGpB,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,oBAAoB,EACpB,UAAU,EACV,kBAAkB,GAEnB,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,aAAa,EAA6B,MAAM,cAAc,CAAA;AACvE,OAAO,EACL,iBAAiB,GAKlB,MAAM,YAAY,CAAA"}
package/dist/jwks.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { type JWTVerifyGetKey } from 'jose';
2
+ import { type Entitlement } from './types.js';
3
+ export declare class LicenseVerifyError extends Error {
4
+ readonly code: 'jwks_fetch_failed' | 'invalid_token' | 'expired' | 'invalid_claims';
5
+ constructor(code: 'jwks_fetch_failed' | 'invalid_token' | 'expired' | 'invalid_claims', message: string);
6
+ }
7
+ export interface VerifyEntitlementJwtOptions {
8
+ /** JWKS endpoint, e.g. `https://nexus.example/api/licenses/jwks.json`, OR a pre-built jose key-getter. */
9
+ jwks: string | JWTVerifyGetKey;
10
+ /** Expected `iss` (e.g. `nexus.digitaleprojekte.at`). Enforced if set. */
11
+ issuer?: string;
12
+ /** Expected `aud` (e.g. `nexus`). Enforced if set. */
13
+ audience?: string;
14
+ }
15
+ /** Build (and let the caller cache) a JWKS key-getter from a URL. */
16
+ export declare function remoteJwks(jwksUrl: string): JWTVerifyGetKey;
17
+ /**
18
+ * Verify a NEXUS agent-JWT against JWKS (alg-pinned EdDSA) and extract the
19
+ * entitlement claims. Throws LicenseVerifyError on any failure — there is no
20
+ * permissive return path (default-deny by construction).
21
+ *
22
+ * NOTE: pass a pre-built `remoteJwks(url)` getter (cached across calls) rather
23
+ * than a URL string in a hot path, so JWKS isn't re-fetched per verify.
24
+ */
25
+ export declare function verifyEntitlementJwt(token: string, opts: VerifyEntitlementJwtOptions): Promise<Entitlement>;
26
+ //# sourceMappingURL=jwks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwks.d.ts","sourceRoot":"","sources":["../src/jwks.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiC,KAAK,eAAe,EAAE,MAAM,MAAM,CAAA;AAC1E,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAA;AAEhE,qBAAa,kBAAmB,SAAQ,KAAK;aAEzB,IAAI,EAAE,mBAAmB,GAAG,eAAe,GAAG,SAAS,GAAG,gBAAgB;gBAA1E,IAAI,EAAE,mBAAmB,GAAG,eAAe,GAAG,SAAS,GAAG,gBAAgB,EAC1F,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,2BAA2B;IAC1C,0GAA0G;IAC1G,IAAI,EAAE,MAAM,GAAG,eAAe,CAAA;IAC9B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAE3D;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,2BAA2B,GAChC,OAAO,CAAC,WAAW,CAAC,CA0CtB"}
package/dist/jwks.js ADDED
@@ -0,0 +1,68 @@
1
+ // Offline JWKS verification of a NEXUS agent-JWT → Entitlement.
2
+ //
3
+ // The host typically already verifies the agent-JWT (for app-licensing) and can
4
+ // pass the verified claims straight to createLicenseClient via resolveEntitlement.
5
+ // This helper is for callers that hold the raw JWT and want the Foundation to do
6
+ // the verify: alg-pinned EdDSA, JWKS-cached, optional iss/aud — default-deny by
7
+ // construction (any verify failure throws, never returns a permissive result).
8
+ import { jwtVerify, createRemoteJWKSet } from 'jose';
9
+ import { EntitlementSchema } from './types.js';
10
+ export class LicenseVerifyError extends Error {
11
+ code;
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.code = code;
15
+ this.name = 'LicenseVerifyError';
16
+ }
17
+ }
18
+ /** Build (and let the caller cache) a JWKS key-getter from a URL. */
19
+ export function remoteJwks(jwksUrl) {
20
+ return createRemoteJWKSet(new URL(jwksUrl));
21
+ }
22
+ /**
23
+ * Verify a NEXUS agent-JWT against JWKS (alg-pinned EdDSA) and extract the
24
+ * entitlement claims. Throws LicenseVerifyError on any failure — there is no
25
+ * permissive return path (default-deny by construction).
26
+ *
27
+ * NOTE: pass a pre-built `remoteJwks(url)` getter (cached across calls) rather
28
+ * than a URL string in a hot path, so JWKS isn't re-fetched per verify.
29
+ */
30
+ export async function verifyEntitlementJwt(token, opts) {
31
+ const keyGetter = typeof opts.jwks === 'string' ? remoteJwks(opts.jwks) : opts.jwks;
32
+ let payload;
33
+ try {
34
+ const result = await jwtVerify(token, keyGetter, {
35
+ algorithms: ['EdDSA'], // alg-pinned: never accept HS*/none/etc.
36
+ ...(opts.issuer !== undefined ? { issuer: opts.issuer } : {}),
37
+ ...(opts.audience !== undefined ? { audience: opts.audience } : {}),
38
+ });
39
+ payload = result.payload;
40
+ }
41
+ catch (err) {
42
+ const e = err;
43
+ const msg = e.message ?? 'verify failed';
44
+ // Token-expiry first — most specific.
45
+ if (e.code === 'ERR_JWT_EXPIRED' || /expired/i.test(msg)) {
46
+ throw new LicenseVerifyError('expired', msg);
47
+ }
48
+ // JWKS unreachable / no key / timeout / transport — distinct from a bad token,
49
+ // so the host can retry-later instead of treating it as token-integrity.
50
+ if (e.code === 'ERR_JWKS_NO_MATCHING_KEY' ||
51
+ e.code === 'ERR_JWKS_TIMEOUT' ||
52
+ e.code === 'ERR_JWKS_MULTIPLE_MATCHING_KEYS' ||
53
+ /jwks|key set|fetch failed|timed out|network|ENOTFOUND|ECONNREFUSED/i.test(msg)) {
54
+ throw new LicenseVerifyError('jwks_fetch_failed', msg);
55
+ }
56
+ throw new LicenseVerifyError('invalid_token', msg);
57
+ }
58
+ const parsed = EntitlementSchema.safeParse(payload);
59
+ if (!parsed.success) {
60
+ throw new LicenseVerifyError('invalid_claims', `entitlement claims invalid: ${parsed.error.issues.map((i) => i.path.join('.')).join(', ')}`);
61
+ }
62
+ return {
63
+ plugins: parsed.data.plugins,
64
+ ent_ver: parsed.data.ent_ver,
65
+ ...(parsed.data.exp !== undefined ? { exp: parsed.data.exp } : {}),
66
+ };
67
+ }
68
+ //# sourceMappingURL=jwks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwks.js","sourceRoot":"","sources":["../src/jwks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod';
2
+ /** Entitlement payload extracted from a verified NEXUS agent-JWT. */
3
+ export interface Entitlement {
4
+ /** Entitled plugin slugs (the authoritative offline list mirror). */
5
+ plugins: string[];
6
+ /** Monotonic entitlements version (revocation / reconcile cursor). */
7
+ ent_ver: number;
8
+ /** JWT `exp` (unix seconds) — the claim's freshness boundary, if known. */
9
+ exp?: number;
10
+ }
11
+ /** Zod schema — parses the relevant claims off a verified JWT payload. Lenient
12
+ * about extra claims (the agent-JWT carries more than just entitlements). */
13
+ export declare const EntitlementSchema: z.ZodObject<{
14
+ plugins: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
15
+ ent_ver: z.ZodDefault<z.ZodNumber>;
16
+ exp: z.ZodNumber;
17
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
18
+ plugins: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
19
+ ent_ver: z.ZodDefault<z.ZodNumber>;
20
+ exp: z.ZodNumber;
21
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
22
+ plugins: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
23
+ ent_ver: z.ZodDefault<z.ZodNumber>;
24
+ exp: z.ZodNumber;
25
+ }, z.ZodTypeAny, "passthrough">>;
26
+ /**
27
+ * Why a license check failed (or `undefined` on success):
28
+ * - `not_entitled` — entitlement resolved, plugin not in `plugins[]`.
29
+ * - `no_entitlement` — could not resolve an entitlement and no valid cache (default-deny).
30
+ * - `stale_entitlement` — only a cached entitlement exists and it is past `exp` + grace.
31
+ * - `purchase_required` — entitle-on-activation returned 402 (paid, not purchased).
32
+ * - `entitlement_error` — resolving/parsing the entitlement threw.
33
+ */
34
+ export type LicenseDenyReason = 'not_entitled' | 'no_entitlement' | 'stale_entitlement' | 'purchase_required' | 'entitlement_error';
35
+ export type LicenseGateResult = {
36
+ ok: true;
37
+ } | {
38
+ ok: false;
39
+ reason: LicenseDenyReason;
40
+ message?: string;
41
+ };
42
+ /** The seam `@theseus/plugin-system` `PluginManager.activate` calls before activation. */
43
+ export interface LicenseGate {
44
+ check(input: {
45
+ pluginId: string;
46
+ userId?: string;
47
+ manifest?: unknown;
48
+ }): Promise<LicenseGateResult>;
49
+ }
50
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAA;IACf,2EAA2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED;8EAC8E;AAC9E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;gCASd,CAAA;AAEhB;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GACzB,cAAc,GACd,gBAAgB,GAChB,mBAAmB,GACnB,mBAAmB,GACnB,mBAAmB,CAAA;AAEvB,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9D,0FAA0F;AAC1F,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,KAAK,EAAE;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,QAAQ,CAAC,EAAE,OAAO,CAAA;KACnB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;CAC/B"}
package/dist/types.js ADDED
@@ -0,0 +1,21 @@
1
+ // Plugin-License Foundation — shared types for the NEXUS entitlement model.
2
+ //
3
+ // Entitlements ride as signed claims on the NEXUS agent-JWT (verified offline
4
+ // via JWKS): `plugins: string[]` (entitled plugin slugs) + `ent_ver: int`
5
+ // (monotonic version, bumped on revocation → host reconciles via SSE). The host
6
+ // already verifies this JWT for app-licensing; this package adds the per-plugin
7
+ // LicenseGate + last-known-good offline grace + the entitle-on-activation call.
8
+ import { z } from 'zod';
9
+ /** Zod schema — parses the relevant claims off a verified JWT payload. Lenient
10
+ * about extra claims (the agent-JWT carries more than just entitlements). */
11
+ export const EntitlementSchema = z
12
+ .object({
13
+ plugins: z.array(z.string()).default([]),
14
+ ent_ver: z.number().int().nonnegative().default(0),
15
+ // REQUIRED: a verified NEXUS agent-JWT always carries `exp` (short TTL). An
16
+ // exp-less token → `invalid_claims` at verify, so the gate's offline grace is
17
+ // always anchored on a signed freshness boundary (closes the exp-less edge).
18
+ exp: z.number().int().positive(),
19
+ })
20
+ .passthrough();
21
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAYvB;8EAC8E;AAC9E,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC;KAC/B,MAAM,CAAC;IACN,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACxC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;CACjC,CAAC;KACD,WAAW,EAAE,CAAA"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@nexus-mindgarden/plugin-license-foundation",
3
+ "version": "0.1.0",
4
+ "description": "Plugin-License Foundation — NEXUS entitlement LicenseGate (offline JWKS-verified, default-deny, last-known-good grace) + entitle-on-activation",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/MrDewitt88/plugin-template.git",
25
+ "directory": "packages/plugin-license-foundation"
26
+ },
27
+ "homepage": "https://github.com/MrDewitt88/plugin-template/tree/main/packages/plugin-license-foundation#readme",
28
+ "bugs": {
29
+ "url": "https://github.com/MrDewitt88/plugin-template/issues"
30
+ },
31
+ "keywords": [
32
+ "nexus-mindgarden",
33
+ "plugin",
34
+ "license",
35
+ "entitlement",
36
+ "licensegate"
37
+ ],
38
+ "dependencies": {
39
+ "jose": "^5.9.0",
40
+ "zod": "^3.23.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.0.0",
44
+ "typescript": "^5.6.0",
45
+ "vitest": "^2.1.0"
46
+ },
47
+ "scripts": {
48
+ "build": "tsc -p tsconfig.json",
49
+ "test": "vitest run",
50
+ "typecheck": "tsc --noEmit -p tsconfig.json"
51
+ }
52
+ }