@odingard/guardian-sdk 0.0.0-stage → 0.1.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.
- package/LICENSE +201 -0
- package/README.md +67 -2
- package/dist/index.js +3248 -0
- package/dist-dts/__tests__/client.test.d.ts +1 -0
- package/dist-dts/client.d.ts +80 -0
- package/dist-dts/contracts/action-parameters.d.ts +34 -0
- package/dist-dts/contracts/action-registry.d.ts +197 -0
- package/dist-dts/contracts/action.d.ts +967 -0
- package/dist-dts/contracts/api.d.ts +78 -0
- package/dist-dts/contracts/approval.d.ts +206 -0
- package/dist-dts/contracts/authority-state.d.ts +93 -0
- package/dist-dts/contracts/authority.d.ts +313 -0
- package/dist-dts/contracts/calendar.d.ts +146 -0
- package/dist-dts/contracts/canonical-json.d.ts +4 -0
- package/dist-dts/contracts/capability.d.ts +28 -0
- package/dist-dts/contracts/commerce.d.ts +133 -0
- package/dist-dts/contracts/digest.d.ts +2 -0
- package/dist-dts/contracts/email.d.ts +45 -0
- package/dist-dts/contracts/evidence.d.ts +1030 -0
- package/dist-dts/contracts/execution.d.ts +1338 -0
- package/dist-dts/contracts/files.d.ts +61 -0
- package/dist-dts/contracts/identifiers.d.ts +33 -0
- package/dist-dts/contracts/index.d.ts +19 -0
- package/dist-dts/contracts/protected-data.d.ts +93 -0
- package/dist-dts/contracts/tools.d.ts +187 -0
- package/dist-dts/index.d.ts +1 -0
- package/dist-dts/proposal.d.ts +25 -0
- package/package.json +50 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,3248 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
// src/proposal.ts
|
|
5
|
+
import { z as z16 } from "zod";
|
|
6
|
+
|
|
7
|
+
// src/contracts/action.ts
|
|
8
|
+
import { z as z3 } from "zod";
|
|
9
|
+
|
|
10
|
+
// src/contracts/authority.ts
|
|
11
|
+
import { z as z2 } from "zod";
|
|
12
|
+
|
|
13
|
+
// src/contracts/identifiers.ts
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
function identifier(prefix, brand) {
|
|
16
|
+
return z.string().regex(
|
|
17
|
+
new RegExp(`^${prefix}_[A-Za-z0-9][A-Za-z0-9_-]{7,119}$`),
|
|
18
|
+
`${brand} must use the ${prefix}_ prefix and contain 8-120 safe characters`
|
|
19
|
+
).brand();
|
|
20
|
+
}
|
|
21
|
+
var TenantIdSchema = identifier("ten", "TenantId");
|
|
22
|
+
var PrincipalIdSchema = identifier("pri", "PrincipalId");
|
|
23
|
+
var AgentIdSchema = identifier("agt", "AgentId");
|
|
24
|
+
var GrantIdSchema = identifier("grt", "GrantId");
|
|
25
|
+
var PolicyIdSchema = identifier("pol", "PolicyId");
|
|
26
|
+
var RequestIdSchema = identifier("req", "RequestId");
|
|
27
|
+
var ApprovalIdSchema = identifier("apr", "ApprovalId");
|
|
28
|
+
var CapabilityIdSchema = identifier("cap", "CapabilityId");
|
|
29
|
+
var CorrelationIdSchema = identifier("cor", "CorrelationId");
|
|
30
|
+
var EvidenceIdSchema = identifier("evd", "EvidenceId");
|
|
31
|
+
var AdapterIdSchema = identifier("adp", "AdapterId");
|
|
32
|
+
var ConnectionIdSchema = identifier("pcn", "ConnectionId");
|
|
33
|
+
var AttemptIdSchema = identifier("att", "AttemptId");
|
|
34
|
+
var BudgetIdSchema = identifier("bdg", "BudgetId");
|
|
35
|
+
var TaskIdSchema = identifier("tsk", "TaskId");
|
|
36
|
+
var NonceSchema = z.string().regex(
|
|
37
|
+
/^nce_[A-Za-z0-9_-]{22,116}$/,
|
|
38
|
+
"Nonce must use the nce_ prefix and contain 22-116 base64url-safe characters"
|
|
39
|
+
).brand();
|
|
40
|
+
|
|
41
|
+
// src/contracts/authority.ts
|
|
42
|
+
var uniqueStrings = (values) => new Set(values).size === values.length;
|
|
43
|
+
var sorted = (values) => [...values].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
44
|
+
var CentsSchema = z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
45
|
+
var FactLabelSchema = z2.string().trim().min(1).max(256);
|
|
46
|
+
var RecurrencePolicySchema = z2.enum(["never", "allowed"]);
|
|
47
|
+
var BudgetConsumptionSchema = z2.object({
|
|
48
|
+
budgetId: BudgetIdSchema,
|
|
49
|
+
amountCents: CentsSchema,
|
|
50
|
+
transactions: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER)
|
|
51
|
+
}).strict();
|
|
52
|
+
var ActionTypeSchema = z2.string().trim().min(1).max(128).regex(/^[a-z][a-z0-9]*(?:[._:-][a-z0-9]+)*$/).brand();
|
|
53
|
+
var ResourceRefSchema = z2.string().trim().min(1).max(512).refine(
|
|
54
|
+
(value) => value.normalize("NFC") === value,
|
|
55
|
+
"resource references must already use NFC normalization"
|
|
56
|
+
).brand();
|
|
57
|
+
var EpochMillisecondsSchema = z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
58
|
+
var AgentInterfaceSchema = z2.enum(["internal", "mcp", "rest"]);
|
|
59
|
+
var PrincipalSchema = z2.object({
|
|
60
|
+
id: PrincipalIdSchema,
|
|
61
|
+
tenantId: TenantIdSchema,
|
|
62
|
+
status: z2.enum(["active", "disabled"])
|
|
63
|
+
}).strict();
|
|
64
|
+
var BudgetStateSchema = z2.object({
|
|
65
|
+
id: BudgetIdSchema,
|
|
66
|
+
scope: z2.enum(["agent", "task"]),
|
|
67
|
+
taskId: TaskIdSchema.optional(),
|
|
68
|
+
limitCents: CentsSchema,
|
|
69
|
+
consumedCents: CentsSchema,
|
|
70
|
+
transactionLimit: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
71
|
+
consumedTransactions: z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
72
|
+
expiresAtEpochMs: EpochMillisecondsSchema.optional(),
|
|
73
|
+
status: z2.enum(["active", "exhausted", "revoked"])
|
|
74
|
+
}).strict().superRefine((budget, context) => {
|
|
75
|
+
if (budget.scope === "task" && budget.taskId === void 0) {
|
|
76
|
+
context.addIssue({
|
|
77
|
+
code: "custom",
|
|
78
|
+
message: "task-scoped budgets must carry a taskId",
|
|
79
|
+
path: ["taskId"]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
var AgentIdentitySchema = z2.object({
|
|
84
|
+
id: AgentIdSchema,
|
|
85
|
+
tenantId: TenantIdSchema,
|
|
86
|
+
ownerPrincipalId: PrincipalIdSchema,
|
|
87
|
+
status: z2.enum(["active", "disabled", "revoked"]),
|
|
88
|
+
allowedInterfaces: z2.array(AgentInterfaceSchema).min(1).refine(uniqueStrings, "allowedInterfaces must not contain duplicates").transform(sorted)
|
|
89
|
+
}).strict();
|
|
90
|
+
var AuthorityConstraintsSchema = z2.object({
|
|
91
|
+
actions: z2.array(ActionTypeSchema).min(1).max(64).refine(uniqueStrings, "actions must not contain duplicates").transform(sorted),
|
|
92
|
+
resources: z2.array(ResourceRefSchema).min(1).max(256).refine(uniqueStrings, "resources must not contain duplicates").transform(sorted),
|
|
93
|
+
adapterIds: z2.array(AdapterIdSchema).min(1).max(64).refine(uniqueStrings, "adapterIds must not contain duplicates").transform(sorted),
|
|
94
|
+
maxValueCents: z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
95
|
+
maxUsesPerRequest: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
96
|
+
notBeforeEpochMs: EpochMillisecondsSchema.optional(),
|
|
97
|
+
expiresAtEpochMs: EpochMillisecondsSchema,
|
|
98
|
+
approvalRequired: z2.boolean(),
|
|
99
|
+
askAboveValueCents: CentsSchema.optional(),
|
|
100
|
+
merchantAllowlist: z2.array(FactLabelSchema).min(1).max(256).refine(uniqueStrings, "merchantAllowlist must not contain duplicates").transform(sorted).optional(),
|
|
101
|
+
categoryAllowlist: z2.array(FactLabelSchema).min(1).max(256).refine(uniqueStrings, "categoryAllowlist must not contain duplicates").transform(sorted).optional(),
|
|
102
|
+
recurrence: RecurrencePolicySchema.optional(),
|
|
103
|
+
budgetIds: z2.array(BudgetIdSchema).min(1).max(64).refine(uniqueStrings, "budgetIds must not contain duplicates").transform(sorted).optional()
|
|
104
|
+
}).strict().superRefine((constraints, context) => {
|
|
105
|
+
if (constraints.askAboveValueCents !== void 0 && constraints.maxValueCents !== void 0 && constraints.askAboveValueCents >= constraints.maxValueCents) {
|
|
106
|
+
context.addIssue({
|
|
107
|
+
code: "custom",
|
|
108
|
+
message: "askAboveValueCents must be below maxValueCents",
|
|
109
|
+
path: ["askAboveValueCents"]
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}).superRefine((constraints, context) => {
|
|
113
|
+
if (constraints.notBeforeEpochMs !== void 0 && constraints.expiresAtEpochMs !== void 0 && constraints.notBeforeEpochMs >= constraints.expiresAtEpochMs) {
|
|
114
|
+
context.addIssue({
|
|
115
|
+
code: "custom",
|
|
116
|
+
message: "notBeforeEpochMs must be earlier than expiresAtEpochMs",
|
|
117
|
+
path: ["expiresAtEpochMs"]
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
var AuthorityGrantSchema = z2.object({
|
|
122
|
+
id: GrantIdSchema,
|
|
123
|
+
tenantId: TenantIdSchema,
|
|
124
|
+
principalId: PrincipalIdSchema,
|
|
125
|
+
agentId: AgentIdSchema,
|
|
126
|
+
parentGrantId: GrantIdSchema.optional(),
|
|
127
|
+
issuedBy: z2.object({
|
|
128
|
+
type: z2.literal("principal"),
|
|
129
|
+
principalId: PrincipalIdSchema
|
|
130
|
+
}).strict(),
|
|
131
|
+
status: z2.enum(["active", "revoked", "disabled"]),
|
|
132
|
+
version: z2.number().int().positive(),
|
|
133
|
+
delegable: z2.boolean().default(false),
|
|
134
|
+
constraints: AuthorityConstraintsSchema
|
|
135
|
+
}).strict().superRefine((grant, context) => {
|
|
136
|
+
if (grant.issuedBy.principalId !== grant.principalId) {
|
|
137
|
+
context.addIssue({
|
|
138
|
+
code: "custom",
|
|
139
|
+
message: "issuedBy principal must match the grant principal",
|
|
140
|
+
path: ["issuedBy", "principalId"]
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (grant.parentGrantId === grant.id) {
|
|
144
|
+
context.addIssue({
|
|
145
|
+
code: "custom",
|
|
146
|
+
message: "a grant cannot be its own parent",
|
|
147
|
+
path: ["parentGrantId"]
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
var AuthorityRequestSchema = z2.object({
|
|
152
|
+
id: RequestIdSchema,
|
|
153
|
+
tenantId: TenantIdSchema,
|
|
154
|
+
principalId: PrincipalIdSchema,
|
|
155
|
+
agentId: AgentIdSchema,
|
|
156
|
+
action: ActionTypeSchema,
|
|
157
|
+
resource: ResourceRefSchema,
|
|
158
|
+
interface: AgentInterfaceSchema,
|
|
159
|
+
requestedAdapterId: AdapterIdSchema,
|
|
160
|
+
valueCents: z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
161
|
+
uses: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
162
|
+
materialActionDigest: z2.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
163
|
+
descriptiveContext: z2.string().max(8192).optional(),
|
|
164
|
+
merchantId: FactLabelSchema.optional(),
|
|
165
|
+
merchantCategory: FactLabelSchema.optional(),
|
|
166
|
+
recurring: z2.boolean().optional(),
|
|
167
|
+
taskId: TaskIdSchema.optional()
|
|
168
|
+
}).strict();
|
|
169
|
+
var DenyReasonCodeSchema = z2.enum([
|
|
170
|
+
"MALFORMED_INPUT",
|
|
171
|
+
"TENANT_MISMATCH",
|
|
172
|
+
"PRINCIPAL_MISMATCH",
|
|
173
|
+
"PRINCIPAL_NOT_ACTIVE",
|
|
174
|
+
"AGENT_TENANT_MISMATCH",
|
|
175
|
+
"AGENT_OWNER_MISMATCH",
|
|
176
|
+
"AGENT_MISMATCH",
|
|
177
|
+
"AGENT_NOT_ACTIVE",
|
|
178
|
+
"INTERFACE_NOT_ALLOWED",
|
|
179
|
+
"AUTHORITY_CHAIN_MISSING",
|
|
180
|
+
"DUPLICATE_GRANT_ID",
|
|
181
|
+
"GRANT_TENANT_MISMATCH",
|
|
182
|
+
"GRANT_PRINCIPAL_MISMATCH",
|
|
183
|
+
"GRANT_AGENT_MISMATCH",
|
|
184
|
+
"GRANT_NOT_ACTIVE",
|
|
185
|
+
"GRANT_REVOKED",
|
|
186
|
+
"GRANT_NOT_YET_VALID",
|
|
187
|
+
"GRANT_EXPIRED",
|
|
188
|
+
"INVALID_PARENT_LINK",
|
|
189
|
+
"DELEGATION_NOT_ALLOWED",
|
|
190
|
+
"AUTHORITY_AMPLIFICATION",
|
|
191
|
+
"ACTION_NOT_ALLOWED",
|
|
192
|
+
"RESOURCE_NOT_ALLOWED",
|
|
193
|
+
"ADAPTER_NOT_ALLOWED",
|
|
194
|
+
"VALUE_LIMIT_EXCEEDED",
|
|
195
|
+
"USE_LIMIT_EXCEEDED",
|
|
196
|
+
"MERCHANT_NOT_ALLOWED",
|
|
197
|
+
"CATEGORY_NOT_ALLOWED",
|
|
198
|
+
"RECURRENCE_NOT_ALLOWED",
|
|
199
|
+
"BUDGET_NOT_FOUND",
|
|
200
|
+
"BUDGET_SCOPE_MISMATCH",
|
|
201
|
+
"BUDGET_NOT_ACTIVE",
|
|
202
|
+
"BUDGET_EXPIRED",
|
|
203
|
+
"BUDGET_EXCEEDED",
|
|
204
|
+
"TRANSACTION_LIMIT_EXCEEDED"
|
|
205
|
+
]);
|
|
206
|
+
var PolicyReferenceSchema = z2.object({
|
|
207
|
+
id: PolicyIdSchema,
|
|
208
|
+
version: z2.number().int().positive()
|
|
209
|
+
}).strict();
|
|
210
|
+
var DecisionBindingSchema = z2.object({
|
|
211
|
+
requestId: RequestIdSchema.nullable(),
|
|
212
|
+
policy: PolicyReferenceSchema.nullable(),
|
|
213
|
+
evaluatedGrants: z2.array(
|
|
214
|
+
z2.object({
|
|
215
|
+
id: GrantIdSchema,
|
|
216
|
+
version: z2.number().int().positive()
|
|
217
|
+
}).strict()
|
|
218
|
+
).max(32),
|
|
219
|
+
inputDigest: z2.string().regex(/^[a-f0-9]{64}$/).nullable(),
|
|
220
|
+
budgetConsumption: z2.array(BudgetConsumptionSchema).max(64).optional()
|
|
221
|
+
}).strict();
|
|
222
|
+
var AuthorityDecisionSchema = z2.discriminatedUnion("decision", [
|
|
223
|
+
z2.object({
|
|
224
|
+
decision: z2.literal("ALLOW"),
|
|
225
|
+
reasonCode: z2.literal("AUTHORIZED"),
|
|
226
|
+
binding: DecisionBindingSchema
|
|
227
|
+
}).strict(),
|
|
228
|
+
z2.object({
|
|
229
|
+
decision: z2.literal("ASK"),
|
|
230
|
+
reasonCode: z2.literal("APPROVAL_REQUIRED"),
|
|
231
|
+
binding: DecisionBindingSchema
|
|
232
|
+
}).strict(),
|
|
233
|
+
z2.object({
|
|
234
|
+
decision: z2.literal("DENY"),
|
|
235
|
+
reasonCode: DenyReasonCodeSchema,
|
|
236
|
+
binding: DecisionBindingSchema
|
|
237
|
+
}).strict()
|
|
238
|
+
]);
|
|
239
|
+
var RevocationSchema = z2.object({
|
|
240
|
+
grantId: GrantIdSchema,
|
|
241
|
+
revokedAtEpochMs: EpochMillisecondsSchema,
|
|
242
|
+
reasonCode: z2.string().trim().min(1).max(128)
|
|
243
|
+
}).strict();
|
|
244
|
+
|
|
245
|
+
// src/contracts/canonical-json.ts
|
|
246
|
+
var MAX_CANONICAL_NODES = 1e5;
|
|
247
|
+
function serialize(value, ancestors, depth = 0, budget = { remainingNodes: MAX_CANONICAL_NODES }) {
|
|
248
|
+
budget.remainingNodes -= 1;
|
|
249
|
+
if (budget.remainingNodes < 0) {
|
|
250
|
+
throw new TypeError("Canonical JSON exceeds maximum node count");
|
|
251
|
+
}
|
|
252
|
+
if (depth > 64) throw new TypeError("Canonical JSON exceeds maximum depth");
|
|
253
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") {
|
|
254
|
+
return JSON.stringify(value);
|
|
255
|
+
}
|
|
256
|
+
if (typeof value === "number") {
|
|
257
|
+
if (!Number.isFinite(value)) {
|
|
258
|
+
throw new TypeError("Canonical JSON does not permit non-finite numbers");
|
|
259
|
+
}
|
|
260
|
+
return JSON.stringify(Object.is(value, -0) ? 0 : value);
|
|
261
|
+
}
|
|
262
|
+
if (Array.isArray(value)) {
|
|
263
|
+
if (ancestors.has(value))
|
|
264
|
+
throw new TypeError("Canonical JSON rejects cycles");
|
|
265
|
+
ancestors.add(value);
|
|
266
|
+
const keys2 = Reflect.ownKeys(value);
|
|
267
|
+
const expectedKeys = /* @__PURE__ */ new Set([
|
|
268
|
+
"length",
|
|
269
|
+
...Array.from({ length: value.length }, (_, index) => String(index))
|
|
270
|
+
]);
|
|
271
|
+
if (keys2.some((key) => typeof key !== "string" || !expectedKeys.has(key)) || keys2.length !== expectedKeys.size) {
|
|
272
|
+
ancestors.delete(value);
|
|
273
|
+
throw new TypeError(
|
|
274
|
+
"Canonical JSON rejects sparse arrays and extra properties"
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
const entries2 = Array.from({ length: value.length }, (_, index) => {
|
|
278
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
279
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) {
|
|
280
|
+
throw new TypeError(
|
|
281
|
+
"Canonical JSON rejects accessors and hidden values"
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
return serialize(descriptor.value, ancestors, depth + 1, budget);
|
|
285
|
+
});
|
|
286
|
+
const result2 = `[${entries2.join(",")}]`;
|
|
287
|
+
ancestors.delete(value);
|
|
288
|
+
return result2;
|
|
289
|
+
}
|
|
290
|
+
if (typeof value !== "object" || value === void 0) {
|
|
291
|
+
throw new TypeError(`Canonical JSON rejects ${typeof value}`);
|
|
292
|
+
}
|
|
293
|
+
const prototype = Object.getPrototypeOf(value);
|
|
294
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
295
|
+
throw new TypeError("Canonical JSON accepts only plain objects and arrays");
|
|
296
|
+
}
|
|
297
|
+
if (ancestors.has(value))
|
|
298
|
+
throw new TypeError("Canonical JSON rejects cycles");
|
|
299
|
+
ancestors.add(value);
|
|
300
|
+
const keys = Reflect.ownKeys(value);
|
|
301
|
+
if (keys.some((key) => typeof key !== "string")) {
|
|
302
|
+
ancestors.delete(value);
|
|
303
|
+
throw new TypeError("Canonical JSON rejects symbol keys");
|
|
304
|
+
}
|
|
305
|
+
const entries = keys.map((key) => {
|
|
306
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
307
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) {
|
|
308
|
+
throw new TypeError(
|
|
309
|
+
"Canonical JSON rejects accessors and hidden values"
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return [key, descriptor.value];
|
|
313
|
+
}).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
314
|
+
const result = `{${entries.map(
|
|
315
|
+
([key, entry]) => `${JSON.stringify(key)}:${serialize(entry, ancestors, depth + 1, budget)}`
|
|
316
|
+
).join(",")}}`;
|
|
317
|
+
ancestors.delete(value);
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
function canonicalJson(value) {
|
|
321
|
+
return serialize(value, /* @__PURE__ */ new WeakSet());
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/contracts/digest.ts
|
|
325
|
+
import { createHash } from "node:crypto";
|
|
326
|
+
function canonicalSha256(value) {
|
|
327
|
+
return createHash("sha256").update(canonicalJson(value), "utf8").digest("hex");
|
|
328
|
+
}
|
|
329
|
+
function sha256Text(value) {
|
|
330
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/contracts/action.ts
|
|
334
|
+
var SHA256Schema = z3.string().regex(/^[a-f0-9]{64}$/);
|
|
335
|
+
var MAX_MATERIAL_BYTES = 32768;
|
|
336
|
+
var MAX_MATERIAL_NODES = 4096;
|
|
337
|
+
function normalizeCanonicalJson(value, depth = 0, ancestors = /* @__PURE__ */ new WeakSet(), budget = { remainingNodes: MAX_MATERIAL_NODES }) {
|
|
338
|
+
budget.remainingNodes -= 1;
|
|
339
|
+
if (budget.remainingNodes < 0) {
|
|
340
|
+
throw new TypeError("material parameters exceed maximum node count");
|
|
341
|
+
}
|
|
342
|
+
if (depth > 8)
|
|
343
|
+
throw new TypeError("material parameters exceed maximum depth");
|
|
344
|
+
if (value === null || typeof value === "boolean") return value;
|
|
345
|
+
if (typeof value === "string") {
|
|
346
|
+
if (value.length > 8192 || value.normalize("NFC") !== value) {
|
|
347
|
+
throw new TypeError("material string is too long or not NFC-normalized");
|
|
348
|
+
}
|
|
349
|
+
return value;
|
|
350
|
+
}
|
|
351
|
+
if (typeof value === "number") {
|
|
352
|
+
if (!Number.isFinite(value) || Math.abs(value) > Number.MAX_SAFE_INTEGER || Object.is(value, -0)) {
|
|
353
|
+
throw new TypeError("material number is not canonical");
|
|
354
|
+
}
|
|
355
|
+
return value;
|
|
356
|
+
}
|
|
357
|
+
if (typeof value !== "object" || value === void 0 || ancestors.has(value)) {
|
|
358
|
+
throw new TypeError(
|
|
359
|
+
"material parameters contain an unsupported value or cycle"
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
ancestors.add(value);
|
|
363
|
+
try {
|
|
364
|
+
if (Array.isArray(value)) {
|
|
365
|
+
if (value.length > 256)
|
|
366
|
+
throw new TypeError("material array is too large");
|
|
367
|
+
const keys2 = Reflect.ownKeys(value);
|
|
368
|
+
const expectedKeys = /* @__PURE__ */ new Set([
|
|
369
|
+
"length",
|
|
370
|
+
...Array.from({ length: value.length }, (_, index) => String(index))
|
|
371
|
+
]);
|
|
372
|
+
if (keys2.some((key) => typeof key !== "string" || !expectedKeys.has(key)) || keys2.length !== expectedKeys.size) {
|
|
373
|
+
throw new TypeError(
|
|
374
|
+
"material arrays must be dense and have no extra properties"
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
const normalized2 = Array.from({ length: value.length }, (_, index) => {
|
|
378
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
379
|
+
value,
|
|
380
|
+
String(index)
|
|
381
|
+
);
|
|
382
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) {
|
|
383
|
+
throw new TypeError(
|
|
384
|
+
"material arrays may contain only enumerable data values"
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return normalizeCanonicalJson(
|
|
388
|
+
descriptor.value,
|
|
389
|
+
depth + 1,
|
|
390
|
+
ancestors,
|
|
391
|
+
budget
|
|
392
|
+
);
|
|
393
|
+
});
|
|
394
|
+
Object.freeze(normalized2);
|
|
395
|
+
return normalized2;
|
|
396
|
+
}
|
|
397
|
+
const prototype = Object.getPrototypeOf(value);
|
|
398
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
399
|
+
throw new TypeError(
|
|
400
|
+
"material parameters accept only plain objects and arrays"
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
const keys = Reflect.ownKeys(value);
|
|
404
|
+
if (keys.length > 128 || keys.some((key) => typeof key !== "string")) {
|
|
405
|
+
throw new TypeError(
|
|
406
|
+
"material objects are too large or contain symbol keys"
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
const normalized = /* @__PURE__ */ Object.create(
|
|
410
|
+
null
|
|
411
|
+
);
|
|
412
|
+
for (const key of keys) {
|
|
413
|
+
if (key.length === 0 || key.length > 128 || key.normalize("NFC") !== key || ["__proto__", "constructor", "prototype"].includes(key)) {
|
|
414
|
+
throw new TypeError("material object key is invalid");
|
|
415
|
+
}
|
|
416
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
417
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) {
|
|
418
|
+
throw new TypeError(
|
|
419
|
+
"material objects may contain only enumerable data properties"
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
Object.defineProperty(normalized, key, {
|
|
423
|
+
value: normalizeCanonicalJson(
|
|
424
|
+
descriptor.value,
|
|
425
|
+
depth + 1,
|
|
426
|
+
ancestors,
|
|
427
|
+
budget
|
|
428
|
+
),
|
|
429
|
+
enumerable: true,
|
|
430
|
+
writable: false,
|
|
431
|
+
configurable: false
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
return Object.freeze(normalized);
|
|
435
|
+
} finally {
|
|
436
|
+
ancestors.delete(value);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function normalizeMaterialParameters(value) {
|
|
440
|
+
const normalized = normalizeCanonicalJson(value);
|
|
441
|
+
if (normalized === null || Array.isArray(normalized) || typeof normalized !== "object") {
|
|
442
|
+
throw new TypeError("material parameters must be an object");
|
|
443
|
+
}
|
|
444
|
+
if (new TextEncoder().encode(canonicalJson(normalized)).length > MAX_MATERIAL_BYTES) {
|
|
445
|
+
throw new TypeError("material parameters exceed maximum encoded size");
|
|
446
|
+
}
|
|
447
|
+
return normalized;
|
|
448
|
+
}
|
|
449
|
+
var MaterialParametersSchema = z3.unknown().transform((value, context) => {
|
|
450
|
+
try {
|
|
451
|
+
return normalizeMaterialParameters(value);
|
|
452
|
+
} catch {
|
|
453
|
+
context.addIssue({
|
|
454
|
+
code: "custom",
|
|
455
|
+
message: "material parameters must be a bounded canonical JSON object"
|
|
456
|
+
});
|
|
457
|
+
return z3.NEVER;
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
var AuthorityReferenceSchema = z3.object({
|
|
461
|
+
policy: PolicyReferenceSchema,
|
|
462
|
+
evaluatedGrants: z3.array(
|
|
463
|
+
z3.object({
|
|
464
|
+
id: GrantIdSchema,
|
|
465
|
+
version: z3.number().int().positive()
|
|
466
|
+
}).strict()
|
|
467
|
+
).min(1).max(32).refine(
|
|
468
|
+
(grants) => new Set(grants.map((grant) => grant.id)).size === grants.length,
|
|
469
|
+
"evaluated grant identifiers must be unique"
|
|
470
|
+
)
|
|
471
|
+
}).strict();
|
|
472
|
+
var MaterialActionSchema = z3.object({
|
|
473
|
+
schemaVersion: z3.literal(1),
|
|
474
|
+
requestId: RequestIdSchema,
|
|
475
|
+
correlationId: CorrelationIdSchema,
|
|
476
|
+
tenantId: TenantIdSchema,
|
|
477
|
+
principalId: PrincipalIdSchema,
|
|
478
|
+
agentId: AgentIdSchema,
|
|
479
|
+
action: ActionTypeSchema,
|
|
480
|
+
resource: ResourceRefSchema,
|
|
481
|
+
interface: AgentInterfaceSchema,
|
|
482
|
+
valueCents: z3.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
483
|
+
merchantId: z3.string().trim().min(1).max(256).optional(),
|
|
484
|
+
merchantCategory: z3.string().trim().min(1).max(256).optional(),
|
|
485
|
+
recurring: z3.boolean().optional(),
|
|
486
|
+
taskId: TaskIdSchema.optional(),
|
|
487
|
+
uses: z3.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
488
|
+
materialParameters: MaterialParametersSchema,
|
|
489
|
+
initiatingContext: z3.enum(["user", "scheduled", "agent", "external-event"]),
|
|
490
|
+
authority: AuthorityReferenceSchema,
|
|
491
|
+
issuedAtEpochMs: EpochMillisecondsSchema,
|
|
492
|
+
expiresAtEpochMs: EpochMillisecondsSchema,
|
|
493
|
+
nonce: NonceSchema,
|
|
494
|
+
requestedAdapterId: AdapterIdSchema
|
|
495
|
+
}).strict().superRefine((value, context) => {
|
|
496
|
+
if (!Object.prototype.hasOwnProperty.call(value, "materialParameters")) {
|
|
497
|
+
context.addIssue({
|
|
498
|
+
code: "custom",
|
|
499
|
+
path: ["materialParameters"],
|
|
500
|
+
message: "materialParameters must be explicitly present"
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
if (value.issuedAtEpochMs >= value.expiresAtEpochMs) {
|
|
504
|
+
context.addIssue({
|
|
505
|
+
code: "custom",
|
|
506
|
+
path: ["expiresAtEpochMs"],
|
|
507
|
+
message: "expiresAtEpochMs must be later than issuedAtEpochMs"
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
var CanonicalActionEnvelopeSchema = MaterialActionSchema.extend({
|
|
512
|
+
purpose: z3.string().trim().min(1).max(1024).refine((value) => value.normalize("NFC") === value)
|
|
513
|
+
}).strict();
|
|
514
|
+
var CanonicalActionSchema = CanonicalActionEnvelopeSchema.extend({
|
|
515
|
+
materialDigest: SHA256Schema
|
|
516
|
+
}).strict().superRefine((value, context) => {
|
|
517
|
+
try {
|
|
518
|
+
const material = MaterialActionSchema.strip().parse(value);
|
|
519
|
+
if (materialActionDigest(material) === value.materialDigest) return;
|
|
520
|
+
context.addIssue({
|
|
521
|
+
code: "custom",
|
|
522
|
+
path: ["materialDigest"],
|
|
523
|
+
message: "materialDigest does not match the canonical action"
|
|
524
|
+
});
|
|
525
|
+
} catch {
|
|
526
|
+
context.addIssue({
|
|
527
|
+
code: "custom",
|
|
528
|
+
path: ["materialDigest"],
|
|
529
|
+
message: "canonical action cannot be safely digested"
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
var RecordedAuthorityEvaluationInputSchema = z3.object({
|
|
534
|
+
nowEpochMs: EpochMillisecondsSchema,
|
|
535
|
+
principal: PrincipalSchema,
|
|
536
|
+
agent: AgentIdentitySchema,
|
|
537
|
+
request: AuthorityRequestSchema,
|
|
538
|
+
policy: PolicyReferenceSchema,
|
|
539
|
+
authorityChain: z3.array(AuthorityGrantSchema).max(32),
|
|
540
|
+
revocations: z3.array(RevocationSchema).max(256).transform(
|
|
541
|
+
(values) => [...values].sort(
|
|
542
|
+
(left, right) => left.grantId < right.grantId ? -1 : left.grantId > right.grantId ? 1 : left.revokedAtEpochMs - right.revokedAtEpochMs
|
|
543
|
+
)
|
|
544
|
+
),
|
|
545
|
+
budgets: z3.array(BudgetStateSchema).max(64).default([]).transform(
|
|
546
|
+
(values) => [...values].sort((left, right) => left.id < right.id ? -1 : 1)
|
|
547
|
+
)
|
|
548
|
+
}).strict();
|
|
549
|
+
var ActionDecisionRecordCoreSchema = z3.object({
|
|
550
|
+
schemaVersion: z3.literal(1),
|
|
551
|
+
tenantId: TenantIdSchema,
|
|
552
|
+
principalId: PrincipalIdSchema,
|
|
553
|
+
agentId: AgentIdSchema,
|
|
554
|
+
requestId: RequestIdSchema,
|
|
555
|
+
correlationId: CorrelationIdSchema,
|
|
556
|
+
actionDigest: SHA256Schema,
|
|
557
|
+
policy: PolicyReferenceSchema,
|
|
558
|
+
evaluatedGrants: AuthorityReferenceSchema.shape.evaluatedGrants,
|
|
559
|
+
action: CanonicalActionSchema,
|
|
560
|
+
authorityRequest: AuthorityRequestSchema,
|
|
561
|
+
authorityEvaluationInput: RecordedAuthorityEvaluationInputSchema,
|
|
562
|
+
authorityDecision: AuthorityDecisionSchema,
|
|
563
|
+
decidedAtEpochMs: EpochMillisecondsSchema
|
|
564
|
+
}).strict();
|
|
565
|
+
var ActionDecisionRecordSchema = ActionDecisionRecordCoreSchema.extend(
|
|
566
|
+
{
|
|
567
|
+
recordDigest: SHA256Schema
|
|
568
|
+
}
|
|
569
|
+
).strict().superRefine((value, context) => {
|
|
570
|
+
try {
|
|
571
|
+
const binding = value.authorityDecision.binding;
|
|
572
|
+
const expectedRequest = toAuthorityRequest(value.action);
|
|
573
|
+
if (value.tenantId !== value.action.tenantId || value.principalId !== value.action.principalId || value.agentId !== value.action.agentId || value.requestId !== value.action.requestId || value.correlationId !== value.action.correlationId || value.actionDigest !== value.action.materialDigest) {
|
|
574
|
+
context.addIssue({
|
|
575
|
+
code: "custom",
|
|
576
|
+
path: ["actionDigest"],
|
|
577
|
+
message: "action decision context does not match the canonical action"
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
if (value.policy.id !== value.action.authority.policy.id || value.policy.version !== value.action.authority.policy.version || canonicalSha256(value.evaluatedGrants) !== canonicalSha256(value.action.authority.evaluatedGrants)) {
|
|
581
|
+
context.addIssue({
|
|
582
|
+
code: "custom",
|
|
583
|
+
path: ["policy"],
|
|
584
|
+
message: "action decision authority references do not match the action"
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
if (canonicalSha256(value.authorityRequest) !== canonicalSha256(expectedRequest)) {
|
|
588
|
+
context.addIssue({
|
|
589
|
+
code: "custom",
|
|
590
|
+
path: ["authorityRequest"],
|
|
591
|
+
message: "authority request does not match the canonical action"
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
if (canonicalSha256(value.authorityEvaluationInput.request) !== canonicalSha256(expectedRequest) || canonicalSha256(value.authorityEvaluationInput.policy) !== canonicalSha256(value.action.authority.policy) || value.authorityEvaluationInput.principal.id !== value.action.principalId || value.authorityEvaluationInput.principal.tenantId !== value.action.tenantId || value.authorityEvaluationInput.agent.id !== value.action.agentId || value.authorityEvaluationInput.agent.tenantId !== value.action.tenantId || value.authorityEvaluationInput.agent.ownerPrincipalId !== value.action.principalId || value.authorityEvaluationInput.principal.status !== "active" || value.authorityEvaluationInput.agent.status !== "active" || !value.authorityEvaluationInput.agent.allowedInterfaces.includes(
|
|
595
|
+
value.action.interface
|
|
596
|
+
) || value.authorityEvaluationInput.authorityChain.some(
|
|
597
|
+
(grant) => grant.tenantId !== value.action.tenantId || grant.principalId !== value.action.principalId || grant.agentId !== value.action.agentId || grant.status !== "active"
|
|
598
|
+
)) {
|
|
599
|
+
context.addIssue({
|
|
600
|
+
code: "custom",
|
|
601
|
+
path: ["authorityEvaluationInput"],
|
|
602
|
+
message: "evaluated authority input does not match the canonical action"
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
if (binding.requestId !== value.action.requestId) {
|
|
606
|
+
context.addIssue({
|
|
607
|
+
code: "custom",
|
|
608
|
+
path: ["authorityDecision", "binding", "requestId"],
|
|
609
|
+
message: "decision request binding does not match the action"
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
if (binding.policy === null || binding.policy.id !== value.action.authority.policy.id || binding.policy.version !== value.action.authority.policy.version) {
|
|
613
|
+
context.addIssue({
|
|
614
|
+
code: "custom",
|
|
615
|
+
path: ["authorityDecision", "binding", "policy"],
|
|
616
|
+
message: "decision policy binding does not match the action"
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
if (canonicalSha256(binding.evaluatedGrants) !== canonicalSha256(value.action.authority.evaluatedGrants)) {
|
|
620
|
+
context.addIssue({
|
|
621
|
+
code: "custom",
|
|
622
|
+
path: ["authorityDecision", "binding", "evaluatedGrants"],
|
|
623
|
+
message: "decision grant bindings do not match the action"
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
if (binding.inputDigest === null || binding.inputDigest !== canonicalSha256(value.authorityEvaluationInput)) {
|
|
627
|
+
context.addIssue({
|
|
628
|
+
code: "custom",
|
|
629
|
+
path: ["authorityDecision", "binding", "inputDigest"],
|
|
630
|
+
message: "decision input digest does not match the evaluated authority input"
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
const grantNotBeforeEpochMs = Math.max(
|
|
634
|
+
0,
|
|
635
|
+
...value.authorityEvaluationInput.authorityChain.map(
|
|
636
|
+
(grant) => grant.constraints.notBeforeEpochMs ?? 0
|
|
637
|
+
)
|
|
638
|
+
);
|
|
639
|
+
const grantExpiresAtEpochMs = Math.min(
|
|
640
|
+
...value.authorityEvaluationInput.authorityChain.map(
|
|
641
|
+
(grant) => grant.constraints.expiresAtEpochMs
|
|
642
|
+
)
|
|
643
|
+
);
|
|
644
|
+
if (value.authorityEvaluationInput.nowEpochMs !== value.decidedAtEpochMs || value.decidedAtEpochMs < value.action.issuedAtEpochMs || value.decidedAtEpochMs >= value.action.expiresAtEpochMs) {
|
|
645
|
+
context.addIssue({
|
|
646
|
+
code: "custom",
|
|
647
|
+
path: ["decidedAtEpochMs"],
|
|
648
|
+
message: "decision time must fall within the action validity window"
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
if (value.authorityEvaluationInput.authorityChain.length === 0 || value.action.issuedAtEpochMs < grantNotBeforeEpochMs || value.action.expiresAtEpochMs > grantExpiresAtEpochMs) {
|
|
652
|
+
context.addIssue({
|
|
653
|
+
code: "custom",
|
|
654
|
+
path: ["action", "expiresAtEpochMs"],
|
|
655
|
+
message: "action validity window must be contained by evaluated grants"
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
const evaluatedGrantReferences = value.authorityEvaluationInput.authorityChain.map(
|
|
659
|
+
({ id, version }) => ({
|
|
660
|
+
id,
|
|
661
|
+
version
|
|
662
|
+
})
|
|
663
|
+
);
|
|
664
|
+
if (canonicalSha256(evaluatedGrantReferences) !== canonicalSha256(value.action.authority.evaluatedGrants)) {
|
|
665
|
+
context.addIssue({
|
|
666
|
+
code: "custom",
|
|
667
|
+
path: ["authorityEvaluationInput", "authorityChain"],
|
|
668
|
+
message: "evaluated authority chain does not match declared grants"
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
const core = ActionDecisionRecordCoreSchema.strip().parse(value);
|
|
672
|
+
if (actionDecisionRecordDigest(core) !== value.recordDigest) {
|
|
673
|
+
context.addIssue({
|
|
674
|
+
code: "custom",
|
|
675
|
+
path: ["recordDigest"],
|
|
676
|
+
message: "recordDigest does not match the action decision record"
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
} catch {
|
|
680
|
+
context.addIssue({
|
|
681
|
+
code: "custom",
|
|
682
|
+
path: ["recordDigest"],
|
|
683
|
+
message: "action decision record cannot be safely verified"
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
var MATERIAL_ACTION_KEYS = Object.keys(
|
|
688
|
+
MaterialActionSchema.shape
|
|
689
|
+
);
|
|
690
|
+
function materialActionDigest(material) {
|
|
691
|
+
return canonicalSha256({
|
|
692
|
+
domain: "odingard.guardian.action.v1",
|
|
693
|
+
materialAction: material
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
function actionDecisionRecordDigest(record) {
|
|
697
|
+
return canonicalSha256({
|
|
698
|
+
domain: "odingard.guardian.action-decision.v1",
|
|
699
|
+
record
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
function toAuthorityRequest(action) {
|
|
703
|
+
return AuthorityRequestSchema.parse({
|
|
704
|
+
id: action.requestId,
|
|
705
|
+
tenantId: action.tenantId,
|
|
706
|
+
principalId: action.principalId,
|
|
707
|
+
agentId: action.agentId,
|
|
708
|
+
action: action.action,
|
|
709
|
+
resource: action.resource,
|
|
710
|
+
interface: action.interface,
|
|
711
|
+
requestedAdapterId: action.requestedAdapterId,
|
|
712
|
+
...action.valueCents === void 0 ? {} : { valueCents: action.valueCents },
|
|
713
|
+
uses: action.uses,
|
|
714
|
+
materialActionDigest: action.materialDigest,
|
|
715
|
+
descriptiveContext: action.purpose,
|
|
716
|
+
...action.merchantId === void 0 ? {} : { merchantId: action.merchantId },
|
|
717
|
+
...action.merchantCategory === void 0 ? {} : { merchantCategory: action.merchantCategory },
|
|
718
|
+
...action.recurring === void 0 ? {} : { recurring: action.recurring },
|
|
719
|
+
...action.taskId === void 0 ? {} : { taskId: action.taskId }
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/contracts/email.ts
|
|
724
|
+
import { z as z4 } from "zod";
|
|
725
|
+
var LOCAL_ATEXT = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~.-]+$/;
|
|
726
|
+
var DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
727
|
+
function isNoncharacter(codePoint) {
|
|
728
|
+
return codePoint >= 64976 && codePoint <= 65007 || (codePoint & 65535) === 65534 || (codePoint & 65535) === 65535;
|
|
729
|
+
}
|
|
730
|
+
function hasNoncharacter(value) {
|
|
731
|
+
return [...value].some(
|
|
732
|
+
(character) => isNoncharacter(character.codePointAt(0) ?? 0)
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
function isCanonicalEmailAddress(value) {
|
|
736
|
+
if (![...value].every((character) => (character.codePointAt(0) ?? 128) < 128) || value.length > 254 || value.normalize("NFC") !== value) {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
const at = value.indexOf("@");
|
|
740
|
+
if (at <= 0 || at !== value.lastIndexOf("@")) return false;
|
|
741
|
+
const local = value.slice(0, at);
|
|
742
|
+
const domain = value.slice(at + 1);
|
|
743
|
+
if (local.length > 64 || !LOCAL_ATEXT.test(local) || local.startsWith(".") || local.endsWith(".") || local.includes("..") || domain.length > 253 || domain !== domain.toLowerCase()) {
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
const labels = domain.split(".");
|
|
747
|
+
if (labels.length < 2 || labels.some((label) => !DOMAIN_LABEL.test(label))) {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
const topLevel = labels.at(-1) ?? "";
|
|
751
|
+
return /^[a-z]{2,63}$/.test(topLevel) || /^xn--[a-z0-9](?:[a-z0-9-]{0,57}[a-z0-9])?$/.test(topLevel);
|
|
752
|
+
}
|
|
753
|
+
function isSafeSubject(value) {
|
|
754
|
+
return value === value.trim() && value.normalize("NFC") === value && [...value].length >= 1 && [...value].length <= 200 && !/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}\p{Co}\p{Cs}\p{Cn}]/u.test(value) && !hasNoncharacter(value);
|
|
755
|
+
}
|
|
756
|
+
function isSafeBody(value) {
|
|
757
|
+
if (value.length < 1 || value.length > 8e3 || value.normalize("NFC") !== value || !/\S/u.test(value) || /\r/u.test(value) || /[\p{Co}\p{Cs}\p{Cn}]/u.test(value) || hasNoncharacter(value)) {
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
for (const character of value) {
|
|
761
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
762
|
+
if (codePoint < 32 && codePoint !== 9 && codePoint !== 10 || codePoint === 127 || codePoint === 1564 || codePoint === 8206 || codePoint === 8207 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8288 && codePoint <= 8292 || codePoint >= 8294 && codePoint <= 8297 || codePoint === 8203 || codePoint === 65279 || codePoint >= 917504 && codePoint <= 917631) {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
var EmailAddressSchema = z4.string().refine(isCanonicalEmailAddress, "email address must be canonical ASCII");
|
|
769
|
+
var EmailSubjectSchema = z4.string().refine(isSafeSubject, "email subject contains unsafe or noncanonical text");
|
|
770
|
+
var EmailBodyTextSchema = z4.string().refine(isSafeBody, "email body contains unsafe or noncanonical text");
|
|
771
|
+
var EmailSendParametersV1Schema = z4.object({
|
|
772
|
+
schema: z4.literal("odingard.guardian.email.send.v1"),
|
|
773
|
+
connectionId: ConnectionIdSchema,
|
|
774
|
+
from: EmailAddressSchema,
|
|
775
|
+
to: z4.array(EmailAddressSchema).min(1).max(20),
|
|
776
|
+
cc: z4.array(EmailAddressSchema).max(20),
|
|
777
|
+
bcc: z4.array(EmailAddressSchema).max(20),
|
|
778
|
+
replyTo: z4.array(z4.never()).max(0),
|
|
779
|
+
subject: EmailSubjectSchema,
|
|
780
|
+
body: z4.object({ format: z4.literal("text/plain"), text: EmailBodyTextSchema }).strict(),
|
|
781
|
+
attachments: z4.array(z4.never()).max(0),
|
|
782
|
+
inReplyTo: z4.null()
|
|
783
|
+
}).strict().superRefine((value, context) => {
|
|
784
|
+
const recipients = [...value.to, ...value.cc, ...value.bcc].map(
|
|
785
|
+
(address) => address.toLowerCase()
|
|
786
|
+
);
|
|
787
|
+
if (recipients.length > 20) {
|
|
788
|
+
context.addIssue({
|
|
789
|
+
code: "custom",
|
|
790
|
+
path: ["to"],
|
|
791
|
+
message: "at most 20 total recipients are allowed"
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
if (new Set(recipients).size !== recipients.length) {
|
|
795
|
+
context.addIssue({
|
|
796
|
+
code: "custom",
|
|
797
|
+
path: ["to"],
|
|
798
|
+
message: "recipient addresses must be unique across all lists"
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
function canonicalBodyForComparison(text) {
|
|
803
|
+
return text.replace(/\r\n?/gu, "\n").replace(/[ \t\n]*$/u, "");
|
|
804
|
+
}
|
|
805
|
+
function canonicalRecipients(addresses) {
|
|
806
|
+
return [...new Set(addresses.map((address) => address.toLowerCase()))].sort();
|
|
807
|
+
}
|
|
808
|
+
function emailVerificationProjection(input) {
|
|
809
|
+
const params = EmailSendParametersV1Schema.parse(input);
|
|
810
|
+
return Object.freeze({
|
|
811
|
+
domain: "odingard.guardian.email-projection.v1",
|
|
812
|
+
sender: params.from.toLowerCase(),
|
|
813
|
+
to: canonicalRecipients(params.to),
|
|
814
|
+
cc: canonicalRecipients(params.cc),
|
|
815
|
+
bcc: canonicalRecipients(params.bcc),
|
|
816
|
+
replyTo: [],
|
|
817
|
+
subject: params.subject,
|
|
818
|
+
bodyDigest: sha256Text(canonicalBodyForComparison(params.body.text)),
|
|
819
|
+
attachmentCount: 0,
|
|
820
|
+
inReplyTo: null
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
function emailEnvelopeProjection(input) {
|
|
824
|
+
const projection = emailVerificationProjection(input);
|
|
825
|
+
return Object.freeze({
|
|
826
|
+
domain: projection.domain,
|
|
827
|
+
sender: projection.sender,
|
|
828
|
+
to: projection.to,
|
|
829
|
+
cc: projection.cc,
|
|
830
|
+
bcc: projection.bcc,
|
|
831
|
+
replyTo: projection.replyTo,
|
|
832
|
+
subject: projection.subject,
|
|
833
|
+
attachmentCount: projection.attachmentCount,
|
|
834
|
+
inReplyTo: projection.inReplyTo
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/contracts/api.ts
|
|
839
|
+
import { z as z5 } from "zod";
|
|
840
|
+
var ApiRequestMethodSchema = z5.enum([
|
|
841
|
+
"GET",
|
|
842
|
+
"HEAD",
|
|
843
|
+
"POST",
|
|
844
|
+
"PUT",
|
|
845
|
+
"PATCH",
|
|
846
|
+
"DELETE"
|
|
847
|
+
]);
|
|
848
|
+
var API_REQUEST_BODY_MAX_LENGTH = 16384;
|
|
849
|
+
var API_REQUEST_MAX_HEADERS = 32;
|
|
850
|
+
var API_RESPONSE_BODY_CAPTURE_MAX_BYTES = 16 * 1024;
|
|
851
|
+
var HEADER_NAME = /^[a-z0-9][a-z0-9-]{0,126}$/;
|
|
852
|
+
var FORBIDDEN_HEADERS = /* @__PURE__ */ new Set([
|
|
853
|
+
"authorization",
|
|
854
|
+
"proxy-authorization",
|
|
855
|
+
"proxy-authenticate",
|
|
856
|
+
"www-authenticate",
|
|
857
|
+
"host",
|
|
858
|
+
"content-length",
|
|
859
|
+
"connection",
|
|
860
|
+
"keep-alive",
|
|
861
|
+
"transfer-encoding",
|
|
862
|
+
"te",
|
|
863
|
+
"trailer",
|
|
864
|
+
"trailers",
|
|
865
|
+
"upgrade",
|
|
866
|
+
"expect",
|
|
867
|
+
"cookie",
|
|
868
|
+
"set-cookie"
|
|
869
|
+
]);
|
|
870
|
+
function isSafeApiHeaderValue(value) {
|
|
871
|
+
if (value.length === 0 || value.length > 8192) return false;
|
|
872
|
+
for (const character of value) {
|
|
873
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
874
|
+
if (codePoint < 32 && codePoint !== 9 || codePoint === 127 || codePoint > 255) {
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return true;
|
|
879
|
+
}
|
|
880
|
+
function canonicalizeApiRequestUrl(value) {
|
|
881
|
+
let url;
|
|
882
|
+
try {
|
|
883
|
+
url = new URL(value);
|
|
884
|
+
} catch {
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
if (url.protocol !== "https:") return null;
|
|
888
|
+
const urlMap = url;
|
|
889
|
+
if (urlMap.username !== "" || urlMap["password"] !== "") return null;
|
|
890
|
+
if (url.hash !== "") return null;
|
|
891
|
+
if (url.hostname.length === 0) return null;
|
|
892
|
+
url.hostname = url.hostname.toLowerCase();
|
|
893
|
+
if (url.port === "443") url.port = "";
|
|
894
|
+
url.searchParams.sort();
|
|
895
|
+
return url.toString();
|
|
896
|
+
}
|
|
897
|
+
var ApiRequestUrlSchema = z5.string().min(8).max(2048).refine(
|
|
898
|
+
(value) => canonicalizeApiRequestUrl(value) === value,
|
|
899
|
+
"api request url must be an already-canonical https URL"
|
|
900
|
+
);
|
|
901
|
+
var ApiRequestHeaderSchema = z5.object({ name: z5.string().regex(HEADER_NAME), value: z5.string() }).strict();
|
|
902
|
+
var ApiRequestParametersV1Schema = z5.object({
|
|
903
|
+
schema: z5.literal("odingard.guardian.api.request.v1"),
|
|
904
|
+
connectionId: ConnectionIdSchema,
|
|
905
|
+
method: ApiRequestMethodSchema,
|
|
906
|
+
url: ApiRequestUrlSchema,
|
|
907
|
+
headers: z5.array(ApiRequestHeaderSchema).max(API_REQUEST_MAX_HEADERS).refine(
|
|
908
|
+
(headers) => {
|
|
909
|
+
const seen = /* @__PURE__ */ new Set();
|
|
910
|
+
for (const header of headers) {
|
|
911
|
+
if (seen.has(header.name) || FORBIDDEN_HEADERS.has(header.name)) {
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
seen.add(header.name);
|
|
915
|
+
}
|
|
916
|
+
return true;
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
message: "header names must be unique lowercase tokens and must not set auth, routing, or hop-by-hop headers"
|
|
920
|
+
}
|
|
921
|
+
).refine(
|
|
922
|
+
(headers) => headers.every((h) => isSafeApiHeaderValue(h.value)),
|
|
923
|
+
{
|
|
924
|
+
message: "header values must be printable latin-1 without controls"
|
|
925
|
+
}
|
|
926
|
+
),
|
|
927
|
+
body: z5.string().min(1).max(API_REQUEST_BODY_MAX_LENGTH).refine(
|
|
928
|
+
(value) => value.normalize("NFC") === value,
|
|
929
|
+
"request body must already use NFC normalization"
|
|
930
|
+
).optional(),
|
|
931
|
+
idempotencyKey: z5.string().min(8).max(128).regex(/^[A-Za-z0-9._-]+$/u).optional()
|
|
932
|
+
}).strict().superRefine((value, context) => {
|
|
933
|
+
if ((value.method === "GET" || value.method === "HEAD") && value.body !== void 0) {
|
|
934
|
+
context.addIssue({
|
|
935
|
+
code: "custom",
|
|
936
|
+
path: ["body"],
|
|
937
|
+
message: "GET and HEAD requests may not carry a body"
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
function canonicalHeaders(headers) {
|
|
942
|
+
return [...headers].map((header) => ({ name: header.name, value: header.value.trim() })).sort((a, b) => a.name.localeCompare(b.name));
|
|
943
|
+
}
|
|
944
|
+
function apiVerificationProjection(input) {
|
|
945
|
+
const params = ApiRequestParametersV1Schema.parse(input);
|
|
946
|
+
return Object.freeze({
|
|
947
|
+
domain: "odingard.guardian.api-projection.v1",
|
|
948
|
+
connectionId: params.connectionId,
|
|
949
|
+
method: params.method,
|
|
950
|
+
url: params.url,
|
|
951
|
+
headersDigest: canonicalSha256({
|
|
952
|
+
domain: "odingard.guardian.api-headers.v1",
|
|
953
|
+
headers: canonicalHeaders(params.headers)
|
|
954
|
+
}),
|
|
955
|
+
bodyDigest: params.body === void 0 ? null : sha256Text(params.body),
|
|
956
|
+
idempotencyKey: params.idempotencyKey ?? null
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
function apiEnvelopeProjection(input) {
|
|
960
|
+
return apiVerificationProjection(input);
|
|
961
|
+
}
|
|
962
|
+
function apiEndpointWithinAllowlist(canonicalUrl, allowlist) {
|
|
963
|
+
for (const entry of allowlist) {
|
|
964
|
+
if (canonicalUrl === entry) return true;
|
|
965
|
+
if (entry.endsWith("/") && canonicalUrl.startsWith(entry)) return true;
|
|
966
|
+
if (!entry.endsWith("/") && (canonicalUrl.startsWith(`${entry}/`) || canonicalUrl.startsWith(`${entry}?`))) {
|
|
967
|
+
return true;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// src/contracts/commerce.ts
|
|
974
|
+
import { z as z6 } from "zod";
|
|
975
|
+
var FactLabelSchema2 = z6.string().trim().min(1).max(256);
|
|
976
|
+
var CentsSchema2 = z6.number().int().positive().max(Number.MAX_SAFE_INTEGER);
|
|
977
|
+
var IdempotencyKeySchema = z6.string().trim().min(8).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9_-]*$/);
|
|
978
|
+
var NfcText = (max) => z6.string().trim().min(1).max(max).refine((value) => value.normalize("NFC") === value);
|
|
979
|
+
var CommercePurchaseParametersV1Schema = z6.object({
|
|
980
|
+
schema: z6.literal("commerce.purchase.v1"),
|
|
981
|
+
connectionId: ConnectionIdSchema,
|
|
982
|
+
merchantId: FactLabelSchema2,
|
|
983
|
+
merchantCategory: FactLabelSchema2,
|
|
984
|
+
amountCents: CentsSchema2,
|
|
985
|
+
currency: z6.literal("USD"),
|
|
986
|
+
orderDescription: NfcText(512),
|
|
987
|
+
idempotencyKey: IdempotencyKeySchema,
|
|
988
|
+
taskId: TaskIdSchema.optional()
|
|
989
|
+
}).strict();
|
|
990
|
+
var CommerceRefundParametersV1Schema = z6.object({
|
|
991
|
+
schema: z6.literal("commerce.refund.v1"),
|
|
992
|
+
connectionId: ConnectionIdSchema,
|
|
993
|
+
chargeId: z6.string().trim().min(1).max(128),
|
|
994
|
+
amountCents: CentsSchema2.optional(),
|
|
995
|
+
reason: NfcText(256)
|
|
996
|
+
}).strict();
|
|
997
|
+
var CommerceReturnParametersV1Schema = z6.object({
|
|
998
|
+
schema: z6.literal("commerce.return.v1"),
|
|
999
|
+
connectionId: ConnectionIdSchema,
|
|
1000
|
+
chargeId: z6.string().trim().min(1).max(128),
|
|
1001
|
+
reason: NfcText(256)
|
|
1002
|
+
}).strict();
|
|
1003
|
+
var SubscriptionCreateParametersV1Schema = z6.object({
|
|
1004
|
+
schema: z6.literal("subscription.create.v1"),
|
|
1005
|
+
connectionId: ConnectionIdSchema,
|
|
1006
|
+
merchantId: FactLabelSchema2,
|
|
1007
|
+
planId: z6.string().trim().min(1).max(128),
|
|
1008
|
+
amountCents: CentsSchema2,
|
|
1009
|
+
currency: z6.literal("USD"),
|
|
1010
|
+
interval: z6.enum(["week", "month", "year"]),
|
|
1011
|
+
idempotencyKey: IdempotencyKeySchema
|
|
1012
|
+
}).strict();
|
|
1013
|
+
function commercePurchaseVerificationProjection(input) {
|
|
1014
|
+
const params = CommercePurchaseParametersV1Schema.parse(input);
|
|
1015
|
+
return Object.freeze({
|
|
1016
|
+
domain: "odingard.guardian.commerce-projection.v1",
|
|
1017
|
+
action: "commerce.purchase",
|
|
1018
|
+
connectionId: params.connectionId,
|
|
1019
|
+
merchantId: params.merchantId,
|
|
1020
|
+
merchantCategory: params.merchantCategory,
|
|
1021
|
+
amountCents: params.amountCents,
|
|
1022
|
+
currency: params.currency,
|
|
1023
|
+
descriptionDigest: sha256Text(params.orderDescription),
|
|
1024
|
+
idempotencyKey: params.idempotencyKey,
|
|
1025
|
+
taskId: params.taskId ?? null
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
function commercePurchaseEnvelopeProjection(input) {
|
|
1029
|
+
return commercePurchaseVerificationProjection(input);
|
|
1030
|
+
}
|
|
1031
|
+
function commerceRefundVerificationProjection(input) {
|
|
1032
|
+
const params = CommerceRefundParametersV1Schema.parse(input);
|
|
1033
|
+
return Object.freeze({
|
|
1034
|
+
domain: "odingard.guardian.commerce-projection.v1",
|
|
1035
|
+
action: "commerce.refund",
|
|
1036
|
+
connectionId: params.connectionId,
|
|
1037
|
+
chargeId: params.chargeId,
|
|
1038
|
+
amountCents: params.amountCents ?? null,
|
|
1039
|
+
reasonDigest: sha256Text(params.reason)
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
function commerceRefundEnvelopeProjection(input) {
|
|
1043
|
+
return commerceRefundVerificationProjection(input);
|
|
1044
|
+
}
|
|
1045
|
+
function commerceReturnVerificationProjection(input) {
|
|
1046
|
+
const params = CommerceReturnParametersV1Schema.parse(input);
|
|
1047
|
+
return Object.freeze({
|
|
1048
|
+
domain: "odingard.guardian.commerce-projection.v1",
|
|
1049
|
+
action: "commerce.return",
|
|
1050
|
+
connectionId: params.connectionId,
|
|
1051
|
+
chargeId: params.chargeId,
|
|
1052
|
+
reasonDigest: sha256Text(params.reason)
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
function commerceReturnEnvelopeProjection(input) {
|
|
1056
|
+
return commerceReturnVerificationProjection(input);
|
|
1057
|
+
}
|
|
1058
|
+
function subscriptionCreateVerificationProjection(input) {
|
|
1059
|
+
const params = SubscriptionCreateParametersV1Schema.parse(input);
|
|
1060
|
+
return Object.freeze({
|
|
1061
|
+
domain: "odingard.guardian.commerce-projection.v1",
|
|
1062
|
+
action: "subscription.create",
|
|
1063
|
+
connectionId: params.connectionId,
|
|
1064
|
+
merchantId: params.merchantId,
|
|
1065
|
+
planId: params.planId,
|
|
1066
|
+
amountCents: params.amountCents,
|
|
1067
|
+
currency: params.currency,
|
|
1068
|
+
interval: params.interval,
|
|
1069
|
+
idempotencyKey: params.idempotencyKey
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
function subscriptionCreateEnvelopeProjection(input) {
|
|
1073
|
+
return subscriptionCreateVerificationProjection(input);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// src/contracts/calendar.ts
|
|
1077
|
+
import { z as z7 } from "zod";
|
|
1078
|
+
var CalendarIdSchema = z7.string().trim().min(1).max(256).regex(/^[A-Za-z0-9_][A-Za-z0-9_.@%+-]*$/, "invalid calendar id");
|
|
1079
|
+
var EventIdSchema = z7.string().trim().min(1).max(512).regex(/^[A-Za-z0-9_-]+$/, "invalid event id");
|
|
1080
|
+
var EventTimeSchema = z7.object({
|
|
1081
|
+
// RFC 3339 timestamp (what the Google Calendar API accepts).
|
|
1082
|
+
dateTime: z7.string().regex(
|
|
1083
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/,
|
|
1084
|
+
"dateTime must be an RFC 3339 timestamp"
|
|
1085
|
+
),
|
|
1086
|
+
timeZone: z7.string().trim().min(1).max(64).optional()
|
|
1087
|
+
}).strict();
|
|
1088
|
+
var SendUpdatesSchema = z7.enum(["all", "externalOnly", "none"]).optional();
|
|
1089
|
+
var AttendeeSchema = z7.object({
|
|
1090
|
+
email: z7.string().trim().min(3).max(320).regex(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, "invalid attendee email"),
|
|
1091
|
+
optional: z7.boolean().optional()
|
|
1092
|
+
}).strict();
|
|
1093
|
+
var CalendarBaseSchema = z7.object({
|
|
1094
|
+
connectionId: ConnectionIdSchema,
|
|
1095
|
+
calendarId: CalendarIdSchema
|
|
1096
|
+
});
|
|
1097
|
+
var CalendarCreateParametersV1Schema = CalendarBaseSchema.extend({
|
|
1098
|
+
schema: z7.literal("odingard.guardian.calendar.create.v1"),
|
|
1099
|
+
summary: z7.string().trim().min(1).max(1024),
|
|
1100
|
+
description: z7.string().max(8192).optional(),
|
|
1101
|
+
location: z7.string().max(1024).optional(),
|
|
1102
|
+
start: EventTimeSchema,
|
|
1103
|
+
end: EventTimeSchema,
|
|
1104
|
+
idempotencyKey: z7.string().min(8).max(128).optional()
|
|
1105
|
+
}).strict();
|
|
1106
|
+
var CalendarUpdateParametersV1Schema = CalendarBaseSchema.extend({
|
|
1107
|
+
schema: z7.literal("odingard.guardian.calendar.update.v1"),
|
|
1108
|
+
eventId: EventIdSchema,
|
|
1109
|
+
summary: z7.string().trim().min(1).max(1024).optional(),
|
|
1110
|
+
description: z7.string().max(8192).optional(),
|
|
1111
|
+
location: z7.string().max(1024).optional(),
|
|
1112
|
+
start: EventTimeSchema.optional(),
|
|
1113
|
+
end: EventTimeSchema.optional(),
|
|
1114
|
+
sendUpdates: SendUpdatesSchema
|
|
1115
|
+
}).strict().refine(
|
|
1116
|
+
(params) => params.summary !== void 0 || params.description !== void 0 || params.location !== void 0 || params.start !== void 0 || params.end !== void 0,
|
|
1117
|
+
"calendar.update must carry at least one changed field"
|
|
1118
|
+
);
|
|
1119
|
+
var CalendarCancelParametersV1Schema = CalendarBaseSchema.extend({
|
|
1120
|
+
schema: z7.literal("odingard.guardian.calendar.cancel.v1"),
|
|
1121
|
+
eventId: EventIdSchema,
|
|
1122
|
+
sendUpdates: SendUpdatesSchema
|
|
1123
|
+
}).strict();
|
|
1124
|
+
var CalendarInviteParametersV1Schema = CalendarBaseSchema.extend({
|
|
1125
|
+
schema: z7.literal("odingard.guardian.calendar.invite.v1"),
|
|
1126
|
+
eventId: EventIdSchema,
|
|
1127
|
+
attendees: z7.array(AttendeeSchema).min(1).max(50),
|
|
1128
|
+
sendUpdates: SendUpdatesSchema
|
|
1129
|
+
}).strict();
|
|
1130
|
+
var CalendarParametersV1Schema = z7.discriminatedUnion("schema", [
|
|
1131
|
+
CalendarCreateParametersV1Schema,
|
|
1132
|
+
CalendarUpdateParametersV1Schema,
|
|
1133
|
+
CalendarCancelParametersV1Schema,
|
|
1134
|
+
CalendarInviteParametersV1Schema
|
|
1135
|
+
]);
|
|
1136
|
+
function attendeeEmails(attendees) {
|
|
1137
|
+
return [...new Set(attendees.map((a) => a.email.toLowerCase()))].sort();
|
|
1138
|
+
}
|
|
1139
|
+
function calendarVerificationProjection(params) {
|
|
1140
|
+
switch (params.schema) {
|
|
1141
|
+
case "odingard.guardian.calendar.create.v1":
|
|
1142
|
+
return {
|
|
1143
|
+
kind: "calendar.create",
|
|
1144
|
+
calendarId: params.calendarId,
|
|
1145
|
+
summary: params.summary,
|
|
1146
|
+
...params.description === void 0 ? {} : { description: params.description },
|
|
1147
|
+
...params.location === void 0 ? {} : { location: params.location },
|
|
1148
|
+
start: params.start,
|
|
1149
|
+
end: params.end
|
|
1150
|
+
};
|
|
1151
|
+
case "odingard.guardian.calendar.update.v1":
|
|
1152
|
+
return {
|
|
1153
|
+
kind: "calendar.update",
|
|
1154
|
+
calendarId: params.calendarId,
|
|
1155
|
+
eventId: params.eventId,
|
|
1156
|
+
...params.summary === void 0 ? {} : { summary: params.summary },
|
|
1157
|
+
...params.description === void 0 ? {} : { description: params.description },
|
|
1158
|
+
...params.location === void 0 ? {} : { location: params.location },
|
|
1159
|
+
...params.start === void 0 ? {} : { start: params.start },
|
|
1160
|
+
...params.end === void 0 ? {} : { end: params.end },
|
|
1161
|
+
sendUpdates: params.sendUpdates ?? "none"
|
|
1162
|
+
};
|
|
1163
|
+
case "odingard.guardian.calendar.cancel.v1":
|
|
1164
|
+
return {
|
|
1165
|
+
kind: "calendar.cancel",
|
|
1166
|
+
calendarId: params.calendarId,
|
|
1167
|
+
eventId: params.eventId,
|
|
1168
|
+
outcome: "cancelled",
|
|
1169
|
+
sendUpdates: params.sendUpdates ?? "none"
|
|
1170
|
+
};
|
|
1171
|
+
case "odingard.guardian.calendar.invite.v1":
|
|
1172
|
+
return {
|
|
1173
|
+
kind: "calendar.invite",
|
|
1174
|
+
calendarId: params.calendarId,
|
|
1175
|
+
eventId: params.eventId,
|
|
1176
|
+
attendees: attendeeEmails(params.attendees),
|
|
1177
|
+
sendUpdates: params.sendUpdates ?? "none"
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
function calendarEnvelopeProjection(params) {
|
|
1182
|
+
const base = {
|
|
1183
|
+
calendarId: params.calendarId,
|
|
1184
|
+
..."eventId" in params && params.eventId !== void 0 ? { eventId: params.eventId } : {}
|
|
1185
|
+
};
|
|
1186
|
+
switch (params.schema) {
|
|
1187
|
+
case "odingard.guardian.calendar.create.v1":
|
|
1188
|
+
return { kind: "calendar.create", ...base };
|
|
1189
|
+
case "odingard.guardian.calendar.update.v1":
|
|
1190
|
+
return { kind: "calendar.update", ...base };
|
|
1191
|
+
case "odingard.guardian.calendar.cancel.v1":
|
|
1192
|
+
return { kind: "calendar.cancel", ...base };
|
|
1193
|
+
case "odingard.guardian.calendar.invite.v1":
|
|
1194
|
+
return { kind: "calendar.invite", ...base };
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// src/contracts/files.ts
|
|
1199
|
+
import { z as z8 } from "zod";
|
|
1200
|
+
var FILE_PATH_MAX_LENGTH = 512;
|
|
1201
|
+
var FILE_CONTENT_MAX_LENGTH = 64e3;
|
|
1202
|
+
var FILE_GRANTEE_MAX_LENGTH = 256;
|
|
1203
|
+
var FILE_PATH_SEGMENT = /^[A-Za-z0-9._~-]+$/;
|
|
1204
|
+
function isCanonicalFilePath(value) {
|
|
1205
|
+
if (value.length < 2 || value.length > FILE_PATH_MAX_LENGTH) return false;
|
|
1206
|
+
if (!value.startsWith("/") || value.endsWith("/")) return false;
|
|
1207
|
+
if (value.includes("//")) return false;
|
|
1208
|
+
for (const segment of value.slice(1).split("/")) {
|
|
1209
|
+
if (segment === "." || segment === "..") return false;
|
|
1210
|
+
if (!FILE_PATH_SEGMENT.test(segment)) return false;
|
|
1211
|
+
}
|
|
1212
|
+
return true;
|
|
1213
|
+
}
|
|
1214
|
+
var FilePathSchema = z8.string().min(2).max(FILE_PATH_MAX_LENGTH).refine(isCanonicalFilePath, "file path must be canonical posix");
|
|
1215
|
+
var CounterpartyTokenSchema = z8.string().min(1).max(FILE_GRANTEE_MAX_LENGTH).regex(/^[A-Za-z0-9._~+@-]+$/u, "counterparty token charset");
|
|
1216
|
+
var FILE_READ_SCHEMA = "odingard.guardian.file.read.v1";
|
|
1217
|
+
var FILE_WRITE_SCHEMA = "odingard.guardian.file.write.v1";
|
|
1218
|
+
var FILE_SHARE_SCHEMA = "odingard.guardian.file.share.v1";
|
|
1219
|
+
var FILE_DELETE_SCHEMA = "odingard.guardian.file.delete.v1";
|
|
1220
|
+
var FileReadParametersV1Schema = z8.object({
|
|
1221
|
+
schema: z8.literal(FILE_READ_SCHEMA),
|
|
1222
|
+
connectionId: ConnectionIdSchema,
|
|
1223
|
+
path: FilePathSchema
|
|
1224
|
+
}).strict();
|
|
1225
|
+
var FileWriteParametersV1Schema = z8.object({
|
|
1226
|
+
schema: z8.literal(FILE_WRITE_SCHEMA),
|
|
1227
|
+
connectionId: ConnectionIdSchema,
|
|
1228
|
+
path: FilePathSchema,
|
|
1229
|
+
content: z8.string().max(FILE_CONTENT_MAX_LENGTH).refine(
|
|
1230
|
+
(value) => value.normalize("NFC") === value,
|
|
1231
|
+
"file content must already use NFC normalization"
|
|
1232
|
+
)
|
|
1233
|
+
}).strict();
|
|
1234
|
+
var FileShareParametersV1Schema = z8.object({
|
|
1235
|
+
schema: z8.literal(FILE_SHARE_SCHEMA),
|
|
1236
|
+
connectionId: ConnectionIdSchema,
|
|
1237
|
+
path: FilePathSchema,
|
|
1238
|
+
grantee: CounterpartyTokenSchema,
|
|
1239
|
+
expiresAtEpochMs: z8.number().int().positive().safe().optional()
|
|
1240
|
+
}).strict();
|
|
1241
|
+
var FileDeleteParametersV1Schema = z8.object({
|
|
1242
|
+
schema: z8.literal(FILE_DELETE_SCHEMA),
|
|
1243
|
+
connectionId: ConnectionIdSchema,
|
|
1244
|
+
path: FilePathSchema
|
|
1245
|
+
}).strict();
|
|
1246
|
+
function filePathWithinAllowlist(path, allowlist) {
|
|
1247
|
+
for (const entry of allowlist) {
|
|
1248
|
+
if (!isCanonicalFilePath(entry) && !entry.endsWith("/")) continue;
|
|
1249
|
+
if (path === entry) return true;
|
|
1250
|
+
if (entry.endsWith("/") && path.startsWith(entry)) return true;
|
|
1251
|
+
if (path.startsWith(`${entry}/`)) return true;
|
|
1252
|
+
}
|
|
1253
|
+
return false;
|
|
1254
|
+
}
|
|
1255
|
+
function fileReadVerificationProjection(input) {
|
|
1256
|
+
const params = FileReadParametersV1Schema.parse(input);
|
|
1257
|
+
return Object.freeze({
|
|
1258
|
+
domain: "odingard.guardian.file-read-projection.v1",
|
|
1259
|
+
connectionId: params.connectionId,
|
|
1260
|
+
path: params.path
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
function fileWriteVerificationProjection(input) {
|
|
1264
|
+
const params = FileWriteParametersV1Schema.parse(input);
|
|
1265
|
+
return Object.freeze({
|
|
1266
|
+
domain: "odingard.guardian.file-write-projection.v1",
|
|
1267
|
+
connectionId: params.connectionId,
|
|
1268
|
+
path: params.path,
|
|
1269
|
+
contentDigest: sha256Text(params.content),
|
|
1270
|
+
bytes: params.content.length
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
function fileShareVerificationProjection(input) {
|
|
1274
|
+
const params = FileShareParametersV1Schema.parse(input);
|
|
1275
|
+
return Object.freeze({
|
|
1276
|
+
domain: "odingard.guardian.file-share-projection.v1",
|
|
1277
|
+
connectionId: params.connectionId,
|
|
1278
|
+
path: params.path,
|
|
1279
|
+
grantee: params.grantee,
|
|
1280
|
+
expiresAtEpochMs: params.expiresAtEpochMs ?? null
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
function fileDeleteVerificationProjection(input) {
|
|
1284
|
+
const params = FileDeleteParametersV1Schema.parse(input);
|
|
1285
|
+
return Object.freeze({
|
|
1286
|
+
domain: "odingard.guardian.file-delete-projection.v1",
|
|
1287
|
+
connectionId: params.connectionId,
|
|
1288
|
+
path: params.path
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// src/contracts/protected-data.ts
|
|
1293
|
+
import { z as z9 } from "zod";
|
|
1294
|
+
var PROTECTED_FIELD_REF_MAX_LENGTH = 64;
|
|
1295
|
+
var PROTECTED_VALUE_MAX_LENGTH = 4096;
|
|
1296
|
+
var PROTECTED_CATALOG_MAX_ENTRIES = 128;
|
|
1297
|
+
var PROTECTED_USE_SINKS_MAX_ENTRIES = 16;
|
|
1298
|
+
var PROTECTED_DISCLOSE_TARGETS_MAX_ENTRIES = 32;
|
|
1299
|
+
var ProtectedFieldRefSchema = z9.string().min(1).max(PROTECTED_FIELD_REF_MAX_LENGTH).regex(
|
|
1300
|
+
/^[a-z0-9][a-z0-9._-]*$/u,
|
|
1301
|
+
"field refs are lowercase tokens (no traversal, no globs)"
|
|
1302
|
+
);
|
|
1303
|
+
var ProtectedUseSinkSchema = z9.object({
|
|
1304
|
+
/** The file connection whose store receives the injected value. */
|
|
1305
|
+
connectionId: ConnectionIdSchema,
|
|
1306
|
+
/** Canonical path prefix the agent's sink path must sit under. */
|
|
1307
|
+
pathPrefix: z9.string().min(1).max(512).refine(
|
|
1308
|
+
(value) => isCanonicalFilePath(value) || value.startsWith("/") && !value.endsWith("/") && value.length > 1 && isCanonicalFilePath(value.slice(0, -1)),
|
|
1309
|
+
"useSink pathPrefix must be a canonical path or directory prefix"
|
|
1310
|
+
)
|
|
1311
|
+
}).strict();
|
|
1312
|
+
var ProtectedCatalogEntrySchema = z9.object({
|
|
1313
|
+
fieldRef: ProtectedFieldRefSchema,
|
|
1314
|
+
label: z9.string().min(1).max(200),
|
|
1315
|
+
/** Safe preview for slips/receipts — never the plaintext. */
|
|
1316
|
+
maskedPreview: z9.string().max(120),
|
|
1317
|
+
allowUse: z9.boolean(),
|
|
1318
|
+
allowView: z9.boolean(),
|
|
1319
|
+
allowPersist: z9.boolean(),
|
|
1320
|
+
/** Declared sinks `protected_data.use` may inject into. */
|
|
1321
|
+
useSinks: z9.array(ProtectedUseSinkSchema).max(PROTECTED_USE_SINKS_MAX_ENTRIES),
|
|
1322
|
+
/** Declared counterparties `protected_data.disclose` may send to. */
|
|
1323
|
+
discloseTargets: z9.array(CounterpartyTokenSchema).max(PROTECTED_DISCLOSE_TARGETS_MAX_ENTRIES)
|
|
1324
|
+
}).strict();
|
|
1325
|
+
var ProtectedCatalogSchema = z9.array(ProtectedCatalogEntrySchema).max(PROTECTED_CATALOG_MAX_ENTRIES).refine(
|
|
1326
|
+
(entries) => new Set(entries.map((entry) => entry.fieldRef)).size === entries.length,
|
|
1327
|
+
"catalog field refs must be unique"
|
|
1328
|
+
);
|
|
1329
|
+
var PROTECTED_USE_SCHEMA = "odingard.guardian.protected-data.use.v1";
|
|
1330
|
+
var PROTECTED_VIEW_SCHEMA = "odingard.guardian.protected-data.view.v1";
|
|
1331
|
+
var PROTECTED_DISCLOSE_SCHEMA = "odingard.guardian.protected-data.disclose.v1";
|
|
1332
|
+
var PROTECTED_PERSIST_SCHEMA = "odingard.guardian.protected-data.persist.v1";
|
|
1333
|
+
var ProtectedUseParametersV1Schema = z9.object({
|
|
1334
|
+
schema: z9.literal(PROTECTED_USE_SCHEMA),
|
|
1335
|
+
connectionId: ConnectionIdSchema,
|
|
1336
|
+
fieldRef: ProtectedFieldRefSchema,
|
|
1337
|
+
/** Where the adapter injects the resolved value. Agent sees no plaintext. */
|
|
1338
|
+
sink: z9.object({
|
|
1339
|
+
connectionId: ConnectionIdSchema,
|
|
1340
|
+
path: FilePathSchema
|
|
1341
|
+
}).strict()
|
|
1342
|
+
}).strict();
|
|
1343
|
+
var ProtectedViewParametersV1Schema = z9.object({
|
|
1344
|
+
schema: z9.literal(PROTECTED_VIEW_SCHEMA),
|
|
1345
|
+
connectionId: ConnectionIdSchema,
|
|
1346
|
+
fieldRef: ProtectedFieldRefSchema
|
|
1347
|
+
}).strict();
|
|
1348
|
+
var ProtectedDiscloseParametersV1Schema = z9.object({
|
|
1349
|
+
schema: z9.literal(PROTECTED_DISCLOSE_SCHEMA),
|
|
1350
|
+
connectionId: ConnectionIdSchema,
|
|
1351
|
+
fieldRef: ProtectedFieldRefSchema,
|
|
1352
|
+
counterparty: CounterpartyTokenSchema
|
|
1353
|
+
}).strict();
|
|
1354
|
+
var ProtectedPersistParametersV1Schema = z9.object({
|
|
1355
|
+
schema: z9.literal(PROTECTED_PERSIST_SCHEMA),
|
|
1356
|
+
connectionId: ConnectionIdSchema,
|
|
1357
|
+
fieldRef: ProtectedFieldRefSchema,
|
|
1358
|
+
value: z9.string().min(1).max(PROTECTED_VALUE_MAX_LENGTH).refine(
|
|
1359
|
+
(value) => value.normalize("NFC") === value,
|
|
1360
|
+
"protected value must already use NFC normalization"
|
|
1361
|
+
)
|
|
1362
|
+
}).strict();
|
|
1363
|
+
function protectedCatalogField(catalog, fieldRef) {
|
|
1364
|
+
return catalog?.find((entry) => entry.fieldRef === fieldRef) ?? null;
|
|
1365
|
+
}
|
|
1366
|
+
function protectedSinkWithinUseSinks(sink, useSinks) {
|
|
1367
|
+
for (const entry of useSinks) {
|
|
1368
|
+
if (entry.connectionId !== sink.connectionId) continue;
|
|
1369
|
+
if (entry.pathPrefix.endsWith("/")) {
|
|
1370
|
+
if (sink.path.startsWith(entry.pathPrefix)) return true;
|
|
1371
|
+
} else if (sink.path === entry.pathPrefix || sink.path.startsWith(`${entry.pathPrefix}/`)) {
|
|
1372
|
+
return true;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
return false;
|
|
1376
|
+
}
|
|
1377
|
+
function protectedUseVerificationProjection(input) {
|
|
1378
|
+
const params = ProtectedUseParametersV1Schema.parse(input);
|
|
1379
|
+
return Object.freeze({
|
|
1380
|
+
domain: "odingard.guardian.protected-use-projection.v1",
|
|
1381
|
+
connectionId: params.connectionId,
|
|
1382
|
+
fieldRef: params.fieldRef,
|
|
1383
|
+
sinkConnectionId: params.sink.connectionId,
|
|
1384
|
+
sinkPath: params.sink.path
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
function protectedViewVerificationProjection(input) {
|
|
1388
|
+
const params = ProtectedViewParametersV1Schema.parse(input);
|
|
1389
|
+
return Object.freeze({
|
|
1390
|
+
domain: "odingard.guardian.protected-view-projection.v1",
|
|
1391
|
+
connectionId: params.connectionId,
|
|
1392
|
+
fieldRef: params.fieldRef
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
function protectedDiscloseVerificationProjection(input) {
|
|
1396
|
+
const params = ProtectedDiscloseParametersV1Schema.parse(input);
|
|
1397
|
+
return Object.freeze({
|
|
1398
|
+
domain: "odingard.guardian.protected-disclose-projection.v1",
|
|
1399
|
+
connectionId: params.connectionId,
|
|
1400
|
+
fieldRef: params.fieldRef,
|
|
1401
|
+
counterparty: params.counterparty
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
function protectedPersistVerificationProjection(input) {
|
|
1405
|
+
const params = ProtectedPersistParametersV1Schema.parse(input);
|
|
1406
|
+
return Object.freeze({
|
|
1407
|
+
domain: "odingard.guardian.protected-persist-projection.v1",
|
|
1408
|
+
connectionId: params.connectionId,
|
|
1409
|
+
fieldRef: params.fieldRef,
|
|
1410
|
+
valueDigest: sha256Text(params.value)
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// src/contracts/tools.ts
|
|
1415
|
+
import { z as z10 } from "zod";
|
|
1416
|
+
var TOOL_NAME_MAX_LENGTH = 64;
|
|
1417
|
+
var TOOL_ARGUMENTS_MAX_BYTES = 16384;
|
|
1418
|
+
var TOOL_CATALOG_MAX_TOOLS = 64;
|
|
1419
|
+
var TOOL_SERVER_NAME_MAX_LENGTH = 128;
|
|
1420
|
+
var TOOL_SERVER_VERSION_MAX_LENGTH = 64;
|
|
1421
|
+
var TOOL_DESCRIPTION_MAX_LENGTH = 512;
|
|
1422
|
+
var ToolNameSchema = z10.string().min(1).max(TOOL_NAME_MAX_LENGTH).regex(
|
|
1423
|
+
/^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/,
|
|
1424
|
+
"tool names are lowercase dotted/dashed identifiers"
|
|
1425
|
+
);
|
|
1426
|
+
var ToolDescriptorSchema = z10.object({
|
|
1427
|
+
name: ToolNameSchema,
|
|
1428
|
+
description: z10.string().max(TOOL_DESCRIPTION_MAX_LENGTH).optional(),
|
|
1429
|
+
method: ApiRequestMethodSchema,
|
|
1430
|
+
url: ApiRequestUrlSchema
|
|
1431
|
+
}).strict();
|
|
1432
|
+
var ToolServerCatalogSchema = z10.object({
|
|
1433
|
+
serverName: z10.string().min(1).max(TOOL_SERVER_NAME_MAX_LENGTH),
|
|
1434
|
+
serverVersion: z10.string().min(1).max(TOOL_SERVER_VERSION_MAX_LENGTH),
|
|
1435
|
+
tools: z10.array(ToolDescriptorSchema).max(TOOL_CATALOG_MAX_TOOLS).refine(
|
|
1436
|
+
(tools) => new Set(tools.map((tool) => tool.name)).size === tools.length,
|
|
1437
|
+
"tool names must be unique within a catalog"
|
|
1438
|
+
)
|
|
1439
|
+
}).strict();
|
|
1440
|
+
var ToolArgumentsSchema = z10.record(z10.string(), z10.unknown()).refine(
|
|
1441
|
+
(value) => {
|
|
1442
|
+
try {
|
|
1443
|
+
return canonicalJson(value).length <= TOOL_ARGUMENTS_MAX_BYTES;
|
|
1444
|
+
} catch {
|
|
1445
|
+
return false;
|
|
1446
|
+
}
|
|
1447
|
+
},
|
|
1448
|
+
{
|
|
1449
|
+
message: "arguments must be a bounded canonical JSON object (no cycles, accessors, or exotic prototypes)"
|
|
1450
|
+
}
|
|
1451
|
+
);
|
|
1452
|
+
var ToolInvokeParametersV1Schema = z10.object({
|
|
1453
|
+
schema: z10.literal("odingard.guardian.tool.invoke.v1"),
|
|
1454
|
+
connectionId: ConnectionIdSchema,
|
|
1455
|
+
toolName: ToolNameSchema,
|
|
1456
|
+
arguments: ToolArgumentsSchema
|
|
1457
|
+
}).strict();
|
|
1458
|
+
var McpInstallParametersV1Schema = z10.object({
|
|
1459
|
+
schema: z10.literal("odingard.guardian.mcp.install.v1"),
|
|
1460
|
+
connectionId: ConnectionIdSchema,
|
|
1461
|
+
tool: ToolDescriptorSchema
|
|
1462
|
+
}).strict();
|
|
1463
|
+
var McpEnableParametersV1Schema = z10.object({
|
|
1464
|
+
schema: z10.literal("odingard.guardian.mcp.enable.v1"),
|
|
1465
|
+
connectionId: ConnectionIdSchema,
|
|
1466
|
+
toolName: ToolNameSchema
|
|
1467
|
+
}).strict();
|
|
1468
|
+
function toolVerificationProjection(input) {
|
|
1469
|
+
const params = ToolInvokeParametersV1Schema.parse(input);
|
|
1470
|
+
return Object.freeze({
|
|
1471
|
+
domain: "odingard.guardian.tool-invoke-projection.v1",
|
|
1472
|
+
connectionId: params.connectionId,
|
|
1473
|
+
toolName: params.toolName,
|
|
1474
|
+
argumentsDigest: sha256Text(canonicalJson(params.arguments))
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
function toolEnvelopeProjection(input) {
|
|
1478
|
+
return toolVerificationProjection(input);
|
|
1479
|
+
}
|
|
1480
|
+
function mcpInstallProjection(input) {
|
|
1481
|
+
const params = McpInstallParametersV1Schema.parse(input);
|
|
1482
|
+
return Object.freeze({
|
|
1483
|
+
domain: "odingard.guardian.mcp-projection.v1",
|
|
1484
|
+
operation: "install",
|
|
1485
|
+
connectionId: params.connectionId,
|
|
1486
|
+
toolName: params.tool.name,
|
|
1487
|
+
descriptorDigest: sha256Text(canonicalJson(params.tool))
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
function mcpEnableProjection(input) {
|
|
1491
|
+
const params = McpEnableParametersV1Schema.parse(input);
|
|
1492
|
+
return Object.freeze({
|
|
1493
|
+
domain: "odingard.guardian.mcp-projection.v1",
|
|
1494
|
+
operation: "enable",
|
|
1495
|
+
connectionId: params.connectionId,
|
|
1496
|
+
toolName: params.toolName,
|
|
1497
|
+
descriptorDigest: null
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
function findCatalogTool(connection, toolName) {
|
|
1501
|
+
return connection.toolServer?.tools.find((tool) => tool.name === toolName);
|
|
1502
|
+
}
|
|
1503
|
+
function validateToolInvokeBinding(input) {
|
|
1504
|
+
const tool = findCatalogTool(input.connection, input.params.toolName);
|
|
1505
|
+
if (tool === void 0) {
|
|
1506
|
+
return { ok: false, reasonCode: "TOOL_NOT_IN_CATALOG" };
|
|
1507
|
+
}
|
|
1508
|
+
if (input.connection.enabledToolNames !== void 0 && !input.connection.enabledToolNames.includes(tool.name)) {
|
|
1509
|
+
return { ok: false, reasonCode: "TOOL_NOT_GRANTED" };
|
|
1510
|
+
}
|
|
1511
|
+
const rendered = ToolDescriptorSchema.safeParse(tool);
|
|
1512
|
+
if (!rendered.success || !apiEndpointWithinAllowlist(
|
|
1513
|
+
rendered.data.url,
|
|
1514
|
+
input.connection.endpointAllowlist ?? []
|
|
1515
|
+
)) {
|
|
1516
|
+
return { ok: false, reasonCode: "ENDPOINT_OUTSIDE_ALLOWLIST" };
|
|
1517
|
+
}
|
|
1518
|
+
return { ok: true };
|
|
1519
|
+
}
|
|
1520
|
+
function validateMcpInstallBinding(input) {
|
|
1521
|
+
const tools = input.connection.toolServer?.tools;
|
|
1522
|
+
if (tools === void 0) {
|
|
1523
|
+
return { ok: false, reasonCode: "CONNECTION_UNAVAILABLE" };
|
|
1524
|
+
}
|
|
1525
|
+
if (tools.length >= TOOL_CATALOG_MAX_TOOLS) {
|
|
1526
|
+
return { ok: false, reasonCode: "TOOL_CATALOG_FULL" };
|
|
1527
|
+
}
|
|
1528
|
+
if (tools.some((tool) => tool.name === input.params.tool.name)) {
|
|
1529
|
+
return { ok: false, reasonCode: "TOOL_ALREADY_INSTALLED" };
|
|
1530
|
+
}
|
|
1531
|
+
if (!apiEndpointWithinAllowlist(
|
|
1532
|
+
input.params.tool.url,
|
|
1533
|
+
input.connection.endpointAllowlist ?? []
|
|
1534
|
+
)) {
|
|
1535
|
+
return { ok: false, reasonCode: "ENDPOINT_OUTSIDE_ALLOWLIST" };
|
|
1536
|
+
}
|
|
1537
|
+
return { ok: true };
|
|
1538
|
+
}
|
|
1539
|
+
function validateMcpEnableBinding(input) {
|
|
1540
|
+
const tool = findCatalogTool(input.connection, input.params.toolName);
|
|
1541
|
+
if (tool === void 0) {
|
|
1542
|
+
return { ok: false, reasonCode: "TOOL_NOT_IN_CATALOG" };
|
|
1543
|
+
}
|
|
1544
|
+
if (input.connection.enabledToolNames?.includes(tool.name)) {
|
|
1545
|
+
return { ok: false, reasonCode: "TOOL_ALREADY_ENABLED" };
|
|
1546
|
+
}
|
|
1547
|
+
return { ok: true };
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// src/contracts/action-registry.ts
|
|
1551
|
+
function createActionRegistry(definitions) {
|
|
1552
|
+
const byAction = /* @__PURE__ */ new Map();
|
|
1553
|
+
for (const definition of definitions) {
|
|
1554
|
+
if (byAction.has(definition.actionId)) {
|
|
1555
|
+
throw new TypeError(
|
|
1556
|
+
`duplicate action definition: ${definition.actionId}`
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
byAction.set(definition.actionId, definition);
|
|
1560
|
+
}
|
|
1561
|
+
const entries = Object.freeze([...definitions]);
|
|
1562
|
+
return Object.freeze({
|
|
1563
|
+
get: (actionId) => byAction.get(actionId),
|
|
1564
|
+
entries: () => entries
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
var emailSendDefinition = {
|
|
1568
|
+
actionId: "email.send",
|
|
1569
|
+
schemaVersion: 1,
|
|
1570
|
+
actionFamily: "communication",
|
|
1571
|
+
humanLabel: "Send email",
|
|
1572
|
+
description: "Send an email message from a leased mailbox connection.",
|
|
1573
|
+
parametersKind: "email.send.v1",
|
|
1574
|
+
requiredParameters: ["connectionId", "from", "to", "subject", "body"],
|
|
1575
|
+
materialParameters: [
|
|
1576
|
+
"connectionId",
|
|
1577
|
+
"from",
|
|
1578
|
+
"to",
|
|
1579
|
+
"cc",
|
|
1580
|
+
"bcc",
|
|
1581
|
+
"replyTo",
|
|
1582
|
+
"subject",
|
|
1583
|
+
"body",
|
|
1584
|
+
"attachments",
|
|
1585
|
+
"inReplyTo"
|
|
1586
|
+
],
|
|
1587
|
+
resourceType: "email:mailbox",
|
|
1588
|
+
providers: ["gmail", "graph"],
|
|
1589
|
+
counterpartyRules: "Recipients are external counterparties; every recipient is material.",
|
|
1590
|
+
dataEffects: "Discloses message content to external recipients.",
|
|
1591
|
+
reversibility: "limited",
|
|
1592
|
+
riskClass: "R3",
|
|
1593
|
+
defaultDecisionPosture: "deny",
|
|
1594
|
+
approvalPresentationSchema: [
|
|
1595
|
+
"from",
|
|
1596
|
+
"to",
|
|
1597
|
+
"subject",
|
|
1598
|
+
"bodyPreview",
|
|
1599
|
+
"attachments"
|
|
1600
|
+
],
|
|
1601
|
+
capabilityRequirements: { uses: 1 },
|
|
1602
|
+
executionContract: "prepare \u2192 readback \u2192 commit \u2192 observe \u2192 verified/unknown/rejected",
|
|
1603
|
+
verificationContract: {
|
|
1604
|
+
level: "pre-commit-content+post-commit-envelope"
|
|
1605
|
+
},
|
|
1606
|
+
evidenceRequirements: [
|
|
1607
|
+
"effect.prepared",
|
|
1608
|
+
"commit.authorized",
|
|
1609
|
+
"effect.verified | effect.unknown | commit.rejected"
|
|
1610
|
+
],
|
|
1611
|
+
version: 1,
|
|
1612
|
+
status: "active",
|
|
1613
|
+
validateParameters(materialParameters) {
|
|
1614
|
+
const parsed = EmailSendParametersV1Schema.safeParse(materialParameters);
|
|
1615
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
1616
|
+
},
|
|
1617
|
+
validateBinding({ params, connection }) {
|
|
1618
|
+
return connection.mailboxAddress !== void 0 && params.from === connection.mailboxAddress ? { ok: true } : { ok: false, reasonCode: "CONNECTION_UNAVAILABLE" };
|
|
1619
|
+
},
|
|
1620
|
+
verificationProjection: (params) => emailVerificationProjection(params),
|
|
1621
|
+
envelopeProjection: (params) => emailEnvelopeProjection(params)
|
|
1622
|
+
};
|
|
1623
|
+
var apiRequestDefinition = {
|
|
1624
|
+
actionId: "api.request",
|
|
1625
|
+
schemaVersion: 1,
|
|
1626
|
+
actionFamily: "networking",
|
|
1627
|
+
humanLabel: "Call external API",
|
|
1628
|
+
description: "Issue an outbound https request within a connection's endpoint allowlist; the adapter injects the leased bearer token at commit.",
|
|
1629
|
+
parametersKind: "api.request.v1",
|
|
1630
|
+
requiredParameters: ["connectionId", "method", "url", "headers"],
|
|
1631
|
+
materialParameters: [
|
|
1632
|
+
"connectionId",
|
|
1633
|
+
"method",
|
|
1634
|
+
"url",
|
|
1635
|
+
"headers",
|
|
1636
|
+
"body",
|
|
1637
|
+
"idempotencyKey"
|
|
1638
|
+
],
|
|
1639
|
+
resourceType: "api:endpoint",
|
|
1640
|
+
providers: ["api"],
|
|
1641
|
+
counterpartyRules: "Host+path must stay inside the connection endpoint allowlist; https only; agent may not set credential or hop-by-hop headers.",
|
|
1642
|
+
dataEffects: "May mutate or read external state; response metadata (status, byte count, headers digest, bounded body digest) is evidenced.",
|
|
1643
|
+
reversibility: "difficult",
|
|
1644
|
+
riskClass: "R3",
|
|
1645
|
+
defaultDecisionPosture: "deny",
|
|
1646
|
+
approvalPresentationSchema: ["method", "url", "headers", "bodyPreview"],
|
|
1647
|
+
capabilityRequirements: { uses: 1 },
|
|
1648
|
+
executionContract: "prepare \u2192 readback \u2192 commit \u2192 observe \u2192 verified/unknown/rejected; observe never re-issues the request.",
|
|
1649
|
+
verificationContract: { level: "post-commit-content" },
|
|
1650
|
+
evidenceRequirements: [
|
|
1651
|
+
"effect.prepared",
|
|
1652
|
+
"commit.authorized",
|
|
1653
|
+
"effect.verified | effect.unknown | commit.rejected"
|
|
1654
|
+
],
|
|
1655
|
+
version: 1,
|
|
1656
|
+
status: "active",
|
|
1657
|
+
validateParameters(materialParameters) {
|
|
1658
|
+
const parsed = ApiRequestParametersV1Schema.safeParse(materialParameters);
|
|
1659
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
1660
|
+
},
|
|
1661
|
+
validateBinding({ params, connection }) {
|
|
1662
|
+
return apiEndpointWithinAllowlist(
|
|
1663
|
+
params.url,
|
|
1664
|
+
connection.endpointAllowlist ?? []
|
|
1665
|
+
) ? { ok: true } : { ok: false, reasonCode: "ENDPOINT_OUTSIDE_ALLOWLIST" };
|
|
1666
|
+
},
|
|
1667
|
+
verificationProjection: (params) => apiVerificationProjection(params),
|
|
1668
|
+
envelopeProjection: (params) => apiEnvelopeProjection(params)
|
|
1669
|
+
};
|
|
1670
|
+
var toolInvokeDefinition = {
|
|
1671
|
+
actionId: "tool.invoke",
|
|
1672
|
+
schemaVersion: 1,
|
|
1673
|
+
actionFamily: "tooling",
|
|
1674
|
+
humanLabel: "Invoke tool",
|
|
1675
|
+
description: "Invoke a catalog tool enabled for the agent on a leased tool-server connection; renders to a bounded https call inside the connection endpoint allowlist.",
|
|
1676
|
+
parametersKind: "tool.invoke.v1",
|
|
1677
|
+
requiredParameters: ["connectionId", "toolName", "arguments"],
|
|
1678
|
+
materialParameters: ["connectionId", "toolName", "arguments"],
|
|
1679
|
+
resourceType: "tool:server",
|
|
1680
|
+
providers: ["tool"],
|
|
1681
|
+
counterpartyRules: "Tool name must exist in the connection catalog and be enabled for the requesting agent; the rendered endpoint stays inside the allowlist.",
|
|
1682
|
+
dataEffects: "May read or mutate external state through the tool; bounded invocation and result metadata is evidenced.",
|
|
1683
|
+
reversibility: "limited",
|
|
1684
|
+
riskClass: "R3",
|
|
1685
|
+
defaultDecisionPosture: "deny",
|
|
1686
|
+
approvalPresentationSchema: ["toolName", "serverName", "argumentsPreview"],
|
|
1687
|
+
capabilityRequirements: { uses: 1 },
|
|
1688
|
+
executionContract: "prepare \u2192 readback \u2192 commit \u2192 observe \u2192 verified/unknown/rejected; observe never re-issues the invocation.",
|
|
1689
|
+
verificationContract: { level: "post-commit-content" },
|
|
1690
|
+
evidenceRequirements: [
|
|
1691
|
+
"effect.prepared",
|
|
1692
|
+
"commit.authorized",
|
|
1693
|
+
"effect.verified | effect.unknown | commit.rejected"
|
|
1694
|
+
],
|
|
1695
|
+
version: 1,
|
|
1696
|
+
status: "active",
|
|
1697
|
+
validateParameters(materialParameters) {
|
|
1698
|
+
const parsed = ToolInvokeParametersV1Schema.safeParse(materialParameters);
|
|
1699
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
1700
|
+
},
|
|
1701
|
+
validateBinding({ params, connection }) {
|
|
1702
|
+
return validateToolInvokeBinding({ params, connection });
|
|
1703
|
+
},
|
|
1704
|
+
verificationProjection: (params) => toolVerificationProjection(params),
|
|
1705
|
+
envelopeProjection: (params) => toolEnvelopeProjection(params)
|
|
1706
|
+
};
|
|
1707
|
+
var mcpInstallDefinition = {
|
|
1708
|
+
actionId: "mcp.install",
|
|
1709
|
+
schemaVersion: 1,
|
|
1710
|
+
actionFamily: "capability-governance",
|
|
1711
|
+
humanLabel: "Install MCP tool",
|
|
1712
|
+
description: "Register an additional tool on a human-installed tool-server connection; capability-expanding \u2014 requires an explicit human grant, never auto-derived.",
|
|
1713
|
+
parametersKind: "mcp.install.v1",
|
|
1714
|
+
requiredParameters: ["connectionId", "tool"],
|
|
1715
|
+
materialParameters: ["connectionId", "tool"],
|
|
1716
|
+
resourceType: "mcp:server",
|
|
1717
|
+
providers: ["tool"],
|
|
1718
|
+
counterpartyRules: "Tool descriptor must be new to the catalog and its url must stay inside the existing endpoint allowlist; installs never create connections or widen scopes.",
|
|
1719
|
+
dataEffects: "Mutates the connection's tool catalog (adds one callable tool).",
|
|
1720
|
+
reversibility: "limited",
|
|
1721
|
+
riskClass: "R3",
|
|
1722
|
+
defaultDecisionPosture: "ask",
|
|
1723
|
+
approvalPresentationSchema: ["tool", "serverName"],
|
|
1724
|
+
capabilityRequirements: { uses: 1 },
|
|
1725
|
+
executionContract: "prepare \u2192 readback \u2192 commit \u2192 observe \u2192 verified/unknown/rejected",
|
|
1726
|
+
verificationContract: { level: "post-commit-content" },
|
|
1727
|
+
evidenceRequirements: [
|
|
1728
|
+
"effect.prepared",
|
|
1729
|
+
"commit.authorized",
|
|
1730
|
+
"effect.verified | effect.unknown | commit.rejected"
|
|
1731
|
+
],
|
|
1732
|
+
version: 1,
|
|
1733
|
+
status: "active",
|
|
1734
|
+
requiresExplicitGrant: true,
|
|
1735
|
+
validateParameters(materialParameters) {
|
|
1736
|
+
const parsed = McpInstallParametersV1Schema.safeParse(materialParameters);
|
|
1737
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
1738
|
+
},
|
|
1739
|
+
validateBinding({ params, connection }) {
|
|
1740
|
+
return validateMcpInstallBinding({ params, connection });
|
|
1741
|
+
},
|
|
1742
|
+
verificationProjection: (params) => mcpInstallProjection(params),
|
|
1743
|
+
envelopeProjection: (params) => mcpInstallProjection(params)
|
|
1744
|
+
};
|
|
1745
|
+
var mcpEnableDefinition = {
|
|
1746
|
+
actionId: "mcp.enable",
|
|
1747
|
+
schemaVersion: 1,
|
|
1748
|
+
actionFamily: "capability-governance",
|
|
1749
|
+
humanLabel: "Enable MCP tool",
|
|
1750
|
+
description: "Enable a catalog tool for the requesting agent on a tool-server connection; capability-expanding \u2014 requires an explicit human grant, never auto-derived.",
|
|
1751
|
+
parametersKind: "mcp.enable.v1",
|
|
1752
|
+
requiredParameters: ["connectionId", "toolName"],
|
|
1753
|
+
materialParameters: ["connectionId", "toolName"],
|
|
1754
|
+
resourceType: "mcp:server",
|
|
1755
|
+
providers: ["tool"],
|
|
1756
|
+
counterpartyRules: "Tool name must exist in the catalog; enabling writes to the per-agent tool registry with the granting evidence.",
|
|
1757
|
+
dataEffects: "Mutates the agent's tool registry (enables one catalog tool).",
|
|
1758
|
+
reversibility: "reversible",
|
|
1759
|
+
riskClass: "R2",
|
|
1760
|
+
defaultDecisionPosture: "ask",
|
|
1761
|
+
approvalPresentationSchema: ["toolName", "serverName"],
|
|
1762
|
+
capabilityRequirements: { uses: 1 },
|
|
1763
|
+
executionContract: "prepare \u2192 readback \u2192 commit \u2192 observe \u2192 verified/unknown/rejected",
|
|
1764
|
+
verificationContract: { level: "post-commit-content" },
|
|
1765
|
+
evidenceRequirements: [
|
|
1766
|
+
"effect.prepared",
|
|
1767
|
+
"commit.authorized",
|
|
1768
|
+
"effect.verified | effect.unknown | commit.rejected"
|
|
1769
|
+
],
|
|
1770
|
+
version: 1,
|
|
1771
|
+
status: "active",
|
|
1772
|
+
requiresExplicitGrant: true,
|
|
1773
|
+
validateParameters(materialParameters) {
|
|
1774
|
+
const parsed = McpEnableParametersV1Schema.safeParse(materialParameters);
|
|
1775
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
1776
|
+
},
|
|
1777
|
+
validateBinding({ params, connection }) {
|
|
1778
|
+
return validateMcpEnableBinding({ params, connection });
|
|
1779
|
+
},
|
|
1780
|
+
verificationProjection: (params) => mcpEnableProjection(params),
|
|
1781
|
+
envelopeProjection: (params) => mcpEnableProjection(params)
|
|
1782
|
+
};
|
|
1783
|
+
var GCAL_ADAPTER_ID = "adp_00000003";
|
|
1784
|
+
var calendarBinding = (params, connection) => connection.provider === "gcalendar" && connection.calendarId !== void 0 && params.calendarId === connection.calendarId ? { ok: true } : { ok: false, reasonCode: "CONNECTION_UNAVAILABLE" };
|
|
1785
|
+
var calendarValidate = (schema) => (materialParameters) => {
|
|
1786
|
+
const parsed = schema.safeParse(materialParameters);
|
|
1787
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
1788
|
+
};
|
|
1789
|
+
var calendarDefinitionBase = {
|
|
1790
|
+
schemaVersion: 1,
|
|
1791
|
+
actionFamily: "scheduling",
|
|
1792
|
+
resourceType: "calendar:calendar",
|
|
1793
|
+
providers: ["gcalendar"],
|
|
1794
|
+
adapterIds: [GCAL_ADAPTER_ID],
|
|
1795
|
+
counterpartyRules: "Events live on the connection's authorized calendar only; external invitees are counterparties and are material.",
|
|
1796
|
+
capabilityRequirements: { uses: 1 },
|
|
1797
|
+
executionContract: "prepare \u2192 readback \u2192 commit \u2192 observe \u2192 verified/unknown/rejected; observe never re-issues the request.",
|
|
1798
|
+
verificationContract: { level: "post-commit-content" },
|
|
1799
|
+
evidenceRequirements: [
|
|
1800
|
+
"effect.prepared",
|
|
1801
|
+
"commit.authorized",
|
|
1802
|
+
"effect.verified | effect.unknown | commit.rejected"
|
|
1803
|
+
],
|
|
1804
|
+
version: 1,
|
|
1805
|
+
status: "active"
|
|
1806
|
+
};
|
|
1807
|
+
var calendarCreateDefinition = {
|
|
1808
|
+
...calendarDefinitionBase,
|
|
1809
|
+
actionId: "calendar.create",
|
|
1810
|
+
humanLabel: "Create calendar event",
|
|
1811
|
+
description: "Create an event on the connection's authorized calendar; attendees are never settable here \u2014 inviting people is a separate material commitment.",
|
|
1812
|
+
parametersKind: "calendar.create.v1",
|
|
1813
|
+
requiredParameters: ["connectionId", "calendarId", "summary", "start", "end"],
|
|
1814
|
+
materialParameters: [
|
|
1815
|
+
"connectionId",
|
|
1816
|
+
"calendarId",
|
|
1817
|
+
"summary",
|
|
1818
|
+
"description",
|
|
1819
|
+
"location",
|
|
1820
|
+
"start",
|
|
1821
|
+
"end",
|
|
1822
|
+
"idempotencyKey"
|
|
1823
|
+
],
|
|
1824
|
+
dataEffects: "Creates an event record on the authorized calendar.",
|
|
1825
|
+
reversibility: "reversible",
|
|
1826
|
+
riskClass: "R2",
|
|
1827
|
+
defaultDecisionPosture: "allow",
|
|
1828
|
+
approvalPresentationSchema: ["summary", "start", "end", "location"],
|
|
1829
|
+
validateParameters: calendarValidate(CalendarCreateParametersV1Schema),
|
|
1830
|
+
validateBinding: ({ params, connection }) => calendarBinding(params, connection),
|
|
1831
|
+
verificationProjection: (params) => calendarVerificationProjection(params),
|
|
1832
|
+
envelopeProjection: (params) => calendarEnvelopeProjection(params)
|
|
1833
|
+
};
|
|
1834
|
+
var calendarUpdateDefinition = {
|
|
1835
|
+
...calendarDefinitionBase,
|
|
1836
|
+
actionId: "calendar.update",
|
|
1837
|
+
humanLabel: "Update calendar event",
|
|
1838
|
+
description: "Change fields of an existing event \u2014 including its committed start/end time \u2014 on the authorized calendar; a material external commitment.",
|
|
1839
|
+
parametersKind: "calendar.update.v1",
|
|
1840
|
+
requiredParameters: ["connectionId", "calendarId", "eventId"],
|
|
1841
|
+
materialParameters: [
|
|
1842
|
+
"connectionId",
|
|
1843
|
+
"calendarId",
|
|
1844
|
+
"eventId",
|
|
1845
|
+
"summary",
|
|
1846
|
+
"description",
|
|
1847
|
+
"location",
|
|
1848
|
+
"start",
|
|
1849
|
+
"end",
|
|
1850
|
+
"sendUpdates"
|
|
1851
|
+
],
|
|
1852
|
+
dataEffects: "Mutates a live commitment; a changed time moves every attendee's schedule.",
|
|
1853
|
+
reversibility: "limited",
|
|
1854
|
+
riskClass: "R3",
|
|
1855
|
+
defaultDecisionPosture: "ask",
|
|
1856
|
+
approvalPresentationSchema: [
|
|
1857
|
+
"eventId",
|
|
1858
|
+
"summary",
|
|
1859
|
+
"start",
|
|
1860
|
+
"end",
|
|
1861
|
+
"location"
|
|
1862
|
+
],
|
|
1863
|
+
validateParameters: calendarValidate(CalendarUpdateParametersV1Schema),
|
|
1864
|
+
validateBinding: ({ params, connection }) => calendarBinding(params, connection),
|
|
1865
|
+
verificationProjection: (params) => calendarVerificationProjection(params),
|
|
1866
|
+
envelopeProjection: (params) => calendarEnvelopeProjection(params)
|
|
1867
|
+
};
|
|
1868
|
+
var calendarCancelDefinition = {
|
|
1869
|
+
...calendarDefinitionBase,
|
|
1870
|
+
actionId: "calendar.cancel",
|
|
1871
|
+
humanLabel: "Cancel calendar event",
|
|
1872
|
+
description: "Cancel (delete) an existing event on the authorized calendar; destroys a commitment.",
|
|
1873
|
+
parametersKind: "calendar.cancel.v1",
|
|
1874
|
+
requiredParameters: ["connectionId", "calendarId", "eventId"],
|
|
1875
|
+
materialParameters: ["connectionId", "calendarId", "eventId", "sendUpdates"],
|
|
1876
|
+
dataEffects: "Removes a commitment; may send cancellation notices to attendees.",
|
|
1877
|
+
reversibility: "difficult",
|
|
1878
|
+
riskClass: "R3",
|
|
1879
|
+
defaultDecisionPosture: "ask",
|
|
1880
|
+
approvalPresentationSchema: ["eventId", "sendUpdates"],
|
|
1881
|
+
validateParameters: calendarValidate(CalendarCancelParametersV1Schema),
|
|
1882
|
+
validateBinding: ({ params, connection }) => calendarBinding(params, connection),
|
|
1883
|
+
verificationProjection: (params) => calendarVerificationProjection(params),
|
|
1884
|
+
envelopeProjection: (params) => calendarEnvelopeProjection(params)
|
|
1885
|
+
};
|
|
1886
|
+
var calendarInviteDefinition = {
|
|
1887
|
+
...calendarDefinitionBase,
|
|
1888
|
+
actionId: "calendar.invite",
|
|
1889
|
+
humanLabel: "Invite attendees to event",
|
|
1890
|
+
description: "Add external attendees to an existing event on the authorized calendar; creates a material external commitment to each invitee.",
|
|
1891
|
+
parametersKind: "calendar.invite.v1",
|
|
1892
|
+
requiredParameters: ["connectionId", "calendarId", "eventId", "attendees"],
|
|
1893
|
+
materialParameters: [
|
|
1894
|
+
"connectionId",
|
|
1895
|
+
"calendarId",
|
|
1896
|
+
"eventId",
|
|
1897
|
+
"attendees",
|
|
1898
|
+
"sendUpdates"
|
|
1899
|
+
],
|
|
1900
|
+
dataEffects: "Sends invitations to external addresses; the attendee set is material and approval-bound.",
|
|
1901
|
+
reversibility: "limited",
|
|
1902
|
+
riskClass: "R3",
|
|
1903
|
+
defaultDecisionPosture: "ask",
|
|
1904
|
+
approvalPresentationSchema: ["eventId", "attendees", "sendUpdates"],
|
|
1905
|
+
validateParameters: calendarValidate(CalendarInviteParametersV1Schema),
|
|
1906
|
+
validateBinding: ({ params, connection }) => calendarBinding(params, connection),
|
|
1907
|
+
verificationProjection: (params) => calendarVerificationProjection(params),
|
|
1908
|
+
envelopeProjection: (params) => calendarEnvelopeProjection(params)
|
|
1909
|
+
};
|
|
1910
|
+
var commercePurchaseDefinition = {
|
|
1911
|
+
actionId: "commerce.purchase",
|
|
1912
|
+
schemaVersion: 1,
|
|
1913
|
+
actionFamily: "commerce",
|
|
1914
|
+
humanLabel: "Purchase",
|
|
1915
|
+
description: "Charge a leased payment connection for a one-shot purchase; the adapter charges adapter-side and no reusable payment credential reaches the agent.",
|
|
1916
|
+
parametersKind: "commerce.purchase.v1",
|
|
1917
|
+
requiredParameters: [
|
|
1918
|
+
"connectionId",
|
|
1919
|
+
"merchantId",
|
|
1920
|
+
"merchantCategory",
|
|
1921
|
+
"amountCents",
|
|
1922
|
+
"currency",
|
|
1923
|
+
"orderDescription",
|
|
1924
|
+
"idempotencyKey"
|
|
1925
|
+
],
|
|
1926
|
+
materialParameters: [
|
|
1927
|
+
"connectionId",
|
|
1928
|
+
"merchantId",
|
|
1929
|
+
"merchantCategory",
|
|
1930
|
+
"amountCents",
|
|
1931
|
+
"currency",
|
|
1932
|
+
"orderDescription",
|
|
1933
|
+
"idempotencyKey",
|
|
1934
|
+
"taskId"
|
|
1935
|
+
],
|
|
1936
|
+
resourceType: "commerce:account",
|
|
1937
|
+
providers: ["payment-sandbox"],
|
|
1938
|
+
counterpartyRules: "merchantId is the external counterparty; merchant/category facts are re-derived from parameters and bound into the material digest.",
|
|
1939
|
+
dataEffects: "Moves money within sandbox/test scope only; charge metadata is evidenced.",
|
|
1940
|
+
reversibility: "limited",
|
|
1941
|
+
riskClass: "R4",
|
|
1942
|
+
defaultDecisionPosture: "allow",
|
|
1943
|
+
approvalPresentationSchema: [
|
|
1944
|
+
"merchantId",
|
|
1945
|
+
"merchantCategory",
|
|
1946
|
+
"amountCents",
|
|
1947
|
+
"currency",
|
|
1948
|
+
"orderDescription",
|
|
1949
|
+
"taskId"
|
|
1950
|
+
],
|
|
1951
|
+
capabilityRequirements: { uses: 1 },
|
|
1952
|
+
executionContract: "prepare \u2192 readback \u2192 commit (charge) \u2192 observe \u2192 verified/unknown/rejected",
|
|
1953
|
+
verificationContract: { level: "post-commit-content" },
|
|
1954
|
+
evidenceRequirements: [
|
|
1955
|
+
"effect.prepared",
|
|
1956
|
+
"commit.authorized",
|
|
1957
|
+
"effect.verified | effect.unknown | commit.rejected"
|
|
1958
|
+
],
|
|
1959
|
+
version: 1,
|
|
1960
|
+
status: "active",
|
|
1961
|
+
validateParameters(materialParameters) {
|
|
1962
|
+
const parsed = CommercePurchaseParametersV1Schema.safeParse(materialParameters);
|
|
1963
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
1964
|
+
},
|
|
1965
|
+
validateBinding() {
|
|
1966
|
+
return { ok: true };
|
|
1967
|
+
},
|
|
1968
|
+
verificationProjection: (params) => commercePurchaseVerificationProjection(params),
|
|
1969
|
+
envelopeProjection: (params) => commercePurchaseEnvelopeProjection(params),
|
|
1970
|
+
authorityFacts: (params) => ({
|
|
1971
|
+
valueCents: params.amountCents,
|
|
1972
|
+
merchantId: params.merchantId,
|
|
1973
|
+
merchantCategory: params.merchantCategory,
|
|
1974
|
+
recurring: false,
|
|
1975
|
+
...params.taskId === void 0 ? {} : { taskId: params.taskId }
|
|
1976
|
+
})
|
|
1977
|
+
};
|
|
1978
|
+
var commerceRefundDefinition = {
|
|
1979
|
+
actionId: "commerce.refund",
|
|
1980
|
+
schemaVersion: 1,
|
|
1981
|
+
actionFamily: "commerce",
|
|
1982
|
+
humanLabel: "Refund",
|
|
1983
|
+
description: "Refund a prior sandbox charge by chargeId; adapter-side only, full refund when amountCents is omitted.",
|
|
1984
|
+
parametersKind: "commerce.refund.v1",
|
|
1985
|
+
requiredParameters: ["connectionId", "chargeId", "reason"],
|
|
1986
|
+
materialParameters: ["connectionId", "chargeId", "amountCents", "reason"],
|
|
1987
|
+
resourceType: "commerce:account",
|
|
1988
|
+
providers: ["payment-sandbox"],
|
|
1989
|
+
counterpartyRules: "Refunds settle to the original charge's counterparty.",
|
|
1990
|
+
dataEffects: "Returns money within sandbox/test scope only.",
|
|
1991
|
+
reversibility: "difficult",
|
|
1992
|
+
riskClass: "R3",
|
|
1993
|
+
defaultDecisionPosture: "allow",
|
|
1994
|
+
approvalPresentationSchema: ["chargeId", "amountCents", "reason"],
|
|
1995
|
+
capabilityRequirements: { uses: 1 },
|
|
1996
|
+
executionContract: "prepare \u2192 readback \u2192 commit (refund) \u2192 observe \u2192 verified/unknown/rejected",
|
|
1997
|
+
verificationContract: { level: "post-commit-content" },
|
|
1998
|
+
evidenceRequirements: [
|
|
1999
|
+
"effect.prepared",
|
|
2000
|
+
"commit.authorized",
|
|
2001
|
+
"effect.verified | effect.unknown | commit.rejected"
|
|
2002
|
+
],
|
|
2003
|
+
version: 1,
|
|
2004
|
+
status: "active",
|
|
2005
|
+
validateParameters(materialParameters) {
|
|
2006
|
+
const parsed = CommerceRefundParametersV1Schema.safeParse(materialParameters);
|
|
2007
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
2008
|
+
},
|
|
2009
|
+
validateBinding() {
|
|
2010
|
+
return { ok: true };
|
|
2011
|
+
},
|
|
2012
|
+
verificationProjection: (params) => commerceRefundVerificationProjection(params),
|
|
2013
|
+
envelopeProjection: (params) => commerceRefundEnvelopeProjection(params),
|
|
2014
|
+
authorityFacts: (params) => params.amountCents === void 0 ? {} : { valueCents: params.amountCents }
|
|
2015
|
+
};
|
|
2016
|
+
var commerceReturnDefinition = {
|
|
2017
|
+
actionId: "commerce.return",
|
|
2018
|
+
schemaVersion: 1,
|
|
2019
|
+
actionFamily: "commerce",
|
|
2020
|
+
humanLabel: "Return",
|
|
2021
|
+
description: "Record a return against a prior sandbox charge by chargeId; adapter-side only.",
|
|
2022
|
+
parametersKind: "commerce.return.v1",
|
|
2023
|
+
requiredParameters: ["connectionId", "chargeId", "reason"],
|
|
2024
|
+
materialParameters: ["connectionId", "chargeId", "reason"],
|
|
2025
|
+
resourceType: "commerce:account",
|
|
2026
|
+
providers: ["payment-sandbox"],
|
|
2027
|
+
counterpartyRules: "Returns settle to the original charge's counterparty.",
|
|
2028
|
+
dataEffects: "Records a goods return within sandbox/test scope only.",
|
|
2029
|
+
reversibility: "reversible",
|
|
2030
|
+
riskClass: "R2",
|
|
2031
|
+
defaultDecisionPosture: "allow",
|
|
2032
|
+
approvalPresentationSchema: ["chargeId", "reason"],
|
|
2033
|
+
capabilityRequirements: { uses: 1 },
|
|
2034
|
+
executionContract: "prepare \u2192 readback \u2192 commit (return) \u2192 observe \u2192 verified/unknown/rejected",
|
|
2035
|
+
verificationContract: { level: "post-commit-content" },
|
|
2036
|
+
evidenceRequirements: [
|
|
2037
|
+
"effect.prepared",
|
|
2038
|
+
"commit.authorized",
|
|
2039
|
+
"effect.verified | effect.unknown | commit.rejected"
|
|
2040
|
+
],
|
|
2041
|
+
version: 1,
|
|
2042
|
+
status: "active",
|
|
2043
|
+
validateParameters(materialParameters) {
|
|
2044
|
+
const parsed = CommerceReturnParametersV1Schema.safeParse(materialParameters);
|
|
2045
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
2046
|
+
},
|
|
2047
|
+
validateBinding() {
|
|
2048
|
+
return { ok: true };
|
|
2049
|
+
},
|
|
2050
|
+
verificationProjection: (params) => commerceReturnVerificationProjection(params),
|
|
2051
|
+
envelopeProjection: (params) => commerceReturnEnvelopeProjection(params),
|
|
2052
|
+
authorityFacts: () => ({})
|
|
2053
|
+
};
|
|
2054
|
+
var subscriptionCreateDefinition = {
|
|
2055
|
+
actionId: "subscription.create",
|
|
2056
|
+
schemaVersion: 1,
|
|
2057
|
+
actionFamily: "commerce",
|
|
2058
|
+
humanLabel: "Create subscription",
|
|
2059
|
+
description: "Start a recurring subscription charge on a leased payment connection; recurrence is a derived authority fact the kernel can deny.",
|
|
2060
|
+
parametersKind: "subscription.create.v1",
|
|
2061
|
+
requiredParameters: [
|
|
2062
|
+
"connectionId",
|
|
2063
|
+
"merchantId",
|
|
2064
|
+
"planId",
|
|
2065
|
+
"amountCents",
|
|
2066
|
+
"currency",
|
|
2067
|
+
"interval",
|
|
2068
|
+
"idempotencyKey"
|
|
2069
|
+
],
|
|
2070
|
+
materialParameters: [
|
|
2071
|
+
"connectionId",
|
|
2072
|
+
"merchantId",
|
|
2073
|
+
"planId",
|
|
2074
|
+
"amountCents",
|
|
2075
|
+
"currency",
|
|
2076
|
+
"interval",
|
|
2077
|
+
"idempotencyKey"
|
|
2078
|
+
],
|
|
2079
|
+
resourceType: "commerce:account",
|
|
2080
|
+
providers: ["payment-sandbox"],
|
|
2081
|
+
counterpartyRules: "merchantId is the external counterparty; recurring is derived as true so recurrence control applies.",
|
|
2082
|
+
dataEffects: "Creates a recurring payment obligation within sandbox/test scope only.",
|
|
2083
|
+
reversibility: "limited",
|
|
2084
|
+
riskClass: "R4",
|
|
2085
|
+
defaultDecisionPosture: "deny",
|
|
2086
|
+
approvalPresentationSchema: [
|
|
2087
|
+
"merchantId",
|
|
2088
|
+
"planId",
|
|
2089
|
+
"amountCents",
|
|
2090
|
+
"currency",
|
|
2091
|
+
"interval"
|
|
2092
|
+
],
|
|
2093
|
+
capabilityRequirements: { uses: 1 },
|
|
2094
|
+
executionContract: "prepare \u2192 readback \u2192 commit (subscribe) \u2192 observe \u2192 verified/unknown/rejected",
|
|
2095
|
+
verificationContract: { level: "post-commit-content" },
|
|
2096
|
+
evidenceRequirements: [
|
|
2097
|
+
"effect.prepared",
|
|
2098
|
+
"commit.authorized",
|
|
2099
|
+
"effect.verified | effect.unknown | commit.rejected"
|
|
2100
|
+
],
|
|
2101
|
+
version: 1,
|
|
2102
|
+
status: "active",
|
|
2103
|
+
validateParameters(materialParameters) {
|
|
2104
|
+
const parsed = SubscriptionCreateParametersV1Schema.safeParse(materialParameters);
|
|
2105
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
2106
|
+
},
|
|
2107
|
+
validateBinding() {
|
|
2108
|
+
return { ok: true };
|
|
2109
|
+
},
|
|
2110
|
+
verificationProjection: (params) => subscriptionCreateVerificationProjection(params),
|
|
2111
|
+
envelopeProjection: (params) => subscriptionCreateEnvelopeProjection(params),
|
|
2112
|
+
authorityFacts: (params) => ({
|
|
2113
|
+
valueCents: params.amountCents,
|
|
2114
|
+
merchantId: params.merchantId,
|
|
2115
|
+
recurring: true
|
|
2116
|
+
})
|
|
2117
|
+
};
|
|
2118
|
+
function fileDefinition(input) {
|
|
2119
|
+
return {
|
|
2120
|
+
actionId: input.actionId,
|
|
2121
|
+
schemaVersion: 1,
|
|
2122
|
+
actionFamily: "files",
|
|
2123
|
+
humanLabel: input.humanLabel,
|
|
2124
|
+
description: input.description,
|
|
2125
|
+
parametersKind: input.parametersKind,
|
|
2126
|
+
requiredParameters: input.requiredParameters,
|
|
2127
|
+
materialParameters: input.materialParameters,
|
|
2128
|
+
resourceType: "file:store",
|
|
2129
|
+
providers: ["file"],
|
|
2130
|
+
counterpartyRules: "Paths must be canonical and inside the connection path allowlist; shares additionally require the declared share allowlist.",
|
|
2131
|
+
dataEffects: input.dataEffects,
|
|
2132
|
+
reversibility: input.reversibility,
|
|
2133
|
+
riskClass: input.riskClass,
|
|
2134
|
+
defaultDecisionPosture: "deny",
|
|
2135
|
+
approvalPresentationSchema: ["path", "grantee", "bytes"],
|
|
2136
|
+
capabilityRequirements: { uses: 1 },
|
|
2137
|
+
executionContract: "prepare \u2192 readback \u2192 commit \u2192 observe \u2192 verified/unknown/rejected",
|
|
2138
|
+
verificationContract: { level: "post-commit-content" },
|
|
2139
|
+
evidenceRequirements: [
|
|
2140
|
+
"effect.prepared",
|
|
2141
|
+
"commit.authorized",
|
|
2142
|
+
"effect.verified | effect.unknown | commit.rejected"
|
|
2143
|
+
],
|
|
2144
|
+
version: 1,
|
|
2145
|
+
status: "active",
|
|
2146
|
+
validateParameters: input.parse,
|
|
2147
|
+
validateBinding: input.bind,
|
|
2148
|
+
verificationProjection: input.projection,
|
|
2149
|
+
envelopeProjection: input.projection
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
var fileReadDefinition = fileDefinition({
|
|
2153
|
+
actionId: "file.read",
|
|
2154
|
+
humanLabel: "Read file",
|
|
2155
|
+
description: "Read a file from the connection's file store; content is delivered to the agent in the execution receipt, digest-correlated with the evidence.",
|
|
2156
|
+
parametersKind: "file.read.v1",
|
|
2157
|
+
requiredParameters: ["connectionId", "path"],
|
|
2158
|
+
materialParameters: ["connectionId", "path"],
|
|
2159
|
+
dataEffects: "Returns stored content to the agent (a SEE-equivalent on files); sealed files are rejected before effect.",
|
|
2160
|
+
reversibility: "reversible",
|
|
2161
|
+
riskClass: "R2",
|
|
2162
|
+
parse: (materialParameters) => {
|
|
2163
|
+
const parsed = FileReadParametersV1Schema.safeParse(materialParameters);
|
|
2164
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
2165
|
+
},
|
|
2166
|
+
bind: ({ params, connection }) => filePathWithinAllowlist(params.path, connection.pathAllowlist ?? []) ? { ok: true } : { ok: false, reasonCode: "PATH_OUTSIDE_ALLOWLIST" },
|
|
2167
|
+
projection: (params) => fileReadVerificationProjection(params)
|
|
2168
|
+
});
|
|
2169
|
+
var fileWriteDefinition = fileDefinition({
|
|
2170
|
+
actionId: "file.write",
|
|
2171
|
+
humanLabel: "Write file",
|
|
2172
|
+
description: "Write agent-supplied content to a path inside the connection path allowlist; the written bytes are verified post-commit.",
|
|
2173
|
+
parametersKind: "file.write.v1",
|
|
2174
|
+
requiredParameters: ["connectionId", "path", "content"],
|
|
2175
|
+
materialParameters: ["connectionId", "path", "content"],
|
|
2176
|
+
dataEffects: "Creates or replaces a stored file.",
|
|
2177
|
+
reversibility: "limited",
|
|
2178
|
+
riskClass: "R2",
|
|
2179
|
+
parse: (materialParameters) => {
|
|
2180
|
+
const parsed = FileWriteParametersV1Schema.safeParse(materialParameters);
|
|
2181
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
2182
|
+
},
|
|
2183
|
+
bind: ({ params, connection }) => filePathWithinAllowlist(params.path, connection.pathAllowlist ?? []) ? { ok: true } : { ok: false, reasonCode: "PATH_OUTSIDE_ALLOWLIST" },
|
|
2184
|
+
projection: (params) => fileWriteVerificationProjection(params)
|
|
2185
|
+
});
|
|
2186
|
+
var fileShareDefinition = fileDefinition({
|
|
2187
|
+
actionId: "file.share",
|
|
2188
|
+
humanLabel: "Share file",
|
|
2189
|
+
description: "Create a share grant for a file inside the connection's declared share allowlist; sealed (protected-materialized) files cannot be shared.",
|
|
2190
|
+
parametersKind: "file.share.v1",
|
|
2191
|
+
requiredParameters: ["connectionId", "path", "grantee"],
|
|
2192
|
+
materialParameters: ["connectionId", "path", "grantee", "expiresAtEpochMs"],
|
|
2193
|
+
dataEffects: "Grants the named grantee read access to the file (a DISCLOSE-equivalent on files); shares outside the declared scope deny before effect.",
|
|
2194
|
+
reversibility: "limited",
|
|
2195
|
+
riskClass: "R3",
|
|
2196
|
+
parse: (materialParameters) => {
|
|
2197
|
+
const parsed = FileShareParametersV1Schema.safeParse(materialParameters);
|
|
2198
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
2199
|
+
},
|
|
2200
|
+
bind: ({ params, connection }) => filePathWithinAllowlist(params.path, connection.shareAllowlist ?? []) ? { ok: true } : { ok: false, reasonCode: "SHARE_OUTSIDE_ALLOWLIST" },
|
|
2201
|
+
projection: (params) => fileShareVerificationProjection(params)
|
|
2202
|
+
});
|
|
2203
|
+
var fileDeleteDefinition = fileDefinition({
|
|
2204
|
+
actionId: "file.delete",
|
|
2205
|
+
humanLabel: "Delete file",
|
|
2206
|
+
description: "Remove a file inside the connection path allowlist; absence is verified post-commit.",
|
|
2207
|
+
parametersKind: "file.delete.v1",
|
|
2208
|
+
requiredParameters: ["connectionId", "path"],
|
|
2209
|
+
materialParameters: ["connectionId", "path"],
|
|
2210
|
+
dataEffects: "Irreversibly removes the stored file.",
|
|
2211
|
+
reversibility: "destructive",
|
|
2212
|
+
riskClass: "R3",
|
|
2213
|
+
parse: (materialParameters) => {
|
|
2214
|
+
const parsed = FileDeleteParametersV1Schema.safeParse(materialParameters);
|
|
2215
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
2216
|
+
},
|
|
2217
|
+
bind: ({ params, connection }) => filePathWithinAllowlist(params.path, connection.pathAllowlist ?? []) ? { ok: true } : { ok: false, reasonCode: "PATH_OUTSIDE_ALLOWLIST" },
|
|
2218
|
+
projection: (params) => fileDeleteVerificationProjection(params)
|
|
2219
|
+
});
|
|
2220
|
+
function protectedDefinition(input) {
|
|
2221
|
+
return {
|
|
2222
|
+
actionId: input.actionId,
|
|
2223
|
+
schemaVersion: 1,
|
|
2224
|
+
actionFamily: "protected-data",
|
|
2225
|
+
humanLabel: input.humanLabel,
|
|
2226
|
+
description: input.description,
|
|
2227
|
+
parametersKind: input.parametersKind,
|
|
2228
|
+
requiredParameters: input.requiredParameters,
|
|
2229
|
+
materialParameters: input.materialParameters,
|
|
2230
|
+
resourceType: "protected:data",
|
|
2231
|
+
providers: ["protected_data"],
|
|
2232
|
+
counterpartyRules: "USE/SEE/DISCLOSE are independent policy operations; the connection's declared catalog binds each one at field level (K12).",
|
|
2233
|
+
dataEffects: input.dataEffects,
|
|
2234
|
+
reversibility: input.reversibility,
|
|
2235
|
+
riskClass: input.riskClass,
|
|
2236
|
+
defaultDecisionPosture: "deny",
|
|
2237
|
+
approvalPresentationSchema: [
|
|
2238
|
+
"fieldRef",
|
|
2239
|
+
"counterparty",
|
|
2240
|
+
"sink",
|
|
2241
|
+
"maskedPreview"
|
|
2242
|
+
],
|
|
2243
|
+
capabilityRequirements: { uses: 1 },
|
|
2244
|
+
executionContract: "prepare \u2192 readback \u2192 commit \u2192 observe \u2192 verified/unknown/rejected; the adapter resolves and injects the protected value at execution.",
|
|
2245
|
+
verificationContract: { level: "post-commit-content" },
|
|
2246
|
+
evidenceRequirements: [
|
|
2247
|
+
"effect.prepared",
|
|
2248
|
+
"commit.authorized",
|
|
2249
|
+
"effect.verified | effect.unknown | commit.rejected"
|
|
2250
|
+
],
|
|
2251
|
+
version: 1,
|
|
2252
|
+
status: "active",
|
|
2253
|
+
validateParameters: input.parse,
|
|
2254
|
+
validateBinding: ({ params, connection }) => {
|
|
2255
|
+
const field = protectedCatalogField(
|
|
2256
|
+
connection.protectedCatalog,
|
|
2257
|
+
params.fieldRef
|
|
2258
|
+
);
|
|
2259
|
+
if (!field) {
|
|
2260
|
+
return { ok: false, reasonCode: "FIELD_NOT_IN_CATALOG" };
|
|
2261
|
+
}
|
|
2262
|
+
return input.bind({ params, field });
|
|
2263
|
+
},
|
|
2264
|
+
verificationProjection: input.projection,
|
|
2265
|
+
envelopeProjection: input.projection
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
var protectedUseDefinition = protectedDefinition({
|
|
2269
|
+
actionId: "protected_data.use",
|
|
2270
|
+
humanLabel: "Use protected data",
|
|
2271
|
+
description: "Inject a catalog-declared protected field into a declared file sink at execution time; the agent never receives the plaintext.",
|
|
2272
|
+
parametersKind: "protected-data.use.v1",
|
|
2273
|
+
requiredParameters: ["connectionId", "fieldRef", "sink"],
|
|
2274
|
+
materialParameters: ["connectionId", "fieldRef", "sink"],
|
|
2275
|
+
dataEffects: "Materializes the protected value inside the declared sink file (sealed); the agent receives only path + digest evidence.",
|
|
2276
|
+
reversibility: "limited",
|
|
2277
|
+
riskClass: "R3",
|
|
2278
|
+
parse: (materialParameters) => {
|
|
2279
|
+
const parsed = ProtectedUseParametersV1Schema.safeParse(materialParameters);
|
|
2280
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
2281
|
+
},
|
|
2282
|
+
bind: ({ params, field }) => field?.allowUse !== true ? { ok: false, reasonCode: "USE_NOT_ALLOWED" } : protectedSinkWithinUseSinks(params.sink, field.useSinks) ? { ok: true } : { ok: false, reasonCode: "SINK_OUTSIDE_DECLARED" },
|
|
2283
|
+
projection: (params) => protectedUseVerificationProjection(params)
|
|
2284
|
+
});
|
|
2285
|
+
var protectedViewDefinition = protectedDefinition({
|
|
2286
|
+
actionId: "protected_data.view",
|
|
2287
|
+
humanLabel: "View protected data",
|
|
2288
|
+
description: "Deliver a catalog-declared protected field to the agent once, via the execution receipt's delivered channel; requires allowView.",
|
|
2289
|
+
parametersKind: "protected-data.view.v1",
|
|
2290
|
+
requiredParameters: ["connectionId", "fieldRef"],
|
|
2291
|
+
materialParameters: ["connectionId", "fieldRef"],
|
|
2292
|
+
dataEffects: "Discloses the protected value to the agent (SEE); not persisted in the ledger.",
|
|
2293
|
+
reversibility: "limited",
|
|
2294
|
+
riskClass: "R3",
|
|
2295
|
+
parse: (materialParameters) => {
|
|
2296
|
+
const parsed = ProtectedViewParametersV1Schema.safeParse(materialParameters);
|
|
2297
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
2298
|
+
},
|
|
2299
|
+
bind: ({ field }) => field?.allowView === true ? { ok: true } : { ok: false, reasonCode: "VIEW_NOT_ALLOWED" },
|
|
2300
|
+
projection: (params) => protectedViewVerificationProjection(params)
|
|
2301
|
+
});
|
|
2302
|
+
var protectedDiscloseDefinition = protectedDefinition({
|
|
2303
|
+
actionId: "protected_data.disclose",
|
|
2304
|
+
humanLabel: "Disclose protected data",
|
|
2305
|
+
description: "Send a catalog-declared protected field to a catalog-declared counterparty; without explicit authority the action denies before execution.",
|
|
2306
|
+
parametersKind: "protected-data.disclose.v1",
|
|
2307
|
+
requiredParameters: ["connectionId", "fieldRef", "counterparty"],
|
|
2308
|
+
materialParameters: ["connectionId", "fieldRef", "counterparty"],
|
|
2309
|
+
dataEffects: "Delivers the protected value to the declared counterparty only (DISCLOSE); the agent never receives it.",
|
|
2310
|
+
reversibility: "difficult",
|
|
2311
|
+
riskClass: "R4",
|
|
2312
|
+
parse: (materialParameters) => {
|
|
2313
|
+
const parsed = ProtectedDiscloseParametersV1Schema.safeParse(materialParameters);
|
|
2314
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
2315
|
+
},
|
|
2316
|
+
bind: ({ params, field }) => field?.discloseTargets.includes(params.counterparty) === true ? { ok: true } : { ok: false, reasonCode: "DISCLOSE_TARGET_NOT_DECLARED" },
|
|
2317
|
+
projection: (params) => protectedDiscloseVerificationProjection(params)
|
|
2318
|
+
});
|
|
2319
|
+
var protectedPersistDefinition = protectedDefinition({
|
|
2320
|
+
actionId: "protected_data.persist",
|
|
2321
|
+
humanLabel: "Persist protected data",
|
|
2322
|
+
description: "Store an agent-supplied value into a catalog-declared protected field; requires allowPersist on the declared field.",
|
|
2323
|
+
parametersKind: "protected-data.persist.v1",
|
|
2324
|
+
requiredParameters: ["connectionId", "fieldRef", "value"],
|
|
2325
|
+
materialParameters: ["connectionId", "fieldRef", "value"],
|
|
2326
|
+
dataEffects: "Writes a new protected value into the declared field slot.",
|
|
2327
|
+
reversibility: "limited",
|
|
2328
|
+
riskClass: "R3",
|
|
2329
|
+
parse: (materialParameters) => {
|
|
2330
|
+
const parsed = ProtectedPersistParametersV1Schema.safeParse(materialParameters);
|
|
2331
|
+
return parsed.success ? { ok: true, params: parsed.data } : { ok: false };
|
|
2332
|
+
},
|
|
2333
|
+
bind: ({ field }) => field?.allowPersist === true ? { ok: true } : { ok: false, reasonCode: "PERSIST_NOT_ALLOWED" },
|
|
2334
|
+
projection: (params) => protectedPersistVerificationProjection(params)
|
|
2335
|
+
});
|
|
2336
|
+
var universalActionRegistry = createActionRegistry([
|
|
2337
|
+
emailSendDefinition,
|
|
2338
|
+
apiRequestDefinition,
|
|
2339
|
+
toolInvokeDefinition,
|
|
2340
|
+
mcpInstallDefinition,
|
|
2341
|
+
mcpEnableDefinition,
|
|
2342
|
+
calendarCreateDefinition,
|
|
2343
|
+
calendarUpdateDefinition,
|
|
2344
|
+
calendarCancelDefinition,
|
|
2345
|
+
calendarInviteDefinition,
|
|
2346
|
+
commercePurchaseDefinition,
|
|
2347
|
+
commerceRefundDefinition,
|
|
2348
|
+
commerceReturnDefinition,
|
|
2349
|
+
subscriptionCreateDefinition,
|
|
2350
|
+
fileReadDefinition,
|
|
2351
|
+
fileWriteDefinition,
|
|
2352
|
+
fileShareDefinition,
|
|
2353
|
+
fileDeleteDefinition,
|
|
2354
|
+
protectedUseDefinition,
|
|
2355
|
+
protectedViewDefinition,
|
|
2356
|
+
protectedDiscloseDefinition,
|
|
2357
|
+
protectedPersistDefinition
|
|
2358
|
+
]);
|
|
2359
|
+
|
|
2360
|
+
// src/contracts/approval.ts
|
|
2361
|
+
import { z as z12 } from "zod";
|
|
2362
|
+
|
|
2363
|
+
// src/contracts/evidence.ts
|
|
2364
|
+
import { z as z11 } from "zod";
|
|
2365
|
+
var Sha256DigestSchema = z11.string().regex(/^[a-f0-9]{64}$/);
|
|
2366
|
+
var AttemptReferenceSchema = z11.string().trim().min(1).max(256).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
2367
|
+
var FailureCodeSchema = z11.string().trim().min(1).max(128).regex(/^[A-Z][A-Z0-9_]*$/);
|
|
2368
|
+
var payload = (eventType, shape) => z11.object({
|
|
2369
|
+
eventType: z11.literal(eventType),
|
|
2370
|
+
payload: z11.object(shape).strict()
|
|
2371
|
+
}).strict();
|
|
2372
|
+
var EvidenceEventSchema = z11.discriminatedUnion("eventType", [
|
|
2373
|
+
payload("AUTHENTICATION_RECORDED", {
|
|
2374
|
+
interface: AgentInterfaceSchema,
|
|
2375
|
+
outcome: z11.enum(["authenticated", "rejected"]),
|
|
2376
|
+
credentialReference: z11.string().trim().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/)
|
|
2377
|
+
}),
|
|
2378
|
+
payload("ADAPTER_AUTHENTICATION_RECORDED", {
|
|
2379
|
+
adapterId: AdapterIdSchema,
|
|
2380
|
+
outcome: z11.literal("authenticated"),
|
|
2381
|
+
credentialReference: AttemptReferenceSchema
|
|
2382
|
+
}),
|
|
2383
|
+
payload("REQUEST_RECEIVED", { actionDigest: Sha256DigestSchema }),
|
|
2384
|
+
payload("ACTION_NORMALIZED", { actionDigest: Sha256DigestSchema }),
|
|
2385
|
+
z11.object({
|
|
2386
|
+
eventType: z11.literal("DECISION_RECORDED"),
|
|
2387
|
+
payload: z11.discriminatedUnion("decision", [
|
|
2388
|
+
z11.object({
|
|
2389
|
+
actionDigest: Sha256DigestSchema,
|
|
2390
|
+
actionDecisionDigest: Sha256DigestSchema,
|
|
2391
|
+
decision: z11.literal("ALLOW"),
|
|
2392
|
+
reasonCode: z11.literal("AUTHORIZED")
|
|
2393
|
+
}).strict(),
|
|
2394
|
+
z11.object({
|
|
2395
|
+
actionDigest: Sha256DigestSchema,
|
|
2396
|
+
actionDecisionDigest: Sha256DigestSchema,
|
|
2397
|
+
decision: z11.literal("ASK"),
|
|
2398
|
+
reasonCode: z11.literal("APPROVAL_REQUIRED")
|
|
2399
|
+
}).strict(),
|
|
2400
|
+
z11.object({
|
|
2401
|
+
actionDigest: Sha256DigestSchema,
|
|
2402
|
+
actionDecisionDigest: Sha256DigestSchema,
|
|
2403
|
+
decision: z11.literal("DENY"),
|
|
2404
|
+
reasonCode: DenyReasonCodeSchema
|
|
2405
|
+
}).strict()
|
|
2406
|
+
])
|
|
2407
|
+
}).strict(),
|
|
2408
|
+
payload("APPROVAL_STATE_REFERENCED", {
|
|
2409
|
+
approvalId: ApprovalIdSchema,
|
|
2410
|
+
/** Approval record version this state evidence describes; versions are contiguous per chain. */
|
|
2411
|
+
version: z11.number().int().positive().safe(),
|
|
2412
|
+
state: z11.enum([
|
|
2413
|
+
"pending",
|
|
2414
|
+
"approved",
|
|
2415
|
+
"denied",
|
|
2416
|
+
"expired",
|
|
2417
|
+
"revoked",
|
|
2418
|
+
"superseded",
|
|
2419
|
+
"consumed"
|
|
2420
|
+
]),
|
|
2421
|
+
approvalDigest: Sha256DigestSchema
|
|
2422
|
+
}),
|
|
2423
|
+
payload("APPROVAL_PRESENTED", {
|
|
2424
|
+
approvalId: ApprovalIdSchema,
|
|
2425
|
+
presentationDigest: Sha256DigestSchema,
|
|
2426
|
+
viewerReference: AttemptReferenceSchema
|
|
2427
|
+
}),
|
|
2428
|
+
payload("APPROVAL_DECISION_RECORDED", {
|
|
2429
|
+
approvalId: ApprovalIdSchema,
|
|
2430
|
+
/** Approval record version the human decision produced. */
|
|
2431
|
+
version: z11.number().int().positive().safe(),
|
|
2432
|
+
decision: z11.enum(["approve", "deny", "revoke"]),
|
|
2433
|
+
actorPrincipalId: PrincipalIdSchema,
|
|
2434
|
+
actorReference: AttemptReferenceSchema,
|
|
2435
|
+
presentationDigest: Sha256DigestSchema
|
|
2436
|
+
}),
|
|
2437
|
+
payload("APPROVAL_REJECTED", {
|
|
2438
|
+
approvalId: ApprovalIdSchema,
|
|
2439
|
+
reasonCode: FailureCodeSchema
|
|
2440
|
+
}),
|
|
2441
|
+
payload("CAPABILITY_STATE_REFERENCED", {
|
|
2442
|
+
actionDigest: Sha256DigestSchema,
|
|
2443
|
+
capabilityId: CapabilityIdSchema,
|
|
2444
|
+
state: z11.enum(["issued", "used", "expired", "revoked"]),
|
|
2445
|
+
capabilityDigest: Sha256DigestSchema
|
|
2446
|
+
}),
|
|
2447
|
+
payload("CAPABILITY_REJECTED", {
|
|
2448
|
+
actionDigest: Sha256DigestSchema,
|
|
2449
|
+
reasonCode: FailureCodeSchema
|
|
2450
|
+
}),
|
|
2451
|
+
payload("EXECUTION_ATTEMPTED", {
|
|
2452
|
+
actionDigest: Sha256DigestSchema,
|
|
2453
|
+
adapterId: AdapterIdSchema,
|
|
2454
|
+
attemptReference: AttemptReferenceSchema,
|
|
2455
|
+
idempotencyDigest: Sha256DigestSchema
|
|
2456
|
+
}),
|
|
2457
|
+
payload("EXECUTION_ABORTED", {
|
|
2458
|
+
actionDigest: Sha256DigestSchema,
|
|
2459
|
+
adapterId: AdapterIdSchema,
|
|
2460
|
+
attemptReference: AttemptReferenceSchema,
|
|
2461
|
+
reasonCode: FailureCodeSchema
|
|
2462
|
+
}),
|
|
2463
|
+
payload("EFFECT_PREPARED", {
|
|
2464
|
+
actionDigest: Sha256DigestSchema,
|
|
2465
|
+
adapterId: AdapterIdSchema,
|
|
2466
|
+
attemptReference: AttemptReferenceSchema,
|
|
2467
|
+
preparedDigest: Sha256DigestSchema
|
|
2468
|
+
}),
|
|
2469
|
+
payload("EFFECT_COMMIT_AUTHORIZED", {
|
|
2470
|
+
actionDigest: Sha256DigestSchema,
|
|
2471
|
+
adapterId: AdapterIdSchema,
|
|
2472
|
+
attemptReference: AttemptReferenceSchema,
|
|
2473
|
+
actionDecisionDigest: Sha256DigestSchema
|
|
2474
|
+
}),
|
|
2475
|
+
payload("EFFECT_OUTCOME_UNKNOWN", {
|
|
2476
|
+
actionDigest: Sha256DigestSchema,
|
|
2477
|
+
adapterId: AdapterIdSchema,
|
|
2478
|
+
attemptReference: AttemptReferenceSchema,
|
|
2479
|
+
reasonCode: FailureCodeSchema
|
|
2480
|
+
}),
|
|
2481
|
+
payload("EFFECT_UNRESOLVED", {
|
|
2482
|
+
actionDigest: Sha256DigestSchema,
|
|
2483
|
+
adapterId: AdapterIdSchema,
|
|
2484
|
+
attemptReference: AttemptReferenceSchema
|
|
2485
|
+
}),
|
|
2486
|
+
payload("EFFECT_VERIFIED", {
|
|
2487
|
+
actionDigest: Sha256DigestSchema,
|
|
2488
|
+
adapterId: AdapterIdSchema,
|
|
2489
|
+
attemptReference: AttemptReferenceSchema,
|
|
2490
|
+
effectDigest: Sha256DigestSchema
|
|
2491
|
+
}),
|
|
2492
|
+
payload("EXECUTION_FAILED", {
|
|
2493
|
+
actionDigest: Sha256DigestSchema,
|
|
2494
|
+
adapterId: AdapterIdSchema,
|
|
2495
|
+
attemptReference: AttemptReferenceSchema,
|
|
2496
|
+
failureCode: FailureCodeSchema,
|
|
2497
|
+
failureDigest: Sha256DigestSchema.optional()
|
|
2498
|
+
}),
|
|
2499
|
+
payload("AUTHORITY_REVOKED", {
|
|
2500
|
+
grantId: GrantIdSchema,
|
|
2501
|
+
grantVersion: z11.number().int().positive(),
|
|
2502
|
+
revocationDigest: Sha256DigestSchema
|
|
2503
|
+
})
|
|
2504
|
+
]);
|
|
2505
|
+
var EvidenceContextShape = {
|
|
2506
|
+
schemaVersion: z11.literal(1),
|
|
2507
|
+
id: EvidenceIdSchema,
|
|
2508
|
+
tenantId: TenantIdSchema,
|
|
2509
|
+
principalId: PrincipalIdSchema,
|
|
2510
|
+
agentId: AgentIdSchema,
|
|
2511
|
+
requestId: RequestIdSchema,
|
|
2512
|
+
correlationId: CorrelationIdSchema,
|
|
2513
|
+
eventTimeEpochMs: EpochMillisecondsSchema
|
|
2514
|
+
};
|
|
2515
|
+
var EvidenceChainContextSchema = z11.object({
|
|
2516
|
+
tenantId: TenantIdSchema,
|
|
2517
|
+
principalId: PrincipalIdSchema,
|
|
2518
|
+
agentId: AgentIdSchema,
|
|
2519
|
+
requestId: RequestIdSchema,
|
|
2520
|
+
correlationId: CorrelationIdSchema
|
|
2521
|
+
}).strict();
|
|
2522
|
+
var EvidenceEventDraftSchema = z11.object({ ...EvidenceContextShape, event: EvidenceEventSchema }).strict();
|
|
2523
|
+
var EvidenceRecordCoreSchema = z11.object({
|
|
2524
|
+
...EvidenceContextShape,
|
|
2525
|
+
event: EvidenceEventSchema,
|
|
2526
|
+
sequence: z11.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
2527
|
+
previousRecordHash: Sha256DigestSchema.nullable(),
|
|
2528
|
+
payloadDigest: Sha256DigestSchema
|
|
2529
|
+
}).strict();
|
|
2530
|
+
var EvidenceRecordSchema = z11.object({
|
|
2531
|
+
...EvidenceRecordCoreSchema.shape,
|
|
2532
|
+
recordHash: Sha256DigestSchema
|
|
2533
|
+
}).strict();
|
|
2534
|
+
var HumanSessionRejectionSchema = z11.enum([
|
|
2535
|
+
"UNAUTHENTICATED",
|
|
2536
|
+
"NOT_HUMAN",
|
|
2537
|
+
"UNSAFE_SESSION_REFERENCE"
|
|
2538
|
+
]);
|
|
2539
|
+
var PreIdentitySecurityEventSchema = z11.discriminatedUnion(
|
|
2540
|
+
"eventType",
|
|
2541
|
+
[
|
|
2542
|
+
z11.object({
|
|
2543
|
+
eventType: z11.literal("AUTHENTICATION_REJECTED"),
|
|
2544
|
+
payload: z11.object({
|
|
2545
|
+
interface: z11.enum(["mcp", "rest"]),
|
|
2546
|
+
reasonCode: z11.literal("UNAUTHENTICATED")
|
|
2547
|
+
}).strict()
|
|
2548
|
+
}).strict(),
|
|
2549
|
+
z11.object({
|
|
2550
|
+
eventType: z11.literal("HUMAN_AUTHENTICATION_REJECTED"),
|
|
2551
|
+
payload: z11.object({
|
|
2552
|
+
surface: z11.literal("approval-service"),
|
|
2553
|
+
reasonCode: HumanSessionRejectionSchema
|
|
2554
|
+
}).strict()
|
|
2555
|
+
}).strict(),
|
|
2556
|
+
z11.object({
|
|
2557
|
+
eventType: z11.literal("ADAPTER_AUTHENTICATION_REJECTED"),
|
|
2558
|
+
payload: z11.object({
|
|
2559
|
+
surface: z11.literal("redemption"),
|
|
2560
|
+
reasonCode: z11.literal("UNAUTHENTICATED")
|
|
2561
|
+
}).strict()
|
|
2562
|
+
}).strict()
|
|
2563
|
+
]
|
|
2564
|
+
);
|
|
2565
|
+
var PreIdentitySecurityRecordCoreSchema = z11.object({
|
|
2566
|
+
schemaVersion: z11.literal(1),
|
|
2567
|
+
id: EvidenceIdSchema,
|
|
2568
|
+
eventTimeEpochMs: EpochMillisecondsSchema,
|
|
2569
|
+
event: PreIdentitySecurityEventSchema
|
|
2570
|
+
}).strict();
|
|
2571
|
+
var PreIdentitySecurityRecordSchema = PreIdentitySecurityRecordCoreSchema.extend({
|
|
2572
|
+
recordHash: Sha256DigestSchema
|
|
2573
|
+
}).strict();
|
|
2574
|
+
|
|
2575
|
+
// src/contracts/approval.ts
|
|
2576
|
+
var GrantVersionReferenceSchema = z12.object({ id: GrantIdSchema, version: z12.number().int().positive() }).strict();
|
|
2577
|
+
var AgentStatementSchema = z12.object({
|
|
2578
|
+
source: z12.literal("agent"),
|
|
2579
|
+
authoritative: z12.literal(false),
|
|
2580
|
+
text: z12.string().trim().min(1).max(1024)
|
|
2581
|
+
}).strict();
|
|
2582
|
+
var ApprovalReasonSchema = z12.object({
|
|
2583
|
+
source: z12.literal("guardian"),
|
|
2584
|
+
code: z12.literal("APPROVAL_REQUIRED"),
|
|
2585
|
+
authority: AuthorityReferenceSchema
|
|
2586
|
+
}).strict();
|
|
2587
|
+
var ApprovalPresentationSchema = z12.object({
|
|
2588
|
+
schemaVersion: z12.literal(1),
|
|
2589
|
+
approvalId: ApprovalIdSchema,
|
|
2590
|
+
tenantId: TenantIdSchema,
|
|
2591
|
+
principalId: PrincipalIdSchema,
|
|
2592
|
+
agentId: AgentIdSchema,
|
|
2593
|
+
requestId: RequestIdSchema,
|
|
2594
|
+
correlationId: CorrelationIdSchema,
|
|
2595
|
+
action: ActionTypeSchema,
|
|
2596
|
+
resource: ResourceRefSchema,
|
|
2597
|
+
adapterId: AdapterIdSchema,
|
|
2598
|
+
interface: AgentInterfaceSchema,
|
|
2599
|
+
initiatingContext: z12.enum(["user", "scheduled", "agent", "external-event"]),
|
|
2600
|
+
materialParameters: MaterialParametersSchema,
|
|
2601
|
+
valueCents: z12.number().int().nonnegative().safe().optional(),
|
|
2602
|
+
uses: z12.number().int().positive().safe(),
|
|
2603
|
+
agentStatement: AgentStatementSchema,
|
|
2604
|
+
reason: ApprovalReasonSchema,
|
|
2605
|
+
actionDigest: Sha256DigestSchema,
|
|
2606
|
+
expiresAtEpochMs: EpochMillisecondsSchema
|
|
2607
|
+
}).strict();
|
|
2608
|
+
var ApprovalBindingSchema = z12.object({
|
|
2609
|
+
schemaVersion: z12.literal(1),
|
|
2610
|
+
approvalId: ApprovalIdSchema,
|
|
2611
|
+
tenantId: TenantIdSchema,
|
|
2612
|
+
principalId: PrincipalIdSchema,
|
|
2613
|
+
agentId: AgentIdSchema,
|
|
2614
|
+
requestId: RequestIdSchema,
|
|
2615
|
+
correlationId: CorrelationIdSchema,
|
|
2616
|
+
actionDigest: Sha256DigestSchema,
|
|
2617
|
+
/**
|
|
2618
|
+
* Evidence link to the exact ASK decision record that created the approval.
|
|
2619
|
+
* It covers evaluation time, so it is never re-derived; askDecisionDigest is
|
|
2620
|
+
* the decision binding that issuance and redemption verify.
|
|
2621
|
+
*/
|
|
2622
|
+
actionDecisionRecordDigest: Sha256DigestSchema,
|
|
2623
|
+
/**
|
|
2624
|
+
* Digest of the time-independent projection of that ASK decision: the exact
|
|
2625
|
+
* canonical action, the ASK outcome, and the principal, agent, and complete
|
|
2626
|
+
* grant chain it was evaluated under. Every later use recomputes it from the
|
|
2627
|
+
* fresh decision or the current trusted authority and requires it to match.
|
|
2628
|
+
*/
|
|
2629
|
+
askDecisionDigest: Sha256DigestSchema,
|
|
2630
|
+
adapterId: AdapterIdSchema,
|
|
2631
|
+
policy: PolicyReferenceSchema,
|
|
2632
|
+
evaluatedGrants: z12.array(GrantVersionReferenceSchema).min(1).max(32),
|
|
2633
|
+
presentationDigest: Sha256DigestSchema,
|
|
2634
|
+
requestedAtEpochMs: EpochMillisecondsSchema,
|
|
2635
|
+
expiresAtEpochMs: EpochMillisecondsSchema
|
|
2636
|
+
}).strict().superRefine((binding, context) => {
|
|
2637
|
+
if (binding.requestedAtEpochMs >= binding.expiresAtEpochMs) {
|
|
2638
|
+
context.addIssue({
|
|
2639
|
+
code: "custom",
|
|
2640
|
+
message: "approval expiry must follow request time",
|
|
2641
|
+
path: ["expiresAtEpochMs"]
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
});
|
|
2645
|
+
var ApprovalStateSchema = z12.enum([
|
|
2646
|
+
"pending",
|
|
2647
|
+
"approved",
|
|
2648
|
+
"denied",
|
|
2649
|
+
"expired",
|
|
2650
|
+
"revoked",
|
|
2651
|
+
"superseded",
|
|
2652
|
+
"consumed"
|
|
2653
|
+
]);
|
|
2654
|
+
var ApprovalRecordSchema = z12.object({
|
|
2655
|
+
schemaVersion: z12.literal(1),
|
|
2656
|
+
binding: ApprovalBindingSchema,
|
|
2657
|
+
state: ApprovalStateSchema,
|
|
2658
|
+
version: z12.number().int().positive(),
|
|
2659
|
+
updatedAtEpochMs: EpochMillisecondsSchema,
|
|
2660
|
+
decidedByPrincipalId: PrincipalIdSchema.nullable()
|
|
2661
|
+
}).strict().superRefine((record, context) => {
|
|
2662
|
+
const humanDecision = ["approved", "denied", "revoked"].includes(
|
|
2663
|
+
record.state
|
|
2664
|
+
);
|
|
2665
|
+
if (humanDecision !== (record.decidedByPrincipalId !== null)) {
|
|
2666
|
+
context.addIssue({
|
|
2667
|
+
code: "custom",
|
|
2668
|
+
message: "human decisions require the deciding principal",
|
|
2669
|
+
path: ["decidedByPrincipalId"]
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
if (record.updatedAtEpochMs < record.binding.requestedAtEpochMs || record.updatedAtEpochMs > record.binding.expiresAtEpochMs) {
|
|
2673
|
+
context.addIssue({
|
|
2674
|
+
code: "custom",
|
|
2675
|
+
message: "approval update time must stay inside its validity window",
|
|
2676
|
+
path: ["updatedAtEpochMs"]
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2679
|
+
});
|
|
2680
|
+
var ApprovalDecisionRequestSchema = z12.object({
|
|
2681
|
+
schemaVersion: z12.literal(1),
|
|
2682
|
+
decision: z12.enum(["approve", "deny"]),
|
|
2683
|
+
expectedVersion: z12.number().int().positive().safe(),
|
|
2684
|
+
presentationDigest: Sha256DigestSchema
|
|
2685
|
+
}).strict();
|
|
2686
|
+
var ApprovalRevocationRequestSchema = z12.object({
|
|
2687
|
+
schemaVersion: z12.literal(1),
|
|
2688
|
+
expectedVersion: z12.number().int().positive().safe()
|
|
2689
|
+
}).strict();
|
|
2690
|
+
var ApprovalChallengeSchema = z12.object({
|
|
2691
|
+
approvalId: ApprovalIdSchema,
|
|
2692
|
+
version: z12.number().int().positive().safe(),
|
|
2693
|
+
presentationDigest: Sha256DigestSchema,
|
|
2694
|
+
expiresAtEpochMs: EpochMillisecondsSchema
|
|
2695
|
+
}).strict();
|
|
2696
|
+
var ApprovalReceiptSchema = z12.object({
|
|
2697
|
+
schemaVersion: z12.literal(1),
|
|
2698
|
+
approvalId: ApprovalIdSchema,
|
|
2699
|
+
state: ApprovalStateSchema,
|
|
2700
|
+
version: z12.number().int().positive().safe(),
|
|
2701
|
+
updatedAtEpochMs: EpochMillisecondsSchema,
|
|
2702
|
+
approvalDigest: Sha256DigestSchema
|
|
2703
|
+
}).strict();
|
|
2704
|
+
|
|
2705
|
+
// src/contracts/authority-state.ts
|
|
2706
|
+
import { z as z13 } from "zod";
|
|
2707
|
+
var AuthorityStateSnapshotSchema = z13.object({
|
|
2708
|
+
principal: PrincipalSchema,
|
|
2709
|
+
agent: AgentIdentitySchema,
|
|
2710
|
+
policy: PolicyReferenceSchema,
|
|
2711
|
+
authorityChain: z13.array(AuthorityGrantSchema).max(32),
|
|
2712
|
+
revocations: z13.array(RevocationSchema).max(256),
|
|
2713
|
+
allowedAdapterIds: z13.array(AdapterIdSchema).max(64),
|
|
2714
|
+
budgets: z13.array(BudgetStateSchema).max(64).default([])
|
|
2715
|
+
}).strict();
|
|
2716
|
+
|
|
2717
|
+
// src/contracts/capability.ts
|
|
2718
|
+
import { z as z14 } from "zod";
|
|
2719
|
+
var CapabilityKeyIdSchema = z14.string().regex(/^key_[A-Za-z0-9][A-Za-z0-9_-]{7,119}$/).brand();
|
|
2720
|
+
var CapabilityClaimsSchema = z14.object({
|
|
2721
|
+
schemaVersion: z14.literal(1),
|
|
2722
|
+
capabilityId: CapabilityIdSchema,
|
|
2723
|
+
tenantId: TenantIdSchema,
|
|
2724
|
+
principalId: PrincipalIdSchema,
|
|
2725
|
+
agentId: AgentIdSchema,
|
|
2726
|
+
requestId: RequestIdSchema,
|
|
2727
|
+
actionDigest: z14.string().regex(/^[a-f0-9]{64}$/),
|
|
2728
|
+
actionNonce: NonceSchema,
|
|
2729
|
+
audience: AdapterIdSchema,
|
|
2730
|
+
policy: PolicyReferenceSchema,
|
|
2731
|
+
evaluatedGrants: z14.array(
|
|
2732
|
+
z14.object({
|
|
2733
|
+
id: GrantIdSchema,
|
|
2734
|
+
version: z14.number().int().positive()
|
|
2735
|
+
}).strict()
|
|
2736
|
+
).min(1).max(32),
|
|
2737
|
+
issuedAtEpochMs: z14.number().int().nonnegative(),
|
|
2738
|
+
expiresAtEpochMs: z14.number().int().positive(),
|
|
2739
|
+
maxUses: z14.literal(1),
|
|
2740
|
+
keyId: CapabilityKeyIdSchema,
|
|
2741
|
+
algorithm: z14.literal("HS256")
|
|
2742
|
+
}).strict().superRefine((value, context) => {
|
|
2743
|
+
if (value.issuedAtEpochMs >= value.expiresAtEpochMs) {
|
|
2744
|
+
context.addIssue({
|
|
2745
|
+
code: "custom",
|
|
2746
|
+
path: ["expiresAtEpochMs"],
|
|
2747
|
+
message: "capability expiry must be later than issuance"
|
|
2748
|
+
});
|
|
2749
|
+
}
|
|
2750
|
+
if (new Set(value.evaluatedGrants.map(({ id }) => id)).size !== value.evaluatedGrants.length) {
|
|
2751
|
+
context.addIssue({
|
|
2752
|
+
code: "custom",
|
|
2753
|
+
path: ["evaluatedGrants"],
|
|
2754
|
+
message: "evaluated grant identifiers must be unique"
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
});
|
|
2758
|
+
|
|
2759
|
+
// src/contracts/execution.ts
|
|
2760
|
+
import { z as z15 } from "zod";
|
|
2761
|
+
var ProviderSchema = z15.enum([
|
|
2762
|
+
"gmail",
|
|
2763
|
+
"graph",
|
|
2764
|
+
"api",
|
|
2765
|
+
"tool",
|
|
2766
|
+
"gcalendar",
|
|
2767
|
+
"payment-sandbox",
|
|
2768
|
+
"file",
|
|
2769
|
+
"protected_data"
|
|
2770
|
+
]);
|
|
2771
|
+
var ConnectionProfileSchema = z15.enum([
|
|
2772
|
+
"gmail-split-compose-metadata",
|
|
2773
|
+
"gmail-compose-readonly",
|
|
2774
|
+
"graph-readwrite-send",
|
|
2775
|
+
"api-bearer-request",
|
|
2776
|
+
"tool-mcp-server",
|
|
2777
|
+
"gcalendar-events-v1",
|
|
2778
|
+
"payment-sandbox-charge",
|
|
2779
|
+
"file-local-store",
|
|
2780
|
+
"protected-data-store"
|
|
2781
|
+
]);
|
|
2782
|
+
var VerificationLevelSchema = z15.enum([
|
|
2783
|
+
"post-commit-content",
|
|
2784
|
+
"pre-commit-content+post-commit-envelope"
|
|
2785
|
+
]);
|
|
2786
|
+
var LeasePurposeSchema = z15.enum([
|
|
2787
|
+
"account",
|
|
2788
|
+
"prepare",
|
|
2789
|
+
"readback",
|
|
2790
|
+
"commit",
|
|
2791
|
+
"observe",
|
|
2792
|
+
"cleanup"
|
|
2793
|
+
]);
|
|
2794
|
+
var ExecutionStateSchema = z15.enum([
|
|
2795
|
+
"reserved",
|
|
2796
|
+
"redeemed",
|
|
2797
|
+
"preparing",
|
|
2798
|
+
"prepared",
|
|
2799
|
+
"committing",
|
|
2800
|
+
"unknown",
|
|
2801
|
+
"unresolved",
|
|
2802
|
+
"verified",
|
|
2803
|
+
"mismatch",
|
|
2804
|
+
"commit-rejected",
|
|
2805
|
+
"aborted"
|
|
2806
|
+
]);
|
|
2807
|
+
var ExecutionFailureCodeSchema = z15.enum([
|
|
2808
|
+
"SENDER_ACCOUNT_MISMATCH",
|
|
2809
|
+
"CREDENTIAL_UNAVAILABLE",
|
|
2810
|
+
"PREPARE_REJECTED",
|
|
2811
|
+
"PREPARE_AMBIGUOUS",
|
|
2812
|
+
"READBACK_UNAVAILABLE",
|
|
2813
|
+
"PREPARED_CONTENT_MISMATCH",
|
|
2814
|
+
"COMMIT_NOT_AUTHORIZED",
|
|
2815
|
+
"AUTHORITY_REVOKED_BEFORE_COMMIT",
|
|
2816
|
+
"COMMIT_DEADLINE_EXPIRED",
|
|
2817
|
+
"ABORTED_BEFORE_COMMIT",
|
|
2818
|
+
"COMMIT_NOT_ISSUED",
|
|
2819
|
+
"COMMIT_REJECTED",
|
|
2820
|
+
"EFFECT_MISMATCH",
|
|
2821
|
+
"DUPLICATE_EFFECT_DETECTED"
|
|
2822
|
+
]);
|
|
2823
|
+
var ExecutionAbortReasonSchema = z15.literal(
|
|
2824
|
+
"INTERRUPTED_BEFORE_ATTEMPT"
|
|
2825
|
+
);
|
|
2826
|
+
var ExecutionUnknownReasonSchema = z15.enum([
|
|
2827
|
+
"COMMIT_RESPONSE_LOST",
|
|
2828
|
+
"COMMIT_TIMEOUT",
|
|
2829
|
+
"COMMIT_SERVER_ERROR",
|
|
2830
|
+
"COMMIT_RESPONSE_INVALID",
|
|
2831
|
+
"COMMIT_REDIRECT_REFUSED",
|
|
2832
|
+
"COMMIT_TARGET_NOT_FOUND",
|
|
2833
|
+
"REJECTION_UNCONFIRMED",
|
|
2834
|
+
"AWAITING_PROVIDER_RECORD",
|
|
2835
|
+
"VERIFICATION_UNAVAILABLE"
|
|
2836
|
+
]);
|
|
2837
|
+
var ProviderIdSchema = z15.string().regex(/^[A-Za-z0-9_=+/-]{1,512}$/);
|
|
2838
|
+
var InternetMessageIdSchema = z15.string().regex(/^<[\x21-\x3b\x3d\x3f-\x7e]{1,995}>$/);
|
|
2839
|
+
var ProviderObjectRefSchema = z15.discriminatedUnion("kind", [
|
|
2840
|
+
z15.object({
|
|
2841
|
+
kind: z15.literal("gmail-draft"),
|
|
2842
|
+
draftId: ProviderIdSchema,
|
|
2843
|
+
messageId: ProviderIdSchema,
|
|
2844
|
+
threadId: ProviderIdSchema
|
|
2845
|
+
}).strict(),
|
|
2846
|
+
z15.object({
|
|
2847
|
+
kind: z15.literal("graph-message"),
|
|
2848
|
+
immutableId: ProviderIdSchema,
|
|
2849
|
+
internetMessageId: InternetMessageIdSchema
|
|
2850
|
+
}).strict(),
|
|
2851
|
+
z15.object({
|
|
2852
|
+
kind: z15.literal("api-request"),
|
|
2853
|
+
requestDigest: ProviderIdSchema
|
|
2854
|
+
}).strict(),
|
|
2855
|
+
z15.object({
|
|
2856
|
+
kind: z15.literal("tool-call"),
|
|
2857
|
+
callDigest: Sha256DigestSchema
|
|
2858
|
+
}).strict(),
|
|
2859
|
+
z15.object({
|
|
2860
|
+
kind: z15.literal("gcalendar-plan"),
|
|
2861
|
+
requestDigest: ProviderIdSchema
|
|
2862
|
+
}).strict(),
|
|
2863
|
+
z15.object({
|
|
2864
|
+
kind: z15.literal("payment-order"),
|
|
2865
|
+
orderId: ProviderIdSchema,
|
|
2866
|
+
intentId: ProviderIdSchema
|
|
2867
|
+
}).strict(),
|
|
2868
|
+
z15.object({
|
|
2869
|
+
kind: z15.literal("file-request"),
|
|
2870
|
+
requestDigest: ProviderIdSchema
|
|
2871
|
+
}).strict(),
|
|
2872
|
+
z15.object({
|
|
2873
|
+
kind: z15.literal("protected_data-op"),
|
|
2874
|
+
operationDigest: ProviderIdSchema
|
|
2875
|
+
}).strict()
|
|
2876
|
+
]);
|
|
2877
|
+
var ProviderSentRefSchema = z15.discriminatedUnion("kind", [
|
|
2878
|
+
z15.object({
|
|
2879
|
+
kind: z15.literal("gmail-message"),
|
|
2880
|
+
messageId: ProviderIdSchema,
|
|
2881
|
+
threadId: ProviderIdSchema
|
|
2882
|
+
}).strict(),
|
|
2883
|
+
z15.object({
|
|
2884
|
+
kind: z15.literal("graph-message"),
|
|
2885
|
+
immutableId: ProviderIdSchema
|
|
2886
|
+
}).strict(),
|
|
2887
|
+
z15.object({
|
|
2888
|
+
kind: z15.literal("api-response"),
|
|
2889
|
+
status: z15.number().int().min(0).max(999),
|
|
2890
|
+
bodyBytes: z15.number().int().min(0),
|
|
2891
|
+
headersDigest: Sha256DigestSchema,
|
|
2892
|
+
bodyDigest: Sha256DigestSchema,
|
|
2893
|
+
bodyPreview: z15.string().max(512),
|
|
2894
|
+
truncated: z15.boolean()
|
|
2895
|
+
}).strict(),
|
|
2896
|
+
z15.object({
|
|
2897
|
+
kind: z15.literal("tool-response"),
|
|
2898
|
+
operation: z15.enum(["invoke", "install", "enable"]),
|
|
2899
|
+
toolName: z15.string().min(1).max(64),
|
|
2900
|
+
status: z15.number().int().min(0).max(999).nullable(),
|
|
2901
|
+
resultDigest: Sha256DigestSchema,
|
|
2902
|
+
detail: z15.string().max(256)
|
|
2903
|
+
}).strict(),
|
|
2904
|
+
z15.object({
|
|
2905
|
+
kind: z15.literal("gcalendar-event"),
|
|
2906
|
+
eventId: ProviderIdSchema,
|
|
2907
|
+
status: z15.enum(["confirmed", "cancelled"])
|
|
2908
|
+
}).strict(),
|
|
2909
|
+
z15.object({
|
|
2910
|
+
kind: z15.literal("payment-charge"),
|
|
2911
|
+
chargeId: ProviderIdSchema,
|
|
2912
|
+
amountCents: z15.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
2913
|
+
currency: z15.string().trim().min(3).max(3),
|
|
2914
|
+
status: z15.enum(["succeeded", "refunded", "returned", "voided"])
|
|
2915
|
+
}).strict(),
|
|
2916
|
+
z15.object({
|
|
2917
|
+
kind: z15.literal("file-effect"),
|
|
2918
|
+
operation: z15.enum(["read", "write", "share", "delete"]),
|
|
2919
|
+
path: z15.string().min(1).max(512),
|
|
2920
|
+
contentDigest: Sha256DigestSchema.nullable(),
|
|
2921
|
+
bytes: z15.number().int().min(0),
|
|
2922
|
+
shareId: ProviderIdSchema.nullable(),
|
|
2923
|
+
grantee: z15.string().max(256).nullable()
|
|
2924
|
+
}).strict(),
|
|
2925
|
+
z15.object({
|
|
2926
|
+
kind: z15.literal("protected_data-effect"),
|
|
2927
|
+
operation: z15.enum(["use", "view", "disclose", "persist"]),
|
|
2928
|
+
fieldRef: z15.string().min(1).max(64),
|
|
2929
|
+
valueDigest: Sha256DigestSchema,
|
|
2930
|
+
counterparty: z15.string().max(256).nullable(),
|
|
2931
|
+
maskedPreview: z15.string().max(120).nullable()
|
|
2932
|
+
}).strict()
|
|
2933
|
+
]);
|
|
2934
|
+
var VerifiedOutcomeSchema = z15.object({
|
|
2935
|
+
kind: z15.literal("verified"),
|
|
2936
|
+
sentRef: ProviderSentRefSchema,
|
|
2937
|
+
observedDigest: Sha256DigestSchema,
|
|
2938
|
+
effectDigest: Sha256DigestSchema,
|
|
2939
|
+
level: VerificationLevelSchema,
|
|
2940
|
+
observedAtEpochMs: EpochMillisecondsSchema
|
|
2941
|
+
}).strict();
|
|
2942
|
+
var MismatchOutcomeSchema = z15.object({
|
|
2943
|
+
kind: z15.literal("mismatch"),
|
|
2944
|
+
code: z15.enum(["EFFECT_MISMATCH", "DUPLICATE_EFFECT_DETECTED"]),
|
|
2945
|
+
observedDigest: Sha256DigestSchema.nullable()
|
|
2946
|
+
}).strict();
|
|
2947
|
+
var CommitRejectedOutcomeSchema = z15.object({
|
|
2948
|
+
kind: z15.literal("commit-rejected"),
|
|
2949
|
+
providerStatus: z15.union([
|
|
2950
|
+
z15.literal(0),
|
|
2951
|
+
z15.literal(400),
|
|
2952
|
+
z15.literal(401),
|
|
2953
|
+
z15.literal(403),
|
|
2954
|
+
z15.literal(404),
|
|
2955
|
+
z15.literal(409),
|
|
2956
|
+
z15.literal(413),
|
|
2957
|
+
z15.literal(422),
|
|
2958
|
+
z15.literal(429)
|
|
2959
|
+
]),
|
|
2960
|
+
confirmedAtEpochMs: EpochMillisecondsSchema
|
|
2961
|
+
}).strict();
|
|
2962
|
+
var UnknownOutcomeSchema = z15.object({
|
|
2963
|
+
kind: z15.literal("unknown"),
|
|
2964
|
+
reason: ExecutionUnknownReasonSchema
|
|
2965
|
+
}).strict();
|
|
2966
|
+
var AbortedOutcomeSchema = z15.object({
|
|
2967
|
+
kind: z15.literal("aborted"),
|
|
2968
|
+
code: ExecutionFailureCodeSchema,
|
|
2969
|
+
orphanDraftPossible: z15.boolean()
|
|
2970
|
+
}).strict();
|
|
2971
|
+
var UnresolvedOutcomeSchema = z15.object({
|
|
2972
|
+
kind: z15.literal("unresolved"),
|
|
2973
|
+
reason: ExecutionUnknownReasonSchema
|
|
2974
|
+
}).strict();
|
|
2975
|
+
var OutcomeSchema = z15.discriminatedUnion("kind", [
|
|
2976
|
+
VerifiedOutcomeSchema,
|
|
2977
|
+
MismatchOutcomeSchema,
|
|
2978
|
+
CommitRejectedOutcomeSchema,
|
|
2979
|
+
UnknownOutcomeSchema,
|
|
2980
|
+
AbortedOutcomeSchema,
|
|
2981
|
+
UnresolvedOutcomeSchema
|
|
2982
|
+
]);
|
|
2983
|
+
var PendingOutcomeSchema = OutcomeSchema;
|
|
2984
|
+
var ExecutionRecordSchema = z15.object({
|
|
2985
|
+
schemaVersion: z15.literal(1),
|
|
2986
|
+
attemptId: AttemptIdSchema,
|
|
2987
|
+
tenantId: TenantIdSchema,
|
|
2988
|
+
principalId: PrincipalIdSchema,
|
|
2989
|
+
agentId: AgentIdSchema,
|
|
2990
|
+
requestId: RequestIdSchema,
|
|
2991
|
+
correlationId: CorrelationIdSchema,
|
|
2992
|
+
capabilityId: CapabilityIdSchema,
|
|
2993
|
+
actionDigest: Sha256DigestSchema,
|
|
2994
|
+
adapterId: AdapterIdSchema,
|
|
2995
|
+
connectionId: ConnectionIdSchema,
|
|
2996
|
+
provider: ProviderSchema,
|
|
2997
|
+
profile: ConnectionProfileSchema,
|
|
2998
|
+
effectKey: Sha256DigestSchema,
|
|
2999
|
+
expectedProjectionDigest: Sha256DigestSchema,
|
|
3000
|
+
expectedEnvelopeDigest: Sha256DigestSchema,
|
|
3001
|
+
state: ExecutionStateSchema,
|
|
3002
|
+
version: z15.number().int().positive().safe(),
|
|
3003
|
+
reservedAtEpochMs: EpochMillisecondsSchema,
|
|
3004
|
+
updatedAtEpochMs: EpochMillisecondsSchema,
|
|
3005
|
+
commitDeadlineEpochMs: EpochMillisecondsSchema,
|
|
3006
|
+
commitLeaseDeadlineEpochMs: EpochMillisecondsSchema.nullable(),
|
|
3007
|
+
reconcileDeadlineEpochMs: EpochMillisecondsSchema.nullable(),
|
|
3008
|
+
lateReconcileDeadlineEpochMs: EpochMillisecondsSchema.nullable(),
|
|
3009
|
+
commitLeaseIssued: z15.boolean(),
|
|
3010
|
+
providerObjectRef: ProviderObjectRefSchema.nullable(),
|
|
3011
|
+
sentRef: ProviderSentRefSchema.nullable(),
|
|
3012
|
+
orphanDraftPossible: z15.boolean(),
|
|
3013
|
+
pendingOutcome: PendingOutcomeSchema.nullable(),
|
|
3014
|
+
outcome: OutcomeSchema.nullable()
|
|
3015
|
+
}).strict();
|
|
3016
|
+
var AdapterRedemptionRequestSchema = z15.object({
|
|
3017
|
+
schemaVersion: z15.literal(1),
|
|
3018
|
+
capabilityToken: z15.string().min(1).max(512),
|
|
3019
|
+
requestId: RequestIdSchema,
|
|
3020
|
+
presentedBy: z15.object({
|
|
3021
|
+
tenantId: TenantIdSchema,
|
|
3022
|
+
principalId: PrincipalIdSchema,
|
|
3023
|
+
agentId: AgentIdSchema
|
|
3024
|
+
}).strict()
|
|
3025
|
+
}).strict();
|
|
3026
|
+
var ExecutionGrantSchema = z15.object({
|
|
3027
|
+
schemaVersion: z15.literal(1),
|
|
3028
|
+
attemptId: AttemptIdSchema,
|
|
3029
|
+
version: z15.number().int().positive().safe(),
|
|
3030
|
+
connectionId: ConnectionIdSchema,
|
|
3031
|
+
provider: ProviderSchema,
|
|
3032
|
+
profile: ConnectionProfileSchema,
|
|
3033
|
+
action: CanonicalActionSchema,
|
|
3034
|
+
expectedProjectionDigest: Sha256DigestSchema,
|
|
3035
|
+
expectedEnvelopeDigest: Sha256DigestSchema,
|
|
3036
|
+
commitDeadlineEpochMs: EpochMillisecondsSchema
|
|
3037
|
+
}).strict();
|
|
3038
|
+
var expectedVersion = z15.number().int().positive().safe();
|
|
3039
|
+
var CommandOutcomeSchema = z15.discriminatedUnion("kind", [
|
|
3040
|
+
VerifiedOutcomeSchema.omit({ effectDigest: true }),
|
|
3041
|
+
MismatchOutcomeSchema,
|
|
3042
|
+
CommitRejectedOutcomeSchema,
|
|
3043
|
+
UnknownOutcomeSchema
|
|
3044
|
+
]);
|
|
3045
|
+
var ExecutionStageCommandSchema = z15.discriminatedUnion("command", [
|
|
3046
|
+
z15.object({ command: z15.literal("begin-prepare"), expectedVersion }).strict(),
|
|
3047
|
+
z15.object({
|
|
3048
|
+
command: z15.literal("prepared"),
|
|
3049
|
+
expectedVersion,
|
|
3050
|
+
providerObjectRef: ProviderObjectRefSchema,
|
|
3051
|
+
preparedProjectionDigest: Sha256DigestSchema
|
|
3052
|
+
}).strict(),
|
|
3053
|
+
z15.object({ command: z15.literal("commit-request"), expectedVersion }).strict(),
|
|
3054
|
+
z15.object({
|
|
3055
|
+
command: z15.literal("outcome"),
|
|
3056
|
+
expectedVersion,
|
|
3057
|
+
outcome: CommandOutcomeSchema
|
|
3058
|
+
}).strict(),
|
|
3059
|
+
z15.object({
|
|
3060
|
+
command: z15.literal("abort"),
|
|
3061
|
+
expectedVersion,
|
|
3062
|
+
code: ExecutionFailureCodeSchema,
|
|
3063
|
+
orphanDraftPossible: z15.boolean()
|
|
3064
|
+
}).strict()
|
|
3065
|
+
]);
|
|
3066
|
+
var AgentExecutionRequestSchema = z15.object({
|
|
3067
|
+
schemaVersion: z15.literal(1),
|
|
3068
|
+
requestId: RequestIdSchema,
|
|
3069
|
+
capabilityToken: z15.string().min(1).max(512)
|
|
3070
|
+
}).strict();
|
|
3071
|
+
var AgentExecutionReceiptSchema = z15.object({
|
|
3072
|
+
schemaVersion: z15.literal(1),
|
|
3073
|
+
requestId: RequestIdSchema,
|
|
3074
|
+
receiptReference: AttemptIdSchema,
|
|
3075
|
+
status: z15.enum([
|
|
3076
|
+
"sent-verified",
|
|
3077
|
+
"not-sent",
|
|
3078
|
+
"outcome-pending",
|
|
3079
|
+
"outcome-unconfirmed"
|
|
3080
|
+
]),
|
|
3081
|
+
/**
|
|
3082
|
+
* Inline-only delivery channel (WO-013). Carries the value the action was
|
|
3083
|
+
* authorized to hand the agent — a file's content for `file.read` or the
|
|
3084
|
+
* field value for `protected_data.view` — digest-correlated with the
|
|
3085
|
+
* sentRef. Never written to the ledger; only `protected_data.view` and
|
|
3086
|
+
* `file.read` produce it and only on a `sent-verified` receipt.
|
|
3087
|
+
*/
|
|
3088
|
+
delivered: z15.object({
|
|
3089
|
+
kind: z15.enum(["file-content", "protected-value"]),
|
|
3090
|
+
fieldRef: z15.string().min(1).max(64).nullable(),
|
|
3091
|
+
path: z15.string().min(1).max(512).nullable(),
|
|
3092
|
+
value: z15.string().max(64e3),
|
|
3093
|
+
valueDigest: Sha256DigestSchema
|
|
3094
|
+
}).strict().optional()
|
|
3095
|
+
}).strict();
|
|
3096
|
+
|
|
3097
|
+
// src/proposal.ts
|
|
3098
|
+
var AgentActionProposalSchema = z16.object({
|
|
3099
|
+
schemaVersion: z16.literal(1),
|
|
3100
|
+
requestId: RequestIdSchema,
|
|
3101
|
+
correlationId: CorrelationIdSchema,
|
|
3102
|
+
action: ActionTypeSchema,
|
|
3103
|
+
resource: ResourceRefSchema,
|
|
3104
|
+
valueCents: z16.number().int().nonnegative().safe().optional(),
|
|
3105
|
+
merchantId: z16.string().trim().min(1).max(256).optional(),
|
|
3106
|
+
merchantCategory: z16.string().trim().min(1).max(256).optional(),
|
|
3107
|
+
recurring: z16.boolean().optional(),
|
|
3108
|
+
taskId: TaskIdSchema.optional(),
|
|
3109
|
+
uses: z16.literal(1),
|
|
3110
|
+
materialParameters: MaterialParametersSchema,
|
|
3111
|
+
purpose: z16.string().trim().min(1).max(1024),
|
|
3112
|
+
initiatingContext: z16.enum(["user", "scheduled", "agent", "external-event"]),
|
|
3113
|
+
requestedAdapterId: AdapterIdSchema,
|
|
3114
|
+
requestedTtlMs: z16.number().int().positive().max(3e5)
|
|
3115
|
+
}).strict();
|
|
3116
|
+
|
|
3117
|
+
// src/client.ts
|
|
3118
|
+
function randomId(prefix) {
|
|
3119
|
+
return `${prefix}_${randomBytes(16).toString("hex")}`;
|
|
3120
|
+
}
|
|
3121
|
+
function materialParametersFor(invocation) {
|
|
3122
|
+
if (invocation.action === "tool.invoke") {
|
|
3123
|
+
const params2 = ToolInvokeParametersV1Schema.parse({
|
|
3124
|
+
schema: "odingard.guardian.tool.invoke.v1",
|
|
3125
|
+
connectionId: invocation.connectionId,
|
|
3126
|
+
toolName: invocation.toolName,
|
|
3127
|
+
arguments: invocation.arguments
|
|
3128
|
+
});
|
|
3129
|
+
return { ...params2 };
|
|
3130
|
+
}
|
|
3131
|
+
if (invocation.action === "mcp.install") {
|
|
3132
|
+
const params2 = McpInstallParametersV1Schema.parse({
|
|
3133
|
+
schema: "odingard.guardian.mcp.install.v1",
|
|
3134
|
+
connectionId: invocation.connectionId,
|
|
3135
|
+
tool: invocation.tool
|
|
3136
|
+
});
|
|
3137
|
+
return { ...params2 };
|
|
3138
|
+
}
|
|
3139
|
+
const params = McpEnableParametersV1Schema.parse({
|
|
3140
|
+
schema: "odingard.guardian.mcp.enable.v1",
|
|
3141
|
+
connectionId: invocation.connectionId,
|
|
3142
|
+
toolName: invocation.toolName
|
|
3143
|
+
});
|
|
3144
|
+
return { ...params };
|
|
3145
|
+
}
|
|
3146
|
+
var RESOURCE_TYPE = {
|
|
3147
|
+
"tool.invoke": "tool:server",
|
|
3148
|
+
"mcp.install": "mcp:server",
|
|
3149
|
+
"mcp.enable": "mcp:server"
|
|
3150
|
+
};
|
|
3151
|
+
function createGuardianClient(config) {
|
|
3152
|
+
const transport = config.fetch ?? fetch;
|
|
3153
|
+
const base = config.baseUrl.replace(/\/+$/, "");
|
|
3154
|
+
const call = async (path, body, method = "POST") => {
|
|
3155
|
+
const init = {
|
|
3156
|
+
method,
|
|
3157
|
+
headers: {
|
|
3158
|
+
authorization: `Bearer ${config.credential}`,
|
|
3159
|
+
...body === void 0 ? {} : { "content-type": "application/json" }
|
|
3160
|
+
},
|
|
3161
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
3162
|
+
};
|
|
3163
|
+
const response = await transport(`${base}${path}`, init);
|
|
3164
|
+
const parsed = await response.json().catch(() => null);
|
|
3165
|
+
return { status: response.status, body: parsed };
|
|
3166
|
+
};
|
|
3167
|
+
const requestCapability = async (path, body) => {
|
|
3168
|
+
const response = await call(path, body);
|
|
3169
|
+
const data = response.body;
|
|
3170
|
+
if (!data || data.ok !== true) {
|
|
3171
|
+
return {
|
|
3172
|
+
ok: false,
|
|
3173
|
+
reasonCode: typeof data?.reasonCode === "string" ? data.reasonCode : `HTTP_${response.status}`
|
|
3174
|
+
};
|
|
3175
|
+
}
|
|
3176
|
+
return {
|
|
3177
|
+
ok: true,
|
|
3178
|
+
requestId: typeof data.requestId === "string" ? data.requestId : body.requestId ?? "",
|
|
3179
|
+
decision: data.decision === "ASK" || data.decision === "DENY" ? data.decision : "ALLOW",
|
|
3180
|
+
...typeof data.capabilityToken === "string" ? { capabilityToken: data.capabilityToken } : typeof data.token === "string" ? { capabilityToken: data.token } : {},
|
|
3181
|
+
...typeof data.reasonCode === "string" ? { reasonCode: data.reasonCode } : {}
|
|
3182
|
+
};
|
|
3183
|
+
};
|
|
3184
|
+
return {
|
|
3185
|
+
request(invocation) {
|
|
3186
|
+
const requestId = randomId("req");
|
|
3187
|
+
const proposal = AgentActionProposalSchema.parse({
|
|
3188
|
+
schemaVersion: 1,
|
|
3189
|
+
requestId,
|
|
3190
|
+
correlationId: randomId("cor"),
|
|
3191
|
+
action: invocation.action,
|
|
3192
|
+
resource: `${RESOURCE_TYPE[invocation.action]}:${invocation.connectionId}`,
|
|
3193
|
+
uses: 1,
|
|
3194
|
+
materialParameters: materialParametersFor(invocation),
|
|
3195
|
+
purpose: invocation.purpose ?? `Guardian ${invocation.action}`,
|
|
3196
|
+
initiatingContext: "agent",
|
|
3197
|
+
requestedAdapterId: invocation.adapterId ?? "adp_00000006",
|
|
3198
|
+
requestedTtlMs: 12e4
|
|
3199
|
+
});
|
|
3200
|
+
return requestCapability("/v1/agent/requests", proposal);
|
|
3201
|
+
},
|
|
3202
|
+
claim(requestId) {
|
|
3203
|
+
return requestCapability("/v1/agent/claims", {
|
|
3204
|
+
schemaVersion: 1,
|
|
3205
|
+
requestId
|
|
3206
|
+
});
|
|
3207
|
+
},
|
|
3208
|
+
async execute({ requestId, capabilityToken }) {
|
|
3209
|
+
const request = AgentExecutionRequestSchema.parse({
|
|
3210
|
+
schemaVersion: 1,
|
|
3211
|
+
requestId,
|
|
3212
|
+
capabilityToken
|
|
3213
|
+
});
|
|
3214
|
+
const response = await call("/v1/agent/execute", request);
|
|
3215
|
+
const data = response.body;
|
|
3216
|
+
const receipt = AgentExecutionReceiptSchema.safeParse(data?.receipt);
|
|
3217
|
+
if (!data || data.ok !== true || !receipt.success) {
|
|
3218
|
+
return {
|
|
3219
|
+
ok: false,
|
|
3220
|
+
reasonCode: typeof data?.reasonCode === "string" ? data.reasonCode : `HTTP_${response.status}`
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
return { ok: true, receipt: receipt.data };
|
|
3224
|
+
},
|
|
3225
|
+
async receipts(requestId) {
|
|
3226
|
+
const response = await call(
|
|
3227
|
+
`/v1/agent/receipts/${encodeURIComponent(requestId)}`,
|
|
3228
|
+
void 0,
|
|
3229
|
+
"GET"
|
|
3230
|
+
);
|
|
3231
|
+
const data = response.body;
|
|
3232
|
+
if (!data || data.ok !== true || !Array.isArray(data.receipts)) {
|
|
3233
|
+
return {
|
|
3234
|
+
ok: false,
|
|
3235
|
+
reasonCode: typeof data?.code === "string" ? data.code : `HTTP_${response.status}`
|
|
3236
|
+
};
|
|
3237
|
+
}
|
|
3238
|
+
return {
|
|
3239
|
+
ok: true,
|
|
3240
|
+
requestId: data.requestId,
|
|
3241
|
+
receipts: data.receipts
|
|
3242
|
+
};
|
|
3243
|
+
}
|
|
3244
|
+
};
|
|
3245
|
+
}
|
|
3246
|
+
export {
|
|
3247
|
+
createGuardianClient
|
|
3248
|
+
};
|