@suveren/gateway 0.7.6 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/content/integrations/mollie.json +103 -28
  2. package/dist/mcp-server/http.mjs +228 -118
  3. package/dist/ui/assets/{index-CsrM6RQr.js → index-BFnSVa9N.js} +22 -22
  4. package/dist/ui/index.html +1 -1
  5. package/node_modules/@hap/core/dist/index.d.mts +29 -6
  6. package/node_modules/@hap/core/dist/index.d.ts +29 -6
  7. package/node_modules/@hap/core/dist/index.js +54 -8
  8. package/node_modules/@hap/core/dist/index.mjs +53 -8
  9. package/node_modules/@hap/core/package.json +2 -1
  10. package/node_modules/@hap/core/src/frame.ts +115 -14
  11. package/node_modules/@hap/core/src/gatekeeper.ts +26 -0
  12. package/node_modules/@hap/core/src/types.ts +7 -1
  13. package/node_modules/@types/node/README.md +1 -1
  14. package/node_modules/@types/node/buffer.buffer.d.ts +5 -0
  15. package/node_modules/@types/node/crypto.d.ts +0 -2
  16. package/node_modules/@types/node/dgram.d.ts +119 -4
  17. package/node_modules/@types/node/ffi.d.ts +1 -1
  18. package/node_modules/@types/node/fs/promises.d.ts +61 -65
  19. package/node_modules/@types/node/fs.d.ts +86 -76
  20. package/node_modules/@types/node/http.d.ts +1 -1
  21. package/node_modules/@types/node/http2.d.ts +2 -2
  22. package/node_modules/@types/node/index.d.ts +1 -0
  23. package/node_modules/@types/node/inspector.d.ts +2 -1
  24. package/node_modules/@types/node/net.d.ts +108 -16
  25. package/node_modules/@types/node/package.json +2 -2
  26. package/node_modules/@types/node/quic.d.ts +37 -11
  27. package/node_modules/@types/node/stream/iter.d.ts +4 -4
  28. package/node_modules/@types/node/tls.d.ts +24 -0
  29. package/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +5 -0
  30. package/node_modules/@types/node/ts5.6/index.d.ts +1 -0
  31. package/node_modules/@types/node/ts5.7/index.d.ts +1 -0
  32. package/node_modules/@types/node/tty.d.ts +4 -3
  33. package/node_modules/@types/node/vfs.d.ts +210 -0
  34. package/node_modules/@types/node/vm.d.ts +1 -1
  35. package/node_modules/express-rate-limit/dist/index.cjs +6 -3
  36. package/node_modules/express-rate-limit/dist/index.d.cts +11 -0
  37. package/node_modules/express-rate-limit/dist/index.d.mts +11 -0
  38. package/node_modules/express-rate-limit/dist/index.d.ts +11 -0
  39. package/node_modules/express-rate-limit/dist/index.mjs +6 -3
  40. package/node_modules/express-rate-limit/{license.md → license} +7 -6
  41. package/node_modules/express-rate-limit/package.json +5 -5
  42. package/node_modules/express-rate-limit/readme.md +1 -0
  43. package/node_modules/fast-uri/index.js +20 -5
  44. package/node_modules/fast-uri/lib/utils.js +8 -1
  45. package/node_modules/fast-uri/package.json +1 -1
  46. package/node_modules/fast-uri/test/component-safe-serialization.test.js +58 -0
  47. package/node_modules/fast-uri/test/ipv6-validation.test.js +34 -0
  48. package/node_modules/ip-address/README.md +92 -82
  49. package/node_modules/ip-address/dist/common.d.ts +20 -0
  50. package/node_modules/ip-address/dist/common.js +36 -0
  51. package/node_modules/ip-address/dist/common.js.map +1 -1
  52. package/node_modules/ip-address/dist/ipv4.d.ts +50 -0
  53. package/node_modules/ip-address/dist/ipv4.js +75 -0
  54. package/node_modules/ip-address/dist/ipv4.js.map +1 -1
  55. package/node_modules/ip-address/dist/ipv6.d.ts +79 -10
  56. package/node_modules/ip-address/dist/ipv6.js +120 -30
  57. package/node_modules/ip-address/dist/ipv6.js.map +1 -1
  58. package/node_modules/ip-address/dist/v4/constants.d.ts +12 -0
  59. package/node_modules/ip-address/dist/v4/constants.js +40 -1
  60. package/node_modules/ip-address/dist/v4/constants.js.map +1 -1
  61. package/node_modules/ip-address/dist/v6/constants.d.ts +14 -0
  62. package/node_modules/ip-address/dist/v6/constants.js +47 -1
  63. package/node_modules/ip-address/dist/v6/constants.js.map +1 -1
  64. package/node_modules/ip-address/package.json +1 -1
  65. package/node_modules/jose/dist/types/jwe/compact/decrypt.d.ts +7 -16
  66. package/node_modules/jose/dist/types/jwe/compact/encrypt.d.ts +6 -17
  67. package/node_modules/jose/dist/types/jwe/flattened/decrypt.d.ts +7 -16
  68. package/node_modules/jose/dist/types/jwe/flattened/encrypt.d.ts +11 -34
  69. package/node_modules/jose/dist/types/jwe/general/decrypt.d.ts +7 -16
  70. package/node_modules/jose/dist/types/jwe/general/encrypt.d.ts +15 -35
  71. package/node_modules/jose/dist/types/jwk/embedded.d.ts +3 -8
  72. package/node_modules/jose/dist/types/jwk/thumbprint.d.ts +5 -5
  73. package/node_modules/jose/dist/types/jwks/local.d.ts +6 -10
  74. package/node_modules/jose/dist/types/jwks/remote.d.ts +13 -24
  75. package/node_modules/jose/dist/types/jws/compact/sign.d.ts +5 -10
  76. package/node_modules/jose/dist/types/jws/compact/verify.d.ts +9 -18
  77. package/node_modules/jose/dist/types/jws/flattened/sign.d.ts +7 -16
  78. package/node_modules/jose/dist/types/jws/flattened/verify.d.ts +9 -18
  79. package/node_modules/jose/dist/types/jws/general/sign.d.ts +11 -21
  80. package/node_modules/jose/dist/types/jws/general/verify.d.ts +9 -18
  81. package/node_modules/jose/dist/types/jwt/decrypt.d.ts +10 -20
  82. package/node_modules/jose/dist/types/jwt/encrypt.d.ts +6 -13
  83. package/node_modules/jose/dist/types/jwt/sign.d.ts +5 -6
  84. package/node_modules/jose/dist/types/jwt/unsecured.d.ts +9 -14
  85. package/node_modules/jose/dist/types/jwt/verify.d.ts +9 -19
  86. package/node_modules/jose/dist/types/key/export.d.ts +4 -4
  87. package/node_modules/jose/dist/types/key/generate_key_pair.d.ts +6 -8
  88. package/node_modules/jose/dist/types/key/generate_secret.d.ts +4 -11
  89. package/node_modules/jose/dist/types/key/import.d.ts +10 -17
  90. package/node_modules/jose/dist/types/types.d.ts +44 -84
  91. package/node_modules/jose/dist/types/util/base64url.d.ts +2 -2
  92. package/node_modules/jose/dist/types/util/decode_jwt.d.ts +3 -10
  93. package/node_modules/jose/dist/types/util/decode_protected_header.d.ts +4 -4
  94. package/node_modules/jose/dist/types/util/errors.d.ts +26 -53
  95. package/node_modules/jose/dist/webapi/index.js +70 -32
  96. package/node_modules/jose/dist/webapi/jwe/compact/decrypt.js +7 -8
  97. package/node_modules/jose/dist/webapi/jwe/compact/encrypt.js +42 -48
  98. package/node_modules/jose/dist/webapi/jwe/flattened/decrypt.js +14 -14
  99. package/node_modules/jose/dist/webapi/jwe/flattened/encrypt.js +53 -64
  100. package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +38 -49
  101. package/node_modules/jose/dist/webapi/jwe/general/encrypt.js +121 -169
  102. package/node_modules/jose/dist/webapi/jwk/embedded.js +30 -32
  103. package/node_modules/jose/dist/webapi/jwk/thumbprint.js +54 -70
  104. package/node_modules/jose/dist/webapi/jwks/local.js +47 -64
  105. package/node_modules/jose/dist/webapi/jwks/remote.js +88 -136
  106. package/node_modules/jose/dist/webapi/jws/compact/sign.js +21 -21
  107. package/node_modules/jose/dist/webapi/jws/compact/verify.js +7 -8
  108. package/node_modules/jose/dist/webapi/jws/flattened/sign.js +29 -31
  109. package/node_modules/jose/dist/webapi/jws/flattened/verify.js +21 -24
  110. package/node_modules/jose/dist/webapi/jws/general/sign.js +61 -72
  111. package/node_modules/jose/dist/webapi/jws/general/verify.js +45 -69
  112. package/node_modules/jose/dist/webapi/jwt/decrypt.js +13 -20
  113. package/node_modules/jose/dist/webapi/jwt/encrypt.js +57 -70
  114. package/node_modules/jose/dist/webapi/jwt/sign.js +17 -16
  115. package/node_modules/jose/dist/webapi/jwt/unsecured.js +32 -40
  116. package/node_modules/jose/dist/webapi/jwt/verify.js +12 -14
  117. package/node_modules/jose/dist/webapi/key/export.js +32 -37
  118. package/node_modules/jose/dist/webapi/key/generate_key_pair.js +40 -48
  119. package/node_modules/jose/dist/webapi/key/generate_secret.js +35 -40
  120. package/node_modules/jose/dist/webapi/key/import.js +52 -56
  121. package/node_modules/jose/dist/webapi/lib/asn1.js +130 -169
  122. package/node_modules/jose/dist/webapi/lib/base64.js +18 -20
  123. package/node_modules/jose/dist/webapi/lib/buffer_utils.js +34 -38
  124. package/node_modules/jose/dist/webapi/lib/content_encryption.js +79 -115
  125. package/node_modules/jose/dist/webapi/lib/crypto_key.js +24 -25
  126. package/node_modules/jose/dist/webapi/lib/deflate.js +45 -47
  127. package/node_modules/jose/dist/webapi/lib/helpers.js +40 -37
  128. package/node_modules/jose/dist/webapi/lib/invalid_key_input.js +10 -24
  129. package/node_modules/jose/dist/webapi/lib/is_key_like.js +17 -15
  130. package/node_modules/jose/dist/webapi/lib/jwe_algorithms.js +106 -93
  131. package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +207 -230
  132. package/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +104 -138
  133. package/node_modules/jose/dist/webapi/lib/jwk_metadata.js +17 -18
  134. package/node_modules/jose/dist/webapi/lib/jwk_to_key.js +9 -16
  135. package/node_modules/jose/dist/webapi/lib/jws_algorithms.js +62 -61
  136. package/node_modules/jose/dist/webapi/lib/jws_sign.js +50 -75
  137. package/node_modules/jose/dist/webapi/lib/jws_verify.js +80 -125
  138. package/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +123 -209
  139. package/node_modules/jose/dist/webapi/lib/key.js +84 -136
  140. package/node_modules/jose/dist/webapi/lib/key_algorithm.js +13 -8
  141. package/node_modules/jose/dist/webapi/lib/key_descriptor.js +8 -6
  142. package/node_modules/jose/dist/webapi/lib/key_management.js +200 -231
  143. package/node_modules/jose/dist/webapi/lib/key_options.js +7 -5
  144. package/node_modules/jose/dist/webapi/lib/options.js +55 -68
  145. package/node_modules/jose/dist/webapi/lib/signing.js +18 -22
  146. package/node_modules/jose/dist/webapi/lib/type_checks.js +26 -29
  147. package/node_modules/jose/dist/webapi/util/base64url.js +26 -35
  148. package/node_modules/jose/dist/webapi/util/decode_jwt.js +32 -31
  149. package/node_modules/jose/dist/webapi/util/decode_protected_header.js +18 -23
  150. package/node_modules/jose/dist/webapi/util/errors.js +94 -84
  151. package/node_modules/jose/package.json +17 -81
  152. package/package.json +2 -2
  153. package/profiles/calendar/0.5.profile.json +190 -0
  154. package/profiles/charge/0.5.profile.json +210 -0
  155. package/profiles/customers/0.7.profile.json +157 -0
  156. package/profiles/deploy/0.9.profile.json +176 -0
  157. package/profiles/email/0.6.profile.json +208 -0
  158. package/profiles/index.json +9 -1
  159. package/profiles/publish/0.5.profile.json +186 -0
  160. package/profiles/purchase/0.5.profile.json +231 -0
  161. package/profiles/records/0.5.profile.json +117 -0
@@ -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) {
@@ -378,11 +462,11 @@ var AttestationCache = class {
378
462
  };
379
463
 
380
464
  // src/lib/gate-store.ts
381
- import { mkdirSync, readFileSync, writeFileSync, existsSync, unlinkSync } from "fs";
465
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, unlinkSync } from "fs";
382
466
  import { homedir } from "os";
383
- import { dirname, join } from "path";
467
+ import { dirname, join as join2 } from "path";
384
468
  import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
385
- var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ?? join(homedir(), ".suveren");
469
+ var DEFAULT_DIR = process.env.SUVEREN_DATA_DIR ?? join2(homedir(), ".suveren");
386
470
  var GateStore = class {
387
471
  entries = /* @__PURE__ */ new Map();
388
472
  baseDir;
@@ -400,11 +484,11 @@ var GateStore = class {
400
484
  */
401
485
  setVaultKey(key) {
402
486
  this.vaultKey = key;
403
- if (existsSync(this.encryptedFilePath)) {
487
+ if (existsSync2(this.encryptedFilePath)) {
404
488
  this.loadEncrypted();
405
489
  } else if (this.entries.size > 0) {
406
490
  this.persistEncrypted();
407
- if (existsSync(this.plaintextFilePath)) {
491
+ if (existsSync2(this.plaintextFilePath)) {
408
492
  try {
409
493
  unlinkSync(this.plaintextFilePath);
410
494
  } catch {
@@ -455,19 +539,19 @@ var GateStore = class {
455
539
  }
456
540
  // ─── File paths ─────────────────────────────────────────────────────────
457
541
  get plaintextFilePath() {
458
- return join(this.baseDir, "gates.json");
542
+ return join2(this.baseDir, "gates.json");
459
543
  }
460
544
  get encryptedFilePath() {
461
- return join(this.baseDir, "gates.enc.json");
545
+ return join2(this.baseDir, "gates.enc.json");
462
546
  }
463
547
  // ─── Load / Persist ─────────────────────────────────────────────────────
464
548
  loadPlaintext() {
465
- if (!existsSync(this.plaintextFilePath)) {
549
+ if (!existsSync2(this.plaintextFilePath)) {
466
550
  mkdirSync(this.baseDir, { recursive: true });
467
551
  return;
468
552
  }
469
553
  try {
470
- const raw = readFileSync(this.plaintextFilePath, "utf-8");
554
+ const raw = readFileSync2(this.plaintextFilePath, "utf-8");
471
555
  const data = JSON.parse(raw);
472
556
  this.entries = new Map(Object.entries(data.entries));
473
557
  } catch {
@@ -476,9 +560,9 @@ var GateStore = class {
476
560
  }
477
561
  }
478
562
  loadEncrypted() {
479
- if (!existsSync(this.encryptedFilePath)) return;
563
+ if (!existsSync2(this.encryptedFilePath)) return;
480
564
  try {
481
- const raw = readFileSync(this.encryptedFilePath, "utf-8");
565
+ const raw = readFileSync2(this.encryptedFilePath, "utf-8");
482
566
  const data = JSON.parse(raw);
483
567
  this.entries = /* @__PURE__ */ new Map();
484
568
  for (const [key, blob] of Object.entries(data.entries)) {
@@ -519,11 +603,11 @@ var GateStore = class {
519
603
  };
520
604
 
521
605
  // src/lib/execution-log.ts
522
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, unlinkSync as unlinkSync2 } from "fs";
606
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "fs";
523
607
  import { homedir as homedir2 } from "os";
524
- import { dirname as dirname2, join as join2 } from "path";
608
+ import { dirname as dirname2, join as join3 } from "path";
525
609
  import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes2 } from "crypto";
526
- var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ?? join2(homedir2(), ".suveren");
610
+ var DEFAULT_DIR2 = process.env.SUVEREN_DATA_DIR ?? join3(homedir2(), ".suveren");
527
611
  var MAX_AGE_SECONDS = 31 * 24 * 60 * 60;
528
612
  var ExecutionLog = class {
529
613
  entries = [];
@@ -542,11 +626,11 @@ var ExecutionLog = class {
542
626
  */
543
627
  setVaultKey(key) {
544
628
  this.vaultKey = key;
545
- if (existsSync2(this.encryptedFilePath)) {
629
+ if (existsSync3(this.encryptedFilePath)) {
546
630
  this.loadEncrypted();
547
631
  } else if (this.entries.length > 0) {
548
632
  this.persistEncrypted();
549
- if (existsSync2(this.plaintextFilePath)) {
633
+ if (existsSync3(this.plaintextFilePath)) {
550
634
  try {
551
635
  unlinkSync2(this.plaintextFilePath);
552
636
  } catch {
@@ -641,19 +725,19 @@ var ExecutionLog = class {
641
725
  }
642
726
  // ─── File paths ─────────────────────────────────────────────────────────
643
727
  get plaintextFilePath() {
644
- return join2(this.baseDir, "execution-log.json");
728
+ return join3(this.baseDir, "execution-log.json");
645
729
  }
646
730
  get encryptedFilePath() {
647
- return join2(this.baseDir, "execution-log.enc.json");
731
+ return join3(this.baseDir, "execution-log.enc.json");
648
732
  }
649
733
  // ─── Load / Persist ─────────────────────────────────────────────────────
650
734
  loadPlaintext() {
651
- if (!existsSync2(this.plaintextFilePath)) {
735
+ if (!existsSync3(this.plaintextFilePath)) {
652
736
  mkdirSync2(this.baseDir, { recursive: true });
653
737
  return;
654
738
  }
655
739
  try {
656
- const raw = readFileSync2(this.plaintextFilePath, "utf-8");
740
+ const raw = readFileSync3(this.plaintextFilePath, "utf-8");
657
741
  const data = JSON.parse(raw);
658
742
  this.entries = data.entries;
659
743
  this.prune();
@@ -663,9 +747,9 @@ var ExecutionLog = class {
663
747
  }
664
748
  }
665
749
  loadEncrypted() {
666
- if (!existsSync2(this.encryptedFilePath)) return;
750
+ if (!existsSync3(this.encryptedFilePath)) return;
667
751
  try {
668
- const raw = readFileSync2(this.encryptedFilePath, "utf-8");
752
+ const raw = readFileSync3(this.encryptedFilePath, "utf-8");
669
753
  const data = JSON.parse(raw);
670
754
  const decrypted = this.decrypt(data.blob);
671
755
  const logFile = JSON.parse(decrypted);
@@ -712,11 +796,11 @@ function windowCutoff(window, now) {
712
796
  }
713
797
 
714
798
  // src/lib/denial-log.ts
715
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, unlinkSync as unlinkSync3 } from "fs";
799
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, unlinkSync as unlinkSync3 } from "fs";
716
800
  import { homedir as homedir3 } from "os";
717
- import { dirname as dirname3, join as join3 } from "path";
801
+ import { dirname as dirname3, join as join4 } from "path";
718
802
  import { createCipheriv as createCipheriv3, createDecipheriv as createDecipheriv3, randomBytes as randomBytes3 } from "crypto";
719
- var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ?? join3(homedir3(), ".suveren");
803
+ var DEFAULT_DIR3 = process.env.SUVEREN_DATA_DIR ?? join4(homedir3(), ".suveren");
720
804
  var MAX_RECORDS = 200;
721
805
  var MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
722
806
  var DenialLog = class {
@@ -729,11 +813,11 @@ var DenialLog = class {
729
813
  }
730
814
  setVaultKey(key) {
731
815
  this.vaultKey = key;
732
- if (existsSync3(this.encryptedFilePath)) {
816
+ if (existsSync4(this.encryptedFilePath)) {
733
817
  this.loadEncrypted();
734
818
  } else if (this.records.length > 0) {
735
819
  this.persistEncrypted();
736
- if (existsSync3(this.plaintextFilePath)) {
820
+ if (existsSync4(this.plaintextFilePath)) {
737
821
  try {
738
822
  unlinkSync3(this.plaintextFilePath);
739
823
  } catch {
@@ -783,18 +867,18 @@ var DenialLog = class {
783
867
  return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]).toString("utf8");
784
868
  }
785
869
  get plaintextFilePath() {
786
- return join3(this.baseDir, "denials.json");
870
+ return join4(this.baseDir, "denials.json");
787
871
  }
788
872
  get encryptedFilePath() {
789
- return join3(this.baseDir, "denials.enc.json");
873
+ return join4(this.baseDir, "denials.enc.json");
790
874
  }
791
875
  loadPlaintext() {
792
- if (!existsSync3(this.plaintextFilePath)) {
876
+ if (!existsSync4(this.plaintextFilePath)) {
793
877
  mkdirSync3(this.baseDir, { recursive: true });
794
878
  return;
795
879
  }
796
880
  try {
797
- const data = JSON.parse(readFileSync3(this.plaintextFilePath, "utf-8"));
881
+ const data = JSON.parse(readFileSync4(this.plaintextFilePath, "utf-8"));
798
882
  this.records = data.records ?? [];
799
883
  } catch {
800
884
  console.error(`[DenialLog] Could not parse ${this.plaintextFilePath}, starting fresh`);
@@ -802,9 +886,9 @@ var DenialLog = class {
802
886
  }
803
887
  }
804
888
  loadEncrypted() {
805
- if (!existsSync3(this.encryptedFilePath)) return;
889
+ if (!existsSync4(this.encryptedFilePath)) return;
806
890
  try {
807
- const data = JSON.parse(readFileSync3(this.encryptedFilePath, "utf-8"));
891
+ const data = JSON.parse(readFileSync4(this.encryptedFilePath, "utf-8"));
808
892
  const logFile = JSON.parse(this.decrypt(data.blob));
809
893
  this.records = logFile.records ?? [];
810
894
  } catch (err) {
@@ -830,11 +914,11 @@ var DenialLog = class {
830
914
  };
831
915
 
832
916
  // src/lib/receipt-archive.ts
833
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4, unlinkSync as unlinkSync4 } from "fs";
917
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, unlinkSync as unlinkSync4 } from "fs";
834
918
  import { homedir as homedir4 } from "os";
835
- import { dirname as dirname4, join as join4 } from "path";
919
+ import { dirname as dirname4, join as join5 } from "path";
836
920
  import { createCipheriv as createCipheriv4, createDecipheriv as createDecipheriv4, randomBytes as randomBytes4 } from "crypto";
837
- var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ?? join4(homedir4(), ".suveren");
921
+ var DEFAULT_DIR4 = process.env.SUVEREN_DATA_DIR ?? join5(homedir4(), ".suveren");
838
922
  function stripPreviews(proposal) {
839
923
  const args = proposal.toolArgs;
840
924
  if (!args || typeof args !== "object") return proposal;
@@ -859,11 +943,11 @@ var ReceiptArchive = class {
859
943
  */
860
944
  setVaultKey(key) {
861
945
  this.vaultKey = key;
862
- if (existsSync4(this.encryptedFilePath)) {
946
+ if (existsSync5(this.encryptedFilePath)) {
863
947
  this.loadEncrypted();
864
948
  } else if (this.data.receipts.length > 0 || Object.keys(this.data.authorizations).length > 0) {
865
949
  this.persistEncrypted();
866
- if (existsSync4(this.plaintextFilePath)) {
950
+ if (existsSync5(this.plaintextFilePath)) {
867
951
  try {
868
952
  unlinkSync4(this.plaintextFilePath);
869
953
  } catch {
@@ -877,7 +961,7 @@ var ReceiptArchive = class {
877
961
  * this state — that would report a hollow success over unreadable evidence.
878
962
  */
879
963
  isLocked() {
880
- return this.vaultKey === null && existsSync4(this.encryptedFilePath);
964
+ return this.vaultKey === null && existsSync5(this.encryptedFilePath);
881
965
  }
882
966
  /**
883
967
  * Archive a signed receipt (and the grant it was issued under).
@@ -978,19 +1062,19 @@ var ReceiptArchive = class {
978
1062
  }
979
1063
  // ─── File paths ─────────────────────────────────────────────────────────
980
1064
  get plaintextFilePath() {
981
- return join4(this.baseDir, "receipt-archive.json");
1065
+ return join5(this.baseDir, "receipt-archive.json");
982
1066
  }
983
1067
  get encryptedFilePath() {
984
- return join4(this.baseDir, "receipt-archive.enc.json");
1068
+ return join5(this.baseDir, "receipt-archive.enc.json");
985
1069
  }
986
1070
  // ─── Load / Persist ─────────────────────────────────────────────────────
987
1071
  loadPlaintext() {
988
- if (!existsSync4(this.plaintextFilePath)) {
1072
+ if (!existsSync5(this.plaintextFilePath)) {
989
1073
  mkdirSync4(this.baseDir, { recursive: true });
990
1074
  return;
991
1075
  }
992
1076
  try {
993
- const raw = readFileSync4(this.plaintextFilePath, "utf-8");
1077
+ const raw = readFileSync5(this.plaintextFilePath, "utf-8");
994
1078
  const parsed = JSON.parse(raw);
995
1079
  this.data = {
996
1080
  version: 1,
@@ -1002,16 +1086,16 @@ var ReceiptArchive = class {
1002
1086
  try {
1003
1087
  writeFileSync4(
1004
1088
  `${this.plaintextFilePath}.corrupt-${Date.now()}`,
1005
- readFileSync4(this.plaintextFilePath)
1089
+ readFileSync5(this.plaintextFilePath)
1006
1090
  );
1007
1091
  } catch {
1008
1092
  }
1009
1093
  }
1010
1094
  }
1011
1095
  loadEncrypted() {
1012
- if (!existsSync4(this.encryptedFilePath)) return;
1096
+ if (!existsSync5(this.encryptedFilePath)) return;
1013
1097
  try {
1014
- const raw = readFileSync4(this.encryptedFilePath, "utf-8");
1098
+ const raw = readFileSync5(this.encryptedFilePath, "utf-8");
1015
1099
  const data = JSON.parse(raw);
1016
1100
  const decrypted = this.decrypt(data.blob);
1017
1101
  const parsed = JSON.parse(decrypted);
@@ -1025,7 +1109,7 @@ var ReceiptArchive = class {
1025
1109
  try {
1026
1110
  writeFileSync4(
1027
1111
  `${this.encryptedFilePath}.corrupt-${Date.now()}`,
1028
- readFileSync4(this.encryptedFilePath)
1112
+ readFileSync5(this.encryptedFilePath)
1029
1113
  );
1030
1114
  } catch {
1031
1115
  }
@@ -1055,9 +1139,16 @@ var ReceiptArchive = class {
1055
1139
  // src/lib/gatekeeper.ts
1056
1140
  import { verify } from "@hap/core";
1057
1141
  var MCPGatekeeper = class {
1058
- constructor(cache, executionLog) {
1142
+ /**
1143
+ * No ExecutionLog dependency by construction. The local record is
1144
+ * display-only (protocol.md → "Executor Gating, Context vs Bounds,
1145
+ * Display-Only Logs"), so the Gatekeeper must not be able to read it: with
1146
+ * no log in scope, a local cumulative check cannot be reintroduced by
1147
+ * accident. `SharedState.executionLog` stays — it feeds the UI's consumption
1148
+ * display and nothing else.
1149
+ */
1150
+ constructor(cache) {
1059
1151
  this.cache = cache;
1060
- this.executionLog = executionLog;
1061
1152
  }
1062
1153
  /**
1063
1154
  * Verify an execution request against a cached authorization.
@@ -1104,16 +1195,15 @@ var MCPGatekeeper = class {
1104
1195
  attestations: auth.attestations.map((a) => a.blob),
1105
1196
  execution,
1106
1197
  context: resolvedContext,
1107
- // Scopes the local cumulative check to the same (profileId, path) key the
1108
- // ExecutionLog records under (see tool-proxy's record call). It cannot go
1109
- // inside `frame`: that is validated against the profile's boundsSchema,
1110
- // which declares no `path` field in any shipped profile, so an extra key
1111
- // there fails verification with "Unknown field". Omitting it entirely
1112
- // the previous behaviour made every cumulative lookup return zero, so
1113
- // the local gate never fired and the AS was doing all the enforcing.
1198
+ // Identifies the grant whose bounds are being checked. hap-core reads it
1199
+ // only to scope a cumulative lookup, which no longer happens here (see
1200
+ // below); it is kept because it belongs to the request, not to the
1201
+ // dropped check. It cannot go inside `frame`: that is validated against
1202
+ // the profile's boundsSchema, which declares no `path` field in any
1203
+ // shipped profile, so an extra key there fails with "Unknown field".
1114
1204
  path: auth.path
1115
1205
  };
1116
- const result = await verify(request, publicKeyHex, void 0, this.executionLog);
1206
+ const result = await verify(request, publicKeyHex);
1117
1207
  return { result, authorization: auth };
1118
1208
  }
1119
1209
  };
@@ -1134,7 +1224,7 @@ var SharedState = class {
1134
1224
  this.executionLog = new ExecutionLog(gateStorePath);
1135
1225
  this.denialLog = new DenialLog(gateStorePath);
1136
1226
  this.receiptArchive = new ReceiptArchive(gateStorePath);
1137
- this.gatekeeper = new MCPGatekeeper(this.cache, this.executionLog);
1227
+ this.gatekeeper = new MCPGatekeeper(this.cache);
1138
1228
  }
1139
1229
  /**
1140
1230
  * Archive a signed receipt into the local receipt archive — the subject's
@@ -1282,17 +1372,17 @@ function formatConsumptionFull(entries) {
1282
1372
  }
1283
1373
 
1284
1374
  // src/lib/context-loader.ts
1285
- import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
1375
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
1286
1376
  import { homedir as homedir5 } from "os";
1287
- import { join as join5 } from "path";
1288
- var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ?? join5(homedir5(), ".suveren");
1377
+ import { join as join6 } from "path";
1378
+ var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ?? join6(homedir5(), ".suveren");
1289
1379
  var BRIEF_MAX_CHARS = 16 * 1024;
1290
1380
  function readContextFile(dataDir) {
1291
1381
  const dir = dataDir ?? DEFAULT_DIR5;
1292
- const filePath = join5(dir, "context.md");
1293
- if (!existsSync5(filePath)) return null;
1382
+ const filePath = join6(dir, "context.md");
1383
+ if (!existsSync6(filePath)) return null;
1294
1384
  try {
1295
- const content = readFileSync5(filePath, "utf-8").trim();
1385
+ const content = readFileSync6(filePath, "utf-8").trim();
1296
1386
  return content || null;
1297
1387
  } catch {
1298
1388
  return null;
@@ -1933,10 +2023,11 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
1933
2023
  return inner(args);
1934
2024
  };
1935
2025
  if (category === "disabled") {
2026
+ const reason = tool.gating.disabledReason;
1936
2027
  return async () => ({
1937
2028
  content: [{
1938
2029
  type: "text",
1939
- text: `Tool "${tool.namespacedName}" is disabled by the integration manifest and cannot be used.`
2030
+ 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
2031
  }],
1941
2032
  isError: true
1942
2033
  });
@@ -2257,7 +2348,12 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
2257
2348
  executionContext: { ...execution },
2258
2349
  amount: typeof execution.amount === "number" ? execution.amount : void 0,
2259
2350
  idempotencyKey: randomUUID(),
2260
- ...binding ?? {}
2351
+ // Privacy: send the hash and how to reproduce it — never the
2352
+ // preimage. `binding` also carries `boundContent`, the plaintext
2353
+ // that was hashed (an email's to/cc/subject/body); it stays on
2354
+ // this machine for the local archive, so it must be picked out
2355
+ // field by field rather than spread.
2356
+ ...binding ? { contentHash: binding.contentHash, contentBinding: binding.contentBinding } : {}
2261
2357
  });
2262
2358
  receiptId = typeof receipt?.id === "string" ? receipt.id : void 0;
2263
2359
  await state2.archiveReceipt(receipt, {
@@ -2407,7 +2503,7 @@ function buildCapabilityMap(profileId, toolGating, integrationManager2) {
2407
2503
  const shortName = shortProfileName2(profileId);
2408
2504
  const gated = [];
2409
2505
  const readOnly = [];
2410
- const defaultGated = [];
2506
+ const undescribed = [];
2411
2507
  for (const tool of allTools) {
2412
2508
  if (!tool.gating || !tool.gating.profile) {
2413
2509
  continue;
@@ -2429,12 +2525,7 @@ function buildCapabilityMap(profileId, toolGating, integrationManager2) {
2429
2525
  const actionType = override.staticExecution?.action_type ?? "unknown";
2430
2526
  gated.push(` - ${tool.originalName}: ${actionType}${mappingDesc ? `, ${mappingDesc}` : ""}`);
2431
2527
  } else {
2432
- const defaultAction = toolGating.default.staticExecution?.action_type;
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
- }
2528
+ undescribed.push(tool.originalName);
2438
2529
  }
2439
2530
  }
2440
2531
  const lines = [];
@@ -2446,10 +2537,12 @@ function buildCapabilityMap(profileId, toolGating, integrationManager2) {
2446
2537
  if (readOnly.length > 0) {
2447
2538
  lines.push(` Read-only (no authorization needed): ${readOnly.join(", ")}`);
2448
2539
  }
2449
- if (defaultGated.length > 0) {
2450
- lines.push(` Default-gated (action_type: ${toolGating.default.staticExecution?.action_type ?? "read"}): ${defaultGated.join(", ")}`);
2540
+ if (undescribed.length > 0) {
2541
+ lines.push(
2542
+ ` Refused (no manifest entry \u2014 do not call): ${undescribed.join(", ")}`
2543
+ );
2451
2544
  }
2452
- if (gated.length === 0 && readOnly.length === 0 && defaultGated.length === 0) {
2545
+ if (gated.length === 0 && readOnly.length === 0 && undescribed.length === 0) {
2453
2546
  lines.push(" No tools discovered for this profile.");
2454
2547
  }
2455
2548
  return lines.join("\n");
@@ -2738,7 +2831,10 @@ async function executeCommitted(proposal, state2, integrationManager2) {
2738
2831
  // SPClient therefore treats proposalId as retry-safe on its own; a
2739
2832
  // PROPOSAL_ALREADY_EXECUTED answer is still a definitive rejection
2740
2833
  // (never retried) and means a *different* caller consumed the proposal.
2741
- ...binding ?? {}
2834
+ // Privacy: hash + how to reproduce it only. `binding.boundContent` is
2835
+ // the plaintext preimage (what the human approved) and never leaves
2836
+ // this machine — see the archive call below.
2837
+ ...binding ? { contentHash: binding.contentHash, contentBinding: binding.contentBinding } : {}
2742
2838
  });
2743
2839
  receiptId = typeof receipt?.id === "string" ? receipt.id : void 0;
2744
2840
  await state2.archiveReceipt(receipt, {
@@ -3076,16 +3172,16 @@ function verifyGateContentHashes(content, auth) {
3076
3172
  }
3077
3173
 
3078
3174
  // src/lib/integration-registry.ts
3079
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
3175
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "fs";
3080
3176
  import { homedir as homedir6 } from "os";
3081
- import { dirname as dirname5, join as join6 } from "path";
3082
- var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join6(homedir6(), ".suveren");
3177
+ import { dirname as dirname5, join as join7 } from "path";
3178
+ var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join7(homedir6(), ".suveren");
3083
3179
  var IntegrationRegistry = class {
3084
3180
  integrations = /* @__PURE__ */ new Map();
3085
3181
  filePath;
3086
3182
  constructor(dataDir) {
3087
3183
  const dir = dataDir ?? DEFAULT_DATA_DIR;
3088
- this.filePath = join6(dir, "integrations.json");
3184
+ this.filePath = join7(dir, "integrations.json");
3089
3185
  this.load();
3090
3186
  }
3091
3187
  // ─── CRUD ───────────────────────────────────────────────────────────────
@@ -3116,12 +3212,12 @@ var IntegrationRegistry = class {
3116
3212
  }
3117
3213
  // ─── Load / Save ────────────────────────────────────────────────────────
3118
3214
  load() {
3119
- if (!existsSync6(this.filePath)) {
3215
+ if (!existsSync7(this.filePath)) {
3120
3216
  mkdirSync5(dirname5(this.filePath), { recursive: true });
3121
3217
  return;
3122
3218
  }
3123
3219
  try {
3124
- const raw = readFileSync6(this.filePath, "utf-8");
3220
+ const raw = readFileSync7(this.filePath, "utf-8");
3125
3221
  const data = JSON.parse(raw);
3126
3222
  const migrated = data.integrations.map((i) => {
3127
3223
  if ("toolGating" in i && !("profile" in i)) {
@@ -3157,25 +3253,31 @@ var IntegrationRegistry = class {
3157
3253
 
3158
3254
  // src/lib/integration-manager.ts
3159
3255
  import { homedir as homedir7 } from "os";
3160
- import { join as join7, delimiter } from "path";
3161
- import { existsSync as existsSync7, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync7, rmSync } from "fs";
3256
+ import { join as join8, delimiter } from "path";
3257
+ import { existsSync as existsSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync8, rmSync } from "fs";
3162
3258
  import { execFile } from "child_process";
3163
3259
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3164
3260
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3165
3261
  import { getProfile as getProfile5 } from "@hap/core";
3166
- var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join7(homedir7(), ".suveren");
3167
- var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join7(DEFAULT_DATA_DIR2, "integrations");
3168
- var INTEGRATIONS_BIN = join7(INTEGRATIONS_DIR, "node_modules", ".bin");
3262
+ var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join8(homedir7(), ".suveren");
3263
+ var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join8(DEFAULT_DATA_DIR2, "integrations");
3264
+ var installLock = Promise.resolve();
3265
+ function withInstallLock(task) {
3266
+ const run = installLock.then(task, task);
3267
+ installLock = run.then(() => void 0, () => void 0);
3268
+ return run;
3269
+ }
3270
+ var INTEGRATIONS_BIN = join8(INTEGRATIONS_DIR, "node_modules", ".bin");
3169
3271
  function buildPath() {
3170
3272
  const base = process.env.PATH ?? "";
3171
3273
  return [INTEGRATIONS_BIN, base].join(delimiter);
3172
3274
  }
3173
3275
  function ensureIntegrationsDir() {
3174
- if (!existsSync7(INTEGRATIONS_DIR)) {
3276
+ if (!existsSync8(INTEGRATIONS_DIR)) {
3175
3277
  mkdirSync6(INTEGRATIONS_DIR, { recursive: true });
3176
3278
  }
3177
- const pkgPath = join7(INTEGRATIONS_DIR, "package.json");
3178
- if (!existsSync7(pkgPath)) {
3279
+ const pkgPath = join8(INTEGRATIONS_DIR, "package.json");
3280
+ if (!existsSync8(pkgPath)) {
3179
3281
  writeFileSync6(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
3180
3282
  }
3181
3283
  }
@@ -3242,19 +3344,19 @@ var IntegrationManager = class {
3242
3344
  * exist, and treat anything else as not-installed (and reinstallable).
3243
3345
  */
3244
3346
  isUsableInstall(npmPackage) {
3245
- const pkgDir = join7(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
3246
- const pkgJson = join7(pkgDir, "package.json");
3247
- if (!existsSync7(pkgDir) || !existsSync7(pkgJson)) return false;
3347
+ const pkgDir = join8(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
3348
+ const pkgJson = join8(pkgDir, "package.json");
3349
+ if (!existsSync8(pkgDir) || !existsSync8(pkgJson)) return false;
3248
3350
  try {
3249
- const pkg = JSON.parse(readFileSync7(pkgJson, "utf8"));
3351
+ const pkg = JSON.parse(readFileSync8(pkgJson, "utf8"));
3250
3352
  const defaultBinName = (pkg.name ?? npmPackage).split("/").pop() ?? "";
3251
3353
  const binMap = typeof pkg.bin === "string" ? { [defaultBinName]: pkg.bin } : pkg.bin ?? {};
3252
3354
  const binEntries = Object.entries(binMap);
3253
3355
  for (const [binName, target] of binEntries) {
3254
- if (!existsSync7(join7(pkgDir, target))) return false;
3356
+ if (!existsSync8(join8(pkgDir, target))) return false;
3255
3357
  if (!this.binShimExists(binName)) return false;
3256
3358
  }
3257
- if (binEntries.length === 0 && pkg.main && !existsSync7(join7(pkgDir, pkg.main))) return false;
3359
+ if (binEntries.length === 0 && pkg.main && !existsSync8(join8(pkgDir, pkg.main))) return false;
3258
3360
  return true;
3259
3361
  } catch {
3260
3362
  return false;
@@ -3268,7 +3370,7 @@ var IntegrationManager = class {
3268
3370
  binShimExists(binName) {
3269
3371
  if (!binName) return false;
3270
3372
  const candidates = process.platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.CMD`, `${binName}.ps1`] : [binName];
3271
- return candidates.some((c) => existsSync7(join7(INTEGRATIONS_BIN, c)));
3373
+ return candidates.some((c) => existsSync8(join8(INTEGRATIONS_BIN, c)));
3272
3374
  }
3273
3375
  /**
3274
3376
  * Install an npm package into the managed integrations directory if not
@@ -3285,8 +3387,15 @@ var IntegrationManager = class {
3285
3387
  async ensureInstalled(npmPackage) {
3286
3388
  ensureIntegrationsDir();
3287
3389
  if (this.isUsableInstall(npmPackage)) return;
3288
- const pkgDir = join7(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
3289
- if (existsSync7(pkgDir)) {
3390
+ return withInstallLock(async () => {
3391
+ if (this.isUsableInstall(npmPackage)) return;
3392
+ await this.installNow(npmPackage);
3393
+ });
3394
+ }
3395
+ /** The actual install. Callers MUST hold the install lock. */
3396
+ async installNow(npmPackage) {
3397
+ const pkgDir = join8(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
3398
+ if (existsSync8(pkgDir)) {
3290
3399
  console.error(`[IntegrationManager] Removing unusable install of ${npmPackage}`);
3291
3400
  try {
3292
3401
  rmSync(pkgDir, { recursive: true, force: true });
@@ -3658,8 +3767,9 @@ var IntegrationManager = class {
3658
3767
  }
3659
3768
  return {
3660
3769
  profile: profileId,
3661
- executionMapping: profileGating.default.executionMapping,
3662
- staticExecution: profileGating.default.staticExecution
3770
+ executionMapping: {},
3771
+ category: "disabled",
3772
+ disabledReason: "not described in manifest"
3663
3773
  };
3664
3774
  }
3665
3775
  /**
@@ -3701,28 +3811,28 @@ var IntegrationManager = class {
3701
3811
  };
3702
3812
 
3703
3813
  // src/lib/profile-loader.ts
3704
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
3705
- import { join as join8, resolve } from "path";
3814
+ import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
3815
+ import { join as join9, resolve } from "path";
3706
3816
  import { registerProfile } from "@hap/core";
3707
3817
  function loadProfiles(profilesDir) {
3708
- const dir = resolve(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join8(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
3709
- const indexPath = join8(dir, "index.json");
3710
- if (!existsSync8(indexPath)) {
3818
+ const dir = resolve(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join9(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
3819
+ const indexPath = join9(dir, "index.json");
3820
+ if (!existsSync9(indexPath)) {
3711
3821
  warnNoProfiles(`No index.json at ${indexPath}`);
3712
3822
  return 0;
3713
3823
  }
3714
3824
  let index;
3715
3825
  try {
3716
- index = JSON.parse(readFileSync8(indexPath, "utf-8"));
3826
+ index = JSON.parse(readFileSync9(indexPath, "utf-8"));
3717
3827
  } catch (err) {
3718
3828
  console.error(`[ProfileLoader] Failed to parse ${indexPath}:`, err);
3719
3829
  return 0;
3720
3830
  }
3721
3831
  let loaded = 0;
3722
3832
  for (const [profileId, relativePath] of Object.entries(index.profiles)) {
3723
- const profilePath = join8(dir, relativePath);
3833
+ const profilePath = join9(dir, relativePath);
3724
3834
  try {
3725
- const profile = JSON.parse(readFileSync8(profilePath, "utf-8"));
3835
+ const profile = JSON.parse(readFileSync9(profilePath, "utf-8"));
3726
3836
  registerProfile(profileId, profile);
3727
3837
  const shortName = profileId.split("/").pop()?.replace(/@.*$/, "");
3728
3838
  if (shortName && shortName !== profileId) {
@@ -3755,30 +3865,30 @@ function warnNoProfiles(reason) {
3755
3865
  }
3756
3866
 
3757
3867
  // src/lib/manifest-loader.ts
3758
- import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
3759
- import { join as join9, resolve as resolve2 } from "path";
3868
+ import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
3869
+ import { join as join10, resolve as resolve2 } from "path";
3760
3870
  var manifests = /* @__PURE__ */ new Map();
3761
3871
  function loadManifests(integrationsDir) {
3762
3872
  const dir = resolve2(
3763
- integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join9(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
3873
+ integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join10(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
3764
3874
  );
3765
- const indexPath = join9(dir, "index.json");
3766
- if (!existsSync9(indexPath)) {
3875
+ const indexPath = join10(dir, "index.json");
3876
+ if (!existsSync10(indexPath)) {
3767
3877
  console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
3768
3878
  return 0;
3769
3879
  }
3770
3880
  let index;
3771
3881
  try {
3772
- index = JSON.parse(readFileSync9(indexPath, "utf-8"));
3882
+ index = JSON.parse(readFileSync10(indexPath, "utf-8"));
3773
3883
  } catch (err) {
3774
3884
  console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
3775
3885
  return 0;
3776
3886
  }
3777
3887
  let loaded = 0;
3778
3888
  for (const [id, relativePath] of Object.entries(index.integrations)) {
3779
- const manifestPath = join9(dir, relativePath);
3889
+ const manifestPath = join10(dir, relativePath);
3780
3890
  try {
3781
- const manifest = JSON.parse(readFileSync9(manifestPath, "utf-8"));
3891
+ const manifest = JSON.parse(readFileSync10(manifestPath, "utf-8"));
3782
3892
  manifests.set(id, manifest);
3783
3893
  loaded++;
3784
3894
  } catch (err) {