@suveren/gateway 0.6.3 → 0.6.5
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 +5 -2
- package/dist/mcp-server/http.mjs +52 -2
- package/node_modules/@hap/core/dist/index.d.mts +22 -0
- package/node_modules/@hap/core/dist/index.d.ts +22 -0
- package/node_modules/@hap/core/package.json +1 -1
- package/node_modules/@hap/core/src/types.ts +23 -0
- package/package.json +1 -1
- package/profiles/deploy/0.7.profile.json +2 -1
- package/profiles/deploy/0.8.profile.json +170 -0
- package/profiles/index.json +2 -1
|
@@ -79,8 +79,11 @@
|
|
|
79
79
|
"staticExecution": {
|
|
80
80
|
"action_type": "release"
|
|
81
81
|
},
|
|
82
|
-
"contentField": "
|
|
83
|
-
"actionLabel": "Deployment released"
|
|
82
|
+
"contentField": "commit",
|
|
83
|
+
"actionLabel": "Deployment released",
|
|
84
|
+
"argNormalization": {
|
|
85
|
+
"commit": "sha"
|
|
86
|
+
}
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
89
|
},
|
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -1270,6 +1270,54 @@ function encodeOutgoingArgs(tool, args) {
|
|
|
1270
1270
|
return out ?? args;
|
|
1271
1271
|
}
|
|
1272
1272
|
|
|
1273
|
+
// src/lib/arg-normalization.ts
|
|
1274
|
+
function normalizeUrl(value) {
|
|
1275
|
+
const trimmed = value.trim();
|
|
1276
|
+
if (!trimmed) return value;
|
|
1277
|
+
const withScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
1278
|
+
let url;
|
|
1279
|
+
try {
|
|
1280
|
+
url = new URL(withScheme);
|
|
1281
|
+
} catch {
|
|
1282
|
+
return value;
|
|
1283
|
+
}
|
|
1284
|
+
if (!url.hostname) return value;
|
|
1285
|
+
const scheme = url.protocol.replace(/:$/, "").toLowerCase();
|
|
1286
|
+
const host = url.hostname.toLowerCase();
|
|
1287
|
+
const defaultPort = scheme === "https" && url.port === "443" || scheme === "http" && url.port === "80";
|
|
1288
|
+
const port2 = url.port && !defaultPort ? `:${url.port}` : "";
|
|
1289
|
+
return `${scheme}://${host}${port2}`;
|
|
1290
|
+
}
|
|
1291
|
+
function normalizeSha(value) {
|
|
1292
|
+
const trimmed = value.trim();
|
|
1293
|
+
return /^[0-9a-fA-F]{7,64}$/.test(trimmed) ? trimmed.toLowerCase() : value;
|
|
1294
|
+
}
|
|
1295
|
+
var NORMALIZERS = {
|
|
1296
|
+
url: normalizeUrl,
|
|
1297
|
+
sha: normalizeSha
|
|
1298
|
+
};
|
|
1299
|
+
function normalizeIncomingArgs(tool, args) {
|
|
1300
|
+
const declared = tool?.gating?.argNormalization;
|
|
1301
|
+
if (!declared || !args) return args;
|
|
1302
|
+
let out = null;
|
|
1303
|
+
for (const [field, form] of Object.entries(declared)) {
|
|
1304
|
+
const normalize = NORMALIZERS[form];
|
|
1305
|
+
if (!normalize) {
|
|
1306
|
+
console.error(
|
|
1307
|
+
`[Suveren MCP] Warning: ${tool?.namespacedName} declares argNormalization "${form}" for "${field}", which this gateway does not implement. Using the value as supplied.`
|
|
1308
|
+
);
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
const value = args[field];
|
|
1312
|
+
if (typeof value !== "string") continue;
|
|
1313
|
+
const normalized = normalize(value);
|
|
1314
|
+
if (normalized === value) continue;
|
|
1315
|
+
out ??= { ...args };
|
|
1316
|
+
out[field] = normalized;
|
|
1317
|
+
}
|
|
1318
|
+
return out ?? args;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1273
1321
|
// src/lib/scope-specificity.ts
|
|
1274
1322
|
function tokenSet(value) {
|
|
1275
1323
|
if (value === void 0 || value === null) return /* @__PURE__ */ new Set();
|
|
@@ -1542,7 +1590,8 @@ function denyRead(state2, tool, reason, detail, target) {
|
|
|
1542
1590
|
return { content: [{ type: "text", text: `Read blocked by Gatekeeper: ${detail}` }], isError: true };
|
|
1543
1591
|
}
|
|
1544
1592
|
function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
1545
|
-
const
|
|
1593
|
+
const gated = createGatedToolHandlerInner(tool, integrationManager2, state2);
|
|
1594
|
+
const inner = async (args) => gated(normalizeIncomingArgs(tool, args));
|
|
1546
1595
|
const blocked = tool.gating?.blockedArgs ?? [];
|
|
1547
1596
|
if (blocked.length === 0) return inner;
|
|
1548
1597
|
return async (args) => {
|
|
@@ -3204,7 +3253,8 @@ var IntegrationManager = class {
|
|
|
3204
3253
|
// simply carries no binding, which only surfaces when a verifier asks.
|
|
3205
3254
|
contentField: ext.contentField,
|
|
3206
3255
|
blockedArgs: ext.blockedArgs,
|
|
3207
|
-
argEncoding: ext.argEncoding
|
|
3256
|
+
argEncoding: ext.argEncoding,
|
|
3257
|
+
argNormalization: ext.argNormalization
|
|
3208
3258
|
};
|
|
3209
3259
|
}
|
|
3210
3260
|
return {
|
|
@@ -415,6 +415,28 @@ interface AgentProfile {
|
|
|
415
415
|
* know why 0.5 exists. Absent → surfaces show the version alone.
|
|
416
416
|
*/
|
|
417
417
|
whatsNew?: string;
|
|
418
|
+
/**
|
|
419
|
+
* Whether receipts under this profile may be looked up BY THEIR CONTENT — a
|
|
420
|
+
* verifier holding the content supplies its hash and learns which receipts
|
|
421
|
+
* bind it, without needing a receipt id.
|
|
422
|
+
*
|
|
423
|
+
* OFF unless declared, and that default is the point. The lookup is a
|
|
424
|
+
* confirmation oracle: given a guess at the content it says whether that
|
|
425
|
+
* content was authorized. Where the bound content has low entropy this is
|
|
426
|
+
* disclosure, not verification — guessing a message body is hopeless,
|
|
427
|
+
* guessing `production` takes a second. It is the same enumeration hazard
|
|
428
|
+
* recorded for per-field commitments, arriving from the other direction.
|
|
429
|
+
*
|
|
430
|
+
* Enable only when the bound content is unguessable enough that producing it
|
|
431
|
+
* is equivalent to already having it: prose, an artifact URL, a whole record
|
|
432
|
+
* payload. Never for a binding over a short value drawn from a small set.
|
|
433
|
+
*
|
|
434
|
+
* Why it must exist at all: most consequential actions cannot carry their
|
|
435
|
+
* receipt id. A released build was built before the receipt existed, a
|
|
436
|
+
* content-addressed artifact would change identity if the id were added, and
|
|
437
|
+
* a forwarded message has usually lost the footer that carried it.
|
|
438
|
+
*/
|
|
439
|
+
receipt_lookup?: boolean;
|
|
418
440
|
/**
|
|
419
441
|
* v0.3 frame schema (deprecated, kept for backward compat).
|
|
420
442
|
* Used when boundsSchema is not present.
|
|
@@ -415,6 +415,28 @@ interface AgentProfile {
|
|
|
415
415
|
* know why 0.5 exists. Absent → surfaces show the version alone.
|
|
416
416
|
*/
|
|
417
417
|
whatsNew?: string;
|
|
418
|
+
/**
|
|
419
|
+
* Whether receipts under this profile may be looked up BY THEIR CONTENT — a
|
|
420
|
+
* verifier holding the content supplies its hash and learns which receipts
|
|
421
|
+
* bind it, without needing a receipt id.
|
|
422
|
+
*
|
|
423
|
+
* OFF unless declared, and that default is the point. The lookup is a
|
|
424
|
+
* confirmation oracle: given a guess at the content it says whether that
|
|
425
|
+
* content was authorized. Where the bound content has low entropy this is
|
|
426
|
+
* disclosure, not verification — guessing a message body is hopeless,
|
|
427
|
+
* guessing `production` takes a second. It is the same enumeration hazard
|
|
428
|
+
* recorded for per-field commitments, arriving from the other direction.
|
|
429
|
+
*
|
|
430
|
+
* Enable only when the bound content is unguessable enough that producing it
|
|
431
|
+
* is equivalent to already having it: prose, an artifact URL, a whole record
|
|
432
|
+
* payload. Never for a binding over a short value drawn from a small set.
|
|
433
|
+
*
|
|
434
|
+
* Why it must exist at all: most consequential actions cannot carry their
|
|
435
|
+
* receipt id. A released build was built before the receipt existed, a
|
|
436
|
+
* content-addressed artifact would change identity if the id were added, and
|
|
437
|
+
* a forwarded message has usually lost the footer that carried it.
|
|
438
|
+
*/
|
|
439
|
+
receipt_lookup?: boolean;
|
|
418
440
|
/**
|
|
419
441
|
* v0.3 frame schema (deprecated, kept for backward compat).
|
|
420
442
|
* Used when boundsSchema is not present.
|
|
@@ -427,6 +427,29 @@ export interface AgentProfile {
|
|
|
427
427
|
*/
|
|
428
428
|
whatsNew?: string;
|
|
429
429
|
|
|
430
|
+
/**
|
|
431
|
+
* Whether receipts under this profile may be looked up BY THEIR CONTENT — a
|
|
432
|
+
* verifier holding the content supplies its hash and learns which receipts
|
|
433
|
+
* bind it, without needing a receipt id.
|
|
434
|
+
*
|
|
435
|
+
* OFF unless declared, and that default is the point. The lookup is a
|
|
436
|
+
* confirmation oracle: given a guess at the content it says whether that
|
|
437
|
+
* content was authorized. Where the bound content has low entropy this is
|
|
438
|
+
* disclosure, not verification — guessing a message body is hopeless,
|
|
439
|
+
* guessing `production` takes a second. It is the same enumeration hazard
|
|
440
|
+
* recorded for per-field commitments, arriving from the other direction.
|
|
441
|
+
*
|
|
442
|
+
* Enable only when the bound content is unguessable enough that producing it
|
|
443
|
+
* is equivalent to already having it: prose, an artifact URL, a whole record
|
|
444
|
+
* payload. Never for a binding over a short value drawn from a small set.
|
|
445
|
+
*
|
|
446
|
+
* Why it must exist at all: most consequential actions cannot carry their
|
|
447
|
+
* receipt id. A released build was built before the receipt existed, a
|
|
448
|
+
* content-addressed artifact would change identity if the id were added, and
|
|
449
|
+
* a forwarded message has usually lost the footer that carried it.
|
|
450
|
+
*/
|
|
451
|
+
receipt_lookup?: boolean;
|
|
452
|
+
|
|
430
453
|
/**
|
|
431
454
|
* v0.3 frame schema (deprecated, kept for backward compat).
|
|
432
455
|
* Used when boundsSchema is not present.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suveren/gateway",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "Suveren gateway — local agent gateway built in compliance with the Human Agency Protocol (HAP). Runs the UI, control plane, and MCP server in one Node process.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "github.com/humanagencyprotocol/hap-profiles/deploy@0.8",
|
|
3
|
+
"name": "Deploy",
|
|
4
|
+
"version": "0.8",
|
|
5
|
+
"description": "Authority to make an already-built version live. Successor to deploy@0.6, which gated 'build and deploy this commit'; this gates the act of putting something in front of real users, because building is not consequential and going live is. The action is a RELEASE: it activates bytes that already exist, so the human can look at the artifact before approving and no rebuild can diverge from what was approved. Host-agnostic — the artifact identifier is a Vercel deployment id, a container image digest, an Azure slot — named by the connector manifest, never here.",
|
|
6
|
+
"boundsSchema": {
|
|
7
|
+
"keyOrder": [
|
|
8
|
+
"profile",
|
|
9
|
+
"release_daily_max",
|
|
10
|
+
"rollback_allowed"
|
|
11
|
+
],
|
|
12
|
+
"fields": {
|
|
13
|
+
"profile": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"required": true
|
|
16
|
+
},
|
|
17
|
+
"release_daily_max": {
|
|
18
|
+
"type": "number",
|
|
19
|
+
"required": true,
|
|
20
|
+
"displayName": "Times per day something may go live",
|
|
21
|
+
"description": "How often the agent may make a build live for real users. Counted by the Authority Server, so a compromised gateway cannot exceed it. Building a preview is NOT counted: previews harm nobody, and charging them against this limit would exhaust it before anything reached anyone.",
|
|
22
|
+
"unit": "count",
|
|
23
|
+
"boundType": {
|
|
24
|
+
"kind": "cumulative_count",
|
|
25
|
+
"window": "daily"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"rollback_allowed": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"required": true,
|
|
31
|
+
"displayName": "May put a previous version back live",
|
|
32
|
+
"description": "Whether the agent may promote a PREVIOUS artifact back to live. Its own action type: reverting carries different risk from releasing, and the artifact being restored was approved for a moment that has passed.",
|
|
33
|
+
"enum": [
|
|
34
|
+
"yes",
|
|
35
|
+
"no"
|
|
36
|
+
],
|
|
37
|
+
"boundType": {
|
|
38
|
+
"kind": "enum",
|
|
39
|
+
"values": [
|
|
40
|
+
"yes",
|
|
41
|
+
"no"
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"contextSchema": {
|
|
48
|
+
"keyOrder": [
|
|
49
|
+
"allowed_repos",
|
|
50
|
+
"allowed_environments",
|
|
51
|
+
"allowed_workflows",
|
|
52
|
+
"allowed_branches"
|
|
53
|
+
],
|
|
54
|
+
"fields": {
|
|
55
|
+
"allowed_repos": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"required": true,
|
|
58
|
+
"displayName": "Allowed repositories",
|
|
59
|
+
"description": "Repositories the agent may deploy from, e.g. humanagencyprotocol/hap",
|
|
60
|
+
"constraint": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"enforceable": [
|
|
63
|
+
"subset"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"allowed_environments": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"required": true,
|
|
70
|
+
"displayName": "Allowed environments",
|
|
71
|
+
"description": "Deployment targets the agent may reach. Deliberately NOT an enum: hosts disagree — GitHub uses names you define, Vercel has production and preview, Netlify has deploy-preview and branch-deploy. The connector discovers the real list.",
|
|
72
|
+
"constraint": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"enforceable": [
|
|
75
|
+
"subset"
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"allowed_workflows": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"required": true,
|
|
82
|
+
"displayName": "Allowed pipelines",
|
|
83
|
+
"description": "Named pipelines the agent may run, e.g. deploy.yml. Scoping by pipeline rather than by 'deploy' means adding CI later — a migration, an infrastructure apply — is an edit to this list, not a new profile. The limit of this approach: the grant knows a pipeline is permitted, not what it does. The name has to carry that meaning to the human signing.",
|
|
84
|
+
"constraint": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"enforceable": [
|
|
87
|
+
"subset"
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"allowed_branches": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"required": false,
|
|
94
|
+
"displayName": "Allowed branches",
|
|
95
|
+
"description": "Branches a deployable commit may come from. Optional: a host with no branch concept leaves it empty.",
|
|
96
|
+
"constraint": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"enforceable": [
|
|
99
|
+
"subset"
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"executionContextSchema": {
|
|
106
|
+
"fields": {
|
|
107
|
+
"action_type": {
|
|
108
|
+
"source": "static",
|
|
109
|
+
"description": "release or rollback — declared by the manifest, never taken from an agent argument",
|
|
110
|
+
"required": true,
|
|
111
|
+
"constraint": {
|
|
112
|
+
"type": "string",
|
|
113
|
+
"enforceable": [
|
|
114
|
+
"equals"
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"allowed_repos": {
|
|
119
|
+
"source": "declared",
|
|
120
|
+
"description": "Repository this action targets, checked against the authorized set",
|
|
121
|
+
"required": true,
|
|
122
|
+
"constraint": {
|
|
123
|
+
"type": "string",
|
|
124
|
+
"enforceable": [
|
|
125
|
+
"subset"
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"allowed_environments": {
|
|
130
|
+
"source": "declared",
|
|
131
|
+
"description": "Environment this action targets, checked against the authorized set. Recorded in the receipt, so the proof names where the release went.",
|
|
132
|
+
"required": true,
|
|
133
|
+
"constraint": {
|
|
134
|
+
"type": "string",
|
|
135
|
+
"enforceable": [
|
|
136
|
+
"subset"
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"allowed_workflows": {
|
|
141
|
+
"source": "declared",
|
|
142
|
+
"description": "Pipeline this action runs, checked against the authorized set. Recorded in the receipt, so the proof names what ran.",
|
|
143
|
+
"required": true,
|
|
144
|
+
"constraint": {
|
|
145
|
+
"type": "string",
|
|
146
|
+
"enforceable": [
|
|
147
|
+
"subset"
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
"requiredGates": [
|
|
154
|
+
"bounds",
|
|
155
|
+
"intent",
|
|
156
|
+
"commitment",
|
|
157
|
+
"decision_owner"
|
|
158
|
+
],
|
|
159
|
+
"ttl": {
|
|
160
|
+
"default": 2592000,
|
|
161
|
+
"max": 31536000
|
|
162
|
+
},
|
|
163
|
+
"retention_minimum": 7776000,
|
|
164
|
+
"content_binding": {
|
|
165
|
+
"version": "1",
|
|
166
|
+
"kind": "text"
|
|
167
|
+
},
|
|
168
|
+
"receipt_lookup": true,
|
|
169
|
+
"whatsNew": "Binds the source commit the build came from, not only the artifact address. A released page can show its commit but never its own deployment URL, so this is what lets a reader tie the page they are on to the approval that put it there."
|
|
170
|
+
}
|
package/profiles/index.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"github.com/humanagencyprotocol/hap-profiles/publish@0.4": "publish/0.4.profile.json",
|
|
13
13
|
"github.com/humanagencyprotocol/hap-profiles/records@0.4": "records/0.4.profile.json",
|
|
14
14
|
"github.com/humanagencyprotocol/hap-profiles/deploy@0.6": "deploy/0.6.profile.json",
|
|
15
|
-
"github.com/humanagencyprotocol/hap-profiles/deploy@0.7": "deploy/0.7.profile.json"
|
|
15
|
+
"github.com/humanagencyprotocol/hap-profiles/deploy@0.7": "deploy/0.7.profile.json",
|
|
16
|
+
"github.com/humanagencyprotocol/hap-profiles/deploy@0.8": "deploy/0.8.profile.json"
|
|
16
17
|
}
|
|
17
18
|
}
|