@suveren/gateway 0.2.7 → 0.2.9
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/mcp-server/http.mjs +75 -5
- package/package.json +1 -1
- package/profiles/README.md +209 -0
- package/profiles/calendar/0.4.profile.json +183 -0
- package/profiles/charge/0.3.profile.json +173 -0
- package/profiles/charge/0.4.profile.json +200 -0
- package/profiles/customers/0.4.profile.json +111 -0
- package/profiles/email/0.3.profile.json +119 -0
- package/profiles/email/0.4.profile.json +174 -0
- package/profiles/index.json +12 -0
- package/profiles/publish/0.4.profile.json +174 -0
- package/profiles/purchase/0.4.profile.json +225 -0
- package/profiles/records/0.4.profile.json +105 -0
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -904,6 +904,54 @@ function buildMandateBrief(opts) {
|
|
|
904
904
|
// src/tools/authorizations.ts
|
|
905
905
|
import { getProfile as getProfile2 } from "@hap/core";
|
|
906
906
|
|
|
907
|
+
// src/lib/receipt-footer.ts
|
|
908
|
+
var AS_BASE = (process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai").replace(/\/$/, "");
|
|
909
|
+
var FOOTER_PROFILES = /* @__PURE__ */ new Set(["email", "calendar", "publish"]);
|
|
910
|
+
var CONTENT_FIELD_CANDIDATES = ["body", "text", "description"];
|
|
911
|
+
var FOOTER_MARKER = "\u2014 Sent by an AI agent via Suveren";
|
|
912
|
+
function shouldAttachFooter() {
|
|
913
|
+
return true;
|
|
914
|
+
}
|
|
915
|
+
function verifyUrl(receiptId) {
|
|
916
|
+
return `${AS_BASE}/r/${receiptId}`;
|
|
917
|
+
}
|
|
918
|
+
function footerText(receiptId) {
|
|
919
|
+
return `
|
|
920
|
+
|
|
921
|
+
${FOOTER_MARKER}. Verify: ${verifyUrl(receiptId)}`;
|
|
922
|
+
}
|
|
923
|
+
function isStringType(t) {
|
|
924
|
+
return t === "string" || Array.isArray(t) && t.includes("string");
|
|
925
|
+
}
|
|
926
|
+
function detectContentField(tool) {
|
|
927
|
+
const schema = tool.inputSchema;
|
|
928
|
+
const props = schema?.properties ?? {};
|
|
929
|
+
for (const candidate of CONTENT_FIELD_CANDIDATES) {
|
|
930
|
+
const prop = props[candidate];
|
|
931
|
+
if (prop && isStringType(prop.type)) return candidate;
|
|
932
|
+
}
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
function stripFooter(value) {
|
|
936
|
+
const i = value.indexOf(FOOTER_MARKER);
|
|
937
|
+
if (i === -1) return value;
|
|
938
|
+
return value.slice(0, i).replace(/\n+$/, "");
|
|
939
|
+
}
|
|
940
|
+
function appendVerificationFooter(tool, args, receiptId) {
|
|
941
|
+
const profile = tool.gating?.profile;
|
|
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
|
+
const field = detectContentField(tool);
|
|
950
|
+
if (!field) return args;
|
|
951
|
+
const current = typeof args[field] === "string" ? args[field] : "";
|
|
952
|
+
return { ...args, [field]: stripFooter(current) + footerText(receiptId) };
|
|
953
|
+
}
|
|
954
|
+
|
|
907
955
|
// src/lib/tool-proxy.ts
|
|
908
956
|
import { readFile } from "fs/promises";
|
|
909
957
|
import { extname } from "path";
|
|
@@ -1064,6 +1112,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
1064
1112
|
};
|
|
1065
1113
|
}
|
|
1066
1114
|
}
|
|
1115
|
+
let receiptId;
|
|
1067
1116
|
try {
|
|
1068
1117
|
const actionType = typeof execution.action_type === "string" ? execution.action_type : void 0;
|
|
1069
1118
|
if (!actionType) {
|
|
@@ -1071,7 +1120,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
1071
1120
|
`[Suveren MCP] Warning: tool ${tool.namespacedName} has no action_type in staticExecution. Bounds check may be skipped. Fix the integration manifest.`
|
|
1072
1121
|
);
|
|
1073
1122
|
}
|
|
1074
|
-
await state2.spClient.postReceipt({
|
|
1123
|
+
const { receipt } = await state2.spClient.postReceipt({
|
|
1075
1124
|
// v0.5: send the bare content address; the AS reconstructs the
|
|
1076
1125
|
// per-user storage key. Fall back to frameHash only for legacy
|
|
1077
1126
|
// (pre-v0.4) records that predate bounds_hash.
|
|
@@ -1083,6 +1132,7 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
1083
1132
|
amount: typeof execution.amount === "number" ? execution.amount : void 0,
|
|
1084
1133
|
idempotencyKey: randomUUID()
|
|
1085
1134
|
});
|
|
1135
|
+
receiptId = typeof receipt?.id === "string" ? receipt.id : void 0;
|
|
1086
1136
|
} catch (err) {
|
|
1087
1137
|
if (err instanceof SPReceiptError && err.statusCode === 409) {
|
|
1088
1138
|
const spBody = err.body;
|
|
@@ -1160,7 +1210,8 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
|
|
|
1160
1210
|
execution: { ...execution },
|
|
1161
1211
|
timestamp: Math.floor(Date.now() / 1e3)
|
|
1162
1212
|
});
|
|
1163
|
-
|
|
1213
|
+
const outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, args, receiptId) : args;
|
|
1214
|
+
return integrationManager2.callTool(tool.integrationId, tool.originalName, outgoingArgs);
|
|
1164
1215
|
}
|
|
1165
1216
|
const reasons = result.errors.map((e) => {
|
|
1166
1217
|
if (e.code === "BOUND_EXCEEDED") {
|
|
@@ -2193,7 +2244,7 @@ function loadProfiles(profilesDir) {
|
|
|
2193
2244
|
const dir = resolve(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join6(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
|
|
2194
2245
|
const indexPath = join6(dir, "index.json");
|
|
2195
2246
|
if (!existsSync6(indexPath)) {
|
|
2196
|
-
|
|
2247
|
+
warnNoProfiles(`No index.json at ${indexPath}`);
|
|
2197
2248
|
return 0;
|
|
2198
2249
|
}
|
|
2199
2250
|
let index;
|
|
@@ -2218,9 +2269,26 @@ function loadProfiles(profilesDir) {
|
|
|
2218
2269
|
console.error(`[ProfileLoader] Failed to load profile ${profileId} from ${profilePath}:`, err);
|
|
2219
2270
|
}
|
|
2220
2271
|
}
|
|
2221
|
-
|
|
2272
|
+
if (loaded === 0) {
|
|
2273
|
+
warnNoProfiles(`index.json at ${indexPath} registered 0 profiles`);
|
|
2274
|
+
} else {
|
|
2275
|
+
console.error(`[ProfileLoader] Loaded ${loaded} profile(s) from ${dir}`);
|
|
2276
|
+
}
|
|
2222
2277
|
return loaded;
|
|
2223
2278
|
}
|
|
2279
|
+
function warnNoProfiles(reason) {
|
|
2280
|
+
console.error(
|
|
2281
|
+
`
|
|
2282
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
2283
|
+
\u2551 \u26A0 NO PROFILES LOADED \u2014 every gated action will be REJECTED with \u2551
|
|
2284
|
+
\u2551 "Unknown profile". This gateway is misconfigured. \u2551
|
|
2285
|
+
\u2551 Reason: ${reason}
|
|
2286
|
+
\u2551 Fix: install a build that bundles profiles, or set \u2551
|
|
2287
|
+
\u2551 SUVEREN_PROFILES_DIR to a hap-profiles checkout, then restart. \u2551
|
|
2288
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
2289
|
+
`
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2224
2292
|
|
|
2225
2293
|
// src/lib/manifest-loader.ts
|
|
2226
2294
|
import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
|
|
@@ -2554,11 +2622,13 @@ app.all("/mcp", async (req, res) => {
|
|
|
2554
2622
|
res.status(405).json({ error: "Method not allowed" });
|
|
2555
2623
|
}
|
|
2556
2624
|
});
|
|
2625
|
+
var profilesLoaded = 0;
|
|
2557
2626
|
app.get("/health", (_req, res) => {
|
|
2558
2627
|
res.json({
|
|
2559
2628
|
status: "ok",
|
|
2560
2629
|
transports: ["sse", "streamable-http"],
|
|
2561
2630
|
sp: spUrl,
|
|
2631
|
+
profilesLoaded,
|
|
2562
2632
|
activeSessions: activeSessions.size,
|
|
2563
2633
|
storedGates: state.gateStore.getAll().length,
|
|
2564
2634
|
serviceCredentials: Array.from(serviceCredentials.keys()),
|
|
@@ -2648,7 +2718,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
2648
2718
|
console.error(`[Suveren MCP] SSE: http://0.0.0.0:${port}/sse`);
|
|
2649
2719
|
console.error(`[Suveren MCP] Streamable: http://0.0.0.0:${port}/mcp`);
|
|
2650
2720
|
console.error(`[Suveren MCP] SP server: ${spUrl}`);
|
|
2651
|
-
loadProfiles();
|
|
2721
|
+
profilesLoaded = loadProfiles();
|
|
2652
2722
|
loadManifests();
|
|
2653
2723
|
if (integrationRegistry.getEnabled().length === 0) {
|
|
2654
2724
|
const personalManifests = getAllManifests().filter((m) => m.personalDefault);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suveren/gateway",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
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,209 @@
|
|
|
1
|
+
# HAP Authority Profiles
|
|
2
|
+
|
|
3
|
+
Authorization templates for the [Human Agency Protocol](https://humanagencyprotocol.org). Each profile defines what an AI agent is allowed to do within a specific domain — the bounds a human sets, and the Gatekeeper enforces.
|
|
4
|
+
|
|
5
|
+
> **Version 0.4** — March 2026
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What Profiles Define
|
|
10
|
+
|
|
11
|
+
A profile is a complete authorization schema. It specifies:
|
|
12
|
+
|
|
13
|
+
- **Bounds schema** — the limits a human commits to (e.g., max amount, allowed currencies)
|
|
14
|
+
- **Context schema** — local parameters that stay encrypted (e.g., allowed services, environment)
|
|
15
|
+
- **Execution context** — what the Gatekeeper checks at runtime, including cumulative limits
|
|
16
|
+
- **Execution paths** — governance tiers with required domain owners and TTLs
|
|
17
|
+
- **Gates** — the structured questions a human must answer before authorization
|
|
18
|
+
|
|
19
|
+
Profiles are referenced by ID (e.g., `charge@0.4`) and are immutable once published.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Profiles
|
|
24
|
+
|
|
25
|
+
### charge — Charge Authority
|
|
26
|
+
|
|
27
|
+
Governs charging customers: payments, refunds, subscriptions.
|
|
28
|
+
|
|
29
|
+
| Bound | Type | Purpose |
|
|
30
|
+
|-------|------|---------|
|
|
31
|
+
| `amount_max` | per-transaction | Maximum monetary amount |
|
|
32
|
+
| `amount_daily_max` | cumulative | Daily charge cap |
|
|
33
|
+
| `amount_monthly_max` | cumulative | Monthly charge cap |
|
|
34
|
+
| `transaction_count_daily_max` | cumulative | Daily transaction limit |
|
|
35
|
+
|
|
36
|
+
| Context | Type | Purpose |
|
|
37
|
+
|---------|------|---------|
|
|
38
|
+
| `currency` | enum | Permitted currencies |
|
|
39
|
+
| `action_type` | enum | charge, refund, subscribe |
|
|
40
|
+
|
|
41
|
+
| Path | Default TTL |
|
|
42
|
+
|------|-------------|
|
|
43
|
+
| `charge-routine` | 24h |
|
|
44
|
+
| `charge-reviewed` | 4h |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### purchase — Purchase Authority
|
|
49
|
+
|
|
50
|
+
Governs spending company money: subscriptions, supplies, services, advertising.
|
|
51
|
+
|
|
52
|
+
| Bound | Type | Purpose |
|
|
53
|
+
|-------|------|---------|
|
|
54
|
+
| `spend_max` | per-transaction | Maximum spend amount |
|
|
55
|
+
| `spend_daily_max` | cumulative | Daily spend cap |
|
|
56
|
+
| `spend_monthly_max` | cumulative | Monthly spend cap |
|
|
57
|
+
| `transaction_count_daily_max` | cumulative | Daily transaction limit |
|
|
58
|
+
|
|
59
|
+
| Context | Type | Purpose |
|
|
60
|
+
|---------|------|---------|
|
|
61
|
+
| `currency` | enum | Permitted currencies |
|
|
62
|
+
| `category` | enum | subscription, supply, service, advertising |
|
|
63
|
+
| `allowed_vendors` | subset | Approved vendor names |
|
|
64
|
+
|
|
65
|
+
| Path | Default TTL |
|
|
66
|
+
|------|-------------|
|
|
67
|
+
| `purchase-routine` | 24h |
|
|
68
|
+
| `purchase-reviewed` | 4h |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### email — Email Authority
|
|
73
|
+
|
|
74
|
+
Governs sending, drafting, and reading email via Gmail.
|
|
75
|
+
|
|
76
|
+
| Bound | Type | Purpose |
|
|
77
|
+
|-------|------|---------|
|
|
78
|
+
| `recipient_max` | per-email | Maximum recipients |
|
|
79
|
+
| `send_daily_max` | cumulative | Daily send/draft limit |
|
|
80
|
+
| `read_max_age_days` | per-query | Max email age for search |
|
|
81
|
+
| `read_daily_max` | cumulative | Daily read limit |
|
|
82
|
+
|
|
83
|
+
| Context | Type | Purpose |
|
|
84
|
+
|---------|------|---------|
|
|
85
|
+
| `allowed_recipients` | subset | Permitted email addresses |
|
|
86
|
+
| `allowed_domains` | subset | Permitted recipient domains |
|
|
87
|
+
|
|
88
|
+
| Path | Default TTL |
|
|
89
|
+
|------|-------------|
|
|
90
|
+
| `email-draft` | 24h |
|
|
91
|
+
| `email-send` | 4h |
|
|
92
|
+
| `email-read` | 24h |
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### customers — Customer Management
|
|
97
|
+
|
|
98
|
+
Governs CRM operations: contacts, activities, deals, tasks.
|
|
99
|
+
|
|
100
|
+
| Bound | Type | Purpose |
|
|
101
|
+
|-------|------|---------|
|
|
102
|
+
| `write_daily_max` | cumulative | Daily write operations |
|
|
103
|
+
|
|
104
|
+
| Context | Type | Purpose |
|
|
105
|
+
|---------|------|---------|
|
|
106
|
+
| `contact_type` | subset | customer, lead, partner, vendor |
|
|
107
|
+
|
|
108
|
+
| Path | Default TTL |
|
|
109
|
+
|------|-------------|
|
|
110
|
+
| `customers-read` | 24h |
|
|
111
|
+
| `customers-write` | 8h |
|
|
112
|
+
| `customers-delete` | 2h |
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### schedule — Scheduling Authority
|
|
117
|
+
|
|
118
|
+
Governs calendar access: reading, drafting, and booking events.
|
|
119
|
+
|
|
120
|
+
| Bound | Type | Purpose |
|
|
121
|
+
|-------|------|---------|
|
|
122
|
+
| `booking_daily_max` | cumulative | Daily new bookings |
|
|
123
|
+
| `booking_duration_max` | per-event | Max event duration (minutes) |
|
|
124
|
+
| `lookahead_days_max` | per-event | Max days into the future |
|
|
125
|
+
|
|
126
|
+
| Context | Type | Purpose |
|
|
127
|
+
|---------|------|---------|
|
|
128
|
+
| `allowed_calendars` | subset | Permitted calendar names |
|
|
129
|
+
| `allowed_attendees` | subset | Permitted attendee emails |
|
|
130
|
+
| `allowed_domains` | subset | Permitted attendee domains |
|
|
131
|
+
|
|
132
|
+
| Path | Default TTL |
|
|
133
|
+
|------|-------------|
|
|
134
|
+
| `schedule-read` | 24h |
|
|
135
|
+
| `schedule-draft` | 24h |
|
|
136
|
+
| `schedule-book` | 4h |
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### publish — Content Publishing
|
|
141
|
+
|
|
142
|
+
Governs posting public content to social media, blogs, and other platforms.
|
|
143
|
+
|
|
144
|
+
| Bound | Type | Purpose |
|
|
145
|
+
|-------|------|---------|
|
|
146
|
+
| `post_daily_max` | cumulative | Daily post limit |
|
|
147
|
+
| `post_monthly_max` | cumulative | Monthly post limit |
|
|
148
|
+
|
|
149
|
+
| Context | Type | Purpose |
|
|
150
|
+
|---------|------|---------|
|
|
151
|
+
| `allowed_platforms` | subset | twitter, linkedin, instagram, blog, medium |
|
|
152
|
+
| `content_type` | enum | text, image, link, thread |
|
|
153
|
+
| `audience` | enum | public, followers, connections |
|
|
154
|
+
|
|
155
|
+
| Path | Default TTL |
|
|
156
|
+
|------|-------------|
|
|
157
|
+
| `publish-draft` | 24h |
|
|
158
|
+
| `publish-post` | 4h |
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### records — Records Authority
|
|
163
|
+
|
|
164
|
+
Governs accessing and modifying personal structured data: queries, schema changes, exports. Renamed from `data` — for personal databases and spreadsheet replacements, not shared CRM data.
|
|
165
|
+
|
|
166
|
+
| Bound | Type | Purpose |
|
|
167
|
+
|-------|------|---------|
|
|
168
|
+
| `row_limit_max` | per-query | Maximum rows returned |
|
|
169
|
+
| `query_count_daily_max` | cumulative | Daily query limit |
|
|
170
|
+
| `export_row_count_daily_max` | cumulative | Daily exported row limit |
|
|
171
|
+
|
|
172
|
+
| Context | Type | Purpose |
|
|
173
|
+
|---------|------|---------|
|
|
174
|
+
| `access_level` | enum | read, write, admin |
|
|
175
|
+
| `data_scope` | enum | public, internal, pii |
|
|
176
|
+
| `scope` | enum | external (production), internal (sandbox) |
|
|
177
|
+
|
|
178
|
+
| Path | Default TTL |
|
|
179
|
+
|------|-------------|
|
|
180
|
+
| `records-read` | 8h |
|
|
181
|
+
| `records-write` | 2h |
|
|
182
|
+
| `records-export` | 1h |
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## How Profiles Work
|
|
187
|
+
|
|
188
|
+
A human creates an authorization by selecting a profile and execution path, setting the bounds, and answering the gate questions. Domain owners cryptographically attest to the bounds. The Gatekeeper then enforces those bounds on every tool call:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
Human sets bounds Agent requests execution Gatekeeper checks
|
|
192
|
+
amount_max: 80 EUR -> amount: 5 EUR -> 5 <= 80, approved
|
|
193
|
+
action_type: [charge] action_type: "charge" "charge" in [charge], approved
|
|
194
|
+
amount_daily_max: 500 amount_daily: 423 (from log) 423 + 5 <= 500, approved
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
If any bound is exceeded, the Gatekeeper blocks execution.
|
|
198
|
+
|
|
199
|
+
All profiles require the same six gates: bounds, problem, objective, tradeoff, commitment, and decision owner.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Community Profiles
|
|
204
|
+
|
|
205
|
+
Anyone can create and publish profiles through the [HAP Service Provider](https://humanagencyprotocol.com). Published profiles are immutable and versioned. There is no approval process — trust decisions are local.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
See [humanagencyprotocol.org](https://humanagencyprotocol.org) for the full protocol specification.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "github.com/humanagencyprotocol/hap-profiles/calendar@0.4",
|
|
3
|
+
"name": "Calendar",
|
|
4
|
+
"version": "0.4",
|
|
5
|
+
"description": "Calendar authority \u2014 governs calendar access: reading, drafting, and booking events",
|
|
6
|
+
"boundsSchema": {
|
|
7
|
+
"keyOrder": [
|
|
8
|
+
"profile",
|
|
9
|
+
"booking_daily_max",
|
|
10
|
+
"booking_duration_max",
|
|
11
|
+
"lookahead_days_max"
|
|
12
|
+
],
|
|
13
|
+
"fields": {
|
|
14
|
+
"profile": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"required": true
|
|
17
|
+
},
|
|
18
|
+
"booking_daily_max": {
|
|
19
|
+
"type": "number",
|
|
20
|
+
"required": true,
|
|
21
|
+
"displayName": "Daily booking limit",
|
|
22
|
+
"description": "Maximum new events booked per day",
|
|
23
|
+
"unit": "count",
|
|
24
|
+
"boundType": {
|
|
25
|
+
"kind": "cumulative_count",
|
|
26
|
+
"window": "daily"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"booking_duration_max": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"required": true,
|
|
32
|
+
"displayName": "Max event duration",
|
|
33
|
+
"description": "Maximum duration of a single event",
|
|
34
|
+
"unit": "minutes",
|
|
35
|
+
"boundType": {
|
|
36
|
+
"kind": "per_transaction",
|
|
37
|
+
"of": "duration"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"lookahead_days_max": {
|
|
41
|
+
"type": "number",
|
|
42
|
+
"required": true,
|
|
43
|
+
"displayName": "Max lookahead",
|
|
44
|
+
"description": "Maximum number of days into the future an event can be booked",
|
|
45
|
+
"unit": "days",
|
|
46
|
+
"boundType": {
|
|
47
|
+
"kind": "per_transaction",
|
|
48
|
+
"of": "lookahead"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"contextSchema": {
|
|
54
|
+
"keyOrder": [
|
|
55
|
+
"allowed_calendars",
|
|
56
|
+
"allowed_attendees",
|
|
57
|
+
"allowed_domains"
|
|
58
|
+
],
|
|
59
|
+
"fields": {
|
|
60
|
+
"allowed_calendars": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"required": false,
|
|
63
|
+
"displayName": "Allowed calendars",
|
|
64
|
+
"description": "Comma-separated list of calendar names the agent may access",
|
|
65
|
+
"constraint": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"enforceable": [
|
|
68
|
+
"subset"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"allowed_attendees": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"required": false,
|
|
75
|
+
"displayName": "Allowed attendees",
|
|
76
|
+
"format": "email",
|
|
77
|
+
"description": "Comma-separated list of allowed attendee email addresses",
|
|
78
|
+
"constraint": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"enforceable": [
|
|
81
|
+
"subset"
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"allowed_domains": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"required": false,
|
|
88
|
+
"displayName": "Allowed domains",
|
|
89
|
+
"format": "domain",
|
|
90
|
+
"description": "Comma-separated list of allowed attendee domains (e.g. acme.com,partner.org)",
|
|
91
|
+
"constraint": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"enforceable": [
|
|
94
|
+
"subset"
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"executionContextSchema": {
|
|
101
|
+
"fields": {
|
|
102
|
+
"duration": {
|
|
103
|
+
"source": "declared",
|
|
104
|
+
"description": "Duration of the event in minutes",
|
|
105
|
+
"required": true,
|
|
106
|
+
"constraint": {
|
|
107
|
+
"type": "number",
|
|
108
|
+
"enforceable": [
|
|
109
|
+
"max"
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"lookahead": {
|
|
114
|
+
"source": "declared",
|
|
115
|
+
"description": "Days from now the event is scheduled",
|
|
116
|
+
"required": true,
|
|
117
|
+
"constraint": {
|
|
118
|
+
"type": "number",
|
|
119
|
+
"enforceable": [
|
|
120
|
+
"max"
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"allowed_calendars": {
|
|
125
|
+
"source": "declared",
|
|
126
|
+
"description": "Calendar being used (checked against authorized calendars)",
|
|
127
|
+
"required": false,
|
|
128
|
+
"constraint": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"enforceable": [
|
|
131
|
+
"subset"
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"allowed_attendees": {
|
|
136
|
+
"source": "declared",
|
|
137
|
+
"description": "Attendee addresses from this event (checked against authorized attendees)",
|
|
138
|
+
"required": false,
|
|
139
|
+
"constraint": {
|
|
140
|
+
"type": "string",
|
|
141
|
+
"enforceable": [
|
|
142
|
+
"subset"
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"allowed_domains": {
|
|
147
|
+
"source": "declared",
|
|
148
|
+
"description": "Unique attendee domains from this event (checked against authorized domains)",
|
|
149
|
+
"required": false,
|
|
150
|
+
"constraint": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"enforceable": [
|
|
153
|
+
"subset"
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
"booking_count_daily": {
|
|
158
|
+
"source": "cumulative",
|
|
159
|
+
"cumulativeField": "_count",
|
|
160
|
+
"window": "daily",
|
|
161
|
+
"description": "Running daily booking count (resolved from execution log)",
|
|
162
|
+
"required": true,
|
|
163
|
+
"constraint": {
|
|
164
|
+
"type": "number",
|
|
165
|
+
"enforceable": [
|
|
166
|
+
"max"
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"requiredGates": [
|
|
173
|
+
"bounds",
|
|
174
|
+
"intent",
|
|
175
|
+
"commitment",
|
|
176
|
+
"decision_owner"
|
|
177
|
+
],
|
|
178
|
+
"ttl": {
|
|
179
|
+
"default": 86400,
|
|
180
|
+
"max": 31536000
|
|
181
|
+
},
|
|
182
|
+
"retention_minimum": 7776000
|
|
183
|
+
}
|