@auths-dev/sdk 0.0.1 → 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.
Files changed (60) hide show
  1. package/Cargo.toml +45 -0
  2. package/README.md +163 -4
  3. package/__test__/client.spec.ts +78 -0
  4. package/__test__/exports.spec.ts +57 -0
  5. package/__test__/integration.spec.ts +407 -0
  6. package/__test__/policy.spec.ts +202 -0
  7. package/__test__/verify.spec.ts +88 -0
  8. package/build.rs +5 -0
  9. package/index.d.ts +259 -0
  10. package/index.js +622 -1
  11. package/lib/artifacts.ts +124 -0
  12. package/lib/attestations.ts +126 -0
  13. package/lib/audit.ts +189 -0
  14. package/lib/client.ts +293 -0
  15. package/lib/commits.ts +70 -0
  16. package/lib/devices.ts +178 -0
  17. package/lib/errors.ts +306 -0
  18. package/lib/identity.ts +280 -0
  19. package/lib/index.ts +125 -0
  20. package/lib/native.ts +255 -0
  21. package/lib/org.ts +235 -0
  22. package/lib/pairing.ts +271 -0
  23. package/lib/policy.ts +669 -0
  24. package/lib/signing.ts +204 -0
  25. package/lib/trust.ts +152 -0
  26. package/lib/types.ts +179 -0
  27. package/lib/verify.ts +241 -0
  28. package/lib/witness.ts +91 -0
  29. package/npm/darwin-arm64/README.md +3 -0
  30. package/npm/darwin-arm64/package.json +23 -0
  31. package/npm/linux-arm64-gnu/README.md +3 -0
  32. package/npm/linux-arm64-gnu/package.json +26 -0
  33. package/npm/linux-x64-gnu/README.md +3 -0
  34. package/npm/linux-x64-gnu/package.json +26 -0
  35. package/npm/win32-arm64-msvc/README.md +3 -0
  36. package/npm/win32-arm64-msvc/package.json +23 -0
  37. package/npm/win32-x64-msvc/README.md +3 -0
  38. package/npm/win32-x64-msvc/package.json +23 -0
  39. package/package.json +51 -16
  40. package/src/artifact.rs +217 -0
  41. package/src/attestation_query.rs +104 -0
  42. package/src/audit.rs +128 -0
  43. package/src/commit_sign.rs +63 -0
  44. package/src/device.rs +212 -0
  45. package/src/diagnostics.rs +106 -0
  46. package/src/error.rs +5 -0
  47. package/src/helpers.rs +60 -0
  48. package/src/identity.rs +467 -0
  49. package/src/lib.rs +26 -0
  50. package/src/org.rs +430 -0
  51. package/src/pairing.rs +454 -0
  52. package/src/policy.rs +147 -0
  53. package/src/sign.rs +215 -0
  54. package/src/trust.rs +189 -0
  55. package/src/types.rs +205 -0
  56. package/src/verify.rs +447 -0
  57. package/src/witness.rs +138 -0
  58. package/tsconfig.json +19 -0
  59. package/typedoc.json +18 -0
  60. package/vitest.config.ts +12 -0
package/lib/policy.ts ADDED
@@ -0,0 +1,669 @@
1
+ import native from './native'
2
+ import { mapNativeError, AuthsError } from './errors'
3
+
4
+ /**
5
+ * Authorization outcome from a policy evaluation.
6
+ *
7
+ * Values match the Rust `Outcome` enum in `auths-policy/src/decision.rs`.
8
+ */
9
+ export const Outcome = {
10
+ Allow: 'Allow',
11
+ Deny: 'Deny',
12
+ Indeterminate: 'Indeterminate',
13
+ RequiresApproval: 'RequiresApproval',
14
+ MissingCredential: 'MissingCredential',
15
+ } as const
16
+ export type Outcome = (typeof Outcome)[keyof typeof Outcome]
17
+
18
+ /**
19
+ * Machine-readable reason code for stable logging and alerting.
20
+ *
21
+ * Values match the Rust `ReasonCode` enum in `auths-policy/src/decision.rs`.
22
+ */
23
+ export const ReasonCode = {
24
+ Unconditional: 'Unconditional',
25
+ AllChecksPassed: 'AllChecksPassed',
26
+ CapabilityPresent: 'CapabilityPresent',
27
+ CapabilityMissing: 'CapabilityMissing',
28
+ IssuerMatch: 'IssuerMatch',
29
+ IssuerMismatch: 'IssuerMismatch',
30
+ Revoked: 'Revoked',
31
+ Expired: 'Expired',
32
+ InsufficientTtl: 'InsufficientTtl',
33
+ IssuedTooLongAgo: 'IssuedTooLongAgo',
34
+ RoleMismatch: 'RoleMismatch',
35
+ ScopeMismatch: 'ScopeMismatch',
36
+ ChainTooDeep: 'ChainTooDeep',
37
+ DelegationMismatch: 'DelegationMismatch',
38
+ AttrMismatch: 'AttrMismatch',
39
+ MissingField: 'MissingField',
40
+ RecursionExceeded: 'RecursionExceeded',
41
+ ShortCircuit: 'ShortCircuit',
42
+ CombinatorResult: 'CombinatorResult',
43
+ WorkloadMismatch: 'WorkloadMismatch',
44
+ WitnessQuorumNotMet: 'WitnessQuorumNotMet',
45
+ SignerTypeMatch: 'SignerTypeMatch',
46
+ SignerTypeMismatch: 'SignerTypeMismatch',
47
+ ApprovalRequired: 'ApprovalRequired',
48
+ ApprovalGranted: 'ApprovalGranted',
49
+ ApprovalExpired: 'ApprovalExpired',
50
+ ApprovalAlreadyUsed: 'ApprovalAlreadyUsed',
51
+ ApprovalRequestMismatch: 'ApprovalRequestMismatch',
52
+ } as const
53
+ export type ReasonCode = (typeof ReasonCode)[keyof typeof ReasonCode]
54
+
55
+ /** Result of evaluating a policy against a context. */
56
+ export interface PolicyDecision {
57
+ /** Raw outcome string (`'allow'` or `'deny'`). */
58
+ outcome: string
59
+ /** Machine-readable reason code. */
60
+ reason: string
61
+ /** Human-readable explanation of the decision. */
62
+ message: string
63
+ /** Convenience: `true` when `outcome === 'allow'`. */
64
+ allowed: boolean
65
+ /** Convenience: `true` when `outcome === 'deny'`. */
66
+ denied: boolean
67
+ }
68
+
69
+ /** A commit verification result (from Git commit verification). */
70
+ export interface CommitResultLike {
71
+ /** Git commit SHA. */
72
+ commitSha: string
73
+ /** Whether the commit signature is valid. */
74
+ isValid: boolean
75
+ /** Hex-encoded public key of the signer, if identified. */
76
+ signer?: string | null
77
+ }
78
+
79
+ /**
80
+ * Build an EvalContext options object from a commit verification result.
81
+ *
82
+ * Extracts the signer hex from the commit result and converts it to a
83
+ * `did:key:` DID for use as the `subject` field.
84
+ *
85
+ * @param commitResult - A commit verification result with a `signer` hex field.
86
+ * @param issuer - The issuer DID (`did:keri:...`).
87
+ * @param capabilities - Optional capability list to include.
88
+ * @returns An EvalContextOpts suitable for `evaluatePolicy()`.
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const ctx = evalContextFromCommitResult(cr, org.orgDid, ['sign_commit'])
93
+ * const decision = evaluatePolicy(compiled, ctx)
94
+ * ```
95
+ */
96
+ export function evalContextFromCommitResult(
97
+ commitResult: CommitResultLike,
98
+ issuer: string,
99
+ capabilities?: string[],
100
+ ): EvalContextOpts {
101
+ const subject = commitResult.signer
102
+ ? `did:key:z${commitResult.signer}`
103
+ : 'unknown'
104
+ const ctx: EvalContextOpts = { issuer, subject }
105
+ if (capabilities) ctx.capabilities = capabilities
106
+ return ctx
107
+ }
108
+
109
+ /**
110
+ * Context for policy evaluation.
111
+ *
112
+ * **DID format requirements:**
113
+ * - `issuer`: Must be a valid DID. Typically `did:keri:E...` for identity DIDs
114
+ * (organizations, individuals) or `did:key:z...` for device DIDs.
115
+ * - `subject`: Same format rules as `issuer`. For device attestations, this is
116
+ * usually a `did:key:z...` device DID.
117
+ *
118
+ * Both `issuer` and `subject` are parsed into `CanonicalDid` values by the
119
+ * Rust policy engine. The engine accepts both `did:keri:` and `did:key:` formats.
120
+ * Invalid DID strings will cause evaluation to fail with a parse error.
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * const ctx: EvalContextOpts = {
125
+ * issuer: 'did:keri:EOrg123', // organization identity
126
+ * subject: 'did:key:z6MkDevice', // device key
127
+ * capabilities: ['sign_commit'],
128
+ * }
129
+ * ```
130
+ */
131
+ export interface EvalContextOpts {
132
+ /**
133
+ * DID of the attestation issuer.
134
+ *
135
+ * Must be a valid `did:keri:` or `did:key:` DID string.
136
+ * Typically the organization or identity that issued the attestation.
137
+ */
138
+ issuer: string
139
+ /**
140
+ * DID of the attestation subject.
141
+ *
142
+ * Must be a valid `did:keri:` or `did:key:` DID string.
143
+ * For device attestations, this is the device's `did:key:z...` DID.
144
+ */
145
+ subject: string
146
+ /** Capabilities held by the subject. */
147
+ capabilities?: string[]
148
+ /** Role of the subject (e.g. `'admin'`, `'member'`). */
149
+ role?: string
150
+ /** Whether the attestation has been revoked. */
151
+ revoked?: boolean
152
+ /** Expiration timestamp (RFC 3339). */
153
+ expiresAt?: string
154
+ /** Repository scope (e.g. `'org/repo'`). */
155
+ repo?: string
156
+ /** Deployment environment (e.g. `'production'`). */
157
+ environment?: string
158
+ /** Signer type constraint. */
159
+ signerType?: 'human' | 'agent' | 'workload'
160
+ /** DID of the delegating identity. */
161
+ delegatedBy?: string
162
+ /** Depth of the attestation chain. */
163
+ chainDepth?: number
164
+ }
165
+
166
+ type Predicate = Record<string, unknown>
167
+
168
+ /**
169
+ * Fluent builder for composing authorization policies.
170
+ *
171
+ * Policies are built by chaining predicates, then compiled and evaluated
172
+ * against an attestation context to produce an allow/deny decision.
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * import { PolicyBuilder } from '@auths-dev/sdk'
177
+ *
178
+ * // Quick standard policy
179
+ * const policy = PolicyBuilder.standard('sign_commit')
180
+ * const decision = policy.evaluate({
181
+ * issuer: 'did:keri:EOrg',
182
+ * subject: 'did:key:zDevice',
183
+ * capabilities: ['sign_commit'],
184
+ * })
185
+ * console.log(decision.allowed) // true
186
+ *
187
+ * // Complex composed policy
188
+ * const ciPolicy = new PolicyBuilder()
189
+ * .notRevoked()
190
+ * .notExpired()
191
+ * .requireCapability('sign')
192
+ * .requireAgent()
193
+ * .requireRepo('org/repo')
194
+ * .build()
195
+ * ```
196
+ */
197
+ export class PolicyBuilder {
198
+ private predicates: Predicate[] = []
199
+
200
+ /** All available predicate method names. */
201
+ static readonly AVAILABLE_PREDICATES: string[] = [
202
+ 'notRevoked',
203
+ 'notExpired',
204
+ 'expiresAfter',
205
+ 'issuedWithin',
206
+ 'requireCapability',
207
+ 'requireAllCapabilities',
208
+ 'requireAnyCapability',
209
+ 'requireIssuer',
210
+ 'requireIssuerIn',
211
+ 'requireSubject',
212
+ 'requireDelegatedBy',
213
+ 'requireAgent',
214
+ 'requireHuman',
215
+ 'requireWorkload',
216
+ 'requireRepo',
217
+ 'requireRepoIn',
218
+ 'requireEnv',
219
+ 'requireEnvIn',
220
+ 'refMatches',
221
+ 'pathAllowed',
222
+ 'maxChainDepth',
223
+ 'attrEquals',
224
+ 'attrIn',
225
+ 'workloadIssuerIs',
226
+ 'workloadClaimEquals',
227
+ ]
228
+
229
+ /** Built-in preset policy names. */
230
+ static readonly AVAILABLE_PRESETS: string[] = ['standard']
231
+
232
+ /** Returns the list of available predicate method names. */
233
+ static availablePredicates(): string[] {
234
+ return [...PolicyBuilder.AVAILABLE_PREDICATES]
235
+ }
236
+
237
+ /** Returns the list of available preset policy names. */
238
+ static availablePresets(): string[] {
239
+ return [...PolicyBuilder.AVAILABLE_PRESETS]
240
+ }
241
+
242
+ /**
243
+ * Creates a standard policy requiring not-revoked, not-expired, and a capability.
244
+ *
245
+ * @param capability - Required capability string.
246
+ * @returns A new builder with the standard predicates.
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * const policy = PolicyBuilder.standard('sign_commit')
251
+ * ```
252
+ */
253
+ static standard(capability: string): PolicyBuilder {
254
+ return new PolicyBuilder()
255
+ .notRevoked()
256
+ .notExpired()
257
+ .requireCapability(capability)
258
+ }
259
+
260
+ /**
261
+ * Reconstructs a PolicyBuilder from a JSON policy expression.
262
+ *
263
+ * @param jsonStr - JSON string from `toJson()` or config files.
264
+ * @returns A new builder with the parsed predicates.
265
+ */
266
+ static fromJson(jsonStr: string): PolicyBuilder {
267
+ const expr = JSON.parse(jsonStr) as Record<string, unknown>
268
+ const result = new PolicyBuilder()
269
+ if (expr.op === 'And' && Array.isArray(expr.args)) {
270
+ result.predicates = expr.args as Predicate[]
271
+ } else {
272
+ result.predicates = [expr as Predicate]
273
+ }
274
+ return result
275
+ }
276
+
277
+ /**
278
+ * Creates a policy that passes if any of the given policies pass.
279
+ *
280
+ * @param builders - Policies to OR together.
281
+ * @returns A new builder combining the policies.
282
+ */
283
+ static anyOf(...builders: PolicyBuilder[]): PolicyBuilder {
284
+ const result = new PolicyBuilder()
285
+ const orArgs = builders.map(b => ({ op: 'And', args: b.predicates }))
286
+ result.predicates = [{ op: 'Or', args: orArgs }]
287
+ return result
288
+ }
289
+
290
+ /** Requires the attestation to not be revoked. */
291
+ notRevoked(): PolicyBuilder {
292
+ this.predicates.push({ op: 'NotRevoked' })
293
+ return this
294
+ }
295
+
296
+ /** Requires the attestation to not be expired. */
297
+ notExpired(): PolicyBuilder {
298
+ this.predicates.push({ op: 'NotExpired' })
299
+ return this
300
+ }
301
+
302
+ /**
303
+ * Requires the attestation to expire after the given number of seconds from now.
304
+ *
305
+ * @param seconds - Minimum remaining lifetime in seconds.
306
+ */
307
+ expiresAfter(seconds: number): PolicyBuilder {
308
+ this.predicates.push({ op: 'ExpiresAfter', args: seconds })
309
+ return this
310
+ }
311
+
312
+ /**
313
+ * Requires the attestation to have been issued within the given time window.
314
+ *
315
+ * @param seconds - Maximum age in seconds.
316
+ */
317
+ issuedWithin(seconds: number): PolicyBuilder {
318
+ this.predicates.push({ op: 'IssuedWithin', args: seconds })
319
+ return this
320
+ }
321
+
322
+ /**
323
+ * Requires the subject to hold a specific capability.
324
+ *
325
+ * @param cap - Capability string (e.g. `'sign'`, `'sign_commit'`).
326
+ */
327
+ requireCapability(cap: string): PolicyBuilder {
328
+ this.predicates.push({ op: 'HasCapability', args: cap })
329
+ return this
330
+ }
331
+
332
+ /**
333
+ * Requires the subject to hold all of the given capabilities.
334
+ *
335
+ * @param caps - Array of required capability strings.
336
+ */
337
+ requireAllCapabilities(caps: string[]): PolicyBuilder {
338
+ for (const cap of caps) {
339
+ this.requireCapability(cap)
340
+ }
341
+ return this
342
+ }
343
+
344
+ /**
345
+ * Requires the subject to hold at least one of the given capabilities.
346
+ *
347
+ * @param caps - Array of acceptable capability strings.
348
+ */
349
+ requireAnyCapability(caps: string[]): PolicyBuilder {
350
+ const orArgs = caps.map(c => ({ op: 'HasCapability', args: c }))
351
+ this.predicates.push({ op: 'Or', args: orArgs })
352
+ return this
353
+ }
354
+
355
+ /**
356
+ * Requires the issuer to match a specific DID.
357
+ *
358
+ * @param did - Required issuer DID.
359
+ */
360
+ requireIssuer(did: string): PolicyBuilder {
361
+ this.predicates.push({ op: 'IssuerIs', args: did })
362
+ return this
363
+ }
364
+
365
+ /**
366
+ * Requires the issuer to be one of the given DIDs.
367
+ *
368
+ * @param dids - Acceptable issuer DIDs.
369
+ */
370
+ requireIssuerIn(dids: string[]): PolicyBuilder {
371
+ const orArgs = dids.map(d => ({ op: 'IssuerIs', args: d }))
372
+ this.predicates.push({ op: 'Or', args: orArgs })
373
+ return this
374
+ }
375
+
376
+ /**
377
+ * Requires the subject to match a specific DID.
378
+ *
379
+ * @param did - Required subject DID.
380
+ */
381
+ requireSubject(did: string): PolicyBuilder {
382
+ this.predicates.push({ op: 'SubjectIs', args: did })
383
+ return this
384
+ }
385
+
386
+ /**
387
+ * Requires the attestation to have been delegated by a specific identity.
388
+ *
389
+ * @param did - DID of the required delegator.
390
+ */
391
+ requireDelegatedBy(did: string): PolicyBuilder {
392
+ this.predicates.push({ op: 'DelegatedBy', args: did })
393
+ return this
394
+ }
395
+
396
+ /** Requires the signer to be an agent. */
397
+ requireAgent(): PolicyBuilder {
398
+ this.predicates.push({ op: 'IsAgent' })
399
+ return this
400
+ }
401
+
402
+ /** Requires the signer to be a human. */
403
+ requireHuman(): PolicyBuilder {
404
+ this.predicates.push({ op: 'IsHuman' })
405
+ return this
406
+ }
407
+
408
+ /** Requires the signer to be a workload identity. */
409
+ requireWorkload(): PolicyBuilder {
410
+ this.predicates.push({ op: 'IsWorkload' })
411
+ return this
412
+ }
413
+
414
+ /**
415
+ * Requires the operation to target a specific repository.
416
+ *
417
+ * @param repo - Repository identifier (e.g. `'org/repo'`).
418
+ */
419
+ requireRepo(repo: string): PolicyBuilder {
420
+ this.predicates.push({ op: 'RepoIs', args: repo })
421
+ return this
422
+ }
423
+
424
+ /**
425
+ * Requires the operation to target one of the given repositories.
426
+ *
427
+ * @param repos - Acceptable repository identifiers.
428
+ */
429
+ requireRepoIn(repos: string[]): PolicyBuilder {
430
+ const orArgs = repos.map(r => ({ op: 'RepoIs', args: r }))
431
+ this.predicates.push({ op: 'Or', args: orArgs })
432
+ return this
433
+ }
434
+
435
+ /**
436
+ * Requires a specific deployment environment.
437
+ *
438
+ * @param env - Environment name (e.g. `'production'`).
439
+ */
440
+ requireEnv(env: string): PolicyBuilder {
441
+ this.predicates.push({ op: 'EnvIs', args: env })
442
+ return this
443
+ }
444
+
445
+ /**
446
+ * Requires one of the given deployment environments.
447
+ *
448
+ * @param envs - Acceptable environment names.
449
+ */
450
+ requireEnvIn(envs: string[]): PolicyBuilder {
451
+ const orArgs = envs.map(e => ({ op: 'EnvIs', args: e }))
452
+ this.predicates.push({ op: 'Or', args: orArgs })
453
+ return this
454
+ }
455
+
456
+ /**
457
+ * Requires the Git ref to match a pattern.
458
+ *
459
+ * @param pattern - Ref pattern (e.g. `'refs/heads/main'`).
460
+ */
461
+ refMatches(pattern: string): PolicyBuilder {
462
+ this.predicates.push({ op: 'RefMatches', args: pattern })
463
+ return this
464
+ }
465
+
466
+ /**
467
+ * Restricts allowed file paths.
468
+ *
469
+ * @param patterns - Glob patterns for allowed paths.
470
+ */
471
+ pathAllowed(patterns: string[]): PolicyBuilder {
472
+ this.predicates.push({ op: 'PathAllowed', args: patterns })
473
+ return this
474
+ }
475
+
476
+ /**
477
+ * Limits the maximum attestation chain depth.
478
+ *
479
+ * @param depth - Maximum allowed chain depth.
480
+ */
481
+ maxChainDepth(depth: number): PolicyBuilder {
482
+ this.predicates.push({ op: 'MaxChainDepth', args: depth })
483
+ return this
484
+ }
485
+
486
+ /**
487
+ * Requires an attestation attribute to equal a specific value.
488
+ *
489
+ * @param key - Attribute key.
490
+ * @param value - Required attribute value.
491
+ */
492
+ attrEquals(key: string, value: string): PolicyBuilder {
493
+ this.predicates.push({ op: 'AttrEquals', args: { key, value } })
494
+ return this
495
+ }
496
+
497
+ /**
498
+ * Requires an attestation attribute to be one of the given values.
499
+ *
500
+ * @param key - Attribute key.
501
+ * @param values - Acceptable attribute values.
502
+ */
503
+ attrIn(key: string, values: string[]): PolicyBuilder {
504
+ this.predicates.push({ op: 'AttrIn', args: { key, values } })
505
+ return this
506
+ }
507
+
508
+ /**
509
+ * Requires the workload attestation issuer to match a specific DID.
510
+ *
511
+ * @param did - Required workload issuer DID.
512
+ */
513
+ workloadIssuerIs(did: string): PolicyBuilder {
514
+ this.predicates.push({ op: 'WorkloadIssuerIs', args: did })
515
+ return this
516
+ }
517
+
518
+ /**
519
+ * Requires a workload attestation claim to equal a specific value.
520
+ *
521
+ * @param key - Claim key.
522
+ * @param value - Required claim value.
523
+ */
524
+ workloadClaimEquals(key: string, value: string): PolicyBuilder {
525
+ this.predicates.push({ op: 'WorkloadClaimEquals', args: { key, value } })
526
+ return this
527
+ }
528
+
529
+ /**
530
+ * Combines this policy with another using OR logic.
531
+ *
532
+ * @param other - The other policy builder.
533
+ * @returns A new builder that passes if either policy passes.
534
+ */
535
+ orPolicy(other: PolicyBuilder): PolicyBuilder {
536
+ return PolicyBuilder.anyOf(this, other)
537
+ }
538
+
539
+ /**
540
+ * Negates this policy — passes when the original would deny, and vice versa.
541
+ *
542
+ * @returns A new negated builder.
543
+ */
544
+ negate(): PolicyBuilder {
545
+ const result = new PolicyBuilder()
546
+ result.predicates = [{ op: 'Not', args: { op: 'And', args: this.predicates } }]
547
+ return result
548
+ }
549
+
550
+ /**
551
+ * Serializes the policy to JSON without compiling.
552
+ *
553
+ * @returns JSON string representation of the policy expression.
554
+ * @throws Error if the policy has no predicates.
555
+ */
556
+ toJson(): string {
557
+ if (this.predicates.length === 0) {
558
+ throw new Error('Cannot export an empty policy.')
559
+ }
560
+ const expr = { op: 'And', args: this.predicates }
561
+ return JSON.stringify(expr)
562
+ }
563
+
564
+ /**
565
+ * Compiles the policy for evaluation using the native policy engine.
566
+ *
567
+ * @returns Compiled policy JSON string.
568
+ * @throws {@link AuthsError} if compilation fails.
569
+ * @throws Error if the policy has no predicates.
570
+ */
571
+ build(): string {
572
+ if (this.predicates.length === 0) {
573
+ throw new Error(
574
+ 'Cannot build an empty policy. Add at least one predicate, ' +
575
+ 'or use PolicyBuilder.standard("capability") for the common case.'
576
+ )
577
+ }
578
+ const json = this.toJson()
579
+ try {
580
+ return native.compilePolicy(json)
581
+ } catch (err) {
582
+ throw mapNativeError(err, AuthsError)
583
+ }
584
+ }
585
+
586
+ /**
587
+ * Builds and evaluates the policy against a context in one step.
588
+ *
589
+ * @param context - The evaluation context.
590
+ * @returns The policy decision.
591
+ * @throws {@link AuthsError} if compilation or evaluation fails.
592
+ *
593
+ * @example
594
+ * ```typescript
595
+ * const decision = PolicyBuilder.standard('sign').evaluate({
596
+ * issuer: 'did:keri:EOrg',
597
+ * subject: 'did:key:zDevice',
598
+ * capabilities: ['sign'],
599
+ * })
600
+ * console.log(decision.allowed) // true
601
+ * ```
602
+ */
603
+ evaluate(context: EvalContextOpts): PolicyDecision {
604
+ const compiledJson = this.build()
605
+ return evaluatePolicy(compiledJson, context)
606
+ }
607
+ }
608
+
609
+ /**
610
+ * Compiles a raw policy JSON string for use with {@link evaluatePolicy}.
611
+ *
612
+ * @param policyJson - JSON string of the policy expression.
613
+ * @returns Compiled policy JSON.
614
+ * @throws {@link AuthsError} if the policy is invalid.
615
+ */
616
+ export function compilePolicy(policyJson: string): string {
617
+ try {
618
+ return native.compilePolicy(policyJson)
619
+ } catch (err) {
620
+ throw mapNativeError(err, AuthsError)
621
+ }
622
+ }
623
+
624
+ /**
625
+ * Evaluates a compiled policy against an attestation context.
626
+ *
627
+ * @param compiledPolicyJson - Compiled policy from {@link compilePolicy} or {@link PolicyBuilder.build}.
628
+ * @param context - The evaluation context.
629
+ * @returns The policy decision with `allowed`/`denied` convenience booleans.
630
+ * @throws {@link AuthsError} if evaluation fails.
631
+ *
632
+ * @example
633
+ * ```typescript
634
+ * import { compilePolicy, evaluatePolicy } from '@auths-dev/sdk'
635
+ *
636
+ * const compiled = compilePolicy(policyJson)
637
+ * const decision = evaluatePolicy(compiled, {
638
+ * issuer: 'did:keri:EOrg',
639
+ * subject: 'did:key:zDevice',
640
+ * })
641
+ * ```
642
+ */
643
+ export function evaluatePolicy(compiledPolicyJson: string, context: EvalContextOpts): PolicyDecision {
644
+ try {
645
+ const result = native.evaluatePolicy(
646
+ compiledPolicyJson,
647
+ context.issuer,
648
+ context.subject,
649
+ context.capabilities ?? null,
650
+ context.role ?? null,
651
+ context.revoked ?? null,
652
+ context.expiresAt ?? null,
653
+ context.repo ?? null,
654
+ context.environment ?? null,
655
+ context.signerType ?? null,
656
+ context.delegatedBy ?? null,
657
+ context.chainDepth ?? null,
658
+ )
659
+ return {
660
+ outcome: result.outcome,
661
+ reason: result.reason,
662
+ message: result.message,
663
+ allowed: result.outcome === 'allow',
664
+ denied: result.outcome === 'deny',
665
+ }
666
+ } catch (err) {
667
+ throw mapNativeError(err, AuthsError)
668
+ }
669
+ }