@suveren/gateway 0.2.10 → 0.2.12

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.
@@ -270,6 +270,12 @@ async function resyncGates() {
270
270
  }
271
271
  return res.json();
272
272
  }
273
+ async function runCommittedProposals() {
274
+ await fetch(`${MCP_BASE}/internal/run-committed`, {
275
+ method: "POST",
276
+ headers: internalHeaders()
277
+ });
278
+ }
273
279
  async function getIntegrations() {
274
280
  const res = await fetch(`${MCP_BASE}/internal/integrations`, {
275
281
  headers: internalHeaders()
@@ -1935,6 +1941,8 @@ app.use(
1935
1941
  eventBus.emit("proposal-added");
1936
1942
  } else if (method === "POST" && /^\/api\/proposals\/[^/]+\/resolve$/.test(path)) {
1937
1943
  eventBus.emit("proposal-resolved");
1944
+ void runCommittedProposals().catch(() => {
1945
+ });
1938
1946
  } else if (method === "POST" && /^\/api\/proposals\/[^/]+\/approve$/.test(path)) {
1939
1947
  eventBus.emit("proposal-approved");
1940
1948
  } else if (method === "POST" && /^\/api\/proposals\/[^/]+\/reject$/.test(path)) {
@@ -902,12 +902,12 @@ function buildMandateBrief(opts) {
902
902
  }
903
903
 
904
904
  // src/tools/authorizations.ts
905
- import { getProfile as getProfile2 } from "@hap/core";
905
+ import { getProfile as getProfile3 } from "@hap/core";
906
906
 
907
907
  // src/lib/receipt-footer.ts
908
908
  var AS_BASE = (process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai").replace(/\/$/, "");
909
909
  var FOOTER_PROFILES = /* @__PURE__ */ new Set(["email", "calendar", "publish"]);
910
- var CONTENT_FIELD_CANDIDATES = ["body", "text", "description"];
910
+ var CONTENT_FIELD_CANDIDATES = ["body", "text", "description", "content"];
911
911
  var FOOTER_MARKER = "\u2014 Sent by an AI agent via Suveren";
912
912
  function shouldAttachFooter() {
913
913
  return true;
@@ -940,18 +940,51 @@ function stripFooter(value) {
940
940
  function appendVerificationFooter(tool, args, receiptId) {
941
941
  const profile = tool.gating?.profile;
942
942
  if (!profile || !FOOTER_PROFILES.has(profile)) return args;
943
- if (typeof args.raw === "string" && args.raw.trim().length > 0) {
944
- console.error(
945
- `[Suveren MCP] ${tool.namespacedName}: 'raw' message present \u2014 skipping verification footer.`
946
- );
947
- return args;
948
- }
949
943
  const field = detectContentField(tool);
950
944
  if (!field) return args;
951
945
  const current = typeof args[field] === "string" ? args[field] : "";
952
946
  return { ...args, [field]: stripFooter(current) + footerText(receiptId) };
953
947
  }
954
948
 
949
+ // src/lib/content-binding.ts
950
+ import { createHash } from "crypto";
951
+ import { canonicalize, getProfile as getProfile2 } from "@hap/core";
952
+ function canonicalizeText(input) {
953
+ const nfc = input.normalize("NFC");
954
+ const lf = nfc.replace(/\r\n?/g, "\n");
955
+ const lines = lf.split("\n").map((line) => line.replace(/[ \t]+$/, ""));
956
+ return lines.join("\n").replace(/\n+$/, "");
957
+ }
958
+ function sha256Hex(bytes) {
959
+ return createHash("sha256").update(bytes, "utf8").digest("hex");
960
+ }
961
+ function getContentBinding(profileId) {
962
+ const profile = getProfile2(profileId);
963
+ return profile?.content_binding;
964
+ }
965
+ function computeContentBinding(profileId, tool, toolArgs) {
966
+ const binding = getContentBinding(profileId);
967
+ if (!binding) return void 0;
968
+ let canonicalBytes;
969
+ if (binding.kind === "jcs") {
970
+ canonicalBytes = canonicalize(toolArgs);
971
+ } else {
972
+ const field = tool ? detectContentField(tool) : null;
973
+ if (!field) return void 0;
974
+ const raw = typeof toolArgs[field] === "string" ? toolArgs[field] : "";
975
+ canonicalBytes = canonicalizeText(raw);
976
+ }
977
+ return {
978
+ contentHash: `sha256:${sha256Hex(canonicalBytes)}`,
979
+ contentBinding: { version: binding.version, kind: binding.kind }
980
+ };
981
+ }
982
+ function attachReceiptId(tool, args, receiptId) {
983
+ const schema = tool.inputSchema;
984
+ if (!schema?.properties || !("receipt_id" in schema.properties)) return args;
985
+ return { ...args, receipt_id: receiptId };
986
+ }
987
+
955
988
  // src/lib/tool-proxy.ts
956
989
  import { readFile } from "fs/promises";
957
990
  import { extname } from "path";
@@ -1120,6 +1153,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
1120
1153
  `[Suveren MCP] Warning: tool ${tool.namespacedName} has no action_type in staticExecution. Bounds check may be skipped. Fix the integration manifest.`
1121
1154
  );
1122
1155
  }
1156
+ const binding = computeContentBinding(auth.profileId, tool, args);
1123
1157
  const { receipt } = await state2.spClient.postReceipt({
1124
1158
  // v0.5: send the bare content address; the AS reconstructs the
1125
1159
  // per-user storage key. Fall back to frameHash only for legacy
@@ -1130,7 +1164,8 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
1130
1164
  actionType,
1131
1165
  executionContext: { ...execution },
1132
1166
  amount: typeof execution.amount === "number" ? execution.amount : void 0,
1133
- idempotencyKey: randomUUID()
1167
+ idempotencyKey: randomUUID(),
1168
+ ...binding ?? {}
1134
1169
  });
1135
1170
  receiptId = typeof receipt?.id === "string" ? receipt.id : void 0;
1136
1171
  } catch (err) {
@@ -1210,7 +1245,8 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
1210
1245
  execution: { ...execution },
1211
1246
  timestamp: Math.floor(Date.now() / 1e3)
1212
1247
  });
1213
- const outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, args, receiptId) : args;
1248
+ let outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, args, receiptId) : args;
1249
+ if (receiptId) outgoingArgs = attachReceiptId(tool, outgoingArgs, receiptId);
1214
1250
  return integrationManager2.callTool(tool.integrationId, tool.originalName, outgoingArgs);
1215
1251
  }
1216
1252
  const reasons = result.errors.map((e) => {
@@ -1287,7 +1323,9 @@ function buildCapabilityMap(profileId, toolGating, integrationManager2) {
1287
1323
  } else if (override !== void 0) {
1288
1324
  const mappingDesc = Object.entries(override.executionMapping ?? {}).map(([arg, mapping]) => {
1289
1325
  if (typeof mapping === "string") return `${mapping} from ${arg}`;
1290
- return `${mapping.field} from ${arg} (/${mapping.divisor})`;
1326
+ if (Array.isArray(mapping)) return `${mapping.map((m) => m.field).join("+")} from ${arg}`;
1327
+ if ("divisor" in mapping) return `${mapping.field} from ${arg} (/${mapping.divisor})`;
1328
+ return `${mapping.field} from ${arg}`;
1291
1329
  }).join(", ");
1292
1330
  const actionType = override.staticExecution?.action_type ?? "unknown";
1293
1331
  gated.push(` - ${tool.originalName}: ${actionType}${mappingDesc ? `, ${mappingDesc}` : ""}`);
@@ -1353,7 +1391,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
1353
1391
  output2.push("");
1354
1392
  output2.push(` Bounds: ${boundsDesc}`);
1355
1393
  const shortName = shortProfileName2(auth.profileId);
1356
- const profile = getProfile2(auth.profileId) ?? getProfile2(shortName);
1394
+ const profile = getProfile3(auth.profileId) ?? getProfile3(shortName);
1357
1395
  if (profile) {
1358
1396
  const consumption = getConsumptionState(auth, state2.executionLog, profile);
1359
1397
  const consumptionText = formatConsumptionFull(consumption);
@@ -1406,7 +1444,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
1406
1444
  const lines = [` [${auth.path}] ${auth.profileId} \u2014 ${remainingMin} min remaining`];
1407
1445
  lines.push(` Bounds: ${boundsDesc}`);
1408
1446
  const shortName = shortProfileName2(auth.profileId);
1409
- const profile = getProfile2(auth.profileId) ?? getProfile2(shortName);
1447
+ const profile = getProfile3(auth.profileId) ?? getProfile3(shortName);
1410
1448
  if (profile) {
1411
1449
  const consumption = getConsumptionState(auth, state2.executionLog, profile);
1412
1450
  const compact = formatConsumptionCompact(consumption);
@@ -1547,6 +1585,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
1547
1585
  }
1548
1586
  const integrationId = proposal.tool.slice(0, sep);
1549
1587
  const toolName = proposal.tool.slice(sep + 2);
1588
+ const discovered = integrationManager2.getAllTools().find((t) => t.integrationId === integrationId && t.originalName === toolName);
1550
1589
  const proposalActionType = typeof proposal.executionContext.action_type === "string" ? proposal.executionContext.action_type : void 0;
1551
1590
  if (!proposalActionType) {
1552
1591
  console.error(
@@ -1556,6 +1595,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
1556
1595
  let receiptId;
1557
1596
  try {
1558
1597
  const boundsHash = proposal.frameHash.split(":").slice(0, 2).join(":");
1598
+ const binding = computeContentBinding(proposal.profileId, discovered, proposal.toolArgs);
1559
1599
  const { receipt } = await state2.spClient.postReceipt({
1560
1600
  boundsHash,
1561
1601
  profileId: proposal.profileId,
@@ -1564,7 +1604,8 @@ async function executeCommitted(proposal, state2, integrationManager2) {
1564
1604
  executionContext: proposal.executionContext,
1565
1605
  amount: typeof proposal.executionContext.amount === "number" ? proposal.executionContext.amount : void 0,
1566
1606
  proposalId: proposal.id,
1567
- toolArgs: proposal.toolArgs
1607
+ toolArgs: proposal.toolArgs,
1608
+ ...binding ?? {}
1568
1609
  });
1569
1610
  receiptId = typeof receipt?.id === "string" ? receipt.id : void 0;
1570
1611
  } catch (err) {
@@ -1587,11 +1628,11 @@ async function executeCommitted(proposal, state2, integrationManager2) {
1587
1628
  }
1588
1629
  try {
1589
1630
  let outgoingArgs = proposal.toolArgs;
1590
- if (shouldAttachFooter() && receiptId) {
1591
- const discovered = integrationManager2.getAllTools().find((t) => t.integrationId === integrationId && t.originalName === toolName);
1592
- if (discovered) {
1593
- outgoingArgs = appendVerificationFooter(discovered, proposal.toolArgs, receiptId);
1631
+ if (discovered && receiptId) {
1632
+ if (shouldAttachFooter()) {
1633
+ outgoingArgs = appendVerificationFooter(discovered, outgoingArgs, receiptId);
1594
1634
  }
1635
+ outgoingArgs = attachReceiptId(discovered, outgoingArgs, receiptId);
1595
1636
  }
1596
1637
  const result = await integrationManager2.callTool(integrationId, toolName, outgoingArgs);
1597
1638
  state2.executionLog.record({
@@ -1825,10 +1866,10 @@ function createMcpServer(state2, integrationManager2) {
1825
1866
  }
1826
1867
 
1827
1868
  // src/lib/gate-content.ts
1828
- import { createHash } from "crypto";
1869
+ import { createHash as createHash2 } from "crypto";
1829
1870
  import { decodeAttestationBlob } from "@hap/core";
1830
1871
  function hashGateContent(text) {
1831
- const hex = createHash("sha256").update(text, "utf-8").digest("hex");
1872
+ const hex = createHash2("sha256").update(text, "utf-8").digest("hex");
1832
1873
  return `sha256:${hex}`;
1833
1874
  }
1834
1875
  function verifyGateContentHashes(content, auth) {
@@ -1902,15 +1943,14 @@ var IntegrationRegistry = class {
1902
1943
  const migrated = data.integrations.map((i) => {
1903
1944
  if ("toolGating" in i && !("profile" in i)) {
1904
1945
  const old = i;
1905
- const toolGating = old.toolGating;
1906
1946
  const config = {
1907
- id: i.id,
1908
- name: i.name,
1909
- command: i.command,
1910
- args: i.args,
1911
- envKeys: i.envKeys,
1912
- profile: toolGating?.profile ?? null,
1913
- enabled: i.enabled
1947
+ id: old.id,
1948
+ name: old.name,
1949
+ command: old.command,
1950
+ args: old.args,
1951
+ envKeys: old.envKeys,
1952
+ profile: old.toolGating?.profile ?? null,
1953
+ enabled: old.enabled
1914
1954
  };
1915
1955
  return config;
1916
1956
  }
@@ -1939,7 +1979,7 @@ import { existsSync as existsSync5, writeFileSync as writeFileSync4, mkdirSync a
1939
1979
  import { execSync } from "child_process";
1940
1980
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1941
1981
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
1942
- import { getProfile as getProfile3 } from "@hap/core";
1982
+ import { getProfile as getProfile4 } from "@hap/core";
1943
1983
  var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join5(homedir5(), ".suveren");
1944
1984
  var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join5(DEFAULT_DATA_DIR2, "integrations");
1945
1985
  var INTEGRATIONS_BIN = join5(INTEGRATIONS_DIR, "node_modules", ".bin");
@@ -2024,7 +2064,7 @@ var IntegrationManager = class {
2024
2064
  await client.connect(transport);
2025
2065
  console.error(`[IntegrationManager] Connected to ${config.id} (${config.command} ${config.args.join(" ")})`);
2026
2066
  const toolsResult = await client.listTools();
2027
- const profileGating = config.toolGating ?? (config.profile ? getProfile3(config.profile)?.toolGating ?? null : null);
2067
+ const profileGating = config.toolGating ?? (config.profile ? getProfile4(config.profile)?.toolGating ?? null : null);
2028
2068
  const tools = (toolsResult.tools ?? []).map((tool) => {
2029
2069
  const gating = this.resolveToolGating(config.profile, profileGating, tool.name);
2030
2070
  return {
@@ -2421,7 +2461,8 @@ app.post("/internal/gate-content", internalOnly, async (req, res) => {
2421
2461
  const { frameHash, boundsHash, contextHash, context, path: rawPath, gateContent } = req.body;
2422
2462
  const storageHash = frameHash ?? boundsHash;
2423
2463
  const hasIntent = !!gateContent?.intent;
2424
- const hasLegacy = !!gateContent?.problem && !!gateContent?.objective && !!gateContent?.tradeoffs;
2464
+ const legacy = gateContent;
2465
+ const hasLegacy = !!legacy?.problem && !!legacy?.objective && !!legacy?.tradeoffs;
2425
2466
  if (!storageHash || !hasIntent && !hasLegacy) {
2426
2467
  res.status(400).json({ error: "Missing required fields: frameHash (or boundsHash), gateContent.{intent} or gateContent.{problem,objective,tradeoffs}" });
2427
2468
  return;
@@ -2477,6 +2518,12 @@ app.post("/internal/start-pending-integrations", internalOnly, async (_req, res)
2477
2518
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
2478
2519
  }
2479
2520
  });
2521
+ var triggerCommittedExecution = () => {
2522
+ };
2523
+ app.post("/internal/run-committed", internalOnly, (_req, res) => {
2524
+ triggerCommittedExecution();
2525
+ res.json({ ok: true });
2526
+ });
2480
2527
  app.post("/internal/resync-gates", internalOnly, async (_req, res) => {
2481
2528
  const gates = state.gateStore.getAll();
2482
2529
  if (gates.length === 0) {
@@ -2790,4 +2837,7 @@ app.listen(port, "0.0.0.0", () => {
2790
2837
  }
2791
2838
  }
2792
2839
  setInterval(executeCommittedProposals, PROPOSAL_POLL_INTERVAL);
2840
+ triggerCommittedExecution = () => {
2841
+ void executeCommittedProposals();
2842
+ };
2793
2843
  });