@apoa/core 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.
@@ -0,0 +1,506 @@
1
+ /** The human granting authority. */
2
+ interface Principal {
3
+ id: string;
4
+ name?: string;
5
+ }
6
+ /** The AI agent receiving authority. */
7
+ interface Agent {
8
+ id: string;
9
+ name?: string;
10
+ provider?: string;
11
+ }
12
+ /** Allowed constraint value types. */
13
+ type ConstraintValue = boolean | number | string | string[];
14
+ /** A map of constraint names to their values. */
15
+ type ConstraintMap = Record<string, ConstraintValue>;
16
+ /** How the agent accesses the service. */
17
+ type AccessMode = 'api' | 'browser';
18
+ /** Configuration for browser-based access via secure credential injection. */
19
+ interface BrowserSessionConfig {
20
+ allowedUrls: string[];
21
+ credentialVaultRef: string;
22
+ allowFormInteraction?: boolean;
23
+ allowNavigation?: boolean;
24
+ maxSessionDuration?: number;
25
+ captureScreenshots?: boolean;
26
+ blockedActions?: string[];
27
+ }
28
+ /** Configuration for API-based access. */
29
+ interface APIAccessConfig {
30
+ authorizationServer?: string;
31
+ oauthScopes?: string[];
32
+ useDPoP?: boolean;
33
+ }
34
+ /** The organization operating the AI agent. */
35
+ interface AgentProvider {
36
+ name: string;
37
+ id?: string;
38
+ contact?: string;
39
+ }
40
+ /** The legal model under which this authorization operates. */
41
+ interface LegalFramework {
42
+ model: 'provider-as-agent';
43
+ jurisdiction?: string;
44
+ legalBasis?: string[];
45
+ pairedLegalInstrument?: boolean;
46
+ }
47
+ /** A service the agent is authorized to access. */
48
+ interface ServiceAuthorization {
49
+ service: string;
50
+ scopes: string[];
51
+ constraints?: ConstraintMap;
52
+ accessMode?: AccessMode;
53
+ browserConfig?: BrowserSessionConfig;
54
+ apiConfig?: APIAccessConfig;
55
+ }
56
+ /** Called when a soft rule is violated. */
57
+ type OnRuleViolation = (violation: RuleViolation) => void | Promise<void>;
58
+ /** Rules that govern agent behavior. */
59
+ interface Rule {
60
+ id: string;
61
+ description: string;
62
+ enforcement: 'hard' | 'soft';
63
+ onViolation?: OnRuleViolation;
64
+ }
65
+ /** Logged when a soft rule is violated. */
66
+ interface RuleViolation {
67
+ ruleId: string;
68
+ tokenId: string;
69
+ action: string;
70
+ service: string;
71
+ timestamp: Date;
72
+ details?: string;
73
+ }
74
+ /** Metadata values are JSON-serializable primitives only. */
75
+ type MetadataValue = string | number | boolean | null;
76
+ /** Token metadata — flat key-value pairs. Max 20 keys, max 1KB serialized. */
77
+ type TokenMetadata = Record<string, MetadataValue>;
78
+ /** The full APOA authorization definition. */
79
+ interface APOADefinition {
80
+ principal: Principal;
81
+ agent: Agent;
82
+ agentProvider?: AgentProvider;
83
+ services: ServiceAuthorization[];
84
+ rules?: Rule[];
85
+ notBefore?: Date | string;
86
+ expires: Date | string;
87
+ revocable?: boolean;
88
+ delegatable?: boolean;
89
+ maxDelegationDepth?: number;
90
+ metadata?: TokenMetadata;
91
+ legal?: LegalFramework;
92
+ }
93
+ /** A signed, issued APOA token. */
94
+ interface APOAToken {
95
+ jti: string;
96
+ definition: APOADefinition;
97
+ issuedAt: Date;
98
+ signature: string;
99
+ issuer: string;
100
+ audience?: string[];
101
+ parentToken?: string;
102
+ raw: string;
103
+ }
104
+ /** Audit detail values — serializable primitives only. */
105
+ type AuditDetailValue = string | number | boolean | null;
106
+ /** What gets logged every time the agent does something. */
107
+ interface AuditEntry {
108
+ tokenId: string;
109
+ timestamp: Date;
110
+ action: string;
111
+ service: string;
112
+ result: 'allowed' | 'denied' | 'escalated';
113
+ details?: Record<string, AuditDetailValue>;
114
+ url?: string;
115
+ screenshotRef?: string;
116
+ accessMode?: AccessMode;
117
+ }
118
+ /** Scope check result. */
119
+ interface ScopeCheckResult {
120
+ allowed: boolean;
121
+ reason: string;
122
+ matchedScope?: string;
123
+ constraint?: string;
124
+ }
125
+ /** Revocation record. */
126
+ interface RevocationRecord {
127
+ tokenId: string;
128
+ revokedAt: Date;
129
+ revokedBy: string;
130
+ reason?: string;
131
+ cascaded: string[];
132
+ }
133
+ /** Options for revoking a token. */
134
+ interface RevocationOptions {
135
+ revokedBy: string;
136
+ reason?: string;
137
+ cascade?: boolean;
138
+ }
139
+ /** Options for querying audit trails. */
140
+ interface AuditQueryOptions {
141
+ from?: Date;
142
+ to?: Date;
143
+ action?: string;
144
+ service?: string;
145
+ result?: 'allowed' | 'denied' | 'escalated';
146
+ limit?: number;
147
+ offset?: number;
148
+ }
149
+ /** Result of verifying a delegation chain. */
150
+ interface ChainVerificationResult {
151
+ valid: boolean;
152
+ depth: number;
153
+ errors: string[];
154
+ failedAt?: number;
155
+ root: APOAToken;
156
+ leaf: APOAToken;
157
+ }
158
+ /** An ordered list of tokens from root to leaf. */
159
+ type DelegationChain = APOAToken[];
160
+ /** Definition for creating a delegated token. */
161
+ interface DelegationDefinition {
162
+ agent: Agent;
163
+ services: ServiceAuthorization[];
164
+ rules?: Rule[];
165
+ expires?: Date | string;
166
+ metadata?: TokenMetadata;
167
+ }
168
+ /** Options for signing tokens. */
169
+ interface SigningOptions {
170
+ privateKey: CryptoKey;
171
+ algorithm?: 'EdDSA' | 'ES256';
172
+ kid?: string;
173
+ }
174
+ /** Options for validating tokens. */
175
+ interface ValidationOptions {
176
+ publicKey?: CryptoKey;
177
+ keyResolver?: KeyResolver;
178
+ publicKeyResolver?: (issuer: string) => Promise<CryptoKey>;
179
+ checkRevocation?: boolean;
180
+ revocationStore?: RevocationStore;
181
+ clockSkew?: number;
182
+ }
183
+ /** Result of token validation. */
184
+ interface ValidationResult {
185
+ valid: boolean;
186
+ errors: string[];
187
+ token?: APOAToken;
188
+ warnings?: string[];
189
+ }
190
+ /** Resolves a public key by its Key ID. */
191
+ interface KeyResolver {
192
+ resolve(kid: string): Promise<CryptoKey | null>;
193
+ }
194
+ /** Options for the authorize() function. */
195
+ interface AuthorizeOptions {
196
+ revocationStore?: RevocationStore;
197
+ auditStore?: AuditStore;
198
+ context?: Record<string, ConstraintValue>;
199
+ }
200
+ /** Result of an authorization check. */
201
+ interface AuthorizationResult {
202
+ authorized: boolean;
203
+ reason?: string;
204
+ checks: {
205
+ revoked: boolean;
206
+ scopeAllowed?: boolean;
207
+ constraintsPassed?: boolean;
208
+ rulesPassed?: boolean;
209
+ };
210
+ violations?: RuleViolation[];
211
+ }
212
+ /** Revocation store interface. */
213
+ interface RevocationStore {
214
+ add(record: RevocationRecord): Promise<void>;
215
+ check(tokenId: string): Promise<RevocationRecord | null>;
216
+ list(principalId: string): Promise<RevocationRecord[]>;
217
+ }
218
+ /** Audit store interface. */
219
+ interface AuditStore {
220
+ append(entry: AuditEntry): Promise<void>;
221
+ query(tokenId: string, options?: AuditQueryOptions): Promise<AuditEntry[]>;
222
+ queryByService(service: string, options?: AuditQueryOptions): Promise<AuditEntry[]>;
223
+ }
224
+ /** Options for creating a client. */
225
+ interface APOAClientOptions {
226
+ revocationStore?: RevocationStore;
227
+ auditStore?: AuditStore;
228
+ keyResolver?: KeyResolver;
229
+ defaultSigningOptions?: Partial<SigningOptions>;
230
+ }
231
+ /** The configured APOA client. */
232
+ interface APOAClient {
233
+ createToken(definition: APOADefinition, options?: SigningOptions): Promise<APOAToken>;
234
+ parseDefinition(input: string, format?: 'yaml' | 'json'): APOADefinition;
235
+ validateToken(token: string | APOAToken, options?: Omit<ValidationOptions, 'revocationStore'>): Promise<ValidationResult>;
236
+ generateKeyPair(algorithm?: 'EdDSA' | 'ES256'): Promise<CryptoKeyPair>;
237
+ checkScope(token: APOAToken, service: string, action: string): ScopeCheckResult;
238
+ checkConstraint(token: APOAToken, service: string, constraint: string): ScopeCheckResult;
239
+ authorize(token: APOAToken, service: string, action: string, options?: Omit<AuthorizeOptions, 'revocationStore' | 'auditStore'>): Promise<AuthorizationResult>;
240
+ delegate(parentToken: APOAToken, childDef: DelegationDefinition, options?: SigningOptions): Promise<APOAToken>;
241
+ verifyChain(chain: DelegationChain): Promise<ChainVerificationResult>;
242
+ revoke(tokenId: string, options: RevocationOptions): Promise<RevocationRecord>;
243
+ isRevoked(tokenId: string): Promise<boolean>;
244
+ logAction(tokenId: string, entry: Omit<AuditEntry, 'tokenId' | 'timestamp'>): Promise<void>;
245
+ getAuditTrail(tokenId: string, options?: AuditQueryOptions): Promise<AuditEntry[]>;
246
+ getAuditTrailByService(service: string, options?: AuditQueryOptions): Promise<AuditEntry[]>;
247
+ }
248
+
249
+ /** Base error class for all APOA errors. */
250
+ declare class APOAError extends Error {
251
+ code: string;
252
+ constructor(message: string, code: string);
253
+ }
254
+ /** Thrown when a token has expired. */
255
+ declare class TokenExpiredError extends APOAError {
256
+ expiredAt: Date;
257
+ constructor(message: string, expiredAt: Date);
258
+ }
259
+ /** Thrown when an action is outside the token's authorized scopes. */
260
+ declare class ScopeViolationError extends APOAError {
261
+ requestedScope: string;
262
+ availableScopes: string[];
263
+ constructor(message: string, requestedScope: string, availableScopes: string[]);
264
+ }
265
+ /** Thrown when a delegated token tries to exceed parent permissions. */
266
+ declare class AttenuationViolationError extends APOAError {
267
+ parentScope: string[];
268
+ requestedScope: string[];
269
+ constructor(message: string, parentScope: string[], requestedScope: string[]);
270
+ }
271
+ /** Thrown when trying to use a revoked token. */
272
+ declare class RevocationError extends APOAError {
273
+ revokedAt: Date;
274
+ revokedBy: string;
275
+ constructor(message: string, revokedAt: Date, revokedBy: string);
276
+ }
277
+ /** Thrown when delegation chain verification fails. */
278
+ declare class ChainVerificationError extends APOAError {
279
+ failedAt: number;
280
+ reason: string;
281
+ constructor(message: string, failedAt: number, reason: string);
282
+ }
283
+ /** Thrown when token metadata fails validation. */
284
+ declare class MetadataValidationError extends APOAError {
285
+ field?: string;
286
+ constructor(message: string, field?: string);
287
+ }
288
+ /** Thrown when a hard rule is violated. */
289
+ declare class RuleEnforcementError extends APOAError {
290
+ ruleId: string;
291
+ enforcement: 'hard';
292
+ constructor(message: string, ruleId: string);
293
+ }
294
+ /** Thrown when a definition fails validation. */
295
+ declare class DefinitionValidationError extends APOAError {
296
+ errors: string[];
297
+ constructor(message: string, errors: string[]);
298
+ }
299
+
300
+ /**
301
+ * Generate an Ed25519 or ES256 key pair for signing and verifying tokens.
302
+ */
303
+ declare function generateKeyPair(algorithm?: 'EdDSA' | 'ES256'): Promise<CryptoKeyPair>;
304
+ /**
305
+ * Sign a payload, producing a compact JWS string.
306
+ */
307
+ declare function sign(payload: Record<string, unknown>, options: SigningOptions): Promise<string>;
308
+ /**
309
+ * Verify a compact JWS string and return the decoded payload.
310
+ */
311
+ declare function verify(token: string, key: CryptoKey): Promise<Record<string, unknown>>;
312
+
313
+ /**
314
+ * Check if a token/date has expired, accounting for clock skew.
315
+ * Returns true if the current time is past the expiration (plus tolerance).
316
+ */
317
+ declare function isExpired(expires: Date | string, clockSkew?: number, now?: Date): boolean;
318
+ /**
319
+ * Check if the current time is before the notBefore date, accounting for clock skew.
320
+ * Returns true if it's too early to use this token.
321
+ */
322
+ declare function isBeforeNotBefore(notBefore: Date | string, clockSkew?: number, now?: Date): boolean;
323
+
324
+ /**
325
+ * Parse a scope string into its segments.
326
+ * e.g., "appointments:read" → ["appointments", "read"]
327
+ */
328
+ declare function parseScope(scope: string): string[];
329
+ /**
330
+ * Check if a scope pattern matches a requested scope.
331
+ *
332
+ * Rules:
333
+ * 1. Root wildcard "*" matches everything
334
+ * 2. Exact match: "appointments:read" matches "appointments:read"
335
+ * 3. Wildcard at level: "appointments:*" matches "appointments:read"
336
+ * but NOT "appointments:read:summary" (wildcards don't cross levels)
337
+ * 4. Segment-by-segment matching with wildcard support at each level
338
+ */
339
+ declare function matchScope(pattern: string, requested: string): boolean;
340
+
341
+ /**
342
+ * In-memory revocation store for dev/testing.
343
+ * Data is lost when the process exits.
344
+ */
345
+ declare class MemoryRevocationStore implements RevocationStore {
346
+ private records;
347
+ add(record: RevocationRecord): Promise<void>;
348
+ check(tokenId: string): Promise<RevocationRecord | null>;
349
+ list(principalId: string): Promise<RevocationRecord[]>;
350
+ }
351
+
352
+ /**
353
+ * In-memory audit store for dev/testing.
354
+ * Data is lost when the process exits.
355
+ */
356
+ declare class MemoryAuditStore implements AuditStore {
357
+ private entries;
358
+ append(entry: AuditEntry): Promise<void>;
359
+ query(tokenId: string, options?: AuditQueryOptions): Promise<AuditEntry[]>;
360
+ queryByService(service: string, options?: AuditQueryOptions): Promise<AuditEntry[]>;
361
+ }
362
+
363
+ /**
364
+ * Check if an action is allowed under a token's scopes for a given service.
365
+ * Synchronous — no rules, no revocation, just scope matching.
366
+ */
367
+ declare function checkScope(token: APOAToken, service: string, action: string): ScopeCheckResult;
368
+ /**
369
+ * Check a specific constraint on a service.
370
+ * Returns allowed: false if the constraint is explicitly set to false.
371
+ * Returns allowed: true if the constraint is not set or is truthy.
372
+ */
373
+ declare function checkConstraint(token: APOAToken, service: string, constraint: string): ScopeCheckResult;
374
+ /**
375
+ * One-stop authorization check: revocation + scope + constraints + rules.
376
+ *
377
+ * Enforcement order:
378
+ * 1. Check revocation (is the token still alive?)
379
+ * 2. Check scope (is the action in the authorized scope set?)
380
+ * 3. Check constraints (only the action's top-level segment is checked
381
+ * against constraint keys — e.g. action "signing:submit" checks
382
+ * constraint "signing". Use checkConstraint() for explicit checks.)
383
+ * 4. Check hard rules (hard rules whose id appears as a prefix of the
384
+ * action → deny. e.g. rule "no-messaging" blocks "messaging:send")
385
+ * 5. Check soft rules (all soft rules → log violation + continue)
386
+ */
387
+ declare function authorize(token: APOAToken, service: string, action: string, options?: AuthorizeOptions): Promise<AuthorizationResult>;
388
+
389
+ /**
390
+ * Parse a YAML or JSON definition string into an APOADefinition.
391
+ * Auto-detects format: starts with '{' = JSON, otherwise YAML.
392
+ * Validates required fields, metadata constraints, browser mode config,
393
+ * and legal framework fields. Throws DefinitionValidationError with all
394
+ * problems found.
395
+ */
396
+ declare function parseDefinition(input: string, format?: 'yaml' | 'json'): APOADefinition;
397
+
398
+ /**
399
+ * Revoke a token. No cascade logic — that's Phase 3.
400
+ */
401
+ declare function revoke(tokenId: string, options: RevocationOptions, store?: RevocationStore): Promise<RevocationRecord>;
402
+ /**
403
+ * Check if a token has been revoked.
404
+ */
405
+ declare function isRevoked(tokenId: string, store?: RevocationStore): Promise<boolean>;
406
+
407
+ /**
408
+ * Log an action against a token.
409
+ */
410
+ declare function logAction(tokenId: string, entry: Omit<AuditEntry, 'tokenId' | 'timestamp'>, store?: AuditStore): Promise<void>;
411
+
412
+ /**
413
+ * Get the audit trail for a token.
414
+ */
415
+ declare function getAuditTrail(tokenId: string, options?: AuditQueryOptions, store?: AuditStore): Promise<AuditEntry[]>;
416
+ /**
417
+ * Get the audit trail for a service — across all tokens.
418
+ */
419
+ declare function getAuditTrailByService(service: string, options?: AuditQueryOptions, store?: AuditStore): Promise<AuditEntry[]>;
420
+
421
+ /**
422
+ * Create an APOA token from a definition.
423
+ * Generates a UUID for jti, validates metadata, derives audience,
424
+ * warns at 4KB, rejects above 8KB.
425
+ */
426
+ declare function createToken(definition: APOADefinition, options: SigningOptions): Promise<APOAToken>;
427
+
428
+ /**
429
+ * Sign an APOA token payload as a compact JWS.
430
+ * Embeds `kid` in the JWT header when provided.
431
+ * Supports EdDSA (default) and ES256.
432
+ */
433
+ declare function signToken(payload: Record<string, unknown>, options: SigningOptions): Promise<string>;
434
+ /**
435
+ * Decode a compact JWS protected header without verifying the signature.
436
+ * Used to extract `kid` and `alg` for key resolution.
437
+ */
438
+ declare function decodeHeader(token: string): Record<string, unknown>;
439
+ /**
440
+ * Verify a compact JWS and return the decoded payload.
441
+ */
442
+ declare function verifySignature(token: string, key: CryptoKey): Promise<Record<string, unknown>>;
443
+
444
+ /**
445
+ * Validate a token — check signature, expiration, structure, revocation.
446
+ * Returns a detailed result with all errors found.
447
+ *
448
+ * Accepts either a raw JWT string or an APOAToken object.
449
+ * When given a string, decodes and verifies the signature.
450
+ * When given an APOAToken, uses the .raw field for signature verification.
451
+ */
452
+ declare function validateToken(token: string | APOAToken, options: ValidationOptions): Promise<ValidationResult>;
453
+
454
+ /**
455
+ * Cascade revoke: revoke a parent token and all child tokens in a delegation chain.
456
+ * Populates RevocationRecord.cascaded with child token IDs.
457
+ *
458
+ * @param parentTokenId - The parent token's jti to revoke
459
+ * @param childTokenIds - Array of child token jti values to cascade-revoke
460
+ * @param options - Revocation options (revokedBy, reason)
461
+ * @param store - Optional revocation store
462
+ */
463
+ declare function cascadeRevoke(parentTokenId: string, childTokenIds: string[], options: RevocationOptions, store?: RevocationStore): Promise<RevocationRecord>;
464
+
465
+ /**
466
+ * Verify that a delegation definition is a valid attenuation of a parent token.
467
+ * Throws AttenuationViolationError on any violation.
468
+ *
469
+ * Checks:
470
+ * 1. Child scopes are a subset of parent scopes (per service)
471
+ * 2. Child constraints do not relax parent constraints
472
+ * 3. Child expiration <= parent expiration
473
+ * 4. maxDelegationDepth is not exceeded
474
+ * 5. Child does not add services the parent doesn't have
475
+ * 6. Child rules only add, never remove parent rules
476
+ */
477
+ declare function verifyAttenuation(parent: APOAToken, child: DelegationDefinition, currentDepth?: number): void;
478
+
479
+ /**
480
+ * Create a delegated (attenuated) token from a parent token.
481
+ *
482
+ * - Inherits principal from parent (cannot be overridden)
483
+ * - Sets parentToken on child to parent's jti
484
+ * - Enforces attenuation rules
485
+ * - Additional rules can only be added, not removed
486
+ */
487
+ declare function delegate(parentToken: APOAToken, childDef: DelegationDefinition, options: SigningOptions): Promise<APOAToken>;
488
+
489
+ /**
490
+ * Verify a full delegation chain.
491
+ *
492
+ * - Verifies every link attenuates the previous one (scopes are subsets)
493
+ * - Checks expiration of every token (if any parent expired, chain is invalid)
494
+ * - If RevocationStore provided, checks revocation of every token
495
+ * - Reports all errors found, plus failedAt index
496
+ */
497
+ declare function verifyChain(chain: DelegationChain, store?: RevocationStore): Promise<ChainVerificationResult>;
498
+
499
+ /**
500
+ * Create a configured APOA client.
501
+ * Wires up RevocationStore and AuditStore so methods don't need explicit store params.
502
+ * Defaults to MemoryRevocationStore + MemoryAuditStore for zero-config dev/testing.
503
+ */
504
+ declare function createClient(options?: APOAClientOptions): APOAClient;
505
+
506
+ export { type APIAccessConfig, type APOAClient, type APOAClientOptions, type APOADefinition, APOAError, type APOAToken, type AccessMode, type Agent, type AgentProvider, AttenuationViolationError, type AuditDetailValue, type AuditEntry, type AuditQueryOptions, type AuditStore, type AuthorizationResult, type AuthorizeOptions, type BrowserSessionConfig, ChainVerificationError, type ChainVerificationResult, type ConstraintMap, type ConstraintValue, DefinitionValidationError, type DelegationChain, type DelegationDefinition, type KeyResolver, type LegalFramework, MemoryAuditStore, MemoryRevocationStore, MetadataValidationError, type MetadataValue, type OnRuleViolation, type Principal, RevocationError, type RevocationOptions, type RevocationRecord, type RevocationStore, type Rule, RuleEnforcementError, type RuleViolation, type ScopeCheckResult, ScopeViolationError, type ServiceAuthorization, type SigningOptions, TokenExpiredError, type TokenMetadata, type ValidationOptions, type ValidationResult, authorize, cascadeRevoke, checkConstraint, checkScope, createClient, createToken, decodeHeader, delegate, generateKeyPair, getAuditTrail, getAuditTrailByService, isBeforeNotBefore, isExpired, isRevoked, logAction, matchScope, parseDefinition, parseScope, revoke, sign, signToken, validateToken, verify, verifyAttenuation, verifyChain, verifySignature };