@servicialo/mcp-server 0.6.0 → 0.8.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.
Files changed (69) hide show
  1. package/LICENSE +191 -0
  2. package/README.en.md +392 -0
  3. package/README.md +315 -147
  4. package/dist/adapter-http.d.ts +51 -0
  5. package/dist/adapter-http.d.ts.map +1 -0
  6. package/dist/adapter-http.js +187 -0
  7. package/dist/adapter-http.js.map +1 -0
  8. package/dist/adapter.d.ts +42 -0
  9. package/dist/adapter.d.ts.map +1 -0
  10. package/dist/adapter.js +42 -0
  11. package/dist/adapter.js.map +1 -0
  12. package/dist/client.d.ts +5 -1
  13. package/dist/client.d.ts.map +1 -1
  14. package/dist/client.js +3 -0
  15. package/dist/client.js.map +1 -1
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +73 -58
  19. package/dist/index.js.map +1 -1
  20. package/dist/mandate.d.ts +321 -0
  21. package/dist/mandate.d.ts.map +1 -0
  22. package/dist/mandate.js +349 -0
  23. package/dist/mandate.js.map +1 -0
  24. package/dist/provider-profile.d.ts +985 -0
  25. package/dist/provider-profile.d.ts.map +1 -0
  26. package/dist/provider-profile.js +210 -0
  27. package/dist/provider-profile.js.map +1 -0
  28. package/dist/tools/authenticated/cerrar.d.ts +5 -5
  29. package/dist/tools/authenticated/cerrar.d.ts.map +1 -1
  30. package/dist/tools/authenticated/cerrar.js.map +1 -1
  31. package/dist/tools/authenticated/comprometer.d.ts +4 -4
  32. package/dist/tools/authenticated/comprometer.d.ts.map +1 -1
  33. package/dist/tools/authenticated/comprometer.js.map +1 -1
  34. package/dist/tools/authenticated/delivery.d.ts +4 -4
  35. package/dist/tools/authenticated/delivery.d.ts.map +1 -1
  36. package/dist/tools/authenticated/delivery.js.map +1 -1
  37. package/dist/tools/authenticated/entender.d.ts +3 -3
  38. package/dist/tools/authenticated/entender.d.ts.map +1 -1
  39. package/dist/tools/authenticated/entender.js.map +1 -1
  40. package/dist/tools/authenticated/lifecycle.d.ts +5 -5
  41. package/dist/tools/authenticated/lifecycle.d.ts.map +1 -1
  42. package/dist/tools/authenticated/lifecycle.js.map +1 -1
  43. package/dist/tools/authenticated/resolve-auth.d.ts +118 -0
  44. package/dist/tools/authenticated/resolve-auth.d.ts.map +1 -0
  45. package/dist/tools/authenticated/resolve-auth.js +62 -0
  46. package/dist/tools/authenticated/resolve-auth.js.map +1 -0
  47. package/dist/tools/authenticated/resource.d.ts +295 -0
  48. package/dist/tools/authenticated/resource.d.ts.map +1 -0
  49. package/dist/tools/authenticated/resource.js +125 -0
  50. package/dist/tools/authenticated/resource.js.map +1 -0
  51. package/dist/tools/public/a2a.d.ts +18 -0
  52. package/dist/tools/public/a2a.d.ts.map +1 -0
  53. package/dist/tools/public/a2a.js +15 -0
  54. package/dist/tools/public/a2a.js.map +1 -0
  55. package/dist/tools/public/availability.d.ts +2 -2
  56. package/dist/tools/public/availability.d.ts.map +1 -1
  57. package/dist/tools/public/availability.js.map +1 -1
  58. package/dist/tools/public/registry.d.ts +10 -9
  59. package/dist/tools/public/registry.d.ts.map +1 -1
  60. package/dist/tools/public/registry.js +10 -2
  61. package/dist/tools/public/registry.js.map +1 -1
  62. package/dist/tools/public/resolve.d.ts +60 -0
  63. package/dist/tools/public/resolve.d.ts.map +1 -0
  64. package/dist/tools/public/resolve.js +60 -0
  65. package/dist/tools/public/resolve.js.map +1 -0
  66. package/dist/tools/public/services.d.ts +2 -2
  67. package/dist/tools/public/services.d.ts.map +1 -1
  68. package/dist/tools/public/services.js.map +1 -1
  69. package/package.json +5 -3
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Servicialo v0.8 — Delegated Agency Model
3
+ * Mandate validation middleware for the MCP server.
4
+ *
5
+ * This module provides:
6
+ * 1. Zod schemas for ServiceMandate and audit entries
7
+ * 2. A validation function that enforces the 8 mandate checks (§10.3.2)
8
+ * 3. A middleware wrapper for tool handlers
9
+ * 4. Audit logging on every agent action
10
+ */
11
+ import { z } from 'zod';
12
+ import type { ServicialoAdapter } from './adapter.js';
13
+ export declare const MandatePrincipalType: z.ZodEnum<["professional", "patient", "organization"]>;
14
+ export declare const MandateActingFor: z.ZodEnum<["professional", "patient", "organization"]>;
15
+ export declare const MandateStatus: z.ZodEnum<["active", "expired", "revoked", "suspended"]>;
16
+ export declare const MandateConstraintsSchema: z.ZodOptional<z.ZodObject<{
17
+ max_actions_per_day: z.ZodOptional<z.ZodNumber>;
18
+ allowed_hours: z.ZodOptional<z.ZodObject<{
19
+ start: z.ZodString;
20
+ end: z.ZodString;
21
+ timezone: z.ZodString;
22
+ }, "strip", z.ZodTypeAny, {
23
+ timezone: string;
24
+ start: string;
25
+ end: string;
26
+ }, {
27
+ timezone: string;
28
+ start: string;
29
+ end: string;
30
+ }>>;
31
+ ip_allowlist: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
32
+ require_confirmation_above: z.ZodOptional<z.ZodObject<{
33
+ amount: z.ZodNumber;
34
+ currency: z.ZodString;
35
+ }, "strip", z.ZodTypeAny, {
36
+ amount: number;
37
+ currency: string;
38
+ }, {
39
+ amount: number;
40
+ currency: string;
41
+ }>>;
42
+ service_types: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
43
+ }, "strip", z.ZodTypeAny, {
44
+ max_actions_per_day?: number | undefined;
45
+ allowed_hours?: {
46
+ timezone: string;
47
+ start: string;
48
+ end: string;
49
+ } | undefined;
50
+ ip_allowlist?: string[] | undefined;
51
+ require_confirmation_above?: {
52
+ amount: number;
53
+ currency: string;
54
+ } | undefined;
55
+ service_types?: string[] | undefined;
56
+ }, {
57
+ max_actions_per_day?: number | undefined;
58
+ allowed_hours?: {
59
+ timezone: string;
60
+ start: string;
61
+ end: string;
62
+ } | undefined;
63
+ ip_allowlist?: string[] | undefined;
64
+ require_confirmation_above?: {
65
+ amount: number;
66
+ currency: string;
67
+ } | undefined;
68
+ service_types?: string[] | undefined;
69
+ }>>;
70
+ export declare const ServiceMandateSchema: z.ZodObject<{
71
+ mandate_id: z.ZodString;
72
+ principal_id: z.ZodString;
73
+ principal_type: z.ZodEnum<["professional", "patient", "organization"]>;
74
+ agent_id: z.ZodString;
75
+ agent_name: z.ZodOptional<z.ZodString>;
76
+ acting_for: z.ZodEnum<["professional", "patient", "organization"]>;
77
+ context: z.ZodString;
78
+ scopes: z.ZodArray<z.ZodString, "many">;
79
+ constraints: z.ZodOptional<z.ZodObject<{
80
+ max_actions_per_day: z.ZodOptional<z.ZodNumber>;
81
+ allowed_hours: z.ZodOptional<z.ZodObject<{
82
+ start: z.ZodString;
83
+ end: z.ZodString;
84
+ timezone: z.ZodString;
85
+ }, "strip", z.ZodTypeAny, {
86
+ timezone: string;
87
+ start: string;
88
+ end: string;
89
+ }, {
90
+ timezone: string;
91
+ start: string;
92
+ end: string;
93
+ }>>;
94
+ ip_allowlist: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
95
+ require_confirmation_above: z.ZodOptional<z.ZodObject<{
96
+ amount: z.ZodNumber;
97
+ currency: z.ZodString;
98
+ }, "strip", z.ZodTypeAny, {
99
+ amount: number;
100
+ currency: string;
101
+ }, {
102
+ amount: number;
103
+ currency: string;
104
+ }>>;
105
+ service_types: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
106
+ }, "strip", z.ZodTypeAny, {
107
+ max_actions_per_day?: number | undefined;
108
+ allowed_hours?: {
109
+ timezone: string;
110
+ start: string;
111
+ end: string;
112
+ } | undefined;
113
+ ip_allowlist?: string[] | undefined;
114
+ require_confirmation_above?: {
115
+ amount: number;
116
+ currency: string;
117
+ } | undefined;
118
+ service_types?: string[] | undefined;
119
+ }, {
120
+ max_actions_per_day?: number | undefined;
121
+ allowed_hours?: {
122
+ timezone: string;
123
+ start: string;
124
+ end: string;
125
+ } | undefined;
126
+ ip_allowlist?: string[] | undefined;
127
+ require_confirmation_above?: {
128
+ amount: number;
129
+ currency: string;
130
+ } | undefined;
131
+ service_types?: string[] | undefined;
132
+ }>>;
133
+ issued_at: z.ZodString;
134
+ expires_at: z.ZodString;
135
+ revoked_at: z.ZodOptional<z.ZodString>;
136
+ revocation_reason: z.ZodOptional<z.ZodString>;
137
+ status: z.ZodEnum<["active", "expired", "revoked", "suspended"]>;
138
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
139
+ }, "strip", z.ZodTypeAny, {
140
+ status: "active" | "expired" | "revoked" | "suspended";
141
+ mandate_id: string;
142
+ principal_id: string;
143
+ principal_type: "organization" | "professional" | "patient";
144
+ agent_id: string;
145
+ acting_for: "organization" | "professional" | "patient";
146
+ context: string;
147
+ scopes: string[];
148
+ issued_at: string;
149
+ expires_at: string;
150
+ agent_name?: string | undefined;
151
+ constraints?: {
152
+ max_actions_per_day?: number | undefined;
153
+ allowed_hours?: {
154
+ timezone: string;
155
+ start: string;
156
+ end: string;
157
+ } | undefined;
158
+ ip_allowlist?: string[] | undefined;
159
+ require_confirmation_above?: {
160
+ amount: number;
161
+ currency: string;
162
+ } | undefined;
163
+ service_types?: string[] | undefined;
164
+ } | undefined;
165
+ revoked_at?: string | undefined;
166
+ revocation_reason?: string | undefined;
167
+ metadata?: Record<string, unknown> | undefined;
168
+ }, {
169
+ status: "active" | "expired" | "revoked" | "suspended";
170
+ mandate_id: string;
171
+ principal_id: string;
172
+ principal_type: "organization" | "professional" | "patient";
173
+ agent_id: string;
174
+ acting_for: "organization" | "professional" | "patient";
175
+ context: string;
176
+ scopes: string[];
177
+ issued_at: string;
178
+ expires_at: string;
179
+ agent_name?: string | undefined;
180
+ constraints?: {
181
+ max_actions_per_day?: number | undefined;
182
+ allowed_hours?: {
183
+ timezone: string;
184
+ start: string;
185
+ end: string;
186
+ } | undefined;
187
+ ip_allowlist?: string[] | undefined;
188
+ require_confirmation_above?: {
189
+ amount: number;
190
+ currency: string;
191
+ } | undefined;
192
+ service_types?: string[] | undefined;
193
+ } | undefined;
194
+ revoked_at?: string | undefined;
195
+ revocation_reason?: string | undefined;
196
+ metadata?: Record<string, unknown> | undefined;
197
+ }>;
198
+ export type ServiceMandate = z.infer<typeof ServiceMandateSchema>;
199
+ export declare const AuditResultType: z.ZodEnum<["success", "failure", "rejected", "halted"]>;
200
+ export declare const AuditEntrySchema: z.ZodObject<{
201
+ audit_id: z.ZodOptional<z.ZodString>;
202
+ mandate_id: z.ZodString;
203
+ agent_id: z.ZodString;
204
+ principal_id: z.ZodString;
205
+ acting_for: z.ZodEnum<["professional", "patient", "organization"]>;
206
+ action: z.ZodString;
207
+ action_input: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
208
+ action_result: z.ZodEnum<["success", "failure", "rejected", "halted"]>;
209
+ failure_reason: z.ZodOptional<z.ZodString>;
210
+ resource_id: z.ZodOptional<z.ZodString>;
211
+ context: z.ZodString;
212
+ ip_address: z.ZodOptional<z.ZodString>;
213
+ request_id: z.ZodOptional<z.ZodString>;
214
+ timestamp: z.ZodOptional<z.ZodString>;
215
+ }, "strip", z.ZodTypeAny, {
216
+ mandate_id: string;
217
+ principal_id: string;
218
+ agent_id: string;
219
+ acting_for: "organization" | "professional" | "patient";
220
+ context: string;
221
+ action: string;
222
+ action_result: "success" | "failure" | "rejected" | "halted";
223
+ resource_id?: string | undefined;
224
+ timestamp?: string | undefined;
225
+ audit_id?: string | undefined;
226
+ action_input?: Record<string, unknown> | undefined;
227
+ failure_reason?: string | undefined;
228
+ ip_address?: string | undefined;
229
+ request_id?: string | undefined;
230
+ }, {
231
+ mandate_id: string;
232
+ principal_id: string;
233
+ agent_id: string;
234
+ acting_for: "organization" | "professional" | "patient";
235
+ context: string;
236
+ action: string;
237
+ action_result: "success" | "failure" | "rejected" | "halted";
238
+ resource_id?: string | undefined;
239
+ timestamp?: string | undefined;
240
+ audit_id?: string | undefined;
241
+ action_input?: Record<string, unknown> | undefined;
242
+ failure_reason?: string | undefined;
243
+ ip_address?: string | undefined;
244
+ request_id?: string | undefined;
245
+ }>;
246
+ export type AuditEntry = z.infer<typeof AuditEntrySchema>;
247
+ export declare const ActorWithMandateSchema: z.ZodObject<{
248
+ type: z.ZodEnum<["client", "provider", "organization", "agent"]>;
249
+ id: z.ZodString;
250
+ mandate_id: z.ZodOptional<z.ZodString>;
251
+ on_behalf_of: z.ZodOptional<z.ZodObject<{
252
+ type: z.ZodString;
253
+ id: z.ZodString;
254
+ }, "strip", z.ZodTypeAny, {
255
+ type: string;
256
+ id: string;
257
+ }, {
258
+ type: string;
259
+ id: string;
260
+ }>>;
261
+ }, "strip", z.ZodTypeAny, {
262
+ type: "client" | "provider" | "organization" | "agent";
263
+ id: string;
264
+ on_behalf_of?: {
265
+ type: string;
266
+ id: string;
267
+ } | undefined;
268
+ mandate_id?: string | undefined;
269
+ }, {
270
+ type: "client" | "provider" | "organization" | "agent";
271
+ id: string;
272
+ on_behalf_of?: {
273
+ type: string;
274
+ id: string;
275
+ } | undefined;
276
+ mandate_id?: string | undefined;
277
+ }>;
278
+ export type ActorWithMandate = z.infer<typeof ActorWithMandateSchema>;
279
+ /**
280
+ * Maps MCP tool names to the scope(s) required to execute them.
281
+ * An agent must hold at least one of the listed scopes in its mandate.
282
+ */
283
+ export declare const TOOL_SCOPE_MAP: Record<string, string[]>;
284
+ export declare class MandateValidationError extends Error {
285
+ readonly code: string;
286
+ readonly mandate_id?: string | undefined;
287
+ constructor(code: string, message: string, mandate_id?: string | undefined);
288
+ }
289
+ export interface MandateValidationContext {
290
+ mandate: ServiceMandate;
291
+ agent_id: string;
292
+ tool_name: string;
293
+ context: string;
294
+ /** For conflict-of-interest check: the acting_for of the counterparty agent, if any */
295
+ counterparty_acting_for?: string;
296
+ /** Current time override for testing */
297
+ now?: Date;
298
+ }
299
+ /**
300
+ * Validates a ServiceMandate against the 8 checks defined in §10.3.2.
301
+ * Throws MandateValidationError on failure.
302
+ */
303
+ export declare function validateMandate(ctx: MandateValidationContext): void;
304
+ export declare function logAuditEntry(client: ServicialoAdapter, entry: Omit<AuditEntry, 'audit_id' | 'timestamp'>): Promise<void>;
305
+ export declare function checkAndIncrementDailyActions(client: ServicialoAdapter, mandate: ServiceMandate): Promise<void>;
306
+ export declare function resolveMandate(client: ServicialoAdapter, mandateId: string): Promise<ServiceMandate>;
307
+ export interface MandateMiddlewareOptions {
308
+ /** Extract the organization context from tool args */
309
+ extractContext?: (args: Record<string, unknown>) => string;
310
+ /** Extract the primary resource ID for audit */
311
+ extractResourceId?: (args: Record<string, unknown>) => string | undefined;
312
+ }
313
+ /**
314
+ * Wraps a tool handler with mandate validation and audit logging.
315
+ *
316
+ * For tools where `actor.type === 'agent'`, validates the mandate before
317
+ * executing and logs an audit entry after. For non-agent actors, passes
318
+ * through without mandate checks.
319
+ */
320
+ export declare function withMandateValidation<TArgs extends Record<string, unknown>>(toolName: string, handler: (client: ServicialoAdapter, args: TArgs) => Promise<unknown>, options?: MandateMiddlewareOptions): (client: ServicialoAdapter, args: TArgs) => Promise<unknown>;
321
+ //# sourceMappingURL=mandate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mandate.d.ts","sourceRoot":"","sources":["../src/mandate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Servicialo v0.8 — Delegated Agency Model
3
+ * Mandate validation middleware for the MCP server.
4
+ *
5
+ * This module provides:
6
+ * 1. Zod schemas for ServiceMandate and audit entries
7
+ * 2. A validation function that enforces the 8 mandate checks (§10.3.2)
8
+ * 3. A middleware wrapper for tool handlers
9
+ * 4. Audit logging on every agent action
10
+ */
11
+ import { z } from 'zod';
12
+ // ---------------------------------------------------------------------------
13
+ // 1. Schemas
14
+ // ---------------------------------------------------------------------------
15
+ export const MandatePrincipalType = z.enum(['professional', 'patient', 'organization']);
16
+ export const MandateActingFor = z.enum(['professional', 'patient', 'organization']);
17
+ export const MandateStatus = z.enum(['active', 'expired', 'revoked', 'suspended']);
18
+ export const MandateConstraintsSchema = z.object({
19
+ max_actions_per_day: z.number().int().positive().optional(),
20
+ allowed_hours: z.object({
21
+ start: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),
22
+ end: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),
23
+ timezone: z.string(),
24
+ }).optional(),
25
+ ip_allowlist: z.array(z.string()).optional(),
26
+ require_confirmation_above: z.object({
27
+ amount: z.number().min(0),
28
+ currency: z.string().regex(/^[A-Z]{3}$/),
29
+ }).optional(),
30
+ service_types: z.array(z.string()).optional(),
31
+ }).optional();
32
+ export const ServiceMandateSchema = z.object({
33
+ mandate_id: z.string().uuid(),
34
+ principal_id: z.string(),
35
+ principal_type: MandatePrincipalType,
36
+ agent_id: z.string(),
37
+ agent_name: z.string().optional(),
38
+ acting_for: MandateActingFor,
39
+ context: z.string().regex(/^[a-z_]+:.+$/),
40
+ scopes: z.array(z.string().regex(/^[a-z_]+:[a-z_]+$/)).min(1),
41
+ constraints: MandateConstraintsSchema,
42
+ issued_at: z.string().datetime(),
43
+ expires_at: z.string().datetime(),
44
+ revoked_at: z.string().datetime().optional(),
45
+ revocation_reason: z.string().optional(),
46
+ status: MandateStatus,
47
+ metadata: z.record(z.unknown()).optional(),
48
+ });
49
+ export const AuditResultType = z.enum(['success', 'failure', 'rejected', 'halted']);
50
+ export const AuditEntrySchema = z.object({
51
+ audit_id: z.string().uuid().optional(), // generated server-side
52
+ mandate_id: z.string().uuid(),
53
+ agent_id: z.string(),
54
+ principal_id: z.string(),
55
+ acting_for: MandateActingFor,
56
+ action: z.string(),
57
+ action_input: z.record(z.unknown()).optional(),
58
+ action_result: AuditResultType,
59
+ failure_reason: z.string().optional(),
60
+ resource_id: z.string().optional(),
61
+ context: z.string(),
62
+ ip_address: z.string().optional(),
63
+ request_id: z.string().optional(),
64
+ timestamp: z.string().datetime().optional(),
65
+ });
66
+ // Extended actor schema for v0.8 — adds mandate_id
67
+ export const ActorWithMandateSchema = z.object({
68
+ type: z.enum(['client', 'provider', 'organization', 'agent']),
69
+ id: z.string(),
70
+ mandate_id: z.string().uuid().optional()
71
+ .describe('Required when type = agent. References a valid ServiceMandate.'),
72
+ on_behalf_of: z.object({
73
+ type: z.string(),
74
+ id: z.string(),
75
+ }).optional(),
76
+ });
77
+ // ---------------------------------------------------------------------------
78
+ // 2. Scope mapping — maps tool names to required scopes
79
+ // ---------------------------------------------------------------------------
80
+ /**
81
+ * Maps MCP tool names to the scope(s) required to execute them.
82
+ * An agent must hold at least one of the listed scopes in its mandate.
83
+ */
84
+ export const TOOL_SCOPE_MAP = {
85
+ // Phase 1 — Discovery (no mandate required for public tools)
86
+ 'registry.search': [],
87
+ 'registry.get_organization': [],
88
+ 'scheduling.check_availability': [],
89
+ 'services.list': [],
90
+ // Phase 2 — Entender
91
+ 'service.get': ['service:read'],
92
+ 'contract.get': ['service:read', 'order:read'],
93
+ // Phase 3 — Comprometer
94
+ 'clients.get_or_create': ['patient:write'],
95
+ 'scheduling.book': ['schedule:write'],
96
+ 'scheduling.confirm': ['schedule:write'],
97
+ // Phase 4 — Ciclo de Vida
98
+ 'lifecycle.get_state': ['service:read'],
99
+ 'lifecycle.transition': ['service:write'],
100
+ 'scheduling.reschedule': ['schedule:write'],
101
+ 'scheduling.cancel': ['schedule:write'],
102
+ // Phase 5 — Verificar Entrega
103
+ 'delivery.checkin': ['evidence:write'],
104
+ 'delivery.checkout': ['evidence:write'],
105
+ 'delivery.record_evidence': ['evidence:write'],
106
+ // Phase 6 — Cerrar
107
+ 'documentation.create': ['document:write'],
108
+ 'payments.create_sale': ['payment:write'],
109
+ 'payments.record_payment': ['payment:write'],
110
+ 'payments.get_status': ['payment:read'],
111
+ // Service Orders
112
+ 'service_orders.list': ['order:read'],
113
+ 'service_orders.get': ['order:read'],
114
+ 'service_orders.create': ['order:write'],
115
+ 'service_orders.propose': ['order:write'],
116
+ 'service_orders.activate': ['order:write'],
117
+ 'service_orders.get_ledger': ['order:read'],
118
+ // Mandate management
119
+ 'mandates.list': ['mandate:read'],
120
+ 'mandates.get': ['mandate:read'],
121
+ 'mandates.suspend': ['mandate:admin'],
122
+ };
123
+ // ---------------------------------------------------------------------------
124
+ // 3. Validation errors
125
+ // ---------------------------------------------------------------------------
126
+ export class MandateValidationError extends Error {
127
+ code;
128
+ mandate_id;
129
+ constructor(code, message, mandate_id) {
130
+ super(message);
131
+ this.code = code;
132
+ this.mandate_id = mandate_id;
133
+ this.name = 'MandateValidationError';
134
+ }
135
+ }
136
+ /**
137
+ * Validates a ServiceMandate against the 8 checks defined in §10.3.2.
138
+ * Throws MandateValidationError on failure.
139
+ */
140
+ export function validateMandate(ctx) {
141
+ const { mandate, agent_id, tool_name, context } = ctx;
142
+ const now = ctx.now ?? new Date();
143
+ // 1. Status check
144
+ if (mandate.status !== 'active') {
145
+ throw new MandateValidationError('MANDATE_INACTIVE', `Mandate ${mandate.mandate_id} status is '${mandate.status}'`, mandate.mandate_id);
146
+ }
147
+ // 2. Temporal validity
148
+ const issuedAt = new Date(mandate.issued_at);
149
+ const expiresAt = new Date(mandate.expires_at);
150
+ if (now < issuedAt) {
151
+ throw new MandateValidationError('MANDATE_NOT_YET_VALID', `Mandate ${mandate.mandate_id} is not yet valid (issued_at: ${mandate.issued_at})`, mandate.mandate_id);
152
+ }
153
+ if (now >= expiresAt) {
154
+ throw new MandateValidationError('MANDATE_EXPIRED', `Mandate ${mandate.mandate_id} has expired (expires_at: ${mandate.expires_at})`, mandate.mandate_id);
155
+ }
156
+ if (mandate.revoked_at) {
157
+ throw new MandateValidationError('MANDATE_REVOKED', `Mandate ${mandate.mandate_id} was revoked at ${mandate.revoked_at}`, mandate.mandate_id);
158
+ }
159
+ // 3. Agent identity
160
+ if (mandate.agent_id !== agent_id) {
161
+ throw new MandateValidationError('AGENT_MISMATCH', `Agent '${agent_id}' does not match mandate agent '${mandate.agent_id}'`, mandate.mandate_id);
162
+ }
163
+ // 4. Scope coverage
164
+ const requiredScopes = TOOL_SCOPE_MAP[tool_name];
165
+ if (requiredScopes === undefined) {
166
+ throw new MandateValidationError('UNKNOWN_TOOL', `Tool '${tool_name}' is not registered in the scope map`, mandate.mandate_id);
167
+ }
168
+ // Public tools (empty scope array) don't require mandate validation
169
+ if (requiredScopes.length > 0) {
170
+ const hasScope = requiredScopes.some(scope => mandate.scopes.includes(scope));
171
+ if (!hasScope) {
172
+ throw new MandateValidationError('SCOPE_INSUFFICIENT', `Mandate lacks required scope(s): ${requiredScopes.join(' | ')}. Granted: ${mandate.scopes.join(', ')}`, mandate.mandate_id);
173
+ }
174
+ }
175
+ // 5. Context match
176
+ if (context && mandate.context !== context && !context.startsWith(mandate.context + ':')) {
177
+ throw new MandateValidationError('CONTEXT_MISMATCH', `Context '${context}' does not match mandate context '${mandate.context}'`, mandate.mandate_id);
178
+ }
179
+ // 6. Conflict-of-interest check
180
+ if (ctx.counterparty_acting_for && ctx.counterparty_acting_for === mandate.acting_for) {
181
+ // Same agent acting for both sides — protocol violation
182
+ throw new MandateValidationError('CONFLICT_OF_INTEREST', `Agent cannot act for '${mandate.acting_for}' when counterparty agent also acts for '${ctx.counterparty_acting_for}'`, mandate.mandate_id);
183
+ }
184
+ // 7. Constraints
185
+ if (mandate.constraints) {
186
+ validateConstraints(mandate, now);
187
+ }
188
+ }
189
+ function validateConstraints(mandate, now) {
190
+ const constraints = mandate.constraints;
191
+ if (!constraints)
192
+ return;
193
+ // Allowed hours
194
+ if (constraints.allowed_hours) {
195
+ const { start, end, timezone } = constraints.allowed_hours;
196
+ const currentTime = now.toLocaleTimeString('en-US', {
197
+ hour12: false,
198
+ hour: '2-digit',
199
+ minute: '2-digit',
200
+ timeZone: timezone,
201
+ });
202
+ if (currentTime < start || currentTime >= end) {
203
+ throw new MandateValidationError('OUTSIDE_ALLOWED_HOURS', `Action at ${currentTime} (${timezone}) is outside allowed hours ${start}-${end}`, mandate.mandate_id);
204
+ }
205
+ }
206
+ // max_actions_per_day is checked asynchronously via the database counter
207
+ // (see incrementDailyActionCount below)
208
+ }
209
+ // ---------------------------------------------------------------------------
210
+ // 5. Audit logger
211
+ // ---------------------------------------------------------------------------
212
+ export async function logAuditEntry(client, entry) {
213
+ try {
214
+ await client.post('/api/coordinalo/mandate-audit', {
215
+ ...entry,
216
+ timestamp: new Date().toISOString(),
217
+ });
218
+ }
219
+ catch (err) {
220
+ // Audit logging failure should not block the operation,
221
+ // but must be reported. In production, send to error tracking.
222
+ console.error('[mandate-audit] Failed to log audit entry:', err);
223
+ }
224
+ }
225
+ // ---------------------------------------------------------------------------
226
+ // 6. Daily action counter
227
+ // ---------------------------------------------------------------------------
228
+ export async function checkAndIncrementDailyActions(client, mandate) {
229
+ if (!mandate.constraints?.max_actions_per_day)
230
+ return;
231
+ try {
232
+ const result = await client.post('/api/coordinalo/mandate-actions/increment', {
233
+ mandateId: mandate.mandate_id,
234
+ });
235
+ const count = result?.actionCount ?? 0;
236
+ if (count > mandate.constraints.max_actions_per_day) {
237
+ throw new MandateValidationError('DAILY_LIMIT_EXCEEDED', `Daily action limit (${mandate.constraints.max_actions_per_day}) exceeded for mandate ${mandate.mandate_id}`, mandate.mandate_id);
238
+ }
239
+ }
240
+ catch (err) {
241
+ if (err instanceof MandateValidationError)
242
+ throw err;
243
+ // If the counter service is unavailable, fail open with warning
244
+ console.warn('[mandate] Daily action counter unavailable:', err);
245
+ }
246
+ }
247
+ // ---------------------------------------------------------------------------
248
+ // 7. Mandate resolution — fetch mandate from backend
249
+ // ---------------------------------------------------------------------------
250
+ export async function resolveMandate(client, mandateId) {
251
+ const result = await client.get(`/api/coordinalo/mandates/${mandateId}`);
252
+ const parsed = ServiceMandateSchema.safeParse(result);
253
+ if (!parsed.success) {
254
+ throw new MandateValidationError('MANDATE_INVALID', `Mandate ${mandateId} failed schema validation: ${parsed.error.message}`, mandateId);
255
+ }
256
+ return parsed.data;
257
+ }
258
+ /**
259
+ * Wraps a tool handler with mandate validation and audit logging.
260
+ *
261
+ * For tools where `actor.type === 'agent'`, validates the mandate before
262
+ * executing and logs an audit entry after. For non-agent actors, passes
263
+ * through without mandate checks.
264
+ */
265
+ export function withMandateValidation(toolName, handler, options) {
266
+ return async (client, args) => {
267
+ const actor = args.actor;
268
+ // Non-agent actors bypass mandate validation
269
+ if (!actor || actor.type !== 'agent') {
270
+ return handler(client, args);
271
+ }
272
+ // Agent actors require a mandate
273
+ if (!actor.mandate_id) {
274
+ throw new MandateValidationError('MANDATE_REQUIRED', `Agent '${actor.id}' must provide a mandate_id to execute '${toolName}'`);
275
+ }
276
+ // Resolve and validate mandate
277
+ const mandate = await resolveMandate(client, actor.mandate_id);
278
+ const context = options?.extractContext?.(args)
279
+ ?? client.orgId
280
+ ? `org:${client.orgId}`
281
+ : mandate.context;
282
+ validateMandate({
283
+ mandate,
284
+ agent_id: actor.id,
285
+ tool_name: toolName,
286
+ context,
287
+ });
288
+ // Check daily action limit
289
+ await checkAndIncrementDailyActions(client, mandate);
290
+ // Write-ahead audit entry (action_result = 'success' will be updated on failure)
291
+ const resourceId = options?.extractResourceId?.(args);
292
+ try {
293
+ const result = await handler(client, args);
294
+ // Log success
295
+ await logAuditEntry(client, {
296
+ mandate_id: mandate.mandate_id,
297
+ agent_id: mandate.agent_id,
298
+ principal_id: mandate.principal_id,
299
+ acting_for: mandate.acting_for,
300
+ action: toolName,
301
+ action_input: sanitizeInput(args),
302
+ action_result: 'success',
303
+ resource_id: resourceId,
304
+ context: mandate.context,
305
+ });
306
+ return result;
307
+ }
308
+ catch (err) {
309
+ // Log failure
310
+ await logAuditEntry(client, {
311
+ mandate_id: mandate.mandate_id,
312
+ agent_id: mandate.agent_id,
313
+ principal_id: mandate.principal_id,
314
+ acting_for: mandate.acting_for,
315
+ action: toolName,
316
+ action_input: sanitizeInput(args),
317
+ action_result: err instanceof MandateValidationError ? 'rejected' : 'failure',
318
+ failure_reason: err instanceof Error ? err.message : String(err),
319
+ resource_id: resourceId,
320
+ context: mandate.context,
321
+ });
322
+ throw err;
323
+ }
324
+ };
325
+ }
326
+ // ---------------------------------------------------------------------------
327
+ // 9. Input sanitization — redact sensitive fields before audit logging
328
+ // ---------------------------------------------------------------------------
329
+ const SENSITIVE_FIELDS = new Set([
330
+ 'password', 'token', 'api_key', 'secret', 'credential',
331
+ 'clinical_notes', 'diagnosis', 'medical_history',
332
+ 'rut', 'ssn', 'national_id',
333
+ ]);
334
+ function sanitizeInput(args) {
335
+ const sanitized = {};
336
+ for (const [key, value] of Object.entries(args)) {
337
+ if (SENSITIVE_FIELDS.has(key.toLowerCase())) {
338
+ sanitized[key] = '[REDACTED]';
339
+ }
340
+ else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
341
+ sanitized[key] = sanitizeInput(value);
342
+ }
343
+ else {
344
+ sanitized[key] = value;
345
+ }
346
+ }
347
+ return sanitized;
348
+ }
349
+ //# sourceMappingURL=mandate.js.map