@humanagencyp/hap-core 0.11.2 → 0.12.1
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 +92 -29
- package/dist/index.d.mts +638 -593
- package/dist/index.d.ts +638 -593
- package/dist/index.js +792 -749
- package/dist/index.mjs +760 -724
- package/package.json +1 -1
- package/src/base64url.ts +39 -0
- package/src/commitment-modes.ts +0 -21
- package/src/content-binding.ts +26 -1
- package/src/errors.ts +76 -0
- package/src/frame.ts +47 -178
- package/src/gatekeeper.ts +219 -600
- package/src/identity.ts +9 -12
- package/src/index.ts +8 -4
- package/src/mandate.ts +177 -166
- package/src/owner-signature.ts +282 -0
- package/src/profile.ts +234 -0
- package/src/ticket.ts +148 -0
- package/src/types.ts +208 -344
- package/src/versions.ts +47 -0
- package/src/attestation.ts +0 -173
- package/src/receipt.ts +0 -74
package/src/types.ts
CHANGED
|
@@ -1,132 +1,74 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HAP Core Types —
|
|
2
|
+
* HAP Core Types — v0.7 ("mandate" / "ticket" / "scope" / "Mandate Owner")
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* v0.7 is a vocabulary release (protocol.md → *Migration from v0.6*): the
|
|
5
|
+
* wire renames *attestation* → *mandate*, *context* → *scope*, *execution
|
|
6
|
+
* receipt* → *mandate ticket*, and *Decision Owner* → *Mandate Owner*. There
|
|
7
|
+
* is no backward-compat alias here — v0.6 and earlier artifacts are
|
|
8
|
+
* verification-only history outside this package (CLAUDE.md "Decided by the
|
|
9
|
+
* owner": no verify path for 0.5/0.6, no old→new code map).
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
|
-
// ───
|
|
12
|
+
// ─── Mandate Types ────────────────────────────────────────────────────────────
|
|
8
13
|
|
|
9
|
-
export interface
|
|
10
|
-
typ: 'HAP-
|
|
14
|
+
export interface MandateHeader {
|
|
15
|
+
typ: 'HAP-mandate';
|
|
11
16
|
alg: 'EdDSA';
|
|
12
|
-
kid?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ResolvedDomain {
|
|
16
|
-
domain: string;
|
|
17
|
-
did: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface AttestationPayload {
|
|
21
|
-
attestation_id: string;
|
|
22
|
-
version: '0.3' | '0.4' | '0.5' | '0.6';
|
|
23
|
-
profile_id: string;
|
|
24
|
-
/** v0.3 (deprecated) — hash of the authorization frame */
|
|
25
|
-
frame_hash?: string;
|
|
26
|
-
/** v0.4 — hash of the bounds parameters */
|
|
27
|
-
bounds_hash?: string;
|
|
28
|
-
/** v0.4 — hash of the context parameters */
|
|
29
|
-
context_hash?: string;
|
|
30
|
-
execution_context_hash: string;
|
|
31
|
-
/**
|
|
32
|
-
* v0.4 (deprecated in v0.5) — domain-scoped owners. Optional so v0.5
|
|
33
|
-
* attestations that carry only `resolved_owners` remain valid. During the
|
|
34
|
-
* transition the AS emits BOTH `resolved_domains` (internal coverage) and
|
|
35
|
-
* `resolved_owners` (the v0.5 signed wire field).
|
|
36
|
-
*/
|
|
37
|
-
resolved_domains?: ResolvedDomain[];
|
|
38
|
-
/**
|
|
39
|
-
* v0.5 — the Decision Owner DIDs this attestation covers (person-centric
|
|
40
|
-
* model; replaces the abstract domain in `resolved_domains`). Carried in the
|
|
41
|
-
* signed payload so verifiers bind the action to the human(s) who authorized it.
|
|
42
|
-
*/
|
|
43
|
-
resolved_owners?: string[];
|
|
44
|
-
gate_content_hashes: Record<string, string>;
|
|
45
17
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* signed payload (see {@link computeIntentDisclosureHash}), so a compromised
|
|
50
|
-
* AS cannot swap the ciphertext/wrapped keys or alter the approver set
|
|
51
|
-
* without invalidating the attestation signature (companion invariant C2).
|
|
18
|
+
* When present MUST identify the same key as `issuer` (for a `did:key`
|
|
19
|
+
* issuer, the multibase key fingerprint) — Mandate rule 8. The field a
|
|
20
|
+
* party writes (`kid`) loses to the field a party cannot forge (`issuer`).
|
|
52
21
|
*/
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* v0.6 (Identity Assurance) — optional signed overlay carrying the verified
|
|
56
|
-
* real-world identity of the Decision Owner(s), one entry per owner. Present
|
|
57
|
-
* only when identity is disclosed; an attestation with no `subjects` renders
|
|
58
|
-
* as `low` (pseudonymous, no name). See {@link Subject} and
|
|
59
|
-
* {@link deriveIdentityLine}.
|
|
60
|
-
*/
|
|
61
|
-
subjects?: Subject[];
|
|
62
|
-
/**
|
|
63
|
-
* v0.4 — commitment mode chosen by the decision owner at attestation time.
|
|
64
|
-
* - 'automatic': agent may invoke bounded tools without per-action review.
|
|
65
|
-
* - 'review': every tool call requires an approved proposal before the
|
|
66
|
-
* receipt route will sign a receipt.
|
|
67
|
-
*
|
|
68
|
-
* Cryptographically bound in the signed payload so a compromised SP cannot
|
|
69
|
-
* silently flip 'review' → 'automatic'. Absent on v0.3 attestations.
|
|
70
|
-
* `review_above_cap` (v0.5): automatic below the signed caps, proposal above.
|
|
71
|
-
*/
|
|
72
|
-
commitment_mode?: 'automatic' | 'review' | 'review_above_cap';
|
|
73
|
-
/**
|
|
74
|
-
* v0.6 (Owner Mandate Signatures) — the Decision Owner's own signature(s)
|
|
75
|
-
* over the mandate projection ({@link MandateProjection}), one entry per
|
|
76
|
-
* co-signing owner. Carried INSIDE the AS-signed payload, so the AS attests
|
|
77
|
-
* to having received it and cannot strip it without invalidating its own
|
|
78
|
-
* signature. Optional and additive: an attestation without it behaves as
|
|
79
|
-
* before (the v0.5 posture — the AS asserts, nobody co-signs).
|
|
80
|
-
*/
|
|
81
|
-
owner_mandates?: OwnerMandate[];
|
|
82
|
-
issued_at: number;
|
|
83
|
-
expires_at: number;
|
|
22
|
+
kid?: string;
|
|
84
23
|
}
|
|
85
24
|
|
|
86
|
-
/**
|
|
87
|
-
* identity assurance ({@link Subject.method}). */
|
|
88
|
-
export type
|
|
25
|
+
/** Signature-assurance axis — what custody signed the mandate projection or
|
|
26
|
+
* approval. Independent of identity assurance ({@link Subject.method}). */
|
|
27
|
+
export type OwnerSignatureBinding = 'raw' | 'webauthn' | 'eudi';
|
|
89
28
|
|
|
90
29
|
/** Which surface showed the owner what they signed. A DECLARATION, not a proof
|
|
91
|
-
* — no verifier can check it; see protocol.md → Owner
|
|
30
|
+
* — no verifier can check it; see protocol.md → *Owner Signatures*. */
|
|
92
31
|
export type SigningSurface = 'gatekeeper_local' | 'as_web' | 'wallet_display';
|
|
93
32
|
|
|
94
33
|
/**
|
|
95
|
-
* v0.
|
|
34
|
+
* v0.7 `mandate_owners` entry (protocol.md → *Mandate field: mandate_owners*).
|
|
35
|
+
* `did` is required; the remaining fields are present together when the
|
|
36
|
+
* owner co-signed and absent together when they did not (Mandate rule 7).
|
|
96
37
|
*
|
|
97
38
|
* There is deliberately NO `public_key` field, and it MUST NOT be added: the
|
|
98
39
|
* verification key is carried in the DID itself (key-bearing `did:key`), so a
|
|
99
40
|
* non-key-bearing DID fails STRUCTURALLY instead of validating against a key
|
|
100
|
-
* the AS could have substituted.
|
|
101
|
-
* hygiene; if it disagrees with the DID's key type, the DID is authoritative.
|
|
41
|
+
* the AS could have substituted.
|
|
102
42
|
*/
|
|
103
|
-
export interface
|
|
104
|
-
/** The
|
|
43
|
+
export interface MandateOwnerEntry {
|
|
44
|
+
/** The Mandate Owner's DID. For a co-signing entry, MUST be key-bearing. */
|
|
105
45
|
did: string;
|
|
106
46
|
/** Signature algorithm hint. The DID's multicodec wins on any disagreement. */
|
|
107
|
-
alg
|
|
47
|
+
alg?: 'EdDSA' | 'ES256';
|
|
108
48
|
/** base64url (no padding) signature over the JCS bytes of the mandate projection. */
|
|
109
|
-
signature
|
|
49
|
+
signature?: string;
|
|
110
50
|
/** When the owner signed (unix seconds). */
|
|
111
|
-
signed_at
|
|
51
|
+
signed_at?: number;
|
|
112
52
|
/** Defence-in-depth against duplicate issuance by an HONEST AS only —
|
|
113
53
|
* AS-side nonce enforcement is no defence against the AS itself. */
|
|
114
|
-
nonce
|
|
115
|
-
|
|
54
|
+
nonce?: string;
|
|
55
|
+
/** Required-present, value-open when a signature is carried: an AS MUST
|
|
56
|
+
* NOT reject an entry for a `binding` value it does not recognize. */
|
|
57
|
+
binding?: OwnerSignatureBinding;
|
|
116
58
|
signing_surface?: SigningSurface;
|
|
117
59
|
}
|
|
118
60
|
|
|
119
61
|
/**
|
|
120
|
-
* v0.6 Identity Assurance — a signed overlay binding a
|
|
121
|
-
* real-world identity to the
|
|
62
|
+
* v0.6 Identity Assurance — a signed overlay binding a Mandate Owner's verified
|
|
63
|
+
* real-world identity to the mandate, gated by HOW the identity was verified.
|
|
122
64
|
*
|
|
123
65
|
* Two display levels (`assurance`): `low` discloses no name; `high` MAY disclose a
|
|
124
66
|
* name. At `high`, two trust roots: `as` (the AS operator vouches — valid only
|
|
125
67
|
* within its own domain) and `external` (an external eID such as EUDI — carries the
|
|
126
|
-
* owner's own signature, AS-independent).
|
|
68
|
+
* owner's own signature, AS-independent).
|
|
127
69
|
*/
|
|
128
70
|
export interface Subject {
|
|
129
|
-
/** The
|
|
71
|
+
/** The Mandate Owner DID this subject describes (matches the `mandate_owners` entry). */
|
|
130
72
|
did: string;
|
|
131
73
|
/** `low` → no name shown; `high` → the name MAY be shown. */
|
|
132
74
|
assurance: 'low' | 'high';
|
|
@@ -140,19 +82,55 @@ export interface Subject {
|
|
|
140
82
|
disclose?: { name: string };
|
|
141
83
|
/** When the underlying verification was performed (unix seconds). */
|
|
142
84
|
verified_at?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** protocol.md → *Mandate Payload (v0.7)*. */
|
|
88
|
+
export interface MandatePayload {
|
|
89
|
+
mandate_id: string;
|
|
90
|
+
version: '0.7';
|
|
91
|
+
profile_id: string;
|
|
92
|
+
/** Hash of the canonical bounds string. */
|
|
93
|
+
bounds_hash: string;
|
|
94
|
+
/** Hash of the canonical scope string (sha256 of "" when scope is empty). */
|
|
95
|
+
scope_hash: string;
|
|
96
|
+
execution_context_hash: string;
|
|
97
|
+
/** `sha256` over the JCS serialization of the profile the Gatekeeper
|
|
98
|
+
* provisioned for `profile_id` — the content address of the rulebook this
|
|
99
|
+
* mandate was issued under (new in v0.7). */
|
|
100
|
+
profile_hash: string;
|
|
101
|
+
/** The Authority Server's DID — SHOULD be the `did:key` of the signing key
|
|
102
|
+
* itself (new in v0.7). */
|
|
103
|
+
issuer: string;
|
|
104
|
+
/** Exactly one entry in v0.7 (Mandate rule 7). */
|
|
105
|
+
mandate_owners: MandateOwnerEntry[];
|
|
106
|
+
gate_content_hashes: Record<string, string>;
|
|
107
|
+
commitment_mode: 'automatic' | 'review' | 'review_above_cap';
|
|
108
|
+
/** Required, together with `above_cap_approvers`, iff `commitment_mode === "review_above_cap"`. */
|
|
109
|
+
above_cap_caps?: Record<string, number>;
|
|
110
|
+
above_cap_approvers?: string[];
|
|
143
111
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* it; verifiers MAY ignore it on artifacts that carry it. Kept readable for
|
|
148
|
-
* audit of pre-0.6 artifacts only.
|
|
112
|
+
* Present iff the mandate carries an encrypted-intent disclosure object
|
|
113
|
+
* (companion spec `intent-disclosure@0.1`) — `sha256:`-prefixed hash
|
|
114
|
+
* binding `intent_ciphertext` + `approvers_frozen` into the signed payload.
|
|
149
115
|
*/
|
|
150
|
-
|
|
116
|
+
intent_disclosure_hash?: string;
|
|
117
|
+
/** Signed identity-assurance block, one entry per owner — present iff the
|
|
118
|
+
* Mandate Owner disclosed identity. */
|
|
119
|
+
subjects?: Subject[];
|
|
120
|
+
/**
|
|
121
|
+
* The owner's narrowing of the profile's `disclose_fields` list (new in
|
|
122
|
+
* v0.7) — a subset of the profile's own list. See *Ticket Disclosure Is
|
|
123
|
+
* Declared*.
|
|
124
|
+
*/
|
|
125
|
+
disclose_fields?: string[];
|
|
126
|
+
issued_at: number;
|
|
127
|
+
expires_at: number;
|
|
151
128
|
}
|
|
152
129
|
|
|
153
|
-
export interface
|
|
154
|
-
header:
|
|
155
|
-
payload:
|
|
130
|
+
export interface Mandate {
|
|
131
|
+
header: MandateHeader;
|
|
132
|
+
payload: MandatePayload;
|
|
133
|
+
/** base64url (no padding) Ed25519 signature over the JCS-canonical payload. */
|
|
156
134
|
signature: string;
|
|
157
135
|
}
|
|
158
136
|
|
|
@@ -191,25 +169,13 @@ export interface FieldConstraint {
|
|
|
191
169
|
}
|
|
192
170
|
|
|
193
171
|
/**
|
|
194
|
-
*
|
|
195
|
-
*/
|
|
196
|
-
export interface ProfileFrameField {
|
|
197
|
-
type: 'string' | 'number';
|
|
198
|
-
required: boolean;
|
|
199
|
-
description?: string;
|
|
200
|
-
constraint?: FieldConstraint;
|
|
201
|
-
enum?: string[];
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Bound enforcement semantics — how a v0.4 bound is checked.
|
|
172
|
+
* Bound enforcement semantics — how a bound is checked.
|
|
206
173
|
*
|
|
207
|
-
* Every bounds field in a
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* dispatch sites.
|
|
174
|
+
* Every bounds field in a profile declares a `boundType` so the AS and
|
|
175
|
+
* hap-core gatekeeper can dispatch on it directly, without parsing field
|
|
176
|
+
* names or guessing conventions. This is the single source of truth for
|
|
177
|
+
* "what does this bound mean at enforcement time" — if a new kind is needed,
|
|
178
|
+
* add a variant here and update the dispatch sites.
|
|
213
179
|
*
|
|
214
180
|
* See `ProfileBoundsField.boundType`.
|
|
215
181
|
*/
|
|
@@ -219,46 +185,45 @@ export type BoundType =
|
|
|
219
185
|
* satisfy `execution[of] <= bound` for the current call. No cumulative
|
|
220
186
|
* tracking. Used by: amount_max, recipient_max, booking_duration_max, etc.
|
|
221
187
|
*
|
|
222
|
-
* `requiredFor`
|
|
223
|
-
*
|
|
188
|
+
* `requiredFor` closes the converse: without it, a call that does not
|
|
189
|
+
* carry `of` at all is simply skipped — a 5,000 cap refuses 6,000 but
|
|
224
190
|
* permits a call that declares no value whatsoever, which is indistinguishable
|
|
225
|
-
* from an unenforced bound.
|
|
226
|
-
* `
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
* skip-on-absence behaviour is unchanged. Meaningful only on
|
|
232
|
-
* `per_transaction`; the other `BoundType` kinds have no per-call "value
|
|
233
|
-
* exposed or not" question — `cumulative_sum`/`cumulative_count` always read
|
|
234
|
-
* from the execution log regardless of what this call declares, and `enum`
|
|
235
|
-
* is a capability flag, not a runtime value — so the field does not exist
|
|
236
|
-
* on those variants and a profile that puts it there has it silently
|
|
237
|
-
* ignored by every enforcement point (TypeScript also refuses it when the
|
|
238
|
-
* profile is authored against this type rather than raw JSON).
|
|
191
|
+
* from an unenforced bound. Values are drawn from the profile's
|
|
192
|
+
* `boundsSchema.actionTypes` registry. For a listed `action_type`, an
|
|
193
|
+
* execution whose context lacks `of` — or whose value is not a finite
|
|
194
|
+
* number — MUST be refused rather than skipped. `appliesTo` MUST NOT be
|
|
195
|
+
* declared on a `per_transaction` bound (protocol.md → Bounds Schema rule 7)
|
|
196
|
+
* — it applies wherever `of` is present in the execution context.
|
|
239
197
|
*/
|
|
240
198
|
| { kind: 'per_transaction'; of: string; requiredFor?: string[] }
|
|
241
199
|
/**
|
|
242
|
-
* Cumulative sum within a time window. The
|
|
243
|
-
* of `execution[of]` across all prior
|
|
200
|
+
* Cumulative sum within a time window. The AS maintains a running sum
|
|
201
|
+
* of `execution[of]` across all prior tickets in the window; the
|
|
244
202
|
* current call is approved iff `running_sum + execution[of] <= bound`.
|
|
245
|
-
*
|
|
203
|
+
* AS-only enforcement (protocol.md → *Enforcement Authority*); the
|
|
204
|
+
* Gatekeeper MUST NOT enforce this locally. Used by: amount_daily_max,
|
|
205
|
+
* spend_monthly_max, etc.
|
|
246
206
|
*/
|
|
247
207
|
| { kind: 'cumulative_sum'; of: string; window: CumulativeWindow }
|
|
248
208
|
/**
|
|
249
|
-
* Cumulative count within a time window. Every qualifying
|
|
209
|
+
* Cumulative count within a time window. Every qualifying ticket
|
|
250
210
|
* counts as +1; the current call is approved iff
|
|
251
211
|
* `running_count + 1 <= bound`. No execution context field is read.
|
|
252
|
-
*
|
|
212
|
+
* AS-only enforcement. `appliesTo` is REQUIRED on a profile published
|
|
213
|
+
* under v0.7 or later (Bounds Schema rule 7) — absence would mean
|
|
214
|
+
* "governs every action type", so a forgotten declaration silently
|
|
215
|
+
* throttles every action. Used by: write_daily_max, post_monthly_max,
|
|
216
|
+
* booking_daily_max, etc.
|
|
253
217
|
*/
|
|
254
218
|
| { kind: 'cumulative_count'; window: CumulativeWindow }
|
|
255
219
|
/**
|
|
256
220
|
* String bound restricted to a fixed set of allowed values. The bound's
|
|
257
|
-
* value must be one of `values` at
|
|
221
|
+
* value must be one of `values` at mandate time. The gateway
|
|
258
222
|
* tool-proxy gates tool calls based on the stored bound (via the
|
|
259
223
|
* integration manifest's `boundField` + `requiredValue`). Not cumulated,
|
|
260
|
-
* not checked by the
|
|
261
|
-
*
|
|
224
|
+
* not checked by the AS ticket route — it's a capability flag.
|
|
225
|
+
* `appliesTo` MUST NOT be declared here (it is a capability flag, not an
|
|
226
|
+
* action-scoped limit). Used by: read_access, delete_access, archive_access.
|
|
262
227
|
*/
|
|
263
228
|
| { kind: 'enum'; values: readonly string[] };
|
|
264
229
|
|
|
@@ -279,11 +244,11 @@ export type FieldUnit =
|
|
|
279
244
|
| 'percent';
|
|
280
245
|
|
|
281
246
|
/**
|
|
282
|
-
* Bounds field definition within a
|
|
247
|
+
* Bounds field definition within a profile.
|
|
283
248
|
*
|
|
284
|
-
*
|
|
285
|
-
* the bound is enforced.
|
|
286
|
-
*
|
|
249
|
+
* Every non-metadata bounds field MUST declare `boundType` — an explicit
|
|
250
|
+
* declaration of how the bound is enforced. Implementations MUST fail
|
|
251
|
+
* closed on any bounds field that omits it (Bounds Schema rule 2).
|
|
287
252
|
*/
|
|
288
253
|
export interface ProfileBoundsField {
|
|
289
254
|
type: 'string' | 'number';
|
|
@@ -291,26 +256,18 @@ export interface ProfileBoundsField {
|
|
|
291
256
|
description?: string;
|
|
292
257
|
displayName?: string;
|
|
293
258
|
format?: 'email' | 'domain' | 'url' | 'currency';
|
|
294
|
-
/**
|
|
295
|
-
* v0.4 enforcement semantics. Required for all new profiles.
|
|
296
|
-
* Optional here only so the type stays backward compatible with v0.3
|
|
297
|
-
* profiles that predate the boundType convention — consumers must
|
|
298
|
-
* treat a missing boundType as an error when running in v0.4 mode.
|
|
299
|
-
*/
|
|
259
|
+
/** Enforcement semantics. REQUIRED on every bounds field except `profile`. */
|
|
300
260
|
boundType?: BoundType;
|
|
301
261
|
/**
|
|
302
262
|
* Which execution action types this bound governs, e.g. `["write"]`.
|
|
303
263
|
*
|
|
304
|
-
* When present, this is authoritative. When absent,
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
* `
|
|
309
|
-
* is
|
|
310
|
-
*
|
|
311
|
-
* Declaring it removes the guess. Deliberately additive: profiles migrate one
|
|
312
|
-
* at a time, and every remaining fallback is logged, so the cases still
|
|
313
|
-
* relying on the naming convention become visible instead of staying silent.
|
|
264
|
+
* When present, this is authoritative. When absent, a `cumulative_sum` bound
|
|
265
|
+
* (or any bound on a profile published before v0.7) governs every action
|
|
266
|
+
* type in the profile's `actionTypes` registry; a `cumulative_count` bound
|
|
267
|
+
* on a v0.7+ profile MUST declare it (Bounds Schema rule 7). MUST NOT be
|
|
268
|
+
* declared on a `per_transaction` bound (it applies wherever its `of`
|
|
269
|
+
* field is present in the execution context) or on an `enum` bound (a
|
|
270
|
+
* capability flag, not an action-scoped limit).
|
|
314
271
|
*/
|
|
315
272
|
appliesTo?: string[];
|
|
316
273
|
/**
|
|
@@ -333,19 +290,15 @@ export interface ProfileBoundsField {
|
|
|
333
290
|
* min(value, maximum). Absent → no ceiling.
|
|
334
291
|
*/
|
|
335
292
|
maximum?: number;
|
|
336
|
-
/** @deprecated v0.4: use boundType instead. */
|
|
337
|
-
constraint?: FieldConstraint;
|
|
338
|
-
/** @deprecated v0.4: use boundType: { kind: 'enum', values: [...] }. */
|
|
339
|
-
enum?: string[];
|
|
340
293
|
}
|
|
341
294
|
|
|
342
295
|
/**
|
|
343
296
|
* v0.5 Content Provenance — how a profile's action content is hashed into a
|
|
344
|
-
* signed
|
|
297
|
+
* signed ticket (`contentHash`). The ephemeral-content analog of Output
|
|
345
298
|
* Provenance: it binds the *bytes* of the action rather than a location.
|
|
346
299
|
*
|
|
347
300
|
* Profile-bound and OPTIONAL. Absent → no content hash is produced (full
|
|
348
|
-
* backward compatibility). The gateway computes the hash; the
|
|
301
|
+
* backward compatibility). The gateway computes the hash; the AS only ever
|
|
349
302
|
* receives the hash, never the content, so HAP's privacy-minimal design holds.
|
|
350
303
|
*
|
|
351
304
|
* At `version:"1"` the profile declares only the *policy* — whether to bind and
|
|
@@ -406,19 +359,37 @@ export interface ContentBinding {
|
|
|
406
359
|
* Absent → the binding applies to every gated action under the profile.
|
|
407
360
|
*/
|
|
408
361
|
appliesTo?: string[];
|
|
362
|
+
/**
|
|
363
|
+
* v2 only — what kind of value each bound field holds, so a verifier (or any
|
|
364
|
+
* view) can collect and show it without guessing from the field's name:
|
|
365
|
+
* - `text` — one string (an email body);
|
|
366
|
+
* - `list` — an array of strings (recipients);
|
|
367
|
+
* - `files` — the sealed fingerprints of documents sent by reference:
|
|
368
|
+
* `[{ filename, sha256 }]` — never the bytes. A verifier asks for the
|
|
369
|
+
* files themselves and computes each SHA-256 locally.
|
|
370
|
+
*
|
|
371
|
+
* A hint only: it never changes what is hashed. Keys MUST be a subset of
|
|
372
|
+
* {@link fields}; a field without an entry is unspecified (a verifier may
|
|
373
|
+
* offer a choice, as before).
|
|
374
|
+
*/
|
|
375
|
+
field_kinds?: Record<string, ContentFieldKind>;
|
|
409
376
|
}
|
|
410
377
|
|
|
378
|
+
/** See {@link ContentBinding.field_kinds}. */
|
|
379
|
+
export type ContentFieldKind = 'text' | 'list' | 'files';
|
|
380
|
+
|
|
411
381
|
/**
|
|
412
|
-
*
|
|
382
|
+
* Scope field definition within a profile (renamed from `ProfileContextField`
|
|
383
|
+
* in v0.7 — protocol.md → *Migration from v0.6*, `contextSchema` → `scopeSchema`).
|
|
413
384
|
*/
|
|
414
|
-
export interface
|
|
385
|
+
export interface ProfileScopeField {
|
|
415
386
|
type: 'string' | 'number';
|
|
416
387
|
required: boolean;
|
|
417
388
|
description?: string;
|
|
418
389
|
displayName?: string;
|
|
419
390
|
format?: 'email' | 'domain' | 'url' | 'currency';
|
|
420
391
|
/**
|
|
421
|
-
*
|
|
392
|
+
* What this field NAMES on the read path. `counterparty` = the other
|
|
422
393
|
* party to a communication (matched against an item's participants);
|
|
423
394
|
* `resource` = the container an item belongs to (a direct attribute match —
|
|
424
395
|
* and a resource scope enforced on writes MUST also bind reads). Absent →
|
|
@@ -427,7 +398,6 @@ export interface ProfileContextField {
|
|
|
427
398
|
*/
|
|
428
399
|
scopeKind?: 'counterparty' | 'resource';
|
|
429
400
|
constraint?: FieldConstraint;
|
|
430
|
-
enum?: string[];
|
|
431
401
|
}
|
|
432
402
|
|
|
433
403
|
/**
|
|
@@ -446,13 +416,11 @@ export interface DeclaredFieldDef {
|
|
|
446
416
|
export type CumulativeWindow = 'daily' | 'weekly' | 'monthly';
|
|
447
417
|
|
|
448
418
|
/**
|
|
449
|
-
* Execution context field definition — cumulative source (resolved from
|
|
419
|
+
* Execution context field definition — cumulative source (resolved from the
|
|
420
|
+
* AS's ticket history; see protocol.md → *Cumulative State*).
|
|
450
421
|
*
|
|
451
|
-
* The gatekeeper resolves these by querying the execution log:
|
|
452
422
|
* - `cumulativeField`: which declared field to sum (use "_count" for plain counting)
|
|
453
423
|
* - `window`: time window for aggregation (daily, weekly, monthly)
|
|
454
|
-
*
|
|
455
|
-
* The resolved value = running total within window + current call value.
|
|
456
424
|
*/
|
|
457
425
|
export interface CumulativeFieldDef {
|
|
458
426
|
source: 'cumulative';
|
|
@@ -469,28 +437,8 @@ export interface CumulativeFieldDef {
|
|
|
469
437
|
export type ExecutionContextFieldDef = DeclaredFieldDef | CumulativeFieldDef;
|
|
470
438
|
|
|
471
439
|
/**
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*/
|
|
475
|
-
export interface GateQuestion {
|
|
476
|
-
question: string;
|
|
477
|
-
required: boolean;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* @deprecated Execution paths removed in v0.4. Kept for backward compatibility.
|
|
482
|
-
*/
|
|
483
|
-
export interface ExecutionPath {
|
|
484
|
-
description: string;
|
|
485
|
-
requiredDomains?: string[];
|
|
486
|
-
ttl?: { default: number; max: number };
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Agent Profile — defines constraint types, execution paths, gate questions,
|
|
491
|
-
* and the frame/bounds/context schemas for bounded execution.
|
|
492
|
-
*
|
|
493
|
-
* Supports both v0.3 (frameSchema) and v0.4 (boundsSchema + contextSchema).
|
|
440
|
+
* Agent Profile — defines the bounds/scope/execution-context schemas, gates,
|
|
441
|
+
* TTL policy, and retention for a bounded authority (protocol.md → *Profiles*).
|
|
494
442
|
*/
|
|
495
443
|
/**
|
|
496
444
|
* A commitment mode a person can choose when signing a mandate. (`review_above_cap`
|
|
@@ -498,6 +446,14 @@ export interface ExecutionPath {
|
|
|
498
446
|
*/
|
|
499
447
|
export type SignableCommitmentMode = 'automatic' | 'review';
|
|
500
448
|
|
|
449
|
+
/** protocol.md → *Owner Signatures* → "Where the requirement lives" — the
|
|
450
|
+
* profile-floor tier: `minBinding` names the weakest `OwnerSignatureBinding`
|
|
451
|
+
* this profile accepts for a co-signing owner. */
|
|
452
|
+
export interface ProfileOwnerSignatureFloor {
|
|
453
|
+
required: boolean;
|
|
454
|
+
minBinding?: OwnerSignatureBinding;
|
|
455
|
+
}
|
|
456
|
+
|
|
501
457
|
export interface AgentProfile {
|
|
502
458
|
id: string;
|
|
503
459
|
name?: string;
|
|
@@ -520,9 +476,10 @@ export interface AgentProfile {
|
|
|
520
476
|
whatsNew?: string;
|
|
521
477
|
|
|
522
478
|
/**
|
|
523
|
-
* Whether
|
|
524
|
-
* verifier holding the content supplies its hash and learns which
|
|
525
|
-
* bind it, without needing a
|
|
479
|
+
* Whether tickets under this profile may be looked up BY THEIR CONTENT — a
|
|
480
|
+
* verifier holding the content supplies its hash and learns which tickets
|
|
481
|
+
* bind it, without needing a ticket id. (Renamed from `receipt_lookup` in
|
|
482
|
+
* v0.7.)
|
|
526
483
|
*
|
|
527
484
|
* OFF unless declared, and that default is the point. The lookup is a
|
|
528
485
|
* confirmation oracle: given a guess at the content it says whether that
|
|
@@ -534,13 +491,8 @@ export interface AgentProfile {
|
|
|
534
491
|
* Enable only when the bound content is unguessable enough that producing it
|
|
535
492
|
* is equivalent to already having it: prose, an artifact URL, a whole record
|
|
536
493
|
* payload. Never for a binding over a short value drawn from a small set.
|
|
537
|
-
*
|
|
538
|
-
* Why it must exist at all: most consequential actions cannot carry their
|
|
539
|
-
* receipt id. A released build was built before the receipt existed, a
|
|
540
|
-
* content-addressed artifact would change identity if the id were added, and
|
|
541
|
-
* a forwarded message has usually lost the footer that carried it.
|
|
542
494
|
*/
|
|
543
|
-
|
|
495
|
+
ticket_lookup?: boolean;
|
|
544
496
|
|
|
545
497
|
/**
|
|
546
498
|
* The commitment modes a mandate under this profile may be signed with.
|
|
@@ -556,22 +508,14 @@ export interface AgentProfile {
|
|
|
556
508
|
commitment_modes?: readonly SignableCommitmentMode[];
|
|
557
509
|
|
|
558
510
|
/**
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*/
|
|
562
|
-
frameSchema?: {
|
|
563
|
-
keyOrder: string[];
|
|
564
|
-
fields: Record<string, ProfileFrameField>;
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* v0.4 bounds schema — defines the authorization bounds parameters.
|
|
511
|
+
* The bounds schema — the enforceable parameters (protocol.md → *Bounds
|
|
512
|
+
* Schema*).
|
|
569
513
|
*/
|
|
570
514
|
boundsSchema?: {
|
|
571
515
|
keyOrder: string[];
|
|
572
516
|
fields: Record<string, ProfileBoundsField>;
|
|
573
517
|
/**
|
|
574
|
-
*
|
|
518
|
+
* Registry of the action types this profile recognizes (e.g.
|
|
575
519
|
* `["send", "delete", "setup"]`). Every `execution.action_type` the
|
|
576
520
|
* Authority Server and gateway accept for this profile MUST be a member
|
|
577
521
|
* when the registry is declared (protocol.md → Bounds Schema, rule 2);
|
|
@@ -583,96 +527,48 @@ export interface AgentProfile {
|
|
|
583
527
|
};
|
|
584
528
|
|
|
585
529
|
/**
|
|
586
|
-
*
|
|
587
|
-
* May be absent or empty for profiles with
|
|
530
|
+
* The scope schema — operational scoping fields that stay local (renamed
|
|
531
|
+
* from `contextSchema` in v0.7). May be absent or empty for profiles with
|
|
532
|
+
* no static scope.
|
|
588
533
|
*/
|
|
589
|
-
|
|
534
|
+
scopeSchema?: {
|
|
590
535
|
keyOrder: string[];
|
|
591
|
-
fields: Record<string,
|
|
536
|
+
fields: Record<string, ProfileScopeField>;
|
|
592
537
|
};
|
|
593
538
|
|
|
594
539
|
executionContextSchema: {
|
|
595
540
|
fields: Record<string, ExecutionContextFieldDef>;
|
|
596
541
|
};
|
|
597
542
|
|
|
598
|
-
/** @deprecated Execution paths removed in v0.4. Kept for backward compatibility. */
|
|
599
|
-
executionPaths?: Record<string, ExecutionPath>;
|
|
600
|
-
|
|
601
543
|
requiredGates: string[];
|
|
602
544
|
|
|
603
|
-
/**
|
|
604
|
-
* v0.4: no gateQuestions — intent prompt is universal, defined in the gateway UI.
|
|
605
|
-
* v0.3: profile-specific gate questions (deprecated).
|
|
606
|
-
*/
|
|
607
|
-
gateQuestions?: {
|
|
608
|
-
problem?: GateQuestion;
|
|
609
|
-
objective?: GateQuestion;
|
|
610
|
-
tradeoffs?: GateQuestion;
|
|
611
|
-
};
|
|
612
|
-
|
|
613
545
|
ttl: { default: number; max: number };
|
|
614
546
|
retention_minimum: number;
|
|
615
547
|
|
|
616
548
|
/**
|
|
617
|
-
*
|
|
549
|
+
* Content Provenance (OPTIONAL, profile-bound). When present, the
|
|
618
550
|
* gateway computes a `contentHash` for gated writes under this profile and
|
|
619
|
-
* passes it (hash only) to the
|
|
551
|
+
* passes it (hash only) to the AS, which signs it into the ticket. Absent
|
|
620
552
|
* → no content hash. See {@link ContentBinding}.
|
|
621
553
|
*/
|
|
622
554
|
content_binding?: ContentBinding;
|
|
623
555
|
|
|
624
556
|
/**
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
557
|
+
* The execution-context fields a ticket under this profile MAY disclose
|
|
558
|
+
* (new in v0.7 — protocol.md → *Ticket Disclosure Is Declared*). Absent →
|
|
559
|
+
* nothing is disclosed by default. A mandate's own `disclose_fields` (when
|
|
560
|
+
* present) MUST be a subset of this list — a mandate may narrow disclosure,
|
|
561
|
+
* never widen it.
|
|
628
562
|
*/
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
// ─── Tool Gating Types ───────────────────────────────────────────────────
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Available transforms for array-aware execution mappings.
|
|
636
|
-
* - length: array length → number
|
|
637
|
-
* - join: array items joined by comma → string
|
|
638
|
-
* - join_domains: extract email domains, deduplicate, sort, join → string
|
|
639
|
-
*/
|
|
640
|
-
export type ExecutionMappingTransform = 'join' | 'join_domains' | 'length';
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* Execution mapping value — how a tool argument maps to execution context field(s).
|
|
644
|
-
* - string: direct copy (argName → fieldName)
|
|
645
|
-
* - { field, divisor }: numeric division (e.g., cents ÷ 100 → EUR)
|
|
646
|
-
* - { field, transform }: array transform (e.g., join_domains)
|
|
647
|
-
* - Array form: one argument maps to multiple execution fields
|
|
648
|
-
*/
|
|
649
|
-
export type ExecutionMappingValue =
|
|
650
|
-
| string
|
|
651
|
-
| { field: string; divisor: number }
|
|
652
|
-
| { field: string; transform: ExecutionMappingTransform }
|
|
653
|
-
| Array<{ field: string; divisor?: number; transform?: ExecutionMappingTransform }>;
|
|
563
|
+
disclose_fields?: string[];
|
|
654
564
|
|
|
655
|
-
/**
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
staticExecution?: Record<string, string | number>;
|
|
663
|
-
/** Read-only tools: require authorization but no execution context checks */
|
|
664
|
-
category?: 'read';
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
/**
|
|
668
|
-
* Profile-level tool gating configuration.
|
|
669
|
-
* - default: applied to all tools not listed in overrides
|
|
670
|
-
* - overrides: per-tool configs keyed by original MCP tool name
|
|
671
|
-
* Use { category: "read" } for read-only tools (null is deprecated)
|
|
672
|
-
*/
|
|
673
|
-
export interface ProfileToolGating {
|
|
674
|
-
default: ProfileToolGatingEntry;
|
|
675
|
-
overrides?: Record<string, ProfileToolGatingEntry | null>;
|
|
565
|
+
/**
|
|
566
|
+
* Profile-floor requirement for an owner signature on a mandate under this
|
|
567
|
+
* profile (protocol.md → *Owner Signatures* → "Where the requirement
|
|
568
|
+
* lives"). Reserved for domains that cannot mean anything without a
|
|
569
|
+
* signature — used more freely it forks `charge` from `charge-with-cosign`.
|
|
570
|
+
*/
|
|
571
|
+
ownerSignature?: ProfileOwnerSignatureFloor;
|
|
676
572
|
}
|
|
677
573
|
|
|
678
574
|
// ─── Execution Log Types ─────────────────────────────────────────────────────
|
|
@@ -719,25 +615,20 @@ export interface ExecutionLogQuery {
|
|
|
719
615
|
): number;
|
|
720
616
|
}
|
|
721
617
|
|
|
722
|
-
// ───
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* Agent frame parameters — mixed types (strings and numbers).
|
|
726
|
-
* Keys and values come from the profile's frameSchema.
|
|
727
|
-
*/
|
|
728
|
-
export type AgentFrameParams = Record<string, string | number>;
|
|
618
|
+
// ─── Bounds / Scope param types ──────────────────────────────────────────────
|
|
729
619
|
|
|
730
620
|
/**
|
|
731
621
|
* Agent bounds parameters — mixed types (strings and numbers).
|
|
732
|
-
* Keys and values come from the profile's boundsSchema
|
|
622
|
+
* Keys and values come from the profile's boundsSchema.
|
|
733
623
|
*/
|
|
734
624
|
export type AgentBoundsParams = Record<string, string | number>;
|
|
735
625
|
|
|
736
626
|
/**
|
|
737
|
-
* Agent
|
|
738
|
-
*
|
|
627
|
+
* Agent scope parameters — mixed types (strings and numbers). Keys and
|
|
628
|
+
* values come from the profile's scopeSchema (renamed from
|
|
629
|
+
* `AgentContextParams` in v0.7).
|
|
739
630
|
*/
|
|
740
|
-
export type
|
|
631
|
+
export type AgentScopeParams = Record<string, string | number>;
|
|
741
632
|
|
|
742
633
|
// ─── Gatekeeper Types ────────────────────────────────────────────────────────
|
|
743
634
|
|
|
@@ -745,60 +636,33 @@ export type AgentContextParams = Record<string, string | number>;
|
|
|
745
636
|
* Request to the Gatekeeper for bounded execution verification.
|
|
746
637
|
*/
|
|
747
638
|
export interface GatekeeperRequest {
|
|
748
|
-
/** The
|
|
749
|
-
|
|
750
|
-
/**
|
|
751
|
-
|
|
752
|
-
/** The agent's execution values for this specific action */
|
|
639
|
+
/** The bounds the mandate(s) committed to. */
|
|
640
|
+
bounds: AgentBoundsParams;
|
|
641
|
+
/** Mandate blobs (base64url) to verify against. */
|
|
642
|
+
mandates: string[];
|
|
643
|
+
/** The agent's execution values for this specific action. */
|
|
753
644
|
execution: Record<string, string | number>;
|
|
754
|
-
/**
|
|
755
|
-
|
|
645
|
+
/** Scope parameters (currency, action_type, allowed recipients, etc.) —
|
|
646
|
+
* enforced locally only; never sent to the Authority Server. */
|
|
647
|
+
scope?: AgentScopeParams;
|
|
756
648
|
/**
|
|
757
|
-
* Authorization path used to scope cumulative lookups in the execution log
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
* profile's boundsSchema, so an extra key there is rejected as an unknown
|
|
761
|
-
* field and breaks attestation verification outright. Cumulative checks
|
|
762
|
-
* previously read the path from the frame, where callers cannot legally put
|
|
763
|
-
* it — so it resolved to "" while the log stored real paths, no entry ever
|
|
764
|
-
* matched, and every running total read zero. The local gate existed but
|
|
765
|
-
* could never fire.
|
|
649
|
+
* Authorization path used to scope cumulative lookups in the execution log
|
|
650
|
+
* (display only — see {@link ExecutionLogQuery}; the Gatekeeper MUST NOT
|
|
651
|
+
* enforce cumulative bounds locally, protocol.md → *Enforcement Authority*).
|
|
766
652
|
*/
|
|
767
653
|
path?: string;
|
|
768
654
|
}
|
|
769
655
|
|
|
770
656
|
/**
|
|
771
|
-
* Structured error from Gatekeeper verification.
|
|
657
|
+
* Structured error from Gatekeeper verification. Codes are the canonical
|
|
658
|
+
* v0.7 surface ({@link HapErrorCode}, `src/errors.ts`) plus the two
|
|
659
|
+
* Gatekeeper-local-only codes that never reach the wire (`FRAME_MISMATCH`
|
|
660
|
+
* is retired with v0.3; a Gatekeeper reports `BOUNDS_HASH_MISMATCH` /
|
|
661
|
+
* `SCOPE_HASH_MISMATCH` for both its own local mismatch and the AS's wire
|
|
662
|
+
* refusal, per protocol.md → *Error Codes*, "Gatekeeper · local").
|
|
772
663
|
*/
|
|
773
664
|
export interface GatekeeperError {
|
|
774
|
-
code:
|
|
775
|
-
| 'BOUND_EXCEEDED'
|
|
776
|
-
| 'CUMULATIVE_LIMIT_EXCEEDED'
|
|
777
|
-
| 'INVALID_SIGNATURE'
|
|
778
|
-
| 'TTL_EXPIRED'
|
|
779
|
-
| 'FRAME_MISMATCH'
|
|
780
|
-
| 'BOUNDS_MISMATCH'
|
|
781
|
-
| 'CONTEXT_MISMATCH'
|
|
782
|
-
| 'DOMAIN_NOT_COVERED'
|
|
783
|
-
| 'INVALID_PROFILE'
|
|
784
|
-
| 'MALFORMED_ATTESTATION'
|
|
785
|
-
// v0.5/v0.6 receipt-path codes a Gatekeeper must recognize as definitive
|
|
786
|
-
// rejections (protocol.md → Pre-flight Receipt Request):
|
|
787
|
-
| 'INVALID_ACTION_TYPE'
|
|
788
|
-
| 'ATTESTATION_REVOKED'
|
|
789
|
-
| 'APPROVAL_REQUIRED'
|
|
790
|
-
| 'IDEMPOTENCY_MISMATCH'
|
|
791
|
-
// v0.6 owner-mandate codes (protocol.md → Owner Mandate Signatures):
|
|
792
|
-
| 'MANDATE_SIGNATURE_REQUIRED'
|
|
793
|
-
| 'MANDATE_SIGNATURE_INVALID'
|
|
794
|
-
| 'APPROVAL_SIGNATURE_REQUIRED'
|
|
795
|
-
| 'APPROVAL_SIGNATURE_INVALID'
|
|
796
|
-
// Canonicalization refusals (protocol.md → Bounds & Scope Canonicalization
|
|
797
|
-
// → Value encoding). Raised when a value cannot be canonicalized at all —
|
|
798
|
-
// a raw LF/CR, or a profile `constraint.pattern` violation. Distinct from
|
|
799
|
-
// BOUNDS_MISMATCH / CONTEXT_MISMATCH, which mean the hashes disagree.
|
|
800
|
-
| 'BOUNDS_INVALID_VALUE'
|
|
801
|
-
| 'CONTEXT_INVALID_VALUE';
|
|
665
|
+
code: import('./errors').HapErrorCode;
|
|
802
666
|
field?: string;
|
|
803
667
|
message: string;
|
|
804
668
|
bound?: string | number;
|