@emilia-protocol/sdk 0.1.0 → 0.9.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/src/index.ts ADDED
@@ -0,0 +1,932 @@
1
+ /** EMILIA Protocol — Full SDK. Zero dependencies, native fetch. @license Apache-2.0 */
2
+
3
+ // -- Params -----------------------------------------------------------------
4
+
5
+ export interface EPClientOptions {
6
+ baseUrl: string;
7
+ apiKey?: string;
8
+ timeout?: number;
9
+ retries?: number;
10
+ }
11
+
12
+ export interface Party {
13
+ entityRef: string;
14
+ role: 'initiator' | 'responder';
15
+ }
16
+
17
+ export interface InitiateHandshakeParams {
18
+ mode: 'mutual' | 'one-way' | 'delegated';
19
+ policyId: string;
20
+ parties: Party[];
21
+ binding?: Record<string, unknown>;
22
+ interactionId?: string;
23
+ }
24
+
25
+ export interface PresentParams {
26
+ partyRole: string;
27
+ presentationType: 'ep_trust_profile' | 'verifiable_credential' | 'attestation';
28
+ claims: Record<string, unknown>;
29
+ issuerRef?: string;
30
+ disclosureMode?: 'full' | 'selective' | 'zk';
31
+ }
32
+
33
+ export interface GateParams {
34
+ entityId: string;
35
+ action: string;
36
+ policy?: 'strict' | 'standard' | 'permissive' | string;
37
+ handshakeId?: string;
38
+ valueUsd?: number;
39
+ delegationId?: string;
40
+ }
41
+
42
+ export interface ConsumeParams {
43
+ receiptData?: Record<string, unknown>;
44
+ }
45
+
46
+ export interface IssueChallengeParams {
47
+ entityId: string;
48
+ scope: string;
49
+ context?: Record<string, unknown>;
50
+ }
51
+
52
+ export interface AttestParams {
53
+ signature: string;
54
+ payload: Record<string, unknown>;
55
+ }
56
+
57
+ export interface ConsumeSignoffParams {
58
+ action: string;
59
+ context?: Record<string, unknown>;
60
+ }
61
+
62
+ export interface DenyChallengeParams {
63
+ reason?: string;
64
+ }
65
+
66
+ export interface RevokeSignoffOptions {
67
+ reason?: string;
68
+ force?: boolean;
69
+ }
70
+
71
+ export interface CreateDelegationParams {
72
+ delegatorId: string;
73
+ delegateeId: string;
74
+ scope: string;
75
+ policyId: string;
76
+ constraints?: Record<string, unknown>;
77
+ expiresAt?: string;
78
+ }
79
+
80
+ export interface IssueCommitParams {
81
+ handshakeId: string;
82
+ action: string;
83
+ payload: Record<string, unknown>;
84
+ binding?: Record<string, unknown>;
85
+ }
86
+
87
+ // -- Eye Params -------------------------------------------------------------
88
+
89
+ export interface RecordObservationParams {
90
+ source_type: string;
91
+ source_ref: string;
92
+ subject_ref: string;
93
+ actor_ref: string;
94
+ action_type: string;
95
+ target_ref?: string;
96
+ issuer_ref?: string;
97
+ observation_type: string;
98
+ severity_hint: string;
99
+ evidence_hash?: string;
100
+ expires_at: string;
101
+ metadata?: Record<string, unknown>;
102
+ }
103
+
104
+ export interface CheckActionParams {
105
+ subject_ref: string;
106
+ actor_ref: string;
107
+ action_type: string;
108
+ target_ref?: string;
109
+ issuer_ref?: string;
110
+ context_hash: string;
111
+ payload_hash?: string;
112
+ policy_class?: string;
113
+ }
114
+
115
+ export interface CreateSuppressionParams {
116
+ scope_binding_hash: string;
117
+ reason_code: string;
118
+ justification: string;
119
+ expires_at: string;
120
+ }
121
+
122
+ // -- Eye Responses ----------------------------------------------------------
123
+
124
+ export interface ObservationResponse {
125
+ observation_id: string;
126
+ observation_type: string;
127
+ severity_hint: string;
128
+ observed_at: string;
129
+ expires_at: string;
130
+ }
131
+
132
+ export interface AdvisoryResponse {
133
+ advisory_id: string;
134
+ status: string;
135
+ reason_codes: string[];
136
+ recommended_policy_action: string;
137
+ evidence_refs: string[];
138
+ scope_binding_hash: string;
139
+ issued_at: string;
140
+ expires_at: string;
141
+ version: number;
142
+ }
143
+
144
+ export interface SuppressionResponse {
145
+ suppression_id: string;
146
+ status: string;
147
+ created_at: string;
148
+ expires_at: string;
149
+ }
150
+
151
+ // -- Cloud Params -----------------------------------------------------------
152
+
153
+ export interface SignoffFilters {
154
+ entityId?: string;
155
+ scope?: string;
156
+ status?: string;
157
+ limit?: number;
158
+ offset?: number;
159
+ }
160
+
161
+ export interface DateRange {
162
+ from?: string;
163
+ to?: string;
164
+ }
165
+
166
+ export interface EscalateSignoffParams {
167
+ challengeId: string;
168
+ escalateTo: string;
169
+ reason: string;
170
+ }
171
+
172
+ export interface NotifySignoffParams {
173
+ challengeId: string;
174
+ channel: string;
175
+ }
176
+
177
+ export interface SearchEventsParams {
178
+ query: string;
179
+ filters?: Record<string, unknown>;
180
+ }
181
+
182
+ export interface ExportAuditParams {
183
+ format?: 'json' | 'csv' | 'pdf';
184
+ dateRange?: DateRange;
185
+ entityId?: string;
186
+ eventTypes?: string[];
187
+ }
188
+
189
+ export interface GetAuditReportParams {
190
+ reportType: string;
191
+ dateRange: DateRange;
192
+ }
193
+
194
+ export interface SimulatePolicyParams {
195
+ policyId: string;
196
+ context: Record<string, unknown>;
197
+ }
198
+
199
+ export interface RolloutPolicyParams {
200
+ policyId: string;
201
+ strategy: 'canary' | 'blue-green' | 'linear' | string;
202
+ percentage?: number;
203
+ }
204
+
205
+ export interface DiffPolicyVersionsParams {
206
+ policyId: string;
207
+ versionA: string;
208
+ versionB: string;
209
+ }
210
+
211
+ // -- Responses --------------------------------------------------------------
212
+
213
+ export interface Policy {
214
+ name: string;
215
+ family: string;
216
+ description: string;
217
+ minConfidence?: string;
218
+ minScore?: number;
219
+ }
220
+
221
+ export interface Handshake {
222
+ id: string;
223
+ status: string;
224
+ mode: string;
225
+ policyId: string;
226
+ parties: Party[];
227
+ createdAt: string;
228
+ }
229
+
230
+ export interface Presentation {
231
+ presentationId: string;
232
+ partyRole: string;
233
+ status: string;
234
+ createdAt: string;
235
+ }
236
+
237
+ export interface VerificationResult {
238
+ handshakeId: string;
239
+ result: 'accepted' | 'rejected' | 'partial';
240
+ reasonCodes: string[];
241
+ evaluatedAt: string;
242
+ }
243
+
244
+ export interface GateResult {
245
+ decision: 'allow' | 'deny' | 'review';
246
+ commitRef?: string;
247
+ reasons: string[];
248
+ appealPath?: string;
249
+ }
250
+
251
+ export interface SignoffChallenge {
252
+ challengeId: string;
253
+ entityId: string;
254
+ scope: string;
255
+ nonce: string;
256
+ expiresAt: string;
257
+ }
258
+
259
+ export interface SignoffAttestation {
260
+ attestationId: string;
261
+ challengeId: string;
262
+ status: 'valid' | 'invalid' | 'expired';
263
+ signoffId?: string;
264
+ createdAt: string;
265
+ }
266
+
267
+ export interface SignoffConsumption {
268
+ signoffId: string;
269
+ consumed: boolean;
270
+ action: string;
271
+ consumedAt?: string;
272
+ }
273
+
274
+ export interface Consumption {
275
+ handshakeId: string;
276
+ consumed: boolean;
277
+ receiptId?: string;
278
+ consumedAt?: string;
279
+ }
280
+
281
+ export interface RevokeResult {
282
+ id: string;
283
+ revoked: boolean;
284
+ revokedAt: string;
285
+ }
286
+
287
+ export interface DenyResult {
288
+ challengeId: string;
289
+ denied: boolean;
290
+ reason?: string;
291
+ deniedAt: string;
292
+ }
293
+
294
+ export interface Delegation {
295
+ delegationId: string;
296
+ delegatorId: string;
297
+ delegateeId: string;
298
+ scope: string;
299
+ policyId: string;
300
+ status: string;
301
+ constraints?: Record<string, unknown>;
302
+ createdAt: string;
303
+ expiresAt?: string;
304
+ }
305
+
306
+ export interface DelegationVerification {
307
+ delegationId: string;
308
+ valid: boolean;
309
+ status: string;
310
+ reasonCodes: string[];
311
+ verifiedAt: string;
312
+ }
313
+
314
+ export interface Commit {
315
+ commitId: string;
316
+ handshakeId: string;
317
+ action: string;
318
+ status: string;
319
+ payload: Record<string, unknown>;
320
+ createdAt: string;
321
+ }
322
+
323
+ export interface CommitVerification {
324
+ commitId: string;
325
+ valid: boolean;
326
+ status: string;
327
+ reasonCodes: string[];
328
+ verifiedAt: string;
329
+ }
330
+
331
+ // -- Cloud Responses --------------------------------------------------------
332
+
333
+ export interface PendingSignoff {
334
+ challengeId: string;
335
+ entityId: string;
336
+ scope: string;
337
+ status: string;
338
+ createdAt: string;
339
+ expiresAt: string;
340
+ }
341
+
342
+ export interface PendingSignoffsResponse {
343
+ items: PendingSignoff[];
344
+ total: number;
345
+ offset: number;
346
+ limit: number;
347
+ }
348
+
349
+ export interface SignoffQueueItem {
350
+ challengeId: string;
351
+ entityId: string;
352
+ scope: string;
353
+ priority: string;
354
+ status: string;
355
+ createdAt: string;
356
+ expiresAt: string;
357
+ }
358
+
359
+ export interface SignoffQueueResponse {
360
+ items: SignoffQueueItem[];
361
+ total: number;
362
+ offset: number;
363
+ limit: number;
364
+ }
365
+
366
+ export interface SignoffDashboard {
367
+ pending: number;
368
+ approved: number;
369
+ denied: number;
370
+ expired: number;
371
+ averageResponseTime: number;
372
+ recentActivity: DashboardActivity[];
373
+ }
374
+
375
+ export interface DashboardActivity {
376
+ challengeId: string;
377
+ action: string;
378
+ entityId: string;
379
+ timestamp: string;
380
+ }
381
+
382
+ export interface SignoffAnalytics {
383
+ totalChallenges: number;
384
+ approvalRate: number;
385
+ averageResponseTime: number;
386
+ byScope: Record<string, number>;
387
+ timeseries: AnalyticsDataPoint[];
388
+ }
389
+
390
+ export interface AnalyticsDataPoint {
391
+ timestamp: string;
392
+ count: number;
393
+ approved: number;
394
+ denied: number;
395
+ }
396
+
397
+ export interface EscalationResult {
398
+ challengeId: string;
399
+ escalatedTo: string;
400
+ escalatedAt: string;
401
+ status: string;
402
+ }
403
+
404
+ export interface NotificationResult {
405
+ challengeId: string;
406
+ channel: string;
407
+ sent: boolean;
408
+ sentAt: string;
409
+ }
410
+
411
+ export interface AuditEvent {
412
+ eventId: string;
413
+ type: string;
414
+ entityId: string;
415
+ action: string;
416
+ metadata: Record<string, unknown>;
417
+ timestamp: string;
418
+ }
419
+
420
+ export interface SearchEventsResponse {
421
+ items: AuditEvent[];
422
+ total: number;
423
+ }
424
+
425
+ export interface EventTimeline {
426
+ handshakeId: string;
427
+ events: AuditEvent[];
428
+ }
429
+
430
+ export interface ExportAuditResult {
431
+ exportId: string;
432
+ format: string;
433
+ status: string;
434
+ downloadUrl?: string;
435
+ createdAt: string;
436
+ }
437
+
438
+ export interface AuditReport {
439
+ reportType: string;
440
+ dateRange: DateRange;
441
+ summary: Record<string, unknown>;
442
+ items: AuditEvent[];
443
+ generatedAt: string;
444
+ }
445
+
446
+ export interface IntegrityCheckResult {
447
+ healthy: boolean;
448
+ checks: IntegrityCheck[];
449
+ checkedAt: string;
450
+ }
451
+
452
+ export interface IntegrityCheck {
453
+ name: string;
454
+ status: 'pass' | 'fail' | 'warn';
455
+ message?: string;
456
+ }
457
+
458
+ export interface PolicySimulationResult {
459
+ policyId: string;
460
+ decision: 'allow' | 'deny' | 'review';
461
+ reasons: string[];
462
+ evaluatedAt: string;
463
+ }
464
+
465
+ export interface PolicyRolloutResult {
466
+ policyId: string;
467
+ strategy: string;
468
+ percentage: number;
469
+ status: string;
470
+ startedAt: string;
471
+ }
472
+
473
+ export interface PolicyVersion {
474
+ versionId: string;
475
+ policyId: string;
476
+ version: number;
477
+ createdAt: string;
478
+ createdBy: string;
479
+ changelog?: string;
480
+ }
481
+
482
+ export interface PolicyDiff {
483
+ policyId: string;
484
+ versionA: string;
485
+ versionB: string;
486
+ changes: PolicyChange[];
487
+ }
488
+
489
+ export interface PolicyChange {
490
+ field: string;
491
+ oldValue: unknown;
492
+ newValue: unknown;
493
+ type: 'added' | 'removed' | 'modified';
494
+ }
495
+
496
+ // -- Error ------------------------------------------------------------------
497
+
498
+ export class EPError extends Error {
499
+ constructor(
500
+ message: string,
501
+ public readonly status?: number,
502
+ public readonly code?: string,
503
+ ) {
504
+ super(message);
505
+ this.name = 'EPError';
506
+ Object.setPrototypeOf(this, new.target.prototype);
507
+ }
508
+ }
509
+
510
+ // -- Cloud Client -----------------------------------------------------------
511
+
512
+ export class EPCloudClient {
513
+ /** @internal */
514
+ constructor(private readonly _request: <T>(method: string, path: string, body?: unknown, auth?: boolean) => Promise<T>) {}
515
+
516
+ /** Get pending signoffs, optionally filtered by entity, scope, or status. */
517
+ async getPendingSignoffs(filters?: SignoffFilters): Promise<PendingSignoffsResponse> {
518
+ const qs = this._buildQs(filters);
519
+ return this._request<PendingSignoffsResponse>('GET', `/api/cloud/signoffs/pending${qs}`, undefined, true);
520
+ }
521
+
522
+ /** Get the signoff queue with optional filtering. */
523
+ async getSignoffQueue(filters?: SignoffFilters): Promise<SignoffQueueResponse> {
524
+ const qs = this._buildQs(filters);
525
+ return this._request<SignoffQueueResponse>('GET', `/api/cloud/signoffs/queue${qs}`, undefined, true);
526
+ }
527
+
528
+ /** Get a dashboard summary of signoff activity over a date range. */
529
+ async getSignoffDashboard(dateRange?: DateRange): Promise<SignoffDashboard> {
530
+ const qs = this._buildDateRangeQs(dateRange);
531
+ return this._request<SignoffDashboard>('GET', `/api/cloud/signoffs/dashboard${qs}`, undefined, true);
532
+ }
533
+
534
+ /** Get analytics for signoff activity with optional granularity. */
535
+ async getSignoffAnalytics(dateRange?: DateRange, granularity?: 'hour' | 'day' | 'week' | 'month'): Promise<SignoffAnalytics> {
536
+ const params: Record<string, string> = {};
537
+ if (dateRange?.from) params['from'] = dateRange.from;
538
+ if (dateRange?.to) params['to'] = dateRange.to;
539
+ if (granularity) params['granularity'] = granularity;
540
+ const qs = this._toQs(params);
541
+ return this._request<SignoffAnalytics>('GET', `/api/cloud/signoffs/analytics${qs}`, undefined, true);
542
+ }
543
+
544
+ /** Escalate a signoff challenge to another entity. */
545
+ async escalateSignoff(challengeId: string, escalateTo: string, reason: string): Promise<EscalationResult> {
546
+ return this._request<EscalationResult>('POST', `/api/cloud/signoffs/${encodeURIComponent(challengeId)}/escalate`, {
547
+ escalateTo,
548
+ reason,
549
+ }, true);
550
+ }
551
+
552
+ /** Send a notification about a signoff challenge via the specified channel. */
553
+ async notifySignoff(challengeId: string, channel: string): Promise<NotificationResult> {
554
+ return this._request<NotificationResult>('POST', `/api/cloud/signoffs/${encodeURIComponent(challengeId)}/notify`, {
555
+ channel,
556
+ }, true);
557
+ }
558
+
559
+ /** Search audit events by query string and optional filters. */
560
+ async searchEvents(query: string, filters?: Record<string, unknown>): Promise<SearchEventsResponse> {
561
+ return this._request<SearchEventsResponse>('POST', '/api/cloud/events/search', {
562
+ query,
563
+ filters,
564
+ }, true);
565
+ }
566
+
567
+ /** Get a chronological timeline of events for a specific handshake. */
568
+ async getEventTimeline(handshakeId: string): Promise<EventTimeline> {
569
+ return this._request<EventTimeline>('GET', `/api/cloud/events/timeline/${encodeURIComponent(handshakeId)}`, undefined, true);
570
+ }
571
+
572
+ /** Export audit data in the specified format. */
573
+ async exportAudit(params: ExportAuditParams): Promise<ExportAuditResult> {
574
+ return this._request<ExportAuditResult>('POST', '/api/cloud/audit/export', params, true);
575
+ }
576
+
577
+ /** Generate an audit report for the given type and date range. */
578
+ async getAuditReport(reportType: string, dateRange: DateRange): Promise<AuditReport> {
579
+ return this._request<AuditReport>('POST', '/api/cloud/audit/report', {
580
+ reportType,
581
+ dateRange,
582
+ }, true);
583
+ }
584
+
585
+ /** Run an integrity check on the protocol data store. */
586
+ async checkIntegrity(): Promise<IntegrityCheckResult> {
587
+ return this._request<IntegrityCheckResult>('POST', '/api/cloud/integrity/check', undefined, true);
588
+ }
589
+
590
+ /** Simulate a policy against a hypothetical context without persisting any state. */
591
+ async simulatePolicy(policyId: string, context: Record<string, unknown>): Promise<PolicySimulationResult> {
592
+ return this._request<PolicySimulationResult>('POST', `/api/cloud/policies/${encodeURIComponent(policyId)}/simulate`, {
593
+ context,
594
+ }, true);
595
+ }
596
+
597
+ /** Begin a rollout of a policy using the specified strategy. */
598
+ async rolloutPolicy(policyId: string, strategy: string, percentage?: number): Promise<PolicyRolloutResult> {
599
+ return this._request<PolicyRolloutResult>('POST', `/api/cloud/policies/${encodeURIComponent(policyId)}/rollout`, {
600
+ strategy,
601
+ percentage,
602
+ }, true);
603
+ }
604
+
605
+ /** List all versions of a policy. */
606
+ async getPolicyVersions(policyId: string): Promise<PolicyVersion[]> {
607
+ return this._request<PolicyVersion[]>('GET', `/api/cloud/policies/${encodeURIComponent(policyId)}/versions`, undefined, true);
608
+ }
609
+
610
+ /** Diff two versions of a policy to see what changed. */
611
+ async diffPolicyVersions(policyId: string, versionA: string, versionB: string): Promise<PolicyDiff> {
612
+ return this._request<PolicyDiff>('POST', `/api/cloud/policies/${encodeURIComponent(policyId)}/diff`, {
613
+ versionA,
614
+ versionB,
615
+ }, true);
616
+ }
617
+
618
+ /** @internal Build query string from SignoffFilters. */
619
+ private _buildQs(filters?: SignoffFilters): string {
620
+ if (!filters) return '';
621
+ const params: Record<string, string> = {};
622
+ if (filters.entityId) params['entityId'] = filters.entityId;
623
+ if (filters.scope) params['scope'] = filters.scope;
624
+ if (filters.status) params['status'] = filters.status;
625
+ if (filters.limit !== undefined) params['limit'] = String(filters.limit);
626
+ if (filters.offset !== undefined) params['offset'] = String(filters.offset);
627
+ return this._toQs(params);
628
+ }
629
+
630
+ /** @internal Build query string from DateRange. */
631
+ private _buildDateRangeQs(dateRange?: DateRange): string {
632
+ if (!dateRange) return '';
633
+ const params: Record<string, string> = {};
634
+ if (dateRange.from) params['from'] = dateRange.from;
635
+ if (dateRange.to) params['to'] = dateRange.to;
636
+ return this._toQs(params);
637
+ }
638
+
639
+ /** @internal Convert key-value pairs to a query string. */
640
+ private _toQs(params: Record<string, string>): string {
641
+ const entries = Object.entries(params).filter(([, v]) => v !== undefined && v !== '');
642
+ if (entries.length === 0) return '';
643
+ return '?' + entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&');
644
+ }
645
+ }
646
+
647
+ // -- Client -----------------------------------------------------------------
648
+
649
+ export class EPClient {
650
+ private readonly baseUrl: string;
651
+ private readonly apiKey: string;
652
+ private readonly timeout: number;
653
+ private readonly retries: number;
654
+
655
+ /** Cloud-specific endpoints (dashboards, analytics, audit, policy management). */
656
+ public readonly cloud: EPCloudClient;
657
+
658
+ constructor(options: EPClientOptions) {
659
+ this.baseUrl = options.baseUrl.replace(/\/+$/, '');
660
+ this.apiKey = options.apiKey ?? '';
661
+ this.timeout = options.timeout ?? 10_000;
662
+ this.retries = options.retries ?? 2;
663
+
664
+ // Bind the internal request method for the cloud sub-client
665
+ this.cloud = new EPCloudClient(this.request.bind(this));
666
+ }
667
+
668
+ private async request<T>(
669
+ method: string,
670
+ path: string,
671
+ body?: unknown,
672
+ auth = false,
673
+ ): Promise<T> {
674
+ const url = `${this.baseUrl}${path}`;
675
+ const headers: Record<string, string> = {
676
+ 'Content-Type': 'application/json',
677
+ 'User-Agent': '@emilia-protocol/sdk/0.9.0',
678
+ };
679
+ if (auth && this.apiKey) {
680
+ headers['Authorization'] = `Bearer ${this.apiKey}`;
681
+ }
682
+
683
+ let lastErr: unknown;
684
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
685
+ const ctrl = new AbortController();
686
+ const timer = setTimeout(() => ctrl.abort(), this.timeout);
687
+ try {
688
+ const res = await fetch(url, {
689
+ method,
690
+ headers,
691
+ body: body !== undefined ? JSON.stringify(body) : undefined,
692
+ signal: ctrl.signal,
693
+ });
694
+ const data: unknown = await res.json().catch(() => undefined);
695
+ if (!res.ok) {
696
+ const p = data as Record<string, unknown> | undefined;
697
+ const msg = typeof p?.['error'] === 'string' ? p['error'] : `EP API error: ${res.status}`;
698
+ const code = typeof p?.['code'] === 'string' ? p['code'] : undefined;
699
+ throw new EPError(msg, res.status, code);
700
+ }
701
+ return data as T;
702
+ } catch (err) {
703
+ lastErr = err;
704
+ if (err instanceof EPError) {
705
+ // Only retry on 5xx
706
+ if (err.status && err.status < 500) throw err;
707
+ }
708
+ if (err instanceof Error && err.name === 'AbortError') {
709
+ lastErr = new EPError(`Request timed out after ${this.timeout}ms`, undefined, 'timeout');
710
+ }
711
+ if (attempt === this.retries) break;
712
+ } finally {
713
+ clearTimeout(timer);
714
+ }
715
+ }
716
+ if (lastErr instanceof EPError) throw lastErr;
717
+ throw new EPError(
718
+ lastErr instanceof Error ? lastErr.message : 'Unknown network error',
719
+ undefined,
720
+ 'network_error',
721
+ );
722
+ }
723
+
724
+ // ------------------------------------------------------------------
725
+ // Core protocol endpoints
726
+ // ------------------------------------------------------------------
727
+
728
+ /** List available trust policies. */
729
+ async listPolicies(params?: { scope?: string }): Promise<Policy[]> {
730
+ const qs = params?.scope ? `?scope=${encodeURIComponent(params.scope)}` : '';
731
+ return this.request<Policy[]>('GET', `/api/policies${qs}`);
732
+ }
733
+
734
+ /** Initiate a trust handshake between parties. */
735
+ async initiateHandshake(params: InitiateHandshakeParams): Promise<Handshake> {
736
+ return this.request<Handshake>('POST', '/api/handshake/initiate', params, true);
737
+ }
738
+
739
+ /** Present credentials to a handshake. */
740
+ async present(handshakeId: string, params: PresentParams): Promise<Presentation> {
741
+ return this.request<Presentation>(
742
+ 'POST',
743
+ `/api/handshake/${encodeURIComponent(handshakeId)}/present`,
744
+ params,
745
+ true,
746
+ );
747
+ }
748
+
749
+ /** Verify a handshake -- evaluate all presentations against policy. */
750
+ async verify(handshakeId: string): Promise<VerificationResult> {
751
+ return this.request<VerificationResult>(
752
+ 'POST',
753
+ `/api/handshake/${encodeURIComponent(handshakeId)}/verify`,
754
+ undefined,
755
+ true,
756
+ );
757
+ }
758
+
759
+ /** Pre-action trust gate. Returns allow/deny/review with commit ref. */
760
+ async gate(params: GateParams): Promise<GateResult> {
761
+ return this.request<GateResult>('POST', '/api/gate', {
762
+ entity_id: params.entityId,
763
+ action: params.action,
764
+ policy: params.policy ?? 'standard',
765
+ handshake_id: params.handshakeId,
766
+ value_usd: params.valueUsd,
767
+ delegation_id: params.delegationId,
768
+ }, true);
769
+ }
770
+
771
+ /** Retrieve details of a specific handshake by ID. */
772
+ async getHandshake(handshakeId: string): Promise<Handshake> {
773
+ return this.request<Handshake>(
774
+ 'GET',
775
+ `/api/handshake/${encodeURIComponent(handshakeId)}`,
776
+ undefined,
777
+ true,
778
+ );
779
+ }
780
+
781
+ /** Revoke an active handshake, invalidating all associated state. */
782
+ async revokeHandshake(handshakeId: string): Promise<RevokeResult> {
783
+ return this.request<RevokeResult>(
784
+ 'POST',
785
+ `/api/handshake/${encodeURIComponent(handshakeId)}/revoke`,
786
+ undefined,
787
+ true,
788
+ );
789
+ }
790
+
791
+ /** Consume a handshake -- finalize and optionally bind a receipt. */
792
+ async consume(handshakeId: string, params?: ConsumeParams): Promise<Consumption> {
793
+ return this.request<Consumption>(
794
+ 'POST',
795
+ `/api/handshake/${encodeURIComponent(handshakeId)}/consume`,
796
+ params,
797
+ true,
798
+ );
799
+ }
800
+
801
+ // ------------------------------------------------------------------
802
+ // Signoff extension
803
+ // ------------------------------------------------------------------
804
+
805
+ /** Issue a signoff challenge for an entity. */
806
+ async issueChallenge(params: IssueChallengeParams): Promise<SignoffChallenge> {
807
+ return this.request<SignoffChallenge>('POST', '/api/signoff/challenge', {
808
+ entity_id: params.entityId,
809
+ scope: params.scope,
810
+ context: params.context,
811
+ }, true);
812
+ }
813
+
814
+ /** Attest to a signoff challenge with a cryptographic signature. */
815
+ async attest(challengeId: string, params: AttestParams): Promise<SignoffAttestation> {
816
+ return this.request<SignoffAttestation>(
817
+ 'POST',
818
+ `/api/signoff/${encodeURIComponent(challengeId)}/attest`,
819
+ params,
820
+ true,
821
+ );
822
+ }
823
+
824
+ /** Deny a signoff challenge with an optional reason. */
825
+ async denyChallenge(challengeId: string, reason?: string): Promise<DenyResult> {
826
+ return this.request<DenyResult>(
827
+ 'POST',
828
+ `/api/signoff/${encodeURIComponent(challengeId)}/deny`,
829
+ reason !== undefined ? { reason } : undefined,
830
+ true,
831
+ );
832
+ }
833
+
834
+ /** Revoke a previously granted signoff. */
835
+ async revokeSignoff(challengeId: string, options?: RevokeSignoffOptions): Promise<RevokeResult> {
836
+ return this.request<RevokeResult>(
837
+ 'POST',
838
+ `/api/signoff/${encodeURIComponent(challengeId)}/revoke`,
839
+ options,
840
+ true,
841
+ );
842
+ }
843
+
844
+ /** Consume a signoff -- mark it as used for a specific action. */
845
+ async consumeSignoff(signoffId: string, params: ConsumeSignoffParams): Promise<SignoffConsumption> {
846
+ return this.request<SignoffConsumption>(
847
+ 'POST',
848
+ `/api/signoff/${encodeURIComponent(signoffId)}/consume`,
849
+ params,
850
+ true,
851
+ );
852
+ }
853
+
854
+ // ------------------------------------------------------------------
855
+ // Delegation
856
+ // ------------------------------------------------------------------
857
+
858
+ /** Create a trust delegation from one entity to another. */
859
+ async createDelegation(params: CreateDelegationParams): Promise<Delegation> {
860
+ return this.request<Delegation>('POST', '/api/delegation', {
861
+ delegatorId: params.delegatorId,
862
+ delegateeId: params.delegateeId,
863
+ scope: params.scope,
864
+ policyId: params.policyId,
865
+ constraints: params.constraints,
866
+ expiresAt: params.expiresAt,
867
+ }, true);
868
+ }
869
+
870
+ /** Verify the validity of an existing delegation. */
871
+ async verifyDelegation(delegationId: string): Promise<DelegationVerification> {
872
+ return this.request<DelegationVerification>(
873
+ 'POST',
874
+ `/api/delegation/${encodeURIComponent(delegationId)}/verify`,
875
+ undefined,
876
+ true,
877
+ );
878
+ }
879
+
880
+ // ------------------------------------------------------------------
881
+ // Commit
882
+ // ------------------------------------------------------------------
883
+
884
+ /** Issue a trust commit binding a handshake to a specific action. */
885
+ async issueCommit(params: IssueCommitParams): Promise<Commit> {
886
+ return this.request<Commit>('POST', '/api/commit', {
887
+ handshakeId: params.handshakeId,
888
+ action: params.action,
889
+ payload: params.payload,
890
+ binding: params.binding,
891
+ }, true);
892
+ }
893
+
894
+ /** Verify a previously issued commit. */
895
+ async verifyCommit(commitId: string): Promise<CommitVerification> {
896
+ return this.request<CommitVerification>(
897
+ 'POST',
898
+ `/api/commit/${encodeURIComponent(commitId)}/verify`,
899
+ undefined,
900
+ true,
901
+ );
902
+ }
903
+
904
+ // ------------------------------------------------------------------
905
+ // Eye — Observation & Advisory
906
+ // ------------------------------------------------------------------
907
+
908
+ /** Record a behavioral or contextual observation for the Eye subsystem. */
909
+ async recordObservation(params: RecordObservationParams): Promise<ObservationResponse> {
910
+ return this.request<ObservationResponse>('POST', '/api/eye/observations', params, true);
911
+ }
912
+
913
+ /** Check an action against recorded observations and return an advisory. */
914
+ async checkAction(params: CheckActionParams): Promise<AdvisoryResponse> {
915
+ return this.request<AdvisoryResponse>('POST', '/api/eye/check', params, true);
916
+ }
917
+
918
+ /** Retrieve an existing advisory by ID. */
919
+ async getAdvisory(advisoryId: string): Promise<AdvisoryResponse> {
920
+ return this.request<AdvisoryResponse>(
921
+ 'GET',
922
+ `/api/eye/advisories/${encodeURIComponent(advisoryId)}`,
923
+ undefined,
924
+ true,
925
+ );
926
+ }
927
+
928
+ /** Create a suppression to exclude a reason code from future advisories. */
929
+ async createSuppression(params: CreateSuppressionParams): Promise<SuppressionResponse> {
930
+ return this.request<SuppressionResponse>('POST', '/api/eye/suppressions', params, true);
931
+ }
932
+ }