@suveren/gateway 0.7.6 → 0.8.1
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/content/integrations/deploy-github.json +10 -1
- package/content/integrations/mollie.json +103 -28
- package/dist/mcp-server/http.mjs +510 -132
- package/dist/ui/assets/{index-CsrM6RQr.js → index-BFnSVa9N.js} +22 -22
- package/dist/ui/index.html +1 -1
- package/node_modules/@hap/core/dist/index.d.mts +29 -6
- package/node_modules/@hap/core/dist/index.d.ts +29 -6
- package/node_modules/@hap/core/dist/index.js +54 -8
- package/node_modules/@hap/core/dist/index.mjs +53 -8
- package/node_modules/@hap/core/package.json +2 -1
- package/node_modules/@hap/core/src/frame.ts +115 -14
- package/node_modules/@hap/core/src/gatekeeper.ts +26 -0
- package/node_modules/@hap/core/src/types.ts +7 -1
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/buffer.buffer.d.ts +5 -0
- package/node_modules/@types/node/crypto.d.ts +0 -2
- package/node_modules/@types/node/dgram.d.ts +119 -4
- package/node_modules/@types/node/ffi.d.ts +1 -1
- package/node_modules/@types/node/fs/promises.d.ts +61 -65
- package/node_modules/@types/node/fs.d.ts +86 -76
- package/node_modules/@types/node/http.d.ts +1 -1
- package/node_modules/@types/node/http2.d.ts +2 -2
- package/node_modules/@types/node/index.d.ts +1 -0
- package/node_modules/@types/node/inspector.d.ts +2 -1
- package/node_modules/@types/node/net.d.ts +108 -16
- package/node_modules/@types/node/package.json +2 -2
- package/node_modules/@types/node/quic.d.ts +37 -11
- package/node_modules/@types/node/stream/iter.d.ts +4 -4
- package/node_modules/@types/node/tls.d.ts +24 -0
- package/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +5 -0
- package/node_modules/@types/node/ts5.6/index.d.ts +1 -0
- package/node_modules/@types/node/ts5.7/index.d.ts +1 -0
- package/node_modules/@types/node/tty.d.ts +4 -3
- package/node_modules/@types/node/vfs.d.ts +210 -0
- package/node_modules/@types/node/vm.d.ts +1 -1
- package/node_modules/express-rate-limit/dist/index.cjs +6 -3
- package/node_modules/express-rate-limit/dist/index.d.cts +11 -0
- package/node_modules/express-rate-limit/dist/index.d.mts +11 -0
- package/node_modules/express-rate-limit/dist/index.d.ts +11 -0
- package/node_modules/express-rate-limit/dist/index.mjs +6 -3
- package/node_modules/express-rate-limit/{license.md → license} +7 -6
- package/node_modules/express-rate-limit/package.json +5 -5
- package/node_modules/express-rate-limit/readme.md +1 -0
- package/node_modules/fast-uri/index.js +20 -5
- package/node_modules/fast-uri/lib/utils.js +8 -1
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/fast-uri/test/component-safe-serialization.test.js +58 -0
- package/node_modules/fast-uri/test/ipv6-validation.test.js +34 -0
- package/node_modules/hono/dist/cjs/client/client.js +4 -4
- package/node_modules/hono/dist/cjs/hono-base.js +7 -6
- package/node_modules/hono/dist/cjs/jsx/base.js +18 -1
- package/node_modules/hono/dist/cjs/jsx/components.js +25 -26
- package/node_modules/hono/dist/cjs/jsx/context.js +5 -5
- package/node_modules/hono/dist/cjs/jsx/dom/server.js +5 -1
- package/node_modules/hono/dist/cjs/jsx/intrinsic-element/components.js +2 -2
- package/node_modules/hono/dist/cjs/jsx/streaming.js +4 -5
- package/node_modules/hono/dist/client/client.js +4 -4
- package/node_modules/hono/dist/hono-base.js +7 -6
- package/node_modules/hono/dist/jsx/base.js +15 -1
- package/node_modules/hono/dist/jsx/components.js +26 -27
- package/node_modules/hono/dist/jsx/context.js +6 -6
- package/node_modules/hono/dist/jsx/dom/server.js +5 -1
- package/node_modules/hono/dist/jsx/intrinsic-element/components.js +3 -3
- package/node_modules/hono/dist/jsx/streaming.js +5 -6
- package/node_modules/hono/dist/types/context.d.ts +2 -2
- package/node_modules/hono/dist/types/jsx/base.d.ts +3 -0
- package/node_modules/hono/package.json +2 -2
- package/node_modules/ip-address/README.md +92 -82
- package/node_modules/ip-address/dist/common.d.ts +20 -0
- package/node_modules/ip-address/dist/common.js +36 -0
- package/node_modules/ip-address/dist/common.js.map +1 -1
- package/node_modules/ip-address/dist/ipv4.d.ts +50 -0
- package/node_modules/ip-address/dist/ipv4.js +75 -0
- package/node_modules/ip-address/dist/ipv4.js.map +1 -1
- package/node_modules/ip-address/dist/ipv6.d.ts +79 -10
- package/node_modules/ip-address/dist/ipv6.js +120 -30
- package/node_modules/ip-address/dist/ipv6.js.map +1 -1
- package/node_modules/ip-address/dist/v4/constants.d.ts +12 -0
- package/node_modules/ip-address/dist/v4/constants.js +40 -1
- package/node_modules/ip-address/dist/v4/constants.js.map +1 -1
- package/node_modules/ip-address/dist/v6/constants.d.ts +14 -0
- package/node_modules/ip-address/dist/v6/constants.js +47 -1
- package/node_modules/ip-address/dist/v6/constants.js.map +1 -1
- package/node_modules/ip-address/package.json +1 -1
- package/node_modules/jose/dist/types/jwe/compact/decrypt.d.ts +7 -16
- package/node_modules/jose/dist/types/jwe/compact/encrypt.d.ts +6 -17
- package/node_modules/jose/dist/types/jwe/flattened/decrypt.d.ts +7 -16
- package/node_modules/jose/dist/types/jwe/flattened/encrypt.d.ts +11 -34
- package/node_modules/jose/dist/types/jwe/general/decrypt.d.ts +7 -16
- package/node_modules/jose/dist/types/jwe/general/encrypt.d.ts +15 -35
- package/node_modules/jose/dist/types/jwk/embedded.d.ts +3 -8
- package/node_modules/jose/dist/types/jwk/thumbprint.d.ts +5 -5
- package/node_modules/jose/dist/types/jwks/local.d.ts +6 -10
- package/node_modules/jose/dist/types/jwks/remote.d.ts +13 -24
- package/node_modules/jose/dist/types/jws/compact/sign.d.ts +5 -10
- package/node_modules/jose/dist/types/jws/compact/verify.d.ts +9 -18
- package/node_modules/jose/dist/types/jws/flattened/sign.d.ts +7 -16
- package/node_modules/jose/dist/types/jws/flattened/verify.d.ts +9 -18
- package/node_modules/jose/dist/types/jws/general/sign.d.ts +11 -21
- package/node_modules/jose/dist/types/jws/general/verify.d.ts +9 -18
- package/node_modules/jose/dist/types/jwt/decrypt.d.ts +10 -20
- package/node_modules/jose/dist/types/jwt/encrypt.d.ts +6 -13
- package/node_modules/jose/dist/types/jwt/sign.d.ts +5 -6
- package/node_modules/jose/dist/types/jwt/unsecured.d.ts +9 -14
- package/node_modules/jose/dist/types/jwt/verify.d.ts +9 -19
- package/node_modules/jose/dist/types/key/export.d.ts +4 -4
- package/node_modules/jose/dist/types/key/generate_key_pair.d.ts +6 -8
- package/node_modules/jose/dist/types/key/generate_secret.d.ts +4 -11
- package/node_modules/jose/dist/types/key/import.d.ts +10 -17
- package/node_modules/jose/dist/types/types.d.ts +44 -84
- package/node_modules/jose/dist/types/util/base64url.d.ts +2 -2
- package/node_modules/jose/dist/types/util/decode_jwt.d.ts +3 -10
- package/node_modules/jose/dist/types/util/decode_protected_header.d.ts +4 -4
- package/node_modules/jose/dist/types/util/errors.d.ts +26 -53
- package/node_modules/jose/dist/webapi/index.js +70 -32
- package/node_modules/jose/dist/webapi/jwe/compact/decrypt.js +7 -8
- package/node_modules/jose/dist/webapi/jwe/compact/encrypt.js +42 -48
- package/node_modules/jose/dist/webapi/jwe/flattened/decrypt.js +14 -14
- package/node_modules/jose/dist/webapi/jwe/flattened/encrypt.js +53 -64
- package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +38 -49
- package/node_modules/jose/dist/webapi/jwe/general/encrypt.js +121 -169
- package/node_modules/jose/dist/webapi/jwk/embedded.js +30 -32
- package/node_modules/jose/dist/webapi/jwk/thumbprint.js +54 -70
- package/node_modules/jose/dist/webapi/jwks/local.js +47 -64
- package/node_modules/jose/dist/webapi/jwks/remote.js +88 -136
- package/node_modules/jose/dist/webapi/jws/compact/sign.js +21 -21
- package/node_modules/jose/dist/webapi/jws/compact/verify.js +7 -8
- package/node_modules/jose/dist/webapi/jws/flattened/sign.js +29 -31
- package/node_modules/jose/dist/webapi/jws/flattened/verify.js +21 -24
- package/node_modules/jose/dist/webapi/jws/general/sign.js +61 -72
- package/node_modules/jose/dist/webapi/jws/general/verify.js +45 -69
- package/node_modules/jose/dist/webapi/jwt/decrypt.js +13 -20
- package/node_modules/jose/dist/webapi/jwt/encrypt.js +57 -70
- package/node_modules/jose/dist/webapi/jwt/sign.js +17 -16
- package/node_modules/jose/dist/webapi/jwt/unsecured.js +32 -40
- package/node_modules/jose/dist/webapi/jwt/verify.js +12 -14
- package/node_modules/jose/dist/webapi/key/export.js +32 -37
- package/node_modules/jose/dist/webapi/key/generate_key_pair.js +40 -48
- package/node_modules/jose/dist/webapi/key/generate_secret.js +35 -40
- package/node_modules/jose/dist/webapi/key/import.js +52 -56
- package/node_modules/jose/dist/webapi/lib/asn1.js +130 -169
- package/node_modules/jose/dist/webapi/lib/base64.js +18 -20
- package/node_modules/jose/dist/webapi/lib/buffer_utils.js +34 -38
- package/node_modules/jose/dist/webapi/lib/content_encryption.js +79 -115
- package/node_modules/jose/dist/webapi/lib/crypto_key.js +24 -25
- package/node_modules/jose/dist/webapi/lib/deflate.js +45 -47
- package/node_modules/jose/dist/webapi/lib/helpers.js +40 -37
- package/node_modules/jose/dist/webapi/lib/invalid_key_input.js +10 -24
- package/node_modules/jose/dist/webapi/lib/is_key_like.js +17 -15
- package/node_modules/jose/dist/webapi/lib/jwe_algorithms.js +106 -93
- package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +207 -230
- package/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +104 -138
- package/node_modules/jose/dist/webapi/lib/jwk_metadata.js +17 -18
- package/node_modules/jose/dist/webapi/lib/jwk_to_key.js +9 -16
- package/node_modules/jose/dist/webapi/lib/jws_algorithms.js +62 -61
- package/node_modules/jose/dist/webapi/lib/jws_sign.js +50 -75
- package/node_modules/jose/dist/webapi/lib/jws_verify.js +80 -125
- package/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +123 -209
- package/node_modules/jose/dist/webapi/lib/key.js +84 -136
- package/node_modules/jose/dist/webapi/lib/key_algorithm.js +13 -8
- package/node_modules/jose/dist/webapi/lib/key_descriptor.js +8 -6
- package/node_modules/jose/dist/webapi/lib/key_management.js +200 -231
- package/node_modules/jose/dist/webapi/lib/key_options.js +7 -5
- package/node_modules/jose/dist/webapi/lib/options.js +55 -68
- package/node_modules/jose/dist/webapi/lib/signing.js +18 -22
- package/node_modules/jose/dist/webapi/lib/type_checks.js +26 -29
- package/node_modules/jose/dist/webapi/util/base64url.js +26 -35
- package/node_modules/jose/dist/webapi/util/decode_jwt.js +32 -31
- package/node_modules/jose/dist/webapi/util/decode_protected_header.js +18 -23
- package/node_modules/jose/dist/webapi/util/errors.js +94 -84
- package/node_modules/jose/package.json +17 -81
- package/package.json +2 -2
- package/profiles/calendar/0.5.profile.json +190 -0
- package/profiles/charge/0.5.profile.json +210 -0
- package/profiles/customers/0.7.profile.json +157 -0
- package/profiles/deploy/0.9.profile.json +176 -0
- package/profiles/email/0.6.profile.json +208 -0
- package/profiles/index.json +9 -1
- package/profiles/publish/0.5.profile.json +186 -0
- package/profiles/purchase/0.5.profile.json +231 -0
- package/profiles/records/0.5.profile.json +117 -0
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -33,6 +33,85 @@ async function notifyControlPlane(type) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// src/lib/client-version.ts
|
|
37
|
+
import { createRequire } from "module";
|
|
38
|
+
import { existsSync, readFileSync } from "fs";
|
|
39
|
+
import { join } from "path";
|
|
40
|
+
var UNKNOWN = "unknown";
|
|
41
|
+
function readJson(path) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function detectGatewayVersion() {
|
|
49
|
+
const override = process.env.SUVEREN_GATEWAY_VERSION;
|
|
50
|
+
if (override) return override;
|
|
51
|
+
const start = import.meta.dirname ?? process.cwd();
|
|
52
|
+
let dir = start;
|
|
53
|
+
for (let up = 0; up < 6; up++) {
|
|
54
|
+
const pkg = readJson(join(dir, "package.json"));
|
|
55
|
+
const name = pkg?.name;
|
|
56
|
+
const version = pkg?.version;
|
|
57
|
+
if (typeof version === "string" && (name === "@suveren/gateway" || name === "suveren-gateway")) {
|
|
58
|
+
return version;
|
|
59
|
+
}
|
|
60
|
+
const parent = join(dir, "..");
|
|
61
|
+
if (parent === dir) break;
|
|
62
|
+
dir = parent;
|
|
63
|
+
}
|
|
64
|
+
return process.env.HAP_BUILD_SHA ?? UNKNOWN;
|
|
65
|
+
}
|
|
66
|
+
function findPackageVersion(entry, wantName) {
|
|
67
|
+
let dir = join(entry, "..");
|
|
68
|
+
for (let up = 0; up < 6; up++) {
|
|
69
|
+
const candidate = join(dir, "package.json");
|
|
70
|
+
if (existsSync(candidate)) {
|
|
71
|
+
const pkg = readJson(candidate);
|
|
72
|
+
if (typeof pkg?.version === "string" && (pkg.name === wantName || pkg.name === "@hap/core")) {
|
|
73
|
+
return pkg.name === wantName ? pkg.version : null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const parent = join(dir, "..");
|
|
77
|
+
if (parent === dir) break;
|
|
78
|
+
dir = parent;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function detectHapCoreVersion() {
|
|
83
|
+
const CORE = "@humanagencyp/hap-core";
|
|
84
|
+
let req;
|
|
85
|
+
try {
|
|
86
|
+
req = createRequire(import.meta.url);
|
|
87
|
+
} catch {
|
|
88
|
+
return UNKNOWN;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const v = findPackageVersion(req.resolve(CORE), CORE);
|
|
92
|
+
if (v) return v;
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const wrapperEntry = req.resolve("@hap/core");
|
|
97
|
+
const v = findPackageVersion(wrapperEntry, CORE);
|
|
98
|
+
if (v) return v;
|
|
99
|
+
const wrapperReq = createRequire(wrapperEntry);
|
|
100
|
+
const v2 = findPackageVersion(wrapperReq.resolve(CORE), CORE);
|
|
101
|
+
if (v2) return v2;
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
return UNKNOWN;
|
|
105
|
+
}
|
|
106
|
+
var GATEWAY_VERSION = detectGatewayVersion();
|
|
107
|
+
var HAP_CORE_VERSION = detectHapCoreVersion();
|
|
108
|
+
function clientVersionHeaders() {
|
|
109
|
+
return {
|
|
110
|
+
"x-suveren-gateway-version": GATEWAY_VERSION,
|
|
111
|
+
"x-hap-core-version": HAP_CORE_VERSION
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
36
115
|
// src/lib/sp-client.ts
|
|
37
116
|
var SPReceiptError = class extends Error {
|
|
38
117
|
constructor(message, statusCode, body) {
|
|
@@ -82,6 +161,11 @@ var SPClient = class {
|
|
|
82
161
|
const url = `${this.baseUrl}${path}`;
|
|
83
162
|
const headers = {
|
|
84
163
|
"Content-Type": "application/json",
|
|
164
|
+
// Identify the caller so the AS can answer "what is deployed against
|
|
165
|
+
// me?" and name a version in an upgrade instruction. Advisory only —
|
|
166
|
+
// never used for authorization. Sent on every call, not just receipts,
|
|
167
|
+
// so the picture is not skewed toward one endpoint.
|
|
168
|
+
...clientVersionHeaders(),
|
|
85
169
|
...init?.headers
|
|
86
170
|
};
|
|
87
171
|
if (this.sessionCookie) {
|
|
@@ -170,7 +254,10 @@ var SPClient = class {
|
|
|
170
254
|
const message = first?.message ?? first?.code ?? respBody.error ?? `SP receipt request failed: ${res.status}`;
|
|
171
255
|
throw new SPReceiptError(message, res.status, respBody);
|
|
172
256
|
}
|
|
173
|
-
return {
|
|
257
|
+
return {
|
|
258
|
+
receipt: respBody.receipt,
|
|
259
|
+
idempotent: respBody.idempotent === true
|
|
260
|
+
};
|
|
174
261
|
}
|
|
175
262
|
throw lastError ?? new Error("postReceipt: retries exhausted");
|
|
176
263
|
}
|
|
@@ -378,11 +465,11 @@ var AttestationCache = class {
|
|
|
378
465
|
};
|
|
379
466
|
|
|
380
467
|
// src/lib/gate-store.ts
|
|
381
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync, unlinkSync } from "fs";
|
|
468
|
+
import { mkdirSync, readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, unlinkSync } from "fs";
|
|
382
469
|
import { homedir } from "os";
|
|
383
|
-
import { dirname, join } from "path";
|
|
470
|
+
import { dirname, join as join2 } from "path";
|
|
384
471
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
385
|
-
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
472
|
+
var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ?? join2(homedir(), ".suveren");
|
|
386
473
|
var GateStore = class {
|
|
387
474
|
entries = /* @__PURE__ */ new Map();
|
|
388
475
|
baseDir;
|
|
@@ -400,11 +487,11 @@ var GateStore = class {
|
|
|
400
487
|
*/
|
|
401
488
|
setVaultKey(key) {
|
|
402
489
|
this.vaultKey = key;
|
|
403
|
-
if (
|
|
490
|
+
if (existsSync2(this.encryptedFilePath)) {
|
|
404
491
|
this.loadEncrypted();
|
|
405
492
|
} else if (this.entries.size > 0) {
|
|
406
493
|
this.persistEncrypted();
|
|
407
|
-
if (
|
|
494
|
+
if (existsSync2(this.plaintextFilePath)) {
|
|
408
495
|
try {
|
|
409
496
|
unlinkSync(this.plaintextFilePath);
|
|
410
497
|
} catch {
|
|
@@ -455,19 +542,19 @@ var GateStore = class {
|
|
|
455
542
|
}
|
|
456
543
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
457
544
|
get plaintextFilePath() {
|
|
458
|
-
return
|
|
545
|
+
return join2(this.baseDir, "gates.json");
|
|
459
546
|
}
|
|
460
547
|
get encryptedFilePath() {
|
|
461
|
-
return
|
|
548
|
+
return join2(this.baseDir, "gates.enc.json");
|
|
462
549
|
}
|
|
463
550
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
464
551
|
loadPlaintext() {
|
|
465
|
-
if (!
|
|
552
|
+
if (!existsSync2(this.plaintextFilePath)) {
|
|
466
553
|
mkdirSync(this.baseDir, { recursive: true });
|
|
467
554
|
return;
|
|
468
555
|
}
|
|
469
556
|
try {
|
|
470
|
-
const raw =
|
|
557
|
+
const raw = readFileSync2(this.plaintextFilePath, "utf-8");
|
|
471
558
|
const data = JSON.parse(raw);
|
|
472
559
|
this.entries = new Map(Object.entries(data.entries));
|
|
473
560
|
} catch {
|
|
@@ -476,9 +563,9 @@ var GateStore = class {
|
|
|
476
563
|
}
|
|
477
564
|
}
|
|
478
565
|
loadEncrypted() {
|
|
479
|
-
if (!
|
|
566
|
+
if (!existsSync2(this.encryptedFilePath)) return;
|
|
480
567
|
try {
|
|
481
|
-
const raw =
|
|
568
|
+
const raw = readFileSync2(this.encryptedFilePath, "utf-8");
|
|
482
569
|
const data = JSON.parse(raw);
|
|
483
570
|
this.entries = /* @__PURE__ */ new Map();
|
|
484
571
|
for (const [key, blob] of Object.entries(data.entries)) {
|
|
@@ -519,11 +606,11 @@ var GateStore = class {
|
|
|
519
606
|
};
|
|
520
607
|
|
|
521
608
|
// src/lib/execution-log.ts
|
|
522
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
609
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
523
610
|
import { homedir as homedir2 } from "os";
|
|
524
|
-
import { dirname as dirname2, join as
|
|
611
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
525
612
|
import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
|
|
526
|
-
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
613
|
+
var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ?? join3(homedir2(), ".suveren");
|
|
527
614
|
var MAX_AGE_SECONDS = 31 * 24 * 60 * 60;
|
|
528
615
|
var ExecutionLog = class {
|
|
529
616
|
entries = [];
|
|
@@ -542,11 +629,11 @@ var ExecutionLog = class {
|
|
|
542
629
|
*/
|
|
543
630
|
setVaultKey(key) {
|
|
544
631
|
this.vaultKey = key;
|
|
545
|
-
if (
|
|
632
|
+
if (existsSync3(this.encryptedFilePath)) {
|
|
546
633
|
this.loadEncrypted();
|
|
547
634
|
} else if (this.entries.length > 0) {
|
|
548
635
|
this.persistEncrypted();
|
|
549
|
-
if (
|
|
636
|
+
if (existsSync3(this.plaintextFilePath)) {
|
|
550
637
|
try {
|
|
551
638
|
unlinkSync2(this.plaintextFilePath);
|
|
552
639
|
} catch {
|
|
@@ -641,19 +728,19 @@ var ExecutionLog = class {
|
|
|
641
728
|
}
|
|
642
729
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
643
730
|
get plaintextFilePath() {
|
|
644
|
-
return
|
|
731
|
+
return join3(this.baseDir, "execution-log.json");
|
|
645
732
|
}
|
|
646
733
|
get encryptedFilePath() {
|
|
647
|
-
return
|
|
734
|
+
return join3(this.baseDir, "execution-log.enc.json");
|
|
648
735
|
}
|
|
649
736
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
650
737
|
loadPlaintext() {
|
|
651
|
-
if (!
|
|
738
|
+
if (!existsSync3(this.plaintextFilePath)) {
|
|
652
739
|
mkdirSync2(this.baseDir, { recursive: true });
|
|
653
740
|
return;
|
|
654
741
|
}
|
|
655
742
|
try {
|
|
656
|
-
const raw =
|
|
743
|
+
const raw = readFileSync3(this.plaintextFilePath, "utf-8");
|
|
657
744
|
const data = JSON.parse(raw);
|
|
658
745
|
this.entries = data.entries;
|
|
659
746
|
this.prune();
|
|
@@ -663,9 +750,9 @@ var ExecutionLog = class {
|
|
|
663
750
|
}
|
|
664
751
|
}
|
|
665
752
|
loadEncrypted() {
|
|
666
|
-
if (!
|
|
753
|
+
if (!existsSync3(this.encryptedFilePath)) return;
|
|
667
754
|
try {
|
|
668
|
-
const raw =
|
|
755
|
+
const raw = readFileSync3(this.encryptedFilePath, "utf-8");
|
|
669
756
|
const data = JSON.parse(raw);
|
|
670
757
|
const decrypted = this.decrypt(data.blob);
|
|
671
758
|
const logFile = JSON.parse(decrypted);
|
|
@@ -712,11 +799,11 @@ function windowCutoff(window, now) {
|
|
|
712
799
|
}
|
|
713
800
|
|
|
714
801
|
// src/lib/denial-log.ts
|
|
715
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
802
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
716
803
|
import { homedir as homedir3 } from "os";
|
|
717
|
-
import { dirname as dirname3, join as
|
|
804
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
718
805
|
import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, randomBytes as randomBytes3 } from "crypto";
|
|
719
|
-
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ??
|
|
806
|
+
var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ?? join4(homedir3(), ".suveren");
|
|
720
807
|
var MAX_RECORDS = 200;
|
|
721
808
|
var MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
722
809
|
var DenialLog = class {
|
|
@@ -729,11 +816,11 @@ var DenialLog = class {
|
|
|
729
816
|
}
|
|
730
817
|
setVaultKey(key) {
|
|
731
818
|
this.vaultKey = key;
|
|
732
|
-
if (
|
|
819
|
+
if (existsSync4(this.encryptedFilePath)) {
|
|
733
820
|
this.loadEncrypted();
|
|
734
821
|
} else if (this.records.length > 0) {
|
|
735
822
|
this.persistEncrypted();
|
|
736
|
-
if (
|
|
823
|
+
if (existsSync4(this.plaintextFilePath)) {
|
|
737
824
|
try {
|
|
738
825
|
unlinkSync3(this.plaintextFilePath);
|
|
739
826
|
} catch {
|
|
@@ -783,18 +870,18 @@ var DenialLog = class {
|
|
|
783
870
|
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]).toString("utf8");
|
|
784
871
|
}
|
|
785
872
|
get plaintextFilePath() {
|
|
786
|
-
return
|
|
873
|
+
return join4(this.baseDir, "denials.json");
|
|
787
874
|
}
|
|
788
875
|
get encryptedFilePath() {
|
|
789
|
-
return
|
|
876
|
+
return join4(this.baseDir, "denials.enc.json");
|
|
790
877
|
}
|
|
791
878
|
loadPlaintext() {
|
|
792
|
-
if (!
|
|
879
|
+
if (!existsSync4(this.plaintextFilePath)) {
|
|
793
880
|
mkdirSync3(this.baseDir, { recursive: true });
|
|
794
881
|
return;
|
|
795
882
|
}
|
|
796
883
|
try {
|
|
797
|
-
const data = JSON.parse(
|
|
884
|
+
const data = JSON.parse(readFileSync4(this.plaintextFilePath, "utf-8"));
|
|
798
885
|
this.records = data.records ?? [];
|
|
799
886
|
} catch {
|
|
800
887
|
console.error(`[DenialLog] Could not parse ${this.plaintextFilePath}, starting fresh`);
|
|
@@ -802,9 +889,9 @@ var DenialLog = class {
|
|
|
802
889
|
}
|
|
803
890
|
}
|
|
804
891
|
loadEncrypted() {
|
|
805
|
-
if (!
|
|
892
|
+
if (!existsSync4(this.encryptedFilePath)) return;
|
|
806
893
|
try {
|
|
807
|
-
const data = JSON.parse(
|
|
894
|
+
const data = JSON.parse(readFileSync4(this.encryptedFilePath, "utf-8"));
|
|
808
895
|
const logFile = JSON.parse(this.decrypt(data.blob));
|
|
809
896
|
this.records = logFile.records ?? [];
|
|
810
897
|
} catch (err) {
|
|
@@ -830,11 +917,11 @@ var DenialLog = class {
|
|
|
830
917
|
};
|
|
831
918
|
|
|
832
919
|
// src/lib/receipt-archive.ts
|
|
833
|
-
import { mkdirSync as mkdirSync4, readFileSync as
|
|
920
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, unlinkSync as unlinkSync4 } from "fs";
|
|
834
921
|
import { homedir as homedir4 } from "os";
|
|
835
|
-
import { dirname as dirname4, join as
|
|
922
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
836
923
|
import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes4 } from "crypto";
|
|
837
|
-
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ??
|
|
924
|
+
var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ?? join5(homedir4(), ".suveren");
|
|
838
925
|
function stripPreviews(proposal) {
|
|
839
926
|
const args = proposal.toolArgs;
|
|
840
927
|
if (!args || typeof args !== "object") return proposal;
|
|
@@ -859,11 +946,11 @@ var ReceiptArchive = class {
|
|
|
859
946
|
*/
|
|
860
947
|
setVaultKey(key) {
|
|
861
948
|
this.vaultKey = key;
|
|
862
|
-
if (
|
|
949
|
+
if (existsSync5(this.encryptedFilePath)) {
|
|
863
950
|
this.loadEncrypted();
|
|
864
951
|
} else if (this.data.receipts.length > 0 || Object.keys(this.data.authorizations).length > 0) {
|
|
865
952
|
this.persistEncrypted();
|
|
866
|
-
if (
|
|
953
|
+
if (existsSync5(this.plaintextFilePath)) {
|
|
867
954
|
try {
|
|
868
955
|
unlinkSync4(this.plaintextFilePath);
|
|
869
956
|
} catch {
|
|
@@ -877,7 +964,7 @@ var ReceiptArchive = class {
|
|
|
877
964
|
* this state — that would report a hollow success over unreadable evidence.
|
|
878
965
|
*/
|
|
879
966
|
isLocked() {
|
|
880
|
-
return this.vaultKey === null &&
|
|
967
|
+
return this.vaultKey === null && existsSync5(this.encryptedFilePath);
|
|
881
968
|
}
|
|
882
969
|
/**
|
|
883
970
|
* Archive a signed receipt (and the grant it was issued under).
|
|
@@ -978,19 +1065,19 @@ var ReceiptArchive = class {
|
|
|
978
1065
|
}
|
|
979
1066
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
980
1067
|
get plaintextFilePath() {
|
|
981
|
-
return
|
|
1068
|
+
return join5(this.baseDir, "receipt-archive.json");
|
|
982
1069
|
}
|
|
983
1070
|
get encryptedFilePath() {
|
|
984
|
-
return
|
|
1071
|
+
return join5(this.baseDir, "receipt-archive.enc.json");
|
|
985
1072
|
}
|
|
986
1073
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
987
1074
|
loadPlaintext() {
|
|
988
|
-
if (!
|
|
1075
|
+
if (!existsSync5(this.plaintextFilePath)) {
|
|
989
1076
|
mkdirSync4(this.baseDir, { recursive: true });
|
|
990
1077
|
return;
|
|
991
1078
|
}
|
|
992
1079
|
try {
|
|
993
|
-
const raw =
|
|
1080
|
+
const raw = readFileSync5(this.plaintextFilePath, "utf-8");
|
|
994
1081
|
const parsed = JSON.parse(raw);
|
|
995
1082
|
this.data = {
|
|
996
1083
|
version: 1,
|
|
@@ -1002,16 +1089,16 @@ var ReceiptArchive = class {
|
|
|
1002
1089
|
try {
|
|
1003
1090
|
writeFileSync4(
|
|
1004
1091
|
`${this.plaintextFilePath}.corrupt-${Date.now()}`,
|
|
1005
|
-
|
|
1092
|
+
readFileSync5(this.plaintextFilePath)
|
|
1006
1093
|
);
|
|
1007
1094
|
} catch {
|
|
1008
1095
|
}
|
|
1009
1096
|
}
|
|
1010
1097
|
}
|
|
1011
1098
|
loadEncrypted() {
|
|
1012
|
-
if (!
|
|
1099
|
+
if (!existsSync5(this.encryptedFilePath)) return;
|
|
1013
1100
|
try {
|
|
1014
|
-
const raw =
|
|
1101
|
+
const raw = readFileSync5(this.encryptedFilePath, "utf-8");
|
|
1015
1102
|
const data = JSON.parse(raw);
|
|
1016
1103
|
const decrypted = this.decrypt(data.blob);
|
|
1017
1104
|
const parsed = JSON.parse(decrypted);
|
|
@@ -1025,7 +1112,7 @@ var ReceiptArchive = class {
|
|
|
1025
1112
|
try {
|
|
1026
1113
|
writeFileSync4(
|
|
1027
1114
|
`${this.encryptedFilePath}.corrupt-${Date.now()}`,
|
|
1028
|
-
|
|
1115
|
+
readFileSync5(this.encryptedFilePath)
|
|
1029
1116
|
);
|
|
1030
1117
|
} catch {
|
|
1031
1118
|
}
|
|
@@ -1052,12 +1139,106 @@ var ReceiptArchive = class {
|
|
|
1052
1139
|
}
|
|
1053
1140
|
};
|
|
1054
1141
|
|
|
1142
|
+
// src/lib/execution-journal.ts
|
|
1143
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6, renameSync } from "fs";
|
|
1144
|
+
import { homedir as homedir5 } from "os";
|
|
1145
|
+
import { dirname as dirname5, join as join6 } from "path";
|
|
1146
|
+
import { createHash } from "crypto";
|
|
1147
|
+
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ?? join6(homedir5(), ".suveren");
|
|
1148
|
+
var FILE_NAME = "execution-journal.json";
|
|
1149
|
+
var MAX_AGE_SECONDS2 = 90 * 24 * 60 * 60;
|
|
1150
|
+
function hashToolArgs(args) {
|
|
1151
|
+
return "sha256:" + createHash("sha256").update(stableStringify(args)).digest("hex");
|
|
1152
|
+
}
|
|
1153
|
+
function stableStringify(value) {
|
|
1154
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
|
|
1155
|
+
if (Array.isArray(value)) return "[" + value.map(stableStringify).join(",") + "]";
|
|
1156
|
+
const obj = value;
|
|
1157
|
+
const keys = Object.keys(obj).sort();
|
|
1158
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
1159
|
+
}
|
|
1160
|
+
var ExecutionJournal = class {
|
|
1161
|
+
filePath;
|
|
1162
|
+
constructor(filePath) {
|
|
1163
|
+
const baseDir = filePath && filePath.endsWith(".json") ? dirname5(filePath) : filePath ?? DEFAULT_DIR5;
|
|
1164
|
+
this.filePath = join6(baseDir, FILE_NAME);
|
|
1165
|
+
}
|
|
1166
|
+
/** Look a ticket up. Always reads from disk — another process may have written. */
|
|
1167
|
+
get(ticketId) {
|
|
1168
|
+
return this.load().entries.find((e) => e.ticketId === ticketId);
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Record intent to execute under `ticketId`. Refuses if any row for that
|
|
1172
|
+
* ticket already exists, whatever its state — deciding what an existing row
|
|
1173
|
+
* means is the caller's job, and it differs by state (see commitments.ts).
|
|
1174
|
+
*/
|
|
1175
|
+
begin(entry) {
|
|
1176
|
+
const file = this.load();
|
|
1177
|
+
const existing = file.entries.find((e) => e.ticketId === entry.ticketId);
|
|
1178
|
+
if (existing) return { ok: false, existing };
|
|
1179
|
+
file.entries.push({
|
|
1180
|
+
...entry,
|
|
1181
|
+
state: "intent",
|
|
1182
|
+
startedAt: nowSec(),
|
|
1183
|
+
pid: process.pid
|
|
1184
|
+
});
|
|
1185
|
+
this.persist(file);
|
|
1186
|
+
return { ok: true };
|
|
1187
|
+
}
|
|
1188
|
+
/** Close an `intent` row. Missing row is a programming error, not a silent no-op. */
|
|
1189
|
+
complete(ticketId, state2) {
|
|
1190
|
+
const file = this.load();
|
|
1191
|
+
const row = file.entries.find((e) => e.ticketId === ticketId);
|
|
1192
|
+
if (!row) throw new Error(`execution journal: no row for ticket ${ticketId} to complete`);
|
|
1193
|
+
row.state = state2;
|
|
1194
|
+
row.finishedAt = nowSec();
|
|
1195
|
+
this.persist(file);
|
|
1196
|
+
}
|
|
1197
|
+
/** All rows, newest last. Diagnostic. */
|
|
1198
|
+
getAll() {
|
|
1199
|
+
return [...this.load().entries];
|
|
1200
|
+
}
|
|
1201
|
+
// ─── Persistence ────────────────────────────────────────────────────────
|
|
1202
|
+
load() {
|
|
1203
|
+
if (!existsSync6(this.filePath)) return { version: 1, entries: [] };
|
|
1204
|
+
try {
|
|
1205
|
+
const parsed = JSON.parse(readFileSync6(this.filePath, "utf8"));
|
|
1206
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.entries)) {
|
|
1207
|
+
throw new Error("unexpected shape");
|
|
1208
|
+
}
|
|
1209
|
+
return parsed;
|
|
1210
|
+
} catch (err) {
|
|
1211
|
+
throw new Error(
|
|
1212
|
+
`execution journal at ${this.filePath} is unreadable (${err instanceof Error ? err.message : String(err)}); refusing to execute until it is inspected`
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
persist(file) {
|
|
1217
|
+
const cutoff = nowSec() - MAX_AGE_SECONDS2;
|
|
1218
|
+
file.entries = file.entries.filter((e) => e.startedAt >= cutoff);
|
|
1219
|
+
mkdirSync5(dirname5(this.filePath), { recursive: true, mode: 448 });
|
|
1220
|
+
const tmp = this.filePath + ".tmp";
|
|
1221
|
+
writeFileSync5(tmp, JSON.stringify(file, null, 2), { mode: 384 });
|
|
1222
|
+
renameSync(tmp, this.filePath);
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
function nowSec() {
|
|
1226
|
+
return Math.floor(Date.now() / 1e3);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1055
1229
|
// src/lib/gatekeeper.ts
|
|
1056
1230
|
import { verify } from "@hap/core";
|
|
1057
1231
|
var MCPGatekeeper = class {
|
|
1058
|
-
|
|
1232
|
+
/**
|
|
1233
|
+
* No ExecutionLog dependency by construction. The local record is
|
|
1234
|
+
* display-only (protocol.md → "Executor Gating, Context vs Bounds,
|
|
1235
|
+
* Display-Only Logs"), so the Gatekeeper must not be able to read it: with
|
|
1236
|
+
* no log in scope, a local cumulative check cannot be reintroduced by
|
|
1237
|
+
* accident. `SharedState.executionLog` stays — it feeds the UI's consumption
|
|
1238
|
+
* display and nothing else.
|
|
1239
|
+
*/
|
|
1240
|
+
constructor(cache) {
|
|
1059
1241
|
this.cache = cache;
|
|
1060
|
-
this.executionLog = executionLog;
|
|
1061
1242
|
}
|
|
1062
1243
|
/**
|
|
1063
1244
|
* Verify an execution request against a cached authorization.
|
|
@@ -1104,16 +1285,15 @@ var MCPGatekeeper = class {
|
|
|
1104
1285
|
attestations: auth.attestations.map((a) => a.blob),
|
|
1105
1286
|
execution,
|
|
1106
1287
|
context: resolvedContext,
|
|
1107
|
-
//
|
|
1108
|
-
//
|
|
1109
|
-
//
|
|
1110
|
-
//
|
|
1111
|
-
//
|
|
1112
|
-
//
|
|
1113
|
-
// the local gate never fired and the AS was doing all the enforcing.
|
|
1288
|
+
// Identifies the grant whose bounds are being checked. hap-core reads it
|
|
1289
|
+
// only to scope a cumulative lookup, which no longer happens here (see
|
|
1290
|
+
// below); it is kept because it belongs to the request, not to the
|
|
1291
|
+
// dropped check. It cannot go inside `frame`: that is validated against
|
|
1292
|
+
// the profile's boundsSchema, which declares no `path` field in any
|
|
1293
|
+
// shipped profile, so an extra key there fails with "Unknown field".
|
|
1114
1294
|
path: auth.path
|
|
1115
1295
|
};
|
|
1116
|
-
const result = await verify(request, publicKeyHex
|
|
1296
|
+
const result = await verify(request, publicKeyHex);
|
|
1117
1297
|
return { result, authorization: auth };
|
|
1118
1298
|
}
|
|
1119
1299
|
};
|
|
@@ -1126,6 +1306,8 @@ var SharedState = class {
|
|
|
1126
1306
|
executionLog;
|
|
1127
1307
|
denialLog;
|
|
1128
1308
|
receiptArchive;
|
|
1309
|
+
/** Which tickets this Gatekeeper has executed — the "one execution per ticket" half of exactly-once. */
|
|
1310
|
+
executionJournal;
|
|
1129
1311
|
gatekeeper;
|
|
1130
1312
|
constructor(spUrl2, gateStorePath) {
|
|
1131
1313
|
this.spClient = new SPClient(spUrl2);
|
|
@@ -1134,7 +1316,8 @@ var SharedState = class {
|
|
|
1134
1316
|
this.executionLog = new ExecutionLog(gateStorePath);
|
|
1135
1317
|
this.denialLog = new DenialLog(gateStorePath);
|
|
1136
1318
|
this.receiptArchive = new ReceiptArchive(gateStorePath);
|
|
1137
|
-
this.
|
|
1319
|
+
this.executionJournal = new ExecutionJournal(gateStorePath);
|
|
1320
|
+
this.gatekeeper = new MCPGatekeeper(this.cache);
|
|
1138
1321
|
}
|
|
1139
1322
|
/**
|
|
1140
1323
|
* Archive a signed receipt into the local receipt archive — the subject's
|
|
@@ -1282,17 +1465,17 @@ function formatConsumptionFull(entries) {
|
|
|
1282
1465
|
}
|
|
1283
1466
|
|
|
1284
1467
|
// src/lib/context-loader.ts
|
|
1285
|
-
import { readFileSync as
|
|
1286
|
-
import { homedir as
|
|
1287
|
-
import { join as
|
|
1288
|
-
var
|
|
1468
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
1469
|
+
import { homedir as homedir6 } from "os";
|
|
1470
|
+
import { join as join7 } from "path";
|
|
1471
|
+
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ?? join7(homedir6(), ".suveren");
|
|
1289
1472
|
var BRIEF_MAX_CHARS = 16 * 1024;
|
|
1290
1473
|
function readContextFile(dataDir) {
|
|
1291
|
-
const dir = dataDir ??
|
|
1292
|
-
const filePath =
|
|
1293
|
-
if (!
|
|
1474
|
+
const dir = dataDir ?? DEFAULT_DIR6;
|
|
1475
|
+
const filePath = join7(dir, "context.md");
|
|
1476
|
+
if (!existsSync7(filePath)) return null;
|
|
1294
1477
|
try {
|
|
1295
|
-
const content =
|
|
1478
|
+
const content = readFileSync7(filePath, "utf-8").trim();
|
|
1296
1479
|
return content || null;
|
|
1297
1480
|
} catch {
|
|
1298
1481
|
return null;
|
|
@@ -1933,10 +2116,11 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
1933
2116
|
return inner(args);
|
|
1934
2117
|
};
|
|
1935
2118
|
if (category === "disabled") {
|
|
2119
|
+
const reason = tool.gating.disabledReason;
|
|
1936
2120
|
return async () => ({
|
|
1937
2121
|
content: [{
|
|
1938
2122
|
type: "text",
|
|
1939
|
-
text: `Tool "${tool.namespacedName}" is disabled by the integration manifest and cannot be used.`
|
|
2123
|
+
text: reason ? `Tool "${tool.namespacedName}" is refused: ${reason}. A tool that no manifest entry describes cannot be gated \u2014 there is nothing declaring which action type it performs or how its arguments map to the bounds. Add an entry for "${tool.originalName}" under this integration's toolGating.overrides (with "category": "read" for a read-only tool) before it can be used.` : `Tool "${tool.namespacedName}" is disabled by the integration manifest and cannot be used.`
|
|
1940
2124
|
}],
|
|
1941
2125
|
isError: true
|
|
1942
2126
|
});
|
|
@@ -2257,7 +2441,12 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
2257
2441
|
executionContext: { ...execution },
|
|
2258
2442
|
amount: typeof execution.amount === "number" ? execution.amount : void 0,
|
|
2259
2443
|
idempotencyKey: randomUUID(),
|
|
2260
|
-
|
|
2444
|
+
// Privacy: send the hash and how to reproduce it — never the
|
|
2445
|
+
// preimage. `binding` also carries `boundContent`, the plaintext
|
|
2446
|
+
// that was hashed (an email's to/cc/subject/body); it stays on
|
|
2447
|
+
// this machine for the local archive, so it must be picked out
|
|
2448
|
+
// field by field rather than spread.
|
|
2449
|
+
...binding ? { contentHash: binding.contentHash, contentBinding: binding.contentBinding } : {}
|
|
2261
2450
|
});
|
|
2262
2451
|
receiptId = typeof receipt?.id === "string" ? receipt.id : void 0;
|
|
2263
2452
|
await state2.archiveReceipt(receipt, {
|
|
@@ -2362,6 +2551,30 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
|
|
|
2362
2551
|
let outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, args, receiptId, auth.subjects?.[0]) : args;
|
|
2363
2552
|
if (receiptId) outgoingArgs = attachReceiptId(tool, outgoingArgs, receiptId);
|
|
2364
2553
|
outgoingArgs = encodeOutgoingArgs(tool, outgoingArgs);
|
|
2554
|
+
if (receiptId) {
|
|
2555
|
+
const begun = state2.executionJournal.begin({
|
|
2556
|
+
ticketId: receiptId,
|
|
2557
|
+
tool: tool.namespacedName,
|
|
2558
|
+
argsHash: hashToolArgs(args)
|
|
2559
|
+
});
|
|
2560
|
+
if (!begun.ok) {
|
|
2561
|
+
return {
|
|
2562
|
+
content: [{
|
|
2563
|
+
type: "text",
|
|
2564
|
+
text: `Blocked: ticket ${receiptId} was already used to execute ${tool.namespacedName} (state: ${begun.existing.state}). Not running it again.`
|
|
2565
|
+
}],
|
|
2566
|
+
isError: true
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
try {
|
|
2570
|
+
const result = await integrationManager2.callTool(tool.integrationId, tool.originalName, outgoingArgs);
|
|
2571
|
+
state2.executionJournal.complete(receiptId, "done");
|
|
2572
|
+
return result;
|
|
2573
|
+
} catch (err) {
|
|
2574
|
+
state2.executionJournal.complete(receiptId, "failed");
|
|
2575
|
+
throw err;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2365
2578
|
return integrationManager2.callTool(tool.integrationId, tool.originalName, outgoingArgs);
|
|
2366
2579
|
}
|
|
2367
2580
|
});
|
|
@@ -2407,7 +2620,7 @@ function buildCapabilityMap(profileId, toolGating, integrationManager2) {
|
|
|
2407
2620
|
const shortName = shortProfileName2(profileId);
|
|
2408
2621
|
const gated = [];
|
|
2409
2622
|
const readOnly = [];
|
|
2410
|
-
const
|
|
2623
|
+
const undescribed = [];
|
|
2411
2624
|
for (const tool of allTools) {
|
|
2412
2625
|
if (!tool.gating || !tool.gating.profile) {
|
|
2413
2626
|
continue;
|
|
@@ -2429,12 +2642,7 @@ function buildCapabilityMap(profileId, toolGating, integrationManager2) {
|
|
|
2429
2642
|
const actionType = override.staticExecution?.action_type ?? "unknown";
|
|
2430
2643
|
gated.push(` - ${tool.originalName}: ${actionType}${mappingDesc ? `, ${mappingDesc}` : ""}`);
|
|
2431
2644
|
} else {
|
|
2432
|
-
|
|
2433
|
-
if (defaultAction === "read" && Object.keys(toolGating.default.executionMapping ?? {}).length === 0) {
|
|
2434
|
-
defaultGated.push(tool.originalName);
|
|
2435
|
-
} else {
|
|
2436
|
-
gated.push(` - ${tool.originalName}: ${defaultAction ?? "default"} (default gating)`);
|
|
2437
|
-
}
|
|
2645
|
+
undescribed.push(tool.originalName);
|
|
2438
2646
|
}
|
|
2439
2647
|
}
|
|
2440
2648
|
const lines = [];
|
|
@@ -2446,10 +2654,12 @@ function buildCapabilityMap(profileId, toolGating, integrationManager2) {
|
|
|
2446
2654
|
if (readOnly.length > 0) {
|
|
2447
2655
|
lines.push(` Read-only (no authorization needed): ${readOnly.join(", ")}`);
|
|
2448
2656
|
}
|
|
2449
|
-
if (
|
|
2450
|
-
lines.push(
|
|
2657
|
+
if (undescribed.length > 0) {
|
|
2658
|
+
lines.push(
|
|
2659
|
+
` Refused (no manifest entry \u2014 do not call): ${undescribed.join(", ")}`
|
|
2660
|
+
);
|
|
2451
2661
|
}
|
|
2452
|
-
if (gated.length === 0 && readOnly.length === 0 &&
|
|
2662
|
+
if (gated.length === 0 && readOnly.length === 0 && undescribed.length === 0) {
|
|
2453
2663
|
lines.push(" No tools discovered for this profile.");
|
|
2454
2664
|
}
|
|
2455
2665
|
return lines.join("\n");
|
|
@@ -2693,6 +2903,13 @@ function listIntegrationsHandler(state2, integrationManager2) {
|
|
|
2693
2903
|
|
|
2694
2904
|
// src/tools/commitments.ts
|
|
2695
2905
|
import { ContentBindingError as ContentBindingError2 } from "@hap/core";
|
|
2906
|
+
var installedExecutor = null;
|
|
2907
|
+
function installCommittedExecutor(executor) {
|
|
2908
|
+
installedExecutor = executor;
|
|
2909
|
+
}
|
|
2910
|
+
function runCommitted(proposal, state2, integrationManager2) {
|
|
2911
|
+
return installedExecutor ? installedExecutor.execute(proposal) : executeCommitted(proposal, state2, integrationManager2);
|
|
2912
|
+
}
|
|
2696
2913
|
async function executeCommitted(proposal, state2, integrationManager2) {
|
|
2697
2914
|
if (!integrationManager2) {
|
|
2698
2915
|
return { text: `Proposal ${proposal.id} committed but integration manager unavailable for execution.`, isError: true };
|
|
@@ -2712,6 +2929,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
2712
2929
|
}
|
|
2713
2930
|
const cachedAuth = state2.cache.getAllAuthorizations().find((a) => a.authorizationId === proposal.authorizationId);
|
|
2714
2931
|
let receiptId;
|
|
2932
|
+
let replayed = false;
|
|
2715
2933
|
try {
|
|
2716
2934
|
const binding = computeContentBinding(
|
|
2717
2935
|
proposal.profileId,
|
|
@@ -2719,7 +2937,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
2719
2937
|
proposal.toolArgs,
|
|
2720
2938
|
proposalActionType
|
|
2721
2939
|
);
|
|
2722
|
-
const { receipt } = await state2.spClient.postReceipt({
|
|
2940
|
+
const { receipt, idempotent } = await state2.spClient.postReceipt({
|
|
2723
2941
|
authorizationId: proposal.authorizationId,
|
|
2724
2942
|
// Optional cross-check — the AS fails closed on a mismatch. Parity
|
|
2725
2943
|
// with the automatic path (tool-proxy.ts).
|
|
@@ -2738,9 +2956,13 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
2738
2956
|
// SPClient therefore treats proposalId as retry-safe on its own; a
|
|
2739
2957
|
// PROPOSAL_ALREADY_EXECUTED answer is still a definitive rejection
|
|
2740
2958
|
// (never retried) and means a *different* caller consumed the proposal.
|
|
2741
|
-
|
|
2959
|
+
// Privacy: hash + how to reproduce it only. `binding.boundContent` is
|
|
2960
|
+
// the plaintext preimage (what the human approved) and never leaves
|
|
2961
|
+
// this machine — see the archive call below.
|
|
2962
|
+
...binding ? { contentHash: binding.contentHash, contentBinding: binding.contentBinding } : {}
|
|
2742
2963
|
});
|
|
2743
2964
|
receiptId = typeof receipt?.id === "string" ? receipt.id : void 0;
|
|
2965
|
+
replayed = idempotent;
|
|
2744
2966
|
await state2.archiveReceipt(receipt, {
|
|
2745
2967
|
authorizationId: proposal.authorizationId,
|
|
2746
2968
|
profileId: proposal.profileId,
|
|
@@ -2778,6 +3000,36 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
2778
3000
|
isError: true
|
|
2779
3001
|
};
|
|
2780
3002
|
}
|
|
3003
|
+
if (!receiptId) {
|
|
3004
|
+
return {
|
|
3005
|
+
text: `Proposal ${proposal.id}: the ticket carries no id \u2014 refusing to execute an action that could not be recorded.`,
|
|
3006
|
+
isError: true
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
const begun = state2.executionJournal.begin({
|
|
3010
|
+
ticketId: receiptId,
|
|
3011
|
+
proposalId: proposal.id,
|
|
3012
|
+
tool: proposal.tool,
|
|
3013
|
+
argsHash: hashToolArgs(proposal.toolArgs)
|
|
3014
|
+
});
|
|
3015
|
+
if (!begun.ok) {
|
|
3016
|
+
const prior = begun.existing;
|
|
3017
|
+
const when = new Date(prior.startedAt * 1e3).toISOString();
|
|
3018
|
+
if (prior.state === "done") {
|
|
3019
|
+
return {
|
|
3020
|
+
text: `Proposal ${proposal.id} was already executed under ticket ${receiptId} at ${when}. Not running it again.`
|
|
3021
|
+
};
|
|
3022
|
+
}
|
|
3023
|
+
return {
|
|
3024
|
+
text: `Proposal ${proposal.id}: an execution under ticket ${receiptId} started at ${when} (pid ${prior.pid}) and ${prior.state === "failed" ? "reported failure" : "never reported completion"}. Not re-running: the action may already have taken effect. Check the downstream system before deciding.`,
|
|
3025
|
+
isError: true
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
3028
|
+
if (replayed) {
|
|
3029
|
+
console.error(
|
|
3030
|
+
`[Suveren MCP] Proposal ${proposal.id}: ticket ${receiptId} was replayed by the AS with no local execution record \u2014 executing once (lost-response recovery).`
|
|
3031
|
+
);
|
|
3032
|
+
}
|
|
2781
3033
|
try {
|
|
2782
3034
|
let outgoingArgs = proposal.toolArgs;
|
|
2783
3035
|
if (discovered && receiptId) {
|
|
@@ -2787,7 +3039,14 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
2787
3039
|
outgoingArgs = attachReceiptId(discovered, outgoingArgs, receiptId);
|
|
2788
3040
|
outgoingArgs = encodeOutgoingArgs(discovered, outgoingArgs);
|
|
2789
3041
|
}
|
|
2790
|
-
|
|
3042
|
+
let result;
|
|
3043
|
+
try {
|
|
3044
|
+
result = await integrationManager2.callTool(integrationId, toolName, outgoingArgs);
|
|
3045
|
+
} catch (err) {
|
|
3046
|
+
state2.executionJournal.complete(receiptId, "failed");
|
|
3047
|
+
throw err;
|
|
3048
|
+
}
|
|
3049
|
+
state2.executionJournal.complete(receiptId, "done");
|
|
2791
3050
|
state2.executionLog.record({
|
|
2792
3051
|
profileId: proposal.profileId,
|
|
2793
3052
|
path: proposal.path,
|
|
@@ -2822,7 +3081,7 @@ function checkPendingCommitmentsHandler(state2, integrationManager2) {
|
|
|
2822
3081
|
};
|
|
2823
3082
|
}
|
|
2824
3083
|
if (match.status === "committed") {
|
|
2825
|
-
const { text, isError } = await
|
|
3084
|
+
const { text, isError } = await runCommitted(match, state2, integrationManager2);
|
|
2826
3085
|
return {
|
|
2827
3086
|
content: [{ type: "text", text }],
|
|
2828
3087
|
...isError ? { isError: true } : {}
|
|
@@ -3076,16 +3335,16 @@ function verifyGateContentHashes(content, auth) {
|
|
|
3076
3335
|
}
|
|
3077
3336
|
|
|
3078
3337
|
// src/lib/integration-registry.ts
|
|
3079
|
-
import { mkdirSync as
|
|
3080
|
-
import { homedir as
|
|
3081
|
-
import { dirname as
|
|
3082
|
-
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
3338
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "fs";
|
|
3339
|
+
import { homedir as homedir7 } from "os";
|
|
3340
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
3341
|
+
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join8(homedir7(), ".suveren");
|
|
3083
3342
|
var IntegrationRegistry = class {
|
|
3084
3343
|
integrations = /* @__PURE__ */ new Map();
|
|
3085
3344
|
filePath;
|
|
3086
3345
|
constructor(dataDir) {
|
|
3087
3346
|
const dir = dataDir ?? DEFAULT_DATA_DIR;
|
|
3088
|
-
this.filePath =
|
|
3347
|
+
this.filePath = join8(dir, "integrations.json");
|
|
3089
3348
|
this.load();
|
|
3090
3349
|
}
|
|
3091
3350
|
// ─── CRUD ───────────────────────────────────────────────────────────────
|
|
@@ -3116,12 +3375,12 @@ var IntegrationRegistry = class {
|
|
|
3116
3375
|
}
|
|
3117
3376
|
// ─── Load / Save ────────────────────────────────────────────────────────
|
|
3118
3377
|
load() {
|
|
3119
|
-
if (!
|
|
3120
|
-
|
|
3378
|
+
if (!existsSync8(this.filePath)) {
|
|
3379
|
+
mkdirSync6(dirname6(this.filePath), { recursive: true });
|
|
3121
3380
|
return;
|
|
3122
3381
|
}
|
|
3123
3382
|
try {
|
|
3124
|
-
const raw =
|
|
3383
|
+
const raw = readFileSync8(this.filePath, "utf-8");
|
|
3125
3384
|
const data = JSON.parse(raw);
|
|
3126
3385
|
const migrated = data.integrations.map((i) => {
|
|
3127
3386
|
if ("toolGating" in i && !("profile" in i)) {
|
|
@@ -3150,33 +3409,39 @@ var IntegrationRegistry = class {
|
|
|
3150
3409
|
version: 1,
|
|
3151
3410
|
integrations: Array.from(this.integrations.values())
|
|
3152
3411
|
};
|
|
3153
|
-
|
|
3154
|
-
|
|
3412
|
+
mkdirSync6(dirname6(this.filePath), { recursive: true });
|
|
3413
|
+
writeFileSync6(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
3155
3414
|
}
|
|
3156
3415
|
};
|
|
3157
3416
|
|
|
3158
3417
|
// src/lib/integration-manager.ts
|
|
3159
|
-
import { homedir as
|
|
3160
|
-
import { join as
|
|
3161
|
-
import { existsSync as
|
|
3418
|
+
import { homedir as homedir8 } from "os";
|
|
3419
|
+
import { join as join9, delimiter } from "path";
|
|
3420
|
+
import { existsSync as existsSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync9, rmSync } from "fs";
|
|
3162
3421
|
import { execFile } from "child_process";
|
|
3163
3422
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3164
3423
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3165
3424
|
import { getProfile as getProfile5 } from "@hap/core";
|
|
3166
|
-
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
3167
|
-
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ??
|
|
3168
|
-
var
|
|
3425
|
+
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join9(homedir8(), ".suveren");
|
|
3426
|
+
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join9(DEFAULT_DATA_DIR2, "integrations");
|
|
3427
|
+
var installLock = Promise.resolve();
|
|
3428
|
+
function withInstallLock(task) {
|
|
3429
|
+
const run = installLock.then(task, task);
|
|
3430
|
+
installLock = run.then(() => void 0, () => void 0);
|
|
3431
|
+
return run;
|
|
3432
|
+
}
|
|
3433
|
+
var INTEGRATIONS_BIN = join9(INTEGRATIONS_DIR, "node_modules", ".bin");
|
|
3169
3434
|
function buildPath() {
|
|
3170
3435
|
const base = process.env.PATH ?? "";
|
|
3171
3436
|
return [INTEGRATIONS_BIN, base].join(delimiter);
|
|
3172
3437
|
}
|
|
3173
3438
|
function ensureIntegrationsDir() {
|
|
3174
|
-
if (!
|
|
3175
|
-
|
|
3439
|
+
if (!existsSync9(INTEGRATIONS_DIR)) {
|
|
3440
|
+
mkdirSync7(INTEGRATIONS_DIR, { recursive: true });
|
|
3176
3441
|
}
|
|
3177
|
-
const pkgPath =
|
|
3178
|
-
if (!
|
|
3179
|
-
|
|
3442
|
+
const pkgPath = join9(INTEGRATIONS_DIR, "package.json");
|
|
3443
|
+
if (!existsSync9(pkgPath)) {
|
|
3444
|
+
writeFileSync7(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
|
|
3180
3445
|
}
|
|
3181
3446
|
}
|
|
3182
3447
|
function readAgeOf(config) {
|
|
@@ -3242,19 +3507,19 @@ var IntegrationManager = class {
|
|
|
3242
3507
|
* exist, and treat anything else as not-installed (and reinstallable).
|
|
3243
3508
|
*/
|
|
3244
3509
|
isUsableInstall(npmPackage) {
|
|
3245
|
-
const pkgDir =
|
|
3246
|
-
const pkgJson =
|
|
3247
|
-
if (!
|
|
3510
|
+
const pkgDir = join9(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
3511
|
+
const pkgJson = join9(pkgDir, "package.json");
|
|
3512
|
+
if (!existsSync9(pkgDir) || !existsSync9(pkgJson)) return false;
|
|
3248
3513
|
try {
|
|
3249
|
-
const pkg = JSON.parse(
|
|
3514
|
+
const pkg = JSON.parse(readFileSync9(pkgJson, "utf8"));
|
|
3250
3515
|
const defaultBinName = (pkg.name ?? npmPackage).split("/").pop() ?? "";
|
|
3251
3516
|
const binMap = typeof pkg.bin === "string" ? { [defaultBinName]: pkg.bin } : pkg.bin ?? {};
|
|
3252
3517
|
const binEntries = Object.entries(binMap);
|
|
3253
3518
|
for (const [binName, target] of binEntries) {
|
|
3254
|
-
if (!
|
|
3519
|
+
if (!existsSync9(join9(pkgDir, target))) return false;
|
|
3255
3520
|
if (!this.binShimExists(binName)) return false;
|
|
3256
3521
|
}
|
|
3257
|
-
if (binEntries.length === 0 && pkg.main && !
|
|
3522
|
+
if (binEntries.length === 0 && pkg.main && !existsSync9(join9(pkgDir, pkg.main))) return false;
|
|
3258
3523
|
return true;
|
|
3259
3524
|
} catch {
|
|
3260
3525
|
return false;
|
|
@@ -3268,7 +3533,7 @@ var IntegrationManager = class {
|
|
|
3268
3533
|
binShimExists(binName) {
|
|
3269
3534
|
if (!binName) return false;
|
|
3270
3535
|
const candidates = process.platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.CMD`, `${binName}.ps1`] : [binName];
|
|
3271
|
-
return candidates.some((c) =>
|
|
3536
|
+
return candidates.some((c) => existsSync9(join9(INTEGRATIONS_BIN, c)));
|
|
3272
3537
|
}
|
|
3273
3538
|
/**
|
|
3274
3539
|
* Install an npm package into the managed integrations directory if not
|
|
@@ -3285,8 +3550,15 @@ var IntegrationManager = class {
|
|
|
3285
3550
|
async ensureInstalled(npmPackage) {
|
|
3286
3551
|
ensureIntegrationsDir();
|
|
3287
3552
|
if (this.isUsableInstall(npmPackage)) return;
|
|
3288
|
-
|
|
3289
|
-
|
|
3553
|
+
return withInstallLock(async () => {
|
|
3554
|
+
if (this.isUsableInstall(npmPackage)) return;
|
|
3555
|
+
await this.installNow(npmPackage);
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
/** The actual install. Callers MUST hold the install lock. */
|
|
3559
|
+
async installNow(npmPackage) {
|
|
3560
|
+
const pkgDir = join9(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
3561
|
+
if (existsSync9(pkgDir)) {
|
|
3290
3562
|
console.error(`[IntegrationManager] Removing unusable install of ${npmPackage}`);
|
|
3291
3563
|
try {
|
|
3292
3564
|
rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -3658,8 +3930,9 @@ var IntegrationManager = class {
|
|
|
3658
3930
|
}
|
|
3659
3931
|
return {
|
|
3660
3932
|
profile: profileId,
|
|
3661
|
-
executionMapping:
|
|
3662
|
-
|
|
3933
|
+
executionMapping: {},
|
|
3934
|
+
category: "disabled",
|
|
3935
|
+
disabledReason: "not described in manifest"
|
|
3663
3936
|
};
|
|
3664
3937
|
}
|
|
3665
3938
|
/**
|
|
@@ -3701,28 +3974,28 @@ var IntegrationManager = class {
|
|
|
3701
3974
|
};
|
|
3702
3975
|
|
|
3703
3976
|
// src/lib/profile-loader.ts
|
|
3704
|
-
import { readFileSync as
|
|
3705
|
-
import { join as
|
|
3977
|
+
import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
|
|
3978
|
+
import { join as join10, resolve } from "path";
|
|
3706
3979
|
import { registerProfile } from "@hap/core";
|
|
3707
3980
|
function loadProfiles(profilesDir) {
|
|
3708
|
-
const dir = resolve(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ??
|
|
3709
|
-
const indexPath =
|
|
3710
|
-
if (!
|
|
3981
|
+
const dir = resolve(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join10(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
|
|
3982
|
+
const indexPath = join10(dir, "index.json");
|
|
3983
|
+
if (!existsSync10(indexPath)) {
|
|
3711
3984
|
warnNoProfiles(`No index.json at ${indexPath}`);
|
|
3712
3985
|
return 0;
|
|
3713
3986
|
}
|
|
3714
3987
|
let index;
|
|
3715
3988
|
try {
|
|
3716
|
-
index = JSON.parse(
|
|
3989
|
+
index = JSON.parse(readFileSync10(indexPath, "utf-8"));
|
|
3717
3990
|
} catch (err) {
|
|
3718
3991
|
console.error(`[ProfileLoader] Failed to parse ${indexPath}:`, err);
|
|
3719
3992
|
return 0;
|
|
3720
3993
|
}
|
|
3721
3994
|
let loaded = 0;
|
|
3722
3995
|
for (const [profileId, relativePath] of Object.entries(index.profiles)) {
|
|
3723
|
-
const profilePath =
|
|
3996
|
+
const profilePath = join10(dir, relativePath);
|
|
3724
3997
|
try {
|
|
3725
|
-
const profile = JSON.parse(
|
|
3998
|
+
const profile = JSON.parse(readFileSync10(profilePath, "utf-8"));
|
|
3726
3999
|
registerProfile(profileId, profile);
|
|
3727
4000
|
const shortName = profileId.split("/").pop()?.replace(/@.*$/, "");
|
|
3728
4001
|
if (shortName && shortName !== profileId) {
|
|
@@ -3755,30 +4028,30 @@ function warnNoProfiles(reason) {
|
|
|
3755
4028
|
}
|
|
3756
4029
|
|
|
3757
4030
|
// src/lib/manifest-loader.ts
|
|
3758
|
-
import { readFileSync as
|
|
3759
|
-
import { join as
|
|
4031
|
+
import { readFileSync as readFileSync11, existsSync as existsSync11 } from "fs";
|
|
4032
|
+
import { join as join11, resolve as resolve2 } from "path";
|
|
3760
4033
|
var manifests = /* @__PURE__ */ new Map();
|
|
3761
4034
|
function loadManifests(integrationsDir) {
|
|
3762
4035
|
const dir = resolve2(
|
|
3763
|
-
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ??
|
|
4036
|
+
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join11(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
|
|
3764
4037
|
);
|
|
3765
|
-
const indexPath =
|
|
3766
|
-
if (!
|
|
4038
|
+
const indexPath = join11(dir, "index.json");
|
|
4039
|
+
if (!existsSync11(indexPath)) {
|
|
3767
4040
|
console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
|
|
3768
4041
|
return 0;
|
|
3769
4042
|
}
|
|
3770
4043
|
let index;
|
|
3771
4044
|
try {
|
|
3772
|
-
index = JSON.parse(
|
|
4045
|
+
index = JSON.parse(readFileSync11(indexPath, "utf-8"));
|
|
3773
4046
|
} catch (err) {
|
|
3774
4047
|
console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
|
|
3775
4048
|
return 0;
|
|
3776
4049
|
}
|
|
3777
4050
|
let loaded = 0;
|
|
3778
4051
|
for (const [id, relativePath] of Object.entries(index.integrations)) {
|
|
3779
|
-
const manifestPath =
|
|
4052
|
+
const manifestPath = join11(dir, relativePath);
|
|
3780
4053
|
try {
|
|
3781
|
-
const manifest = JSON.parse(
|
|
4054
|
+
const manifest = JSON.parse(readFileSync11(manifestPath, "utf-8"));
|
|
3782
4055
|
manifests.set(id, manifest);
|
|
3783
4056
|
loaded++;
|
|
3784
4057
|
} catch (err) {
|
|
@@ -3797,6 +4070,90 @@ function getAllManifests() {
|
|
|
3797
4070
|
|
|
3798
4071
|
// bin/http.ts
|
|
3799
4072
|
import { decodeAttestationBlob as decodeAttestationBlob3 } from "@hap/core";
|
|
4073
|
+
|
|
4074
|
+
// src/lib/committed-executor.ts
|
|
4075
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync12, unlinkSync as unlinkSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
4076
|
+
import { dirname as dirname7, join as join12 } from "path";
|
|
4077
|
+
var CommittedExecutor = class {
|
|
4078
|
+
constructor(run) {
|
|
4079
|
+
this.run = run;
|
|
4080
|
+
}
|
|
4081
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
4082
|
+
chain = Promise.resolve();
|
|
4083
|
+
/** Execute `proposal` once, however many callers ask while it is running. */
|
|
4084
|
+
execute(proposal) {
|
|
4085
|
+
const existing = this.inFlight.get(proposal.id);
|
|
4086
|
+
if (existing) return existing;
|
|
4087
|
+
const p = this.chain.then(() => this.run(proposal)).finally(() => this.inFlight.delete(proposal.id));
|
|
4088
|
+
this.chain = p.then(
|
|
4089
|
+
() => void 0,
|
|
4090
|
+
() => void 0
|
|
4091
|
+
);
|
|
4092
|
+
this.inFlight.set(proposal.id, p);
|
|
4093
|
+
return p;
|
|
4094
|
+
}
|
|
4095
|
+
/** Proposal ids currently queued or running. Diagnostic. */
|
|
4096
|
+
get pending() {
|
|
4097
|
+
return [...this.inFlight.keys()];
|
|
4098
|
+
}
|
|
4099
|
+
};
|
|
4100
|
+
var ExecutorLock = class {
|
|
4101
|
+
path;
|
|
4102
|
+
held = false;
|
|
4103
|
+
constructor(dataDir) {
|
|
4104
|
+
this.path = join12(dataDir, "executor.lock");
|
|
4105
|
+
}
|
|
4106
|
+
/** Try to take the lock. Returns the holder's pid when someone else has it. */
|
|
4107
|
+
acquire() {
|
|
4108
|
+
const current = this.read();
|
|
4109
|
+
if (current && current.pid !== process.pid && isAlive(current.pid)) {
|
|
4110
|
+
return { held: false, holderPid: current.pid };
|
|
4111
|
+
}
|
|
4112
|
+
mkdirSync8(dirname7(this.path), { recursive: true, mode: 448 });
|
|
4113
|
+
const lock = { pid: process.pid, startedAt: Math.floor(Date.now() / 1e3) };
|
|
4114
|
+
writeFileSync8(this.path, JSON.stringify(lock), { mode: 384 });
|
|
4115
|
+
this.held = true;
|
|
4116
|
+
return { held: true };
|
|
4117
|
+
}
|
|
4118
|
+
/** True if this process currently holds the lock (re-checks the file). */
|
|
4119
|
+
isHeld() {
|
|
4120
|
+
if (!this.held) return false;
|
|
4121
|
+
const current = this.read();
|
|
4122
|
+
return current?.pid === process.pid;
|
|
4123
|
+
}
|
|
4124
|
+
release() {
|
|
4125
|
+
if (!this.held) return;
|
|
4126
|
+
this.held = false;
|
|
4127
|
+
const current = this.read();
|
|
4128
|
+
if (current?.pid === process.pid) {
|
|
4129
|
+
try {
|
|
4130
|
+
unlinkSync5(this.path);
|
|
4131
|
+
} catch {
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
read() {
|
|
4136
|
+
if (!existsSync12(this.path)) return null;
|
|
4137
|
+
try {
|
|
4138
|
+
const parsed = JSON.parse(readFileSync12(this.path, "utf8"));
|
|
4139
|
+
return typeof parsed?.pid === "number" ? parsed : null;
|
|
4140
|
+
} catch {
|
|
4141
|
+
return null;
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
};
|
|
4145
|
+
function isAlive(pid) {
|
|
4146
|
+
try {
|
|
4147
|
+
process.kill(pid, 0);
|
|
4148
|
+
return true;
|
|
4149
|
+
} catch (err) {
|
|
4150
|
+
return err.code === "EPERM";
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
// bin/http.ts
|
|
4155
|
+
import { homedir as homedir9 } from "os";
|
|
4156
|
+
import { join as join13 } from "path";
|
|
3800
4157
|
var spUrl = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
|
|
3801
4158
|
var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
|
|
3802
4159
|
var state = new SharedState(spUrl);
|
|
@@ -4287,12 +4644,31 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
4287
4644
|
}
|
|
4288
4645
|
});
|
|
4289
4646
|
const PROPOSAL_POLL_INTERVAL = 5e3;
|
|
4647
|
+
const committedExecutor = new CommittedExecutor(
|
|
4648
|
+
(proposal) => executeCommitted(proposal, state, integrationManager)
|
|
4649
|
+
);
|
|
4650
|
+
installCommittedExecutor(committedExecutor);
|
|
4651
|
+
const dataDir = process.env.SUVEREN_DATA_DIR ?? join13(homedir9(), ".suveren");
|
|
4652
|
+
const executorLock = new ExecutorLock(dataDir);
|
|
4653
|
+
const lock = executorLock.acquire();
|
|
4654
|
+
if (!lock.held) {
|
|
4655
|
+
console.error(
|
|
4656
|
+
`[Suveren MCP] Committed-proposal execution is DISABLED in this process: pid ${lock.holderPid} holds ${dataDir}/executor.lock. Approved proposals will run there. Stop that gateway if this one should execute.`
|
|
4657
|
+
);
|
|
4658
|
+
}
|
|
4659
|
+
for (const sig of ["SIGINT", "SIGTERM", "exit"]) {
|
|
4660
|
+
process.once(sig, () => executorLock.release());
|
|
4661
|
+
}
|
|
4662
|
+
let draining = false;
|
|
4290
4663
|
async function executeCommittedProposals() {
|
|
4664
|
+
if (!executorLock.isHeld()) return;
|
|
4665
|
+
if (draining) return;
|
|
4666
|
+
draining = true;
|
|
4291
4667
|
try {
|
|
4292
4668
|
const committed = await state.spClient.getCommittedProposals();
|
|
4293
4669
|
for (const proposal of committed) {
|
|
4294
4670
|
try {
|
|
4295
|
-
const { text, isError } = await
|
|
4671
|
+
const { text, isError } = await committedExecutor.execute(proposal);
|
|
4296
4672
|
console.error(
|
|
4297
4673
|
isError ? `[Suveren MCP] Auto-exec proposal ${proposal.id}: ${text}` : `[Suveren MCP] Auto-executed proposal ${proposal.id}: ${proposal.tool}`
|
|
4298
4674
|
);
|
|
@@ -4301,6 +4677,8 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
4301
4677
|
}
|
|
4302
4678
|
}
|
|
4303
4679
|
} catch {
|
|
4680
|
+
} finally {
|
|
4681
|
+
draining = false;
|
|
4304
4682
|
}
|
|
4305
4683
|
}
|
|
4306
4684
|
setInterval(executeCommittedProposals, PROPOSAL_POLL_INTERVAL);
|