@suveren/gateway 0.3.3 → 0.3.4
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/dist/control-plane/index.mjs +2 -2
- package/dist/mcp-server/http.mjs +57 -59
- package/dist/ui/assets/index-mg8gMTzG.js +105 -0
- package/dist/ui/index.html +1 -1
- package/node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite/encodings/sbcs-data.js +2 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite/encodings/utf32.js +10 -3
- package/node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite/package.json +2 -2
- package/node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite/types/encodings.d.ts +2 -0
- package/node_modules/raw-body/node_modules/iconv-lite/encodings/sbcs-data.js +2 -0
- package/node_modules/raw-body/node_modules/iconv-lite/encodings/utf32.js +10 -3
- package/node_modules/raw-body/node_modules/iconv-lite/package.json +2 -2
- package/node_modules/raw-body/node_modules/iconv-lite/types/encodings.d.ts +2 -0
- package/package.json +1 -1
- package/dist/ui/assets/index-RkZBhIxU.js +0 -105
|
@@ -2009,8 +2009,8 @@ app.get("/active-authorizations", authGuard, async (_req, res) => {
|
|
|
2009
2009
|
});
|
|
2010
2010
|
app.post("/gate-content", jsonParser, authGuard, async (req, res) => {
|
|
2011
2011
|
try {
|
|
2012
|
-
const {
|
|
2013
|
-
await pushGateContent({
|
|
2012
|
+
const { authorizationId, boundsHash, contextHash, context, path, gateContent } = req.body;
|
|
2013
|
+
await pushGateContent({ authorizationId, boundsHash, contextHash, context, path, gateContent });
|
|
2014
2014
|
res.json({ ok: true });
|
|
2015
2015
|
} catch (err) {
|
|
2016
2016
|
console.error("[Control Plane] Gate content forward error:", err);
|
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -63,8 +63,8 @@ var SPClient = class {
|
|
|
63
63
|
/**
|
|
64
64
|
* Get all attestations for a frame hash.
|
|
65
65
|
*/
|
|
66
|
-
async getAttestations(
|
|
67
|
-
const res = await this.fetch(`/api/attestations?
|
|
66
|
+
async getAttestations(authorizationId) {
|
|
67
|
+
const res = await this.fetch(`/api/attestations?authorization_id=${encodeURIComponent(authorizationId)}`);
|
|
68
68
|
if (!res.ok) throw new Error(`SP attestations request failed: ${res.status}`);
|
|
69
69
|
return res.json();
|
|
70
70
|
}
|
|
@@ -138,7 +138,7 @@ var SPClient = class {
|
|
|
138
138
|
const res = await this.fetch("/api/proposals", {
|
|
139
139
|
method: "POST",
|
|
140
140
|
body: JSON.stringify({
|
|
141
|
-
|
|
141
|
+
authorization_id: data.authorizationId,
|
|
142
142
|
profile_id: data.profileId,
|
|
143
143
|
path: data.path,
|
|
144
144
|
pending_domains: data.pendingDomains,
|
|
@@ -157,11 +157,11 @@ var SPClient = class {
|
|
|
157
157
|
return res.json();
|
|
158
158
|
}
|
|
159
159
|
/**
|
|
160
|
-
* Phase 6: Fetch
|
|
160
|
+
* Phase 6: Fetch the authorization summary by its per-ceremony id.
|
|
161
161
|
* Used to read aboveCap and approversFrozen at action time.
|
|
162
162
|
*/
|
|
163
|
-
async
|
|
164
|
-
const res = await this.fetch(`/api/
|
|
163
|
+
async getAuthorizationSummary(authorizationId) {
|
|
164
|
+
const res = await this.fetch(`/api/authorizations/${encodeURIComponent(authorizationId)}`);
|
|
165
165
|
if (res.status === 404) return null;
|
|
166
166
|
if (!res.ok) return null;
|
|
167
167
|
return res.json();
|
|
@@ -228,16 +228,19 @@ var AttestationCache = class {
|
|
|
228
228
|
/**
|
|
229
229
|
* Fetch attestation data from SP for a frame hash and cache it.
|
|
230
230
|
*/
|
|
231
|
-
async syncAuthorization(
|
|
232
|
-
const result = await this.spClient.getAttestations(
|
|
233
|
-
if (!result.profile_id
|
|
231
|
+
async syncAuthorization(authorizationId) {
|
|
232
|
+
const result = await this.spClient.getAttestations(authorizationId);
|
|
233
|
+
if (!result.profile_id) return null;
|
|
234
234
|
if (result.revoked) {
|
|
235
|
-
this.authorizations.delete(
|
|
236
|
-
if (result.frame_hash) this.authorizations.delete(result.frame_hash);
|
|
235
|
+
this.authorizations.delete(authorizationId);
|
|
237
236
|
return null;
|
|
238
237
|
}
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
if (!result.authorization_id) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
"Authority Server response lacks authorization_id \u2014 the AS predates per-ceremony identity. Update the Authority Server (lockstep deploy)."
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
const bounds = result.bounds ?? result.frame ?? {};
|
|
241
244
|
let signedCommitmentMode;
|
|
242
245
|
let subjects;
|
|
243
246
|
const firstBlob = result.attestations[0]?.blob;
|
|
@@ -251,12 +254,12 @@ var AttestationCache = class {
|
|
|
251
254
|
}
|
|
252
255
|
}
|
|
253
256
|
const auth = {
|
|
254
|
-
|
|
257
|
+
authorizationId: result.authorization_id,
|
|
255
258
|
boundsHash: result.bounds_hash,
|
|
256
259
|
// content fingerprint (undefined for pre-v0.4 records)
|
|
257
260
|
contextHash: result.context_hash,
|
|
258
261
|
profileId: result.profile_id,
|
|
259
|
-
path: result.
|
|
262
|
+
path: result.profile_id,
|
|
260
263
|
frame: bounds,
|
|
261
264
|
// compat alias
|
|
262
265
|
bounds: result.bounds,
|
|
@@ -273,7 +276,7 @@ var AttestationCache = class {
|
|
|
273
276
|
subjects,
|
|
274
277
|
complete: result.complete
|
|
275
278
|
};
|
|
276
|
-
this.authorizations.set(auth.
|
|
279
|
+
this.authorizations.set(auth.authorizationId, auth);
|
|
277
280
|
return auth;
|
|
278
281
|
}
|
|
279
282
|
/**
|
|
@@ -302,7 +305,7 @@ var AttestationCache = class {
|
|
|
302
305
|
* Cache an authorization directly (e.g., from SP response after creation).
|
|
303
306
|
*/
|
|
304
307
|
cacheAuthorization(auth) {
|
|
305
|
-
this.authorizations.set(auth.
|
|
308
|
+
this.authorizations.set(auth.authorizationId, auth);
|
|
306
309
|
}
|
|
307
310
|
/**
|
|
308
311
|
* Remove a cached authorization by path. Called when the SP reports the
|
|
@@ -717,9 +720,9 @@ var SharedState = class {
|
|
|
717
720
|
this.executionLog = new ExecutionLog(gateStorePath);
|
|
718
721
|
this.gatekeeper = new MCPGatekeeper(this.cache, this.executionLog);
|
|
719
722
|
}
|
|
720
|
-
setGateContent(path,
|
|
721
|
-
this.gateStore.set(
|
|
722
|
-
|
|
723
|
+
setGateContent(path, authorizationId, profileId, content, opts) {
|
|
724
|
+
this.gateStore.set(authorizationId, {
|
|
725
|
+
authorizationId,
|
|
723
726
|
boundsHash: opts?.boundsHash,
|
|
724
727
|
contextHash: opts?.contextHash,
|
|
725
728
|
path,
|
|
@@ -739,7 +742,7 @@ var SharedState = class {
|
|
|
739
742
|
getEnrichedAuthorizations() {
|
|
740
743
|
const authorizations = this.cache.getAllAuthorizations();
|
|
741
744
|
return authorizations.map((auth) => {
|
|
742
|
-
const gateEntry =
|
|
745
|
+
const gateEntry = this.gateStore.get(auth.authorizationId) ?? null;
|
|
743
746
|
return {
|
|
744
747
|
...auth,
|
|
745
748
|
gateContent: gateEntry?.gateContent ?? null,
|
|
@@ -1134,14 +1137,14 @@ function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
|
1134
1137
|
}
|
|
1135
1138
|
const errors = [];
|
|
1136
1139
|
for (const auth of matchingAuths) {
|
|
1137
|
-
const { result } = await state2.gatekeeper.verifyExecution(auth.
|
|
1140
|
+
const { result } = await state2.gatekeeper.verifyExecution(auth.authorizationId, execution, {
|
|
1138
1141
|
bounds: auth.bounds,
|
|
1139
1142
|
context: auth.context
|
|
1140
1143
|
});
|
|
1141
1144
|
if (!result.approved) {
|
|
1142
1145
|
}
|
|
1143
1146
|
if (result.approved) {
|
|
1144
|
-
const
|
|
1147
|
+
const authzId = auth.authorizationId;
|
|
1145
1148
|
if (isCommitmentDowngrade(auth)) {
|
|
1146
1149
|
return {
|
|
1147
1150
|
content: [{
|
|
@@ -1155,7 +1158,7 @@ function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
|
1155
1158
|
try {
|
|
1156
1159
|
const enrichedArgs = await attachImagePreview(args);
|
|
1157
1160
|
const { proposal } = await state2.spClient.submitProposal({
|
|
1158
|
-
|
|
1161
|
+
authorizationId: authzId,
|
|
1159
1162
|
profileId: auth.profileId,
|
|
1160
1163
|
path: auth.path,
|
|
1161
1164
|
pendingDomains: auth.deferredCommitmentDomains,
|
|
@@ -1187,10 +1190,9 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
1187
1190
|
}
|
|
1188
1191
|
const binding = computeContentBinding(auth.profileId, tool, args);
|
|
1189
1192
|
const { receipt } = await state2.spClient.postReceipt({
|
|
1190
|
-
|
|
1191
|
-
//
|
|
1192
|
-
|
|
1193
|
-
boundsHash: auth.boundsHash ?? authHash,
|
|
1193
|
+
authorizationId: authzId,
|
|
1194
|
+
// Optional cross-check — the AS fails closed on a mismatch.
|
|
1195
|
+
boundsHash: auth.boundsHash,
|
|
1194
1196
|
profileId: auth.profileId,
|
|
1195
1197
|
action: tool.namespacedName,
|
|
1196
1198
|
actionType,
|
|
@@ -1206,20 +1208,20 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
1206
1208
|
let pendingApprovers = spBody.approvers ?? [];
|
|
1207
1209
|
if (pendingApprovers.length === 0) {
|
|
1208
1210
|
try {
|
|
1209
|
-
const
|
|
1210
|
-
if (
|
|
1211
|
-
pendingApprovers =
|
|
1211
|
+
const summary = await state2.spClient.getAuthorizationSummary(authzId);
|
|
1212
|
+
if (summary?.approvers_frozen) {
|
|
1213
|
+
pendingApprovers = summary.approvers_frozen;
|
|
1212
1214
|
}
|
|
1213
|
-
if (
|
|
1214
|
-
pendingApprovers = [
|
|
1215
|
+
if (summary?.created_by) {
|
|
1216
|
+
pendingApprovers = [summary.created_by, ...pendingApprovers];
|
|
1215
1217
|
}
|
|
1216
1218
|
} catch {
|
|
1217
1219
|
}
|
|
1218
1220
|
} else {
|
|
1219
1221
|
try {
|
|
1220
|
-
const
|
|
1221
|
-
if (
|
|
1222
|
-
pendingApprovers = [
|
|
1222
|
+
const summary = await state2.spClient.getAuthorizationSummary(authzId);
|
|
1223
|
+
if (summary?.created_by) {
|
|
1224
|
+
pendingApprovers = [summary.created_by, ...pendingApprovers];
|
|
1223
1225
|
}
|
|
1224
1226
|
} catch {
|
|
1225
1227
|
}
|
|
@@ -1228,7 +1230,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
1228
1230
|
try {
|
|
1229
1231
|
const enrichedArgs = await attachImagePreview(args);
|
|
1230
1232
|
const { proposal } = await state2.spClient.submitProposal({
|
|
1231
|
-
|
|
1233
|
+
authorizationId: authzId,
|
|
1232
1234
|
profileId: auth.profileId,
|
|
1233
1235
|
path: auth.path,
|
|
1234
1236
|
pendingDomains: [],
|
|
@@ -1259,7 +1261,7 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
|
|
|
1259
1261
|
}
|
|
1260
1262
|
if (err instanceof SPReceiptError && err.statusCode === 403) {
|
|
1261
1263
|
if (/revoked/i.test(err.message)) {
|
|
1262
|
-
state2.cache.invalidate(auth.
|
|
1264
|
+
state2.cache.invalidate(auth.authorizationId);
|
|
1263
1265
|
}
|
|
1264
1266
|
return {
|
|
1265
1267
|
content: [{ type: "text", text: `Blocked by SP: ${err.message}` }],
|
|
@@ -1425,8 +1427,8 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
1425
1427
|
const reviewDomains = auth.deferredCommitmentDomains ?? [];
|
|
1426
1428
|
output2.push(reviewDomains.length > 0 ? " Mode: review \u2014 each action requires your approval before it runs (a proposal is created, not executed)" : " Mode: automatic \u2014 actions run immediately within bounds");
|
|
1427
1429
|
try {
|
|
1428
|
-
const meta = await state2.spClient.
|
|
1429
|
-
if (meta?.
|
|
1430
|
+
const meta = await state2.spClient.getAuthorizationSummary(auth.authorizationId);
|
|
1431
|
+
if (meta?.above_cap) {
|
|
1430
1432
|
output2.push(" \u26A0 Above team cap \u2014 actions here require approval even within these bounds.");
|
|
1431
1433
|
}
|
|
1432
1434
|
} catch {
|
|
@@ -1638,10 +1640,9 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
1638
1640
|
}
|
|
1639
1641
|
let receiptId;
|
|
1640
1642
|
try {
|
|
1641
|
-
const boundsHash = proposal.frameHash.split(":").slice(0, 2).join(":");
|
|
1642
1643
|
const binding = computeContentBinding(proposal.profileId, discovered, proposal.toolArgs);
|
|
1643
1644
|
const { receipt } = await state2.spClient.postReceipt({
|
|
1644
|
-
|
|
1645
|
+
authorizationId: proposal.authorizationId,
|
|
1645
1646
|
profileId: proposal.profileId,
|
|
1646
1647
|
action: proposal.tool,
|
|
1647
1648
|
actionType: proposalActionType,
|
|
@@ -2501,18 +2502,17 @@ app.post("/internal/configure", internalOnly, (req, res) => {
|
|
|
2501
2502
|
});
|
|
2502
2503
|
app.post("/internal/gate-content", internalOnly, async (req, res) => {
|
|
2503
2504
|
try {
|
|
2504
|
-
const {
|
|
2505
|
-
const storageHash = frameHash ?? boundsHash;
|
|
2505
|
+
const { authorizationId, boundsHash, contextHash, context, path: rawPath, gateContent } = req.body;
|
|
2506
2506
|
const hasIntent = !!gateContent?.intent;
|
|
2507
2507
|
const legacy = gateContent;
|
|
2508
2508
|
const hasLegacy = !!legacy?.problem && !!legacy?.objective && !!legacy?.tradeoffs;
|
|
2509
|
-
if (!
|
|
2510
|
-
res.status(400).json({ error: "Missing required fields:
|
|
2509
|
+
if (!authorizationId || !hasIntent && !hasLegacy) {
|
|
2510
|
+
res.status(400).json({ error: "Missing required fields: authorizationId, gateContent.{intent} or gateContent.{problem,objective,tradeoffs}" });
|
|
2511
2511
|
return;
|
|
2512
2512
|
}
|
|
2513
|
-
const auth = await state.cache.syncAuthorization(
|
|
2513
|
+
const auth = await state.cache.syncAuthorization(authorizationId);
|
|
2514
2514
|
if (!auth) {
|
|
2515
|
-
res.status(404).json({ error: `No
|
|
2515
|
+
res.status(404).json({ error: `No authorization found for ${authorizationId}` });
|
|
2516
2516
|
return;
|
|
2517
2517
|
}
|
|
2518
2518
|
const verification = verifyGateContentHashes(gateContent, auth);
|
|
@@ -2520,8 +2520,8 @@ app.post("/internal/gate-content", internalOnly, async (req, res) => {
|
|
|
2520
2520
|
res.status(400).json({ error: "Gate content hash mismatch", details: verification.errors });
|
|
2521
2521
|
return;
|
|
2522
2522
|
}
|
|
2523
|
-
const path = rawPath ||
|
|
2524
|
-
state.setGateContent(path,
|
|
2523
|
+
const path = rawPath || authorizationId;
|
|
2524
|
+
state.setGateContent(path, authorizationId, auth.profileId, gateContent, {
|
|
2525
2525
|
boundsHash,
|
|
2526
2526
|
contextHash,
|
|
2527
2527
|
context
|
|
@@ -2577,10 +2577,10 @@ app.post("/internal/resync-gates", internalOnly, async (_req, res) => {
|
|
|
2577
2577
|
let orphaned = 0;
|
|
2578
2578
|
for (const gate of gates) {
|
|
2579
2579
|
try {
|
|
2580
|
-
const
|
|
2581
|
-
const auth = await state.cache.syncAuthorization(
|
|
2580
|
+
const authzId = gate.authorizationId;
|
|
2581
|
+
const auth = await state.cache.syncAuthorization(authzId);
|
|
2582
2582
|
if (auth) {
|
|
2583
|
-
state.setGateContent(
|
|
2583
|
+
state.setGateContent(auth.path, authzId, auth.profileId, gate.gateContent, {
|
|
2584
2584
|
boundsHash: gate.boundsHash,
|
|
2585
2585
|
contextHash: gate.contextHash,
|
|
2586
2586
|
context: gate.context
|
|
@@ -2588,8 +2588,8 @@ app.post("/internal/resync-gates", internalOnly, async (_req, res) => {
|
|
|
2588
2588
|
synced++;
|
|
2589
2589
|
console.error(`[Suveren MCP] Re-synced gate: ${gate.path}`);
|
|
2590
2590
|
} else {
|
|
2591
|
-
state.cache.invalidate(
|
|
2592
|
-
state.gateStore.delete(
|
|
2591
|
+
state.cache.invalidate(authzId);
|
|
2592
|
+
state.gateStore.delete(authzId);
|
|
2593
2593
|
orphaned++;
|
|
2594
2594
|
console.error(`[Suveren MCP] Orphan gate purged (SP attestation deleted): ${gate.path}`);
|
|
2595
2595
|
}
|
|
@@ -2744,9 +2744,7 @@ app.get("/internal/gate-content", internalOnly, (req, res) => {
|
|
|
2744
2744
|
const path = req.query.path;
|
|
2745
2745
|
const gates = state.gateStore.getAll();
|
|
2746
2746
|
if (path) {
|
|
2747
|
-
const entry = gates.find(
|
|
2748
|
-
(g) => g.path === path || g.profileId === path || g.boundsHash === path || g.frameHash === path
|
|
2749
|
-
);
|
|
2747
|
+
const entry = gates.find((g) => g.authorizationId === path);
|
|
2750
2748
|
res.json({ entry: entry ?? null });
|
|
2751
2749
|
} else {
|
|
2752
2750
|
res.json({ entries: gates });
|
|
@@ -2755,7 +2753,7 @@ app.get("/internal/gate-content", internalOnly, (req, res) => {
|
|
|
2755
2753
|
app.get("/internal/authorizations", internalOnly, (_req, res) => {
|
|
2756
2754
|
const authorizations = state.getEnrichedAuthorizations().map((a) => ({
|
|
2757
2755
|
profileId: a.profileId,
|
|
2758
|
-
|
|
2756
|
+
authorizationId: a.authorizationId,
|
|
2759
2757
|
bounds: a.frame,
|
|
2760
2758
|
context: a.context ?? {},
|
|
2761
2759
|
intent: a.gateContent?.intent ?? null,
|