@humanagencyp/hap-core 0.11.1 → 0.12.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 +92 -29
- package/dist/index.d.mts +627 -593
- package/dist/index.d.ts +627 -593
- package/dist/index.js +774 -749
- package/dist/index.mjs +744 -724
- package/package.json +1 -1
- package/src/base64url.ts +39 -0
- package/src/commitment-modes.ts +0 -21
- 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 +204 -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
|
/**
|
|
@@ -320,19 +277,28 @@ export interface ProfileBoundsField {
|
|
|
320
277
|
* kinds, and the same boundType can carry different units.
|
|
321
278
|
*/
|
|
322
279
|
unit?: FieldUnit;
|
|
323
|
-
/**
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
280
|
+
/**
|
|
281
|
+
* The largest value a mandate may set for this (numeric) bound — a ceiling
|
|
282
|
+
* on what the owner can grant, declared by the profile. E.g. reporting@0.2
|
|
283
|
+
* `read_max_age_days: { maximum: 366 }`: no reporting mandate may read back
|
|
284
|
+
* further than a year.
|
|
285
|
+
*
|
|
286
|
+
* Distinct from the bound itself: the bound limits each ACTION; `maximum`
|
|
287
|
+
* limits the MANDATE. A value above it is not a valid mandate for this
|
|
288
|
+
* profile — the mandate editor caps its input, the issuing Authority Server
|
|
289
|
+
* should refuse it, and an enforcement point that reads the bound applies
|
|
290
|
+
* min(value, maximum). Absent → no ceiling.
|
|
291
|
+
*/
|
|
292
|
+
maximum?: number;
|
|
327
293
|
}
|
|
328
294
|
|
|
329
295
|
/**
|
|
330
296
|
* v0.5 Content Provenance — how a profile's action content is hashed into a
|
|
331
|
-
* signed
|
|
297
|
+
* signed ticket (`contentHash`). The ephemeral-content analog of Output
|
|
332
298
|
* Provenance: it binds the *bytes* of the action rather than a location.
|
|
333
299
|
*
|
|
334
300
|
* Profile-bound and OPTIONAL. Absent → no content hash is produced (full
|
|
335
|
-
* backward compatibility). The gateway computes the hash; the
|
|
301
|
+
* backward compatibility). The gateway computes the hash; the AS only ever
|
|
336
302
|
* receives the hash, never the content, so HAP's privacy-minimal design holds.
|
|
337
303
|
*
|
|
338
304
|
* At `version:"1"` the profile declares only the *policy* — whether to bind and
|
|
@@ -396,16 +362,17 @@ export interface ContentBinding {
|
|
|
396
362
|
}
|
|
397
363
|
|
|
398
364
|
/**
|
|
399
|
-
*
|
|
365
|
+
* Scope field definition within a profile (renamed from `ProfileContextField`
|
|
366
|
+
* in v0.7 — protocol.md → *Migration from v0.6*, `contextSchema` → `scopeSchema`).
|
|
400
367
|
*/
|
|
401
|
-
export interface
|
|
368
|
+
export interface ProfileScopeField {
|
|
402
369
|
type: 'string' | 'number';
|
|
403
370
|
required: boolean;
|
|
404
371
|
description?: string;
|
|
405
372
|
displayName?: string;
|
|
406
373
|
format?: 'email' | 'domain' | 'url' | 'currency';
|
|
407
374
|
/**
|
|
408
|
-
*
|
|
375
|
+
* What this field NAMES on the read path. `counterparty` = the other
|
|
409
376
|
* party to a communication (matched against an item's participants);
|
|
410
377
|
* `resource` = the container an item belongs to (a direct attribute match —
|
|
411
378
|
* and a resource scope enforced on writes MUST also bind reads). Absent →
|
|
@@ -414,7 +381,6 @@ export interface ProfileContextField {
|
|
|
414
381
|
*/
|
|
415
382
|
scopeKind?: 'counterparty' | 'resource';
|
|
416
383
|
constraint?: FieldConstraint;
|
|
417
|
-
enum?: string[];
|
|
418
384
|
}
|
|
419
385
|
|
|
420
386
|
/**
|
|
@@ -433,13 +399,11 @@ export interface DeclaredFieldDef {
|
|
|
433
399
|
export type CumulativeWindow = 'daily' | 'weekly' | 'monthly';
|
|
434
400
|
|
|
435
401
|
/**
|
|
436
|
-
* Execution context field definition — cumulative source (resolved from
|
|
402
|
+
* Execution context field definition — cumulative source (resolved from the
|
|
403
|
+
* AS's ticket history; see protocol.md → *Cumulative State*).
|
|
437
404
|
*
|
|
438
|
-
* The gatekeeper resolves these by querying the execution log:
|
|
439
405
|
* - `cumulativeField`: which declared field to sum (use "_count" for plain counting)
|
|
440
406
|
* - `window`: time window for aggregation (daily, weekly, monthly)
|
|
441
|
-
*
|
|
442
|
-
* The resolved value = running total within window + current call value.
|
|
443
407
|
*/
|
|
444
408
|
export interface CumulativeFieldDef {
|
|
445
409
|
source: 'cumulative';
|
|
@@ -456,28 +420,8 @@ export interface CumulativeFieldDef {
|
|
|
456
420
|
export type ExecutionContextFieldDef = DeclaredFieldDef | CumulativeFieldDef;
|
|
457
421
|
|
|
458
422
|
/**
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*/
|
|
462
|
-
export interface GateQuestion {
|
|
463
|
-
question: string;
|
|
464
|
-
required: boolean;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* @deprecated Execution paths removed in v0.4. Kept for backward compatibility.
|
|
469
|
-
*/
|
|
470
|
-
export interface ExecutionPath {
|
|
471
|
-
description: string;
|
|
472
|
-
requiredDomains?: string[];
|
|
473
|
-
ttl?: { default: number; max: number };
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Agent Profile — defines constraint types, execution paths, gate questions,
|
|
478
|
-
* and the frame/bounds/context schemas for bounded execution.
|
|
479
|
-
*
|
|
480
|
-
* Supports both v0.3 (frameSchema) and v0.4 (boundsSchema + contextSchema).
|
|
423
|
+
* Agent Profile — defines the bounds/scope/execution-context schemas, gates,
|
|
424
|
+
* TTL policy, and retention for a bounded authority (protocol.md → *Profiles*).
|
|
481
425
|
*/
|
|
482
426
|
/**
|
|
483
427
|
* A commitment mode a person can choose when signing a mandate. (`review_above_cap`
|
|
@@ -485,6 +429,14 @@ export interface ExecutionPath {
|
|
|
485
429
|
*/
|
|
486
430
|
export type SignableCommitmentMode = 'automatic' | 'review';
|
|
487
431
|
|
|
432
|
+
/** protocol.md → *Owner Signatures* → "Where the requirement lives" — the
|
|
433
|
+
* profile-floor tier: `minBinding` names the weakest `OwnerSignatureBinding`
|
|
434
|
+
* this profile accepts for a co-signing owner. */
|
|
435
|
+
export interface ProfileOwnerSignatureFloor {
|
|
436
|
+
required: boolean;
|
|
437
|
+
minBinding?: OwnerSignatureBinding;
|
|
438
|
+
}
|
|
439
|
+
|
|
488
440
|
export interface AgentProfile {
|
|
489
441
|
id: string;
|
|
490
442
|
name?: string;
|
|
@@ -507,9 +459,10 @@ export interface AgentProfile {
|
|
|
507
459
|
whatsNew?: string;
|
|
508
460
|
|
|
509
461
|
/**
|
|
510
|
-
* Whether
|
|
511
|
-
* verifier holding the content supplies its hash and learns which
|
|
512
|
-
* bind it, without needing a
|
|
462
|
+
* Whether tickets under this profile may be looked up BY THEIR CONTENT — a
|
|
463
|
+
* verifier holding the content supplies its hash and learns which tickets
|
|
464
|
+
* bind it, without needing a ticket id. (Renamed from `receipt_lookup` in
|
|
465
|
+
* v0.7.)
|
|
513
466
|
*
|
|
514
467
|
* OFF unless declared, and that default is the point. The lookup is a
|
|
515
468
|
* confirmation oracle: given a guess at the content it says whether that
|
|
@@ -521,13 +474,8 @@ export interface AgentProfile {
|
|
|
521
474
|
* Enable only when the bound content is unguessable enough that producing it
|
|
522
475
|
* is equivalent to already having it: prose, an artifact URL, a whole record
|
|
523
476
|
* payload. Never for a binding over a short value drawn from a small set.
|
|
524
|
-
*
|
|
525
|
-
* Why it must exist at all: most consequential actions cannot carry their
|
|
526
|
-
* receipt id. A released build was built before the receipt existed, a
|
|
527
|
-
* content-addressed artifact would change identity if the id were added, and
|
|
528
|
-
* a forwarded message has usually lost the footer that carried it.
|
|
529
477
|
*/
|
|
530
|
-
|
|
478
|
+
ticket_lookup?: boolean;
|
|
531
479
|
|
|
532
480
|
/**
|
|
533
481
|
* The commitment modes a mandate under this profile may be signed with.
|
|
@@ -543,22 +491,14 @@ export interface AgentProfile {
|
|
|
543
491
|
commitment_modes?: readonly SignableCommitmentMode[];
|
|
544
492
|
|
|
545
493
|
/**
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*/
|
|
549
|
-
frameSchema?: {
|
|
550
|
-
keyOrder: string[];
|
|
551
|
-
fields: Record<string, ProfileFrameField>;
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* v0.4 bounds schema — defines the authorization bounds parameters.
|
|
494
|
+
* The bounds schema — the enforceable parameters (protocol.md → *Bounds
|
|
495
|
+
* Schema*).
|
|
556
496
|
*/
|
|
557
497
|
boundsSchema?: {
|
|
558
498
|
keyOrder: string[];
|
|
559
499
|
fields: Record<string, ProfileBoundsField>;
|
|
560
500
|
/**
|
|
561
|
-
*
|
|
501
|
+
* Registry of the action types this profile recognizes (e.g.
|
|
562
502
|
* `["send", "delete", "setup"]`). Every `execution.action_type` the
|
|
563
503
|
* Authority Server and gateway accept for this profile MUST be a member
|
|
564
504
|
* when the registry is declared (protocol.md → Bounds Schema, rule 2);
|
|
@@ -570,96 +510,48 @@ export interface AgentProfile {
|
|
|
570
510
|
};
|
|
571
511
|
|
|
572
512
|
/**
|
|
573
|
-
*
|
|
574
|
-
* May be absent or empty for profiles with
|
|
513
|
+
* The scope schema — operational scoping fields that stay local (renamed
|
|
514
|
+
* from `contextSchema` in v0.7). May be absent or empty for profiles with
|
|
515
|
+
* no static scope.
|
|
575
516
|
*/
|
|
576
|
-
|
|
517
|
+
scopeSchema?: {
|
|
577
518
|
keyOrder: string[];
|
|
578
|
-
fields: Record<string,
|
|
519
|
+
fields: Record<string, ProfileScopeField>;
|
|
579
520
|
};
|
|
580
521
|
|
|
581
522
|
executionContextSchema: {
|
|
582
523
|
fields: Record<string, ExecutionContextFieldDef>;
|
|
583
524
|
};
|
|
584
525
|
|
|
585
|
-
/** @deprecated Execution paths removed in v0.4. Kept for backward compatibility. */
|
|
586
|
-
executionPaths?: Record<string, ExecutionPath>;
|
|
587
|
-
|
|
588
526
|
requiredGates: string[];
|
|
589
527
|
|
|
590
|
-
/**
|
|
591
|
-
* v0.4: no gateQuestions — intent prompt is universal, defined in the gateway UI.
|
|
592
|
-
* v0.3: profile-specific gate questions (deprecated).
|
|
593
|
-
*/
|
|
594
|
-
gateQuestions?: {
|
|
595
|
-
problem?: GateQuestion;
|
|
596
|
-
objective?: GateQuestion;
|
|
597
|
-
tradeoffs?: GateQuestion;
|
|
598
|
-
};
|
|
599
|
-
|
|
600
528
|
ttl: { default: number; max: number };
|
|
601
529
|
retention_minimum: number;
|
|
602
530
|
|
|
603
531
|
/**
|
|
604
|
-
*
|
|
532
|
+
* Content Provenance (OPTIONAL, profile-bound). When present, the
|
|
605
533
|
* gateway computes a `contentHash` for gated writes under this profile and
|
|
606
|
-
* passes it (hash only) to the
|
|
534
|
+
* passes it (hash only) to the AS, which signs it into the ticket. Absent
|
|
607
535
|
* → no content hash. See {@link ContentBinding}.
|
|
608
536
|
*/
|
|
609
537
|
content_binding?: ContentBinding;
|
|
610
538
|
|
|
611
539
|
/**
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
540
|
+
* The execution-context fields a ticket under this profile MAY disclose
|
|
541
|
+
* (new in v0.7 — protocol.md → *Ticket Disclosure Is Declared*). Absent →
|
|
542
|
+
* nothing is disclosed by default. A mandate's own `disclose_fields` (when
|
|
543
|
+
* present) MUST be a subset of this list — a mandate may narrow disclosure,
|
|
544
|
+
* never widen it.
|
|
615
545
|
*/
|
|
616
|
-
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// ─── Tool Gating Types ───────────────────────────────────────────────────
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* Available transforms for array-aware execution mappings.
|
|
623
|
-
* - length: array length → number
|
|
624
|
-
* - join: array items joined by comma → string
|
|
625
|
-
* - join_domains: extract email domains, deduplicate, sort, join → string
|
|
626
|
-
*/
|
|
627
|
-
export type ExecutionMappingTransform = 'join' | 'join_domains' | 'length';
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
* Execution mapping value — how a tool argument maps to execution context field(s).
|
|
631
|
-
* - string: direct copy (argName → fieldName)
|
|
632
|
-
* - { field, divisor }: numeric division (e.g., cents ÷ 100 → EUR)
|
|
633
|
-
* - { field, transform }: array transform (e.g., join_domains)
|
|
634
|
-
* - Array form: one argument maps to multiple execution fields
|
|
635
|
-
*/
|
|
636
|
-
export type ExecutionMappingValue =
|
|
637
|
-
| string
|
|
638
|
-
| { field: string; divisor: number }
|
|
639
|
-
| { field: string; transform: ExecutionMappingTransform }
|
|
640
|
-
| Array<{ field: string; divisor?: number; transform?: ExecutionMappingTransform }>;
|
|
546
|
+
disclose_fields?: string[];
|
|
641
547
|
|
|
642
|
-
/**
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
staticExecution?: Record<string, string | number>;
|
|
650
|
-
/** Read-only tools: require authorization but no execution context checks */
|
|
651
|
-
category?: 'read';
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
* Profile-level tool gating configuration.
|
|
656
|
-
* - default: applied to all tools not listed in overrides
|
|
657
|
-
* - overrides: per-tool configs keyed by original MCP tool name
|
|
658
|
-
* Use { category: "read" } for read-only tools (null is deprecated)
|
|
659
|
-
*/
|
|
660
|
-
export interface ProfileToolGating {
|
|
661
|
-
default: ProfileToolGatingEntry;
|
|
662
|
-
overrides?: Record<string, ProfileToolGatingEntry | null>;
|
|
548
|
+
/**
|
|
549
|
+
* Profile-floor requirement for an owner signature on a mandate under this
|
|
550
|
+
* profile (protocol.md → *Owner Signatures* → "Where the requirement
|
|
551
|
+
* lives"). Reserved for domains that cannot mean anything without a
|
|
552
|
+
* signature — used more freely it forks `charge` from `charge-with-cosign`.
|
|
553
|
+
*/
|
|
554
|
+
ownerSignature?: ProfileOwnerSignatureFloor;
|
|
663
555
|
}
|
|
664
556
|
|
|
665
557
|
// ─── Execution Log Types ─────────────────────────────────────────────────────
|
|
@@ -706,25 +598,20 @@ export interface ExecutionLogQuery {
|
|
|
706
598
|
): number;
|
|
707
599
|
}
|
|
708
600
|
|
|
709
|
-
// ───
|
|
710
|
-
|
|
711
|
-
/**
|
|
712
|
-
* Agent frame parameters — mixed types (strings and numbers).
|
|
713
|
-
* Keys and values come from the profile's frameSchema.
|
|
714
|
-
*/
|
|
715
|
-
export type AgentFrameParams = Record<string, string | number>;
|
|
601
|
+
// ─── Bounds / Scope param types ──────────────────────────────────────────────
|
|
716
602
|
|
|
717
603
|
/**
|
|
718
604
|
* Agent bounds parameters — mixed types (strings and numbers).
|
|
719
|
-
* Keys and values come from the profile's boundsSchema
|
|
605
|
+
* Keys and values come from the profile's boundsSchema.
|
|
720
606
|
*/
|
|
721
607
|
export type AgentBoundsParams = Record<string, string | number>;
|
|
722
608
|
|
|
723
609
|
/**
|
|
724
|
-
* Agent
|
|
725
|
-
*
|
|
610
|
+
* Agent scope parameters — mixed types (strings and numbers). Keys and
|
|
611
|
+
* values come from the profile's scopeSchema (renamed from
|
|
612
|
+
* `AgentContextParams` in v0.7).
|
|
726
613
|
*/
|
|
727
|
-
export type
|
|
614
|
+
export type AgentScopeParams = Record<string, string | number>;
|
|
728
615
|
|
|
729
616
|
// ─── Gatekeeper Types ────────────────────────────────────────────────────────
|
|
730
617
|
|
|
@@ -732,60 +619,33 @@ export type AgentContextParams = Record<string, string | number>;
|
|
|
732
619
|
* Request to the Gatekeeper for bounded execution verification.
|
|
733
620
|
*/
|
|
734
621
|
export interface GatekeeperRequest {
|
|
735
|
-
/** The
|
|
736
|
-
|
|
737
|
-
/**
|
|
738
|
-
|
|
739
|
-
/** The agent's execution values for this specific action */
|
|
622
|
+
/** The bounds the mandate(s) committed to. */
|
|
623
|
+
bounds: AgentBoundsParams;
|
|
624
|
+
/** Mandate blobs (base64url) to verify against. */
|
|
625
|
+
mandates: string[];
|
|
626
|
+
/** The agent's execution values for this specific action. */
|
|
740
627
|
execution: Record<string, string | number>;
|
|
741
|
-
/**
|
|
742
|
-
|
|
628
|
+
/** Scope parameters (currency, action_type, allowed recipients, etc.) —
|
|
629
|
+
* enforced locally only; never sent to the Authority Server. */
|
|
630
|
+
scope?: AgentScopeParams;
|
|
743
631
|
/**
|
|
744
|
-
* Authorization path used to scope cumulative lookups in the execution log
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
* profile's boundsSchema, so an extra key there is rejected as an unknown
|
|
748
|
-
* field and breaks attestation verification outright. Cumulative checks
|
|
749
|
-
* previously read the path from the frame, where callers cannot legally put
|
|
750
|
-
* it — so it resolved to "" while the log stored real paths, no entry ever
|
|
751
|
-
* matched, and every running total read zero. The local gate existed but
|
|
752
|
-
* could never fire.
|
|
632
|
+
* Authorization path used to scope cumulative lookups in the execution log
|
|
633
|
+
* (display only — see {@link ExecutionLogQuery}; the Gatekeeper MUST NOT
|
|
634
|
+
* enforce cumulative bounds locally, protocol.md → *Enforcement Authority*).
|
|
753
635
|
*/
|
|
754
636
|
path?: string;
|
|
755
637
|
}
|
|
756
638
|
|
|
757
639
|
/**
|
|
758
|
-
* Structured error from Gatekeeper verification.
|
|
640
|
+
* Structured error from Gatekeeper verification. Codes are the canonical
|
|
641
|
+
* v0.7 surface ({@link HapErrorCode}, `src/errors.ts`) plus the two
|
|
642
|
+
* Gatekeeper-local-only codes that never reach the wire (`FRAME_MISMATCH`
|
|
643
|
+
* is retired with v0.3; a Gatekeeper reports `BOUNDS_HASH_MISMATCH` /
|
|
644
|
+
* `SCOPE_HASH_MISMATCH` for both its own local mismatch and the AS's wire
|
|
645
|
+
* refusal, per protocol.md → *Error Codes*, "Gatekeeper · local").
|
|
759
646
|
*/
|
|
760
647
|
export interface GatekeeperError {
|
|
761
|
-
code:
|
|
762
|
-
| 'BOUND_EXCEEDED'
|
|
763
|
-
| 'CUMULATIVE_LIMIT_EXCEEDED'
|
|
764
|
-
| 'INVALID_SIGNATURE'
|
|
765
|
-
| 'TTL_EXPIRED'
|
|
766
|
-
| 'FRAME_MISMATCH'
|
|
767
|
-
| 'BOUNDS_MISMATCH'
|
|
768
|
-
| 'CONTEXT_MISMATCH'
|
|
769
|
-
| 'DOMAIN_NOT_COVERED'
|
|
770
|
-
| 'INVALID_PROFILE'
|
|
771
|
-
| 'MALFORMED_ATTESTATION'
|
|
772
|
-
// v0.5/v0.6 receipt-path codes a Gatekeeper must recognize as definitive
|
|
773
|
-
// rejections (protocol.md → Pre-flight Receipt Request):
|
|
774
|
-
| 'INVALID_ACTION_TYPE'
|
|
775
|
-
| 'ATTESTATION_REVOKED'
|
|
776
|
-
| 'APPROVAL_REQUIRED'
|
|
777
|
-
| 'IDEMPOTENCY_MISMATCH'
|
|
778
|
-
// v0.6 owner-mandate codes (protocol.md → Owner Mandate Signatures):
|
|
779
|
-
| 'MANDATE_SIGNATURE_REQUIRED'
|
|
780
|
-
| 'MANDATE_SIGNATURE_INVALID'
|
|
781
|
-
| 'APPROVAL_SIGNATURE_REQUIRED'
|
|
782
|
-
| 'APPROVAL_SIGNATURE_INVALID'
|
|
783
|
-
// Canonicalization refusals (protocol.md → Bounds & Scope Canonicalization
|
|
784
|
-
// → Value encoding). Raised when a value cannot be canonicalized at all —
|
|
785
|
-
// a raw LF/CR, or a profile `constraint.pattern` violation. Distinct from
|
|
786
|
-
// BOUNDS_MISMATCH / CONTEXT_MISMATCH, which mean the hashes disagree.
|
|
787
|
-
| 'BOUNDS_INVALID_VALUE'
|
|
788
|
-
| 'CONTEXT_INVALID_VALUE';
|
|
648
|
+
code: import('./errors').HapErrorCode;
|
|
789
649
|
field?: string;
|
|
790
650
|
message: string;
|
|
791
651
|
bound?: string | number;
|