@humanagencyp/hap-core 0.4.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/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @humanagencyprotocol/hap-core
2
+
3
+ Core types, cryptographic primitives, and verification logic for the [Human Agency Protocol](https://humanagencyprotocol.com).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @humanagencyprotocol/hap-core
9
+ ```
10
+
11
+ ## What's included
12
+
13
+ - **Types** — `AgentProfile`, `AgentBoundsParams`, `AgentContextParams`, execution context schemas, gate questions
14
+ - **Frame** — Canonical hashing of bounds and context (`computeBoundsHash`, `computeContextHash`)
15
+ - **Attestation** — Ed25519 signing and verification of attestation blobs
16
+ - **Gatekeeper** — Execution verification against bounds, context constraints, and cumulative tracking
17
+ - **Profiles** — Runtime profile registry (`registerProfile`, `getProfile`, `getAllProfiles`, `clearProfiles`)
18
+
19
+ ## Usage
20
+
21
+ ```typescript
22
+ import {
23
+ type AgentProfile,
24
+ computeBoundsHash,
25
+ computeContextHash,
26
+ verify,
27
+ registerProfile,
28
+ getProfile,
29
+ } from '@humanagencyprotocol/hap-core';
30
+
31
+ // Register a profile
32
+ registerProfile(profile.id, profile);
33
+
34
+ // Compute deterministic hashes for bounds and context
35
+ const boundsHash = computeBoundsHash(bounds, profile);
36
+ const contextHash = computeContextHash(context, profile);
37
+
38
+ // Verify an execution against bounds and attestations
39
+ const result = await verify(
40
+ { frame, attestations, execution, context },
41
+ publicKeyHex,
42
+ );
43
+
44
+ if (result.approved) {
45
+ // Execution is within bounds
46
+ }
47
+ ```
48
+
49
+ ## Constraint types
50
+
51
+ The gatekeeper enforces three types of constraints from the profile's bounds and context schemas:
52
+
53
+ | Type | Description | Example |
54
+ |---|---|---|
55
+ | `max` | Numeric upper bound | `amount_max: 1000` |
56
+ | `enum` | Value must be in allowed set | `currency: ["USD", "EUR"]` |
57
+ | `subset` | Every item must appear in bound | `allowed_domains: "acme.com,partner.org"` |
58
+
59
+ ## Cumulative tracking
60
+
61
+ Bounds fields ending in `_daily_max` or `_monthly_max` are checked against cumulative execution logs. The gatekeeper resolves running totals from an `ExecutionLog` interface:
62
+
63
+ ```typescript
64
+ interface ExecutionLog {
65
+ sumByWindow(profileId: string, path: string, field: string, window: 'daily' | 'monthly'): Promise<number>;
66
+ }
67
+ ```
68
+
69
+ ## License
70
+
71
+ MIT
@@ -0,0 +1,499 @@
1
+ /**
2
+ * HAP Core Types — Agent Demo
3
+ *
4
+ * Types for agent-oriented profiles with bounded execution.
5
+ */
6
+ interface AttestationHeader {
7
+ typ: 'HAP-attestation';
8
+ alg: 'EdDSA';
9
+ kid?: string;
10
+ }
11
+ interface ResolvedDomain {
12
+ domain: string;
13
+ did: string;
14
+ }
15
+ interface AttestationPayload {
16
+ attestation_id: string;
17
+ version: '0.3' | '0.4';
18
+ profile_id: string;
19
+ /** v0.3 (deprecated) — hash of the authorization frame */
20
+ frame_hash?: string;
21
+ /** v0.4 — hash of the bounds parameters */
22
+ bounds_hash?: string;
23
+ /** v0.4 — hash of the context parameters */
24
+ context_hash?: string;
25
+ execution_context_hash: string;
26
+ resolved_domains: ResolvedDomain[];
27
+ gate_content_hashes: Record<string, string>;
28
+ issued_at: number;
29
+ expires_at: number;
30
+ }
31
+ interface Attestation {
32
+ header: AttestationHeader;
33
+ payload: AttestationPayload;
34
+ signature: string;
35
+ }
36
+ /**
37
+ * Field constraint type — what kind of bound a field supports.
38
+ * - max: numeric upper bound (actual <= bound)
39
+ * - enum: value must be in the allowed set
40
+ * - subset: every item in actual must appear in bound (comma-separated, case-insensitive)
41
+ */
42
+ interface FieldConstraint {
43
+ type: 'number' | 'string';
44
+ enforceable: Array<'max' | 'enum' | 'subset'>;
45
+ }
46
+ /**
47
+ * Frame field definition within a profile.
48
+ */
49
+ interface ProfileFrameField {
50
+ type: 'string' | 'number';
51
+ required: boolean;
52
+ description?: string;
53
+ constraint?: FieldConstraint;
54
+ enum?: string[];
55
+ }
56
+ /**
57
+ * Bounds field definition within a v0.4 profile.
58
+ */
59
+ interface ProfileBoundsField {
60
+ type: 'string' | 'number';
61
+ required: boolean;
62
+ description?: string;
63
+ displayName?: string;
64
+ format?: 'email' | 'domain' | 'url' | 'currency';
65
+ constraint?: FieldConstraint;
66
+ enum?: string[];
67
+ }
68
+ /**
69
+ * Context field definition within a v0.4 profile.
70
+ */
71
+ interface ProfileContextField {
72
+ type: 'string' | 'number';
73
+ required: boolean;
74
+ description?: string;
75
+ displayName?: string;
76
+ format?: 'email' | 'domain' | 'url' | 'currency';
77
+ constraint?: FieldConstraint;
78
+ enum?: string[];
79
+ }
80
+ /**
81
+ * Execution context field definition — declared source (value comes from the agent's tool call).
82
+ */
83
+ interface DeclaredFieldDef {
84
+ source: 'declared';
85
+ description: string;
86
+ required: boolean;
87
+ constraint?: FieldConstraint;
88
+ }
89
+ /**
90
+ * Cumulative window types for stateful limit tracking.
91
+ */
92
+ type CumulativeWindow = 'daily' | 'weekly' | 'monthly';
93
+ /**
94
+ * Execution context field definition — cumulative source (resolved from execution log).
95
+ *
96
+ * The gatekeeper resolves these by querying the execution log:
97
+ * - `cumulativeField`: which declared field to sum (use "_count" for plain counting)
98
+ * - `window`: time window for aggregation (daily, weekly, monthly)
99
+ *
100
+ * The resolved value = running total within window + current call value.
101
+ */
102
+ interface CumulativeFieldDef {
103
+ source: 'cumulative';
104
+ cumulativeField: string;
105
+ window: CumulativeWindow;
106
+ description: string;
107
+ required: boolean;
108
+ constraint?: FieldConstraint;
109
+ }
110
+ /**
111
+ * Execution context field definition — either declared or cumulative.
112
+ */
113
+ type ExecutionContextFieldDef = DeclaredFieldDef | CumulativeFieldDef;
114
+ /**
115
+ * Gate question definition.
116
+ */
117
+ interface GateQuestion {
118
+ question: string;
119
+ required: boolean;
120
+ }
121
+ /**
122
+ * Execution path definition within a profile.
123
+ */
124
+ interface ExecutionPath {
125
+ description: string;
126
+ requiredDomains?: string[];
127
+ ttl?: {
128
+ default: number;
129
+ max: number;
130
+ };
131
+ }
132
+ /**
133
+ * Agent Profile — defines constraint types, execution paths, gate questions,
134
+ * and the frame/bounds/context schemas for bounded execution.
135
+ *
136
+ * Supports both v0.3 (frameSchema) and v0.4 (boundsSchema + contextSchema).
137
+ */
138
+ interface AgentProfile {
139
+ id: string;
140
+ name?: string;
141
+ version: string;
142
+ description: string;
143
+ /**
144
+ * v0.3 frame schema (deprecated, kept for backward compat).
145
+ * Used when boundsSchema is not present.
146
+ */
147
+ frameSchema?: {
148
+ keyOrder: string[];
149
+ fields: Record<string, ProfileFrameField>;
150
+ };
151
+ /**
152
+ * v0.4 bounds schema — defines the authorization bounds parameters.
153
+ */
154
+ boundsSchema?: {
155
+ keyOrder: string[];
156
+ fields: Record<string, ProfileBoundsField>;
157
+ };
158
+ /**
159
+ * v0.4 context schema — defines the execution context parameters (e.g., currency, action_type).
160
+ * May be absent or empty for profiles with no static context.
161
+ */
162
+ contextSchema?: {
163
+ keyOrder: string[];
164
+ fields: Record<string, ProfileContextField>;
165
+ };
166
+ executionContextSchema: {
167
+ fields: Record<string, ExecutionContextFieldDef>;
168
+ };
169
+ executionPaths: Record<string, ExecutionPath>;
170
+ requiredGates: string[];
171
+ gateQuestions: {
172
+ problem: GateQuestion;
173
+ objective: GateQuestion;
174
+ tradeoffs: GateQuestion;
175
+ };
176
+ ttl: {
177
+ default: number;
178
+ max: number;
179
+ };
180
+ retention_minimum: number;
181
+ /**
182
+ * Tool gating configuration — how MCP tools map to execution context.
183
+ * @deprecated Tool gating now lives in integration manifests (content/integrations/*.json).
184
+ * Kept for backward compatibility with profiles that still include it.
185
+ */
186
+ toolGating?: ProfileToolGating;
187
+ }
188
+ /**
189
+ * Available transforms for array-aware execution mappings.
190
+ * - length: array length → number
191
+ * - join: array items joined by comma → string
192
+ * - join_domains: extract email domains, deduplicate, sort, join → string
193
+ */
194
+ type ExecutionMappingTransform = 'join' | 'join_domains' | 'length';
195
+ /**
196
+ * Execution mapping value — how a tool argument maps to execution context field(s).
197
+ * - string: direct copy (argName → fieldName)
198
+ * - { field, divisor }: numeric division (e.g., cents ÷ 100 → EUR)
199
+ * - { field, transform }: array transform (e.g., join_domains)
200
+ * - Array form: one argument maps to multiple execution fields
201
+ */
202
+ type ExecutionMappingValue = string | {
203
+ field: string;
204
+ divisor: number;
205
+ } | {
206
+ field: string;
207
+ transform: ExecutionMappingTransform;
208
+ } | Array<{
209
+ field: string;
210
+ divisor?: number;
211
+ transform?: ExecutionMappingTransform;
212
+ }>;
213
+ /**
214
+ * Tool gating entry — how a tool's calls map to execution context fields.
215
+ * Read-only tools use { category: "read" } — they require authorization
216
+ * but skip execution context verification.
217
+ */
218
+ interface ProfileToolGatingEntry {
219
+ executionMapping: Record<string, ExecutionMappingValue>;
220
+ staticExecution?: Record<string, string | number>;
221
+ /** Read-only tools: require authorization but no execution context checks */
222
+ category?: 'read';
223
+ }
224
+ /**
225
+ * Profile-level tool gating configuration.
226
+ * - default: applied to all tools not listed in overrides
227
+ * - overrides: per-tool configs keyed by original MCP tool name
228
+ * Use { category: "read" } for read-only tools (null is deprecated)
229
+ */
230
+ interface ProfileToolGating {
231
+ default: ProfileToolGatingEntry;
232
+ overrides?: Record<string, ProfileToolGatingEntry | null>;
233
+ }
234
+ /**
235
+ * A recorded execution — stored after gatekeeper approval for cumulative tracking.
236
+ */
237
+ interface ExecutionLogEntry {
238
+ profileId: string;
239
+ path: string;
240
+ execution: Record<string, string | number>;
241
+ timestamp: number;
242
+ }
243
+ /**
244
+ * Interface for querying cumulative execution data.
245
+ * Implementations live in the MCP server layer (not hap-core).
246
+ */
247
+ interface ExecutionLogQuery {
248
+ /**
249
+ * Sum a field's values within a time window for a given profile.
250
+ * Use field="_count" to count executions instead of summing a field.
251
+ */
252
+ sumByWindow(profileId: string, path: string, field: string, window: CumulativeWindow, now?: number): number;
253
+ }
254
+ /**
255
+ * Agent frame parameters — mixed types (strings and numbers).
256
+ * Keys and values come from the profile's frameSchema.
257
+ */
258
+ type AgentFrameParams = Record<string, string | number>;
259
+ /**
260
+ * Agent bounds parameters — mixed types (strings and numbers).
261
+ * Keys and values come from the profile's boundsSchema (v0.4).
262
+ */
263
+ type AgentBoundsParams = Record<string, string | number>;
264
+ /**
265
+ * Agent context parameters — mixed types (strings and numbers).
266
+ * Keys and values come from the profile's contextSchema (v0.4).
267
+ */
268
+ type AgentContextParams = Record<string, string | number>;
269
+ /**
270
+ * Request to the Gatekeeper for bounded execution verification.
271
+ */
272
+ interface GatekeeperRequest {
273
+ /** The authorization frame (what was attested to) — v0.3 */
274
+ frame: AgentFrameParams;
275
+ /** Attestation blobs (base64url) for each domain */
276
+ attestations: string[];
277
+ /** The agent's execution values for this specific action */
278
+ execution: Record<string, string | number>;
279
+ /** v0.4: context parameters (currency, action_type, etc.) */
280
+ context?: AgentContextParams;
281
+ }
282
+ /**
283
+ * Structured error from Gatekeeper verification.
284
+ */
285
+ interface GatekeeperError {
286
+ code: 'BOUND_EXCEEDED' | 'CUMULATIVE_LIMIT_EXCEEDED' | 'INVALID_SIGNATURE' | 'TTL_EXPIRED' | 'FRAME_MISMATCH' | 'BOUNDS_MISMATCH' | 'CONTEXT_MISMATCH' | 'DOMAIN_NOT_COVERED' | 'INVALID_PROFILE' | 'MALFORMED_ATTESTATION';
287
+ field?: string;
288
+ message: string;
289
+ bound?: string | number;
290
+ actual?: string | number;
291
+ }
292
+ /**
293
+ * Gatekeeper verification result.
294
+ */
295
+ type GatekeeperResult = {
296
+ approved: true;
297
+ } | {
298
+ approved: false;
299
+ errors: GatekeeperError[];
300
+ };
301
+
302
+ /**
303
+ * Frame Canonicalization for Agent Profiles
304
+ *
305
+ * Agent profiles support mixed-type fields (strings and numbers).
306
+ * Canonical form: all values are converted to strings via String(value).
307
+ * Keys are ordered according to the profile's keyOrder.
308
+ *
309
+ * v0.3: frameSchema
310
+ * v0.4: boundsSchema + contextSchema (separate hashes)
311
+ */
312
+
313
+ /**
314
+ * Validates frame parameters against the profile's frame schema.
315
+ */
316
+ declare function validateFrameParams(params: AgentFrameParams, profile: AgentProfile): {
317
+ valid: boolean;
318
+ errors: string[];
319
+ };
320
+ /**
321
+ * Builds the canonical frame string from parameters.
322
+ * All values are converted to strings. Keys are ordered per profile's keyOrder.
323
+ *
324
+ * @throws Error if any field fails validation
325
+ */
326
+ declare function canonicalFrame(params: AgentFrameParams, profile: AgentProfile): string;
327
+ /**
328
+ * Computes the frame hash from a canonical frame string.
329
+ *
330
+ * @returns Hash in format "sha256:<64 hex chars>"
331
+ */
332
+ declare function frameHash(canonicalFrameString: string): string;
333
+ /**
334
+ * Convenience: builds canonical frame and computes hash in one step.
335
+ */
336
+ declare function computeFrameHash(params: AgentFrameParams, profile: AgentProfile): string;
337
+ /**
338
+ * Validates bounds parameters against the profile's boundsSchema (v0.4).
339
+ */
340
+ declare function validateBoundsParams(params: AgentBoundsParams, profile: AgentProfile): {
341
+ valid: boolean;
342
+ errors: string[];
343
+ };
344
+ /**
345
+ * Validates context parameters against the profile's contextSchema (v0.4).
346
+ */
347
+ declare function validateContextParams(params: AgentContextParams, profile: AgentProfile): {
348
+ valid: boolean;
349
+ errors: string[];
350
+ };
351
+ /**
352
+ * Builds the canonical bounds string from parameters.
353
+ * All values are converted to strings. Keys are ordered per profile's boundsSchema.keyOrder.
354
+ *
355
+ * @throws Error if any field fails validation
356
+ */
357
+ declare function canonicalBounds(params: AgentBoundsParams, profile: AgentProfile): string;
358
+ /**
359
+ * Builds the canonical context string from parameters.
360
+ * All values are converted to strings. Keys are ordered per profile's contextSchema.keyOrder.
361
+ * For empty context (no contextSchema or no fields), returns "".
362
+ *
363
+ * @throws Error if any field fails validation
364
+ */
365
+ declare function canonicalContext(params: AgentContextParams, profile: AgentProfile): string;
366
+ /**
367
+ * Computes the bounds hash from bounds parameters (v0.4).
368
+ *
369
+ * @returns Hash in format "sha256:<64 hex chars>"
370
+ */
371
+ declare function computeBoundsHash(params: AgentBoundsParams, profile: AgentProfile): string;
372
+ /**
373
+ * Computes the context hash from context parameters (v0.4).
374
+ * For empty context {}, returns the sha256 of "":
375
+ * "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
376
+ *
377
+ * @returns Hash in format "sha256:<64 hex chars>"
378
+ */
379
+ declare function computeContextHash(params: AgentContextParams, profile: AgentProfile): string;
380
+
381
+ /**
382
+ * Attestation Encoding, Decoding, and Verification
383
+ *
384
+ * Supports both v0.3 (frame_hash) and v0.4 (bounds_hash + context_hash).
385
+ */
386
+
387
+ /**
388
+ * Decodes a base64url-encoded attestation blob.
389
+ */
390
+ declare function decodeAttestationBlob(blob: string): Attestation;
391
+ /**
392
+ * Encodes an attestation as a base64url blob (no padding).
393
+ */
394
+ declare function encodeAttestationBlob(attestation: Attestation): string;
395
+ /**
396
+ * Computes the attestation ID (hash of the blob).
397
+ */
398
+ declare function attestationId(blob: string): string;
399
+ /**
400
+ * Verifies an attestation signature using the SP public key.
401
+ *
402
+ * @returns true if valid
403
+ * @throws Error with code prefix if invalid
404
+ */
405
+ declare function verifyAttestationSignature(attestation: Attestation, publicKeyHex: string): Promise<void>;
406
+ /**
407
+ * Checks if an attestation has expired.
408
+ *
409
+ * @throws Error if expired
410
+ */
411
+ declare function checkAttestationExpiry(payload: AttestationPayload, now?: number): void;
412
+ /**
413
+ * Verifies that the frame hash in the attestation matches the expected hash (v0.3).
414
+ *
415
+ * @throws Error if frame hash doesn't match
416
+ */
417
+ declare function verifyFrameHash(attestation: Attestation, expectedFrameHash: string): void;
418
+ /**
419
+ * Detects whether an attestation is v0.4 (has bounds_hash + context_hash)
420
+ * or v0.3 (has frame_hash).
421
+ *
422
+ * Migration rule: if attestation has frame_hash but not bounds_hash, it is v0.3.
423
+ */
424
+ declare function isV4Attestation(attestation: Attestation): boolean;
425
+ /**
426
+ * Verifies that the bounds hash in the attestation matches the expected hash (v0.4).
427
+ *
428
+ * @throws Error if bounds hash doesn't match
429
+ */
430
+ declare function verifyBoundsHash(attestation: Attestation, expectedBoundsHash: string): void;
431
+ /**
432
+ * Verifies that the context hash in the attestation matches the expected hash (v0.4).
433
+ *
434
+ * @throws Error if context hash doesn't match
435
+ */
436
+ declare function verifyContextHash(attestation: Attestation, expectedContextHash: string): void;
437
+ /**
438
+ * Full attestation verification (signature + expiry + frame hash) — v0.3.
439
+ *
440
+ * @returns The decoded attestation payload
441
+ * @throws Error on any validation failure
442
+ */
443
+ declare function verifyAttestation(blob: string, publicKeyHex: string, expectedFrameHash: string): Promise<AttestationPayload>;
444
+ /**
445
+ * Full attestation verification for v0.4 (signature + expiry + bounds_hash + context_hash).
446
+ *
447
+ * @returns The decoded attestation payload
448
+ * @throws Error on any validation failure
449
+ */
450
+ declare function verifyAttestationV4(blob: string, publicKeyHex: string, expectedBoundsHash: string, expectedContextHash: string): Promise<AttestationPayload>;
451
+
452
+ /**
453
+ * Gatekeeper — Stateless Verification for Bounded Execution
454
+ *
455
+ * Supports both v0.3 (frameSchema / frame_hash) and v0.4 (boundsSchema + contextSchema /
456
+ * bounds_hash + context_hash).
457
+ *
458
+ * v0.3 flow (§8.6):
459
+ * 1. Resolve profile from frame
460
+ * 2. Recompute frame_hash
461
+ * 3. For each required domain: find attestation, verify signature, verify frame_hash, verify TTL
462
+ * 4. Check bounds: max → actual <= bound, enum → actual in allowed
463
+ * 5. Return { approved } or { approved: false, errors: [...] }
464
+ *
465
+ * v0.4 flow:
466
+ * 1. Resolve profile from bounds.profile
467
+ * 2. Recompute bounds_hash and context_hash
468
+ * 3. For each required domain: find attestation, verify signature, verify bounds_hash + context_hash, verify TTL
469
+ * 4. Check bounds (from boundsSchema), check context constraints (from contextSchema)
470
+ * 5. Resolve cumulative fields, check cumulative limits
471
+ * 6. Return { approved } or { approved: false, errors: [...] }
472
+ */
473
+
474
+ /**
475
+ * Verify an execution request against attested authorization.
476
+ *
477
+ * For v0.4 profiles (have boundsSchema), the `frame` param is interpreted as `bounds`,
478
+ * and the optional `context` param is used for the context hash check.
479
+ *
480
+ * For v0.3 profiles (have frameSchema only), existing logic is used unchanged.
481
+ *
482
+ * @param request - The frame/bounds, attestations, execution values, and optional context
483
+ * @param publicKeyHex - The SP's public key in hex (cached locally by MCP server)
484
+ * @param now - Current timestamp in seconds (for testing)
485
+ * @param executionLog - Optional execution log for resolving cumulative fields
486
+ */
487
+ declare function verify(request: GatekeeperRequest, publicKeyHex: string, now?: number, executionLog?: ExecutionLogQuery): Promise<GatekeeperResult>;
488
+
489
+ /**
490
+ * Profile Registry — dynamically populated from git-hosted profile sources.
491
+ */
492
+
493
+ declare function registerProfile(profileId: string, profile: AgentProfile): void;
494
+ declare function getProfile(profileId: string): AgentProfile | undefined;
495
+ declare function listProfiles(): string[];
496
+ declare function getAllProfiles(): AgentProfile[];
497
+ declare function clearProfiles(): void;
498
+
499
+ export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type Attestation, type AttestationHeader, type AttestationPayload, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ResolvedDomain, attestationId, canonicalBounds, canonicalContext, canonicalFrame, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContextHash, computeFrameHash, decodeAttestationBlob, encodeAttestationBlob, frameHash, getAllProfiles, getProfile, isV4Attestation, listProfiles, registerProfile, validateBoundsParams, validateContextParams, validateFrameParams, verify, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash };