@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/dist/index.d.ts
CHANGED
|
@@ -1,123 +1,97 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Canonical v0.7 error codes — protocol.md → *Error Codes*.
|
|
3
|
+
*
|
|
4
|
+
* "Error codes are canonical across the protocol. Implementations MUST emit
|
|
5
|
+
* exactly these codes and MUST NOT alias them under different names." This
|
|
6
|
+
* module is the single list both the Gatekeeper (local checks) and any AS
|
|
7
|
+
* client built on this library read from, so the two cannot drift.
|
|
8
|
+
*
|
|
9
|
+
* v0.7 renamed every code that carried a retired word (attestation → mandate,
|
|
10
|
+
* context → scope, receipt → ticket) — see protocol.md → *Migration from
|
|
11
|
+
* v0.6* → "Error-code renames" for the old→new map. There is no alias: a v0.6
|
|
12
|
+
* code name is simply gone from this list, by design (no backward
|
|
13
|
+
* compatibility — CLAUDE.md "Decided by the owner").
|
|
14
|
+
*/
|
|
15
|
+
/** Codes an Authority Server (or a local Gatekeeper check) emits refusing a mandate request. */
|
|
16
|
+
declare const MANDATE_ERROR_CODES: readonly ["BOUNDS_HASH_MISMATCH", "SCOPE_HASH_MISMATCH", "INVALID_SIGNATURE", "OWNER_NOT_COVERED", "TTL_EXPIRED", "PROFILE_NOT_FOUND", "PROFILE_INVALID", "PROFILE_HASH_MISMATCH", "COVERAGE_INSUFFICIENT", "MALFORMED_MANDATE", "VERSION_UNSUPPORTED", "OWNER_NOT_APPROVER", "BOUNDS_INVALID_VALUE", "SCOPE_INVALID_VALUE", "ABOVE_CAP_CONFIG_INVALID", "OWNER_SIGNATURE_REQUIRED", "OWNER_SIGNATURE_INVALID"];
|
|
17
|
+
/** Codes an Authority Server emits refusing a ticket request. */
|
|
18
|
+
declare const TICKET_ERROR_CODES: readonly ["MANDATE_NOT_FOUND", "MANDATE_EXPIRED", "MANDATE_REVOKED", "BOUND_EXCEEDED", "CUMULATIVE_LIMIT_EXCEEDED", "COVERAGE_INSUFFICIENT", "PROFILE_NOT_FOUND", "INVALID_ACTION_TYPE", "APPROVAL_REQUIRED", "PROPOSAL_REQUIRED", "PROPOSAL_NOT_FOUND", "PROPOSAL_NOT_APPROVED", "PROPOSAL_EXPIRED", "PROPOSAL_REJECTED", "PROPOSAL_MISMATCH", "PROPOSAL_MANDATE_MISMATCH", "PROPOSAL_ALREADY_EXECUTED", "MALFORMED_TICKET_REQUEST", "IDEMPOTENCY_KEY_REQUIRED", "IDEMPOTENCY_MISMATCH", "APPROVAL_SIGNATURE_REQUIRED", "APPROVAL_SIGNATURE_INVALID"];
|
|
19
|
+
/** The full canonical surface — every code a conformant implementation may emit. */
|
|
20
|
+
type HapErrorCode = (typeof MANDATE_ERROR_CODES)[number] | (typeof TICKET_ERROR_CODES)[number];
|
|
21
|
+
/**
|
|
22
|
+
* A refusal carrying one of the canonical codes above. Thrown by local
|
|
23
|
+
* (Gatekeeper-side, no-wire-round-trip) checks in this library; an AS client
|
|
24
|
+
* built on hap-core maps a wire `{ code, message }` error onto the same type.
|
|
25
|
+
*/
|
|
26
|
+
declare class HapError extends Error {
|
|
27
|
+
readonly code: HapErrorCode;
|
|
28
|
+
constructor(code: HapErrorCode, message: string);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* HAP Core Types — v0.7 ("mandate" / "ticket" / "scope" / "Mandate Owner")
|
|
3
33
|
*
|
|
4
|
-
*
|
|
34
|
+
* v0.7 is a vocabulary release (protocol.md → *Migration from v0.6*): the
|
|
35
|
+
* wire renames *attestation* → *mandate*, *context* → *scope*, *execution
|
|
36
|
+
* receipt* → *mandate ticket*, and *Decision Owner* → *Mandate Owner*. There
|
|
37
|
+
* is no backward-compat alias here — v0.6 and earlier artifacts are
|
|
38
|
+
* verification-only history outside this package (CLAUDE.md "Decided by the
|
|
39
|
+
* owner": no verify path for 0.5/0.6, no old→new code map).
|
|
5
40
|
*/
|
|
6
|
-
interface
|
|
7
|
-
typ: 'HAP-
|
|
41
|
+
interface MandateHeader {
|
|
42
|
+
typ: 'HAP-mandate';
|
|
8
43
|
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' | '0.5' | '0.6';
|
|
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
|
-
/**
|
|
27
|
-
* v0.4 (deprecated in v0.5) — domain-scoped owners. Optional so v0.5
|
|
28
|
-
* attestations that carry only `resolved_owners` remain valid. During the
|
|
29
|
-
* transition the AS emits BOTH `resolved_domains` (internal coverage) and
|
|
30
|
-
* `resolved_owners` (the v0.5 signed wire field).
|
|
31
|
-
*/
|
|
32
|
-
resolved_domains?: ResolvedDomain[];
|
|
33
|
-
/**
|
|
34
|
-
* v0.5 — the Decision Owner DIDs this attestation covers (person-centric
|
|
35
|
-
* model; replaces the abstract domain in `resolved_domains`). Carried in the
|
|
36
|
-
* signed payload so verifiers bind the action to the human(s) who authorized it.
|
|
37
|
-
*/
|
|
38
|
-
resolved_owners?: string[];
|
|
39
|
-
gate_content_hashes: Record<string, string>;
|
|
40
|
-
/**
|
|
41
|
-
* v0.5 (companion spec `intent-disclosure@0.1`) — present iff the attestation
|
|
42
|
-
* carries an encrypted-intent disclosure object. `sha256:`-prefixed hash that
|
|
43
|
-
* binds the disclosure's `intent_ciphertext` + `approvers_frozen` into the
|
|
44
|
-
* signed payload (see {@link computeIntentDisclosureHash}), so a compromised
|
|
45
|
-
* AS cannot swap the ciphertext/wrapped keys or alter the approver set
|
|
46
|
-
* without invalidating the attestation signature (companion invariant C2).
|
|
47
|
-
*/
|
|
48
|
-
intent_disclosure_hash?: string;
|
|
49
44
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* as `low` (pseudonymous, no name). See {@link Subject} and
|
|
54
|
-
* {@link deriveIdentityLine}.
|
|
55
|
-
*/
|
|
56
|
-
subjects?: Subject[];
|
|
57
|
-
/**
|
|
58
|
-
* v0.4 — commitment mode chosen by the decision owner at attestation time.
|
|
59
|
-
* - 'automatic': agent may invoke bounded tools without per-action review.
|
|
60
|
-
* - 'review': every tool call requires an approved proposal before the
|
|
61
|
-
* receipt route will sign a receipt.
|
|
62
|
-
*
|
|
63
|
-
* Cryptographically bound in the signed payload so a compromised SP cannot
|
|
64
|
-
* silently flip 'review' → 'automatic'. Absent on v0.3 attestations.
|
|
65
|
-
* `review_above_cap` (v0.5): automatic below the signed caps, proposal above.
|
|
45
|
+
* When present MUST identify the same key as `issuer` (for a `did:key`
|
|
46
|
+
* issuer, the multibase key fingerprint) — Mandate rule 8. The field a
|
|
47
|
+
* party writes (`kid`) loses to the field a party cannot forge (`issuer`).
|
|
66
48
|
*/
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* v0.6 (Owner Mandate Signatures) — the Decision Owner's own signature(s)
|
|
70
|
-
* over the mandate projection ({@link MandateProjection}), one entry per
|
|
71
|
-
* co-signing owner. Carried INSIDE the AS-signed payload, so the AS attests
|
|
72
|
-
* to having received it and cannot strip it without invalidating its own
|
|
73
|
-
* signature. Optional and additive: an attestation without it behaves as
|
|
74
|
-
* before (the v0.5 posture — the AS asserts, nobody co-signs).
|
|
75
|
-
*/
|
|
76
|
-
owner_mandates?: OwnerMandate[];
|
|
77
|
-
issued_at: number;
|
|
78
|
-
expires_at: number;
|
|
49
|
+
kid?: string;
|
|
79
50
|
}
|
|
80
|
-
/**
|
|
81
|
-
* identity assurance ({@link Subject.method}). */
|
|
82
|
-
type
|
|
51
|
+
/** Signature-assurance axis — what custody signed the mandate projection or
|
|
52
|
+
* approval. Independent of identity assurance ({@link Subject.method}). */
|
|
53
|
+
type OwnerSignatureBinding = 'raw' | 'webauthn' | 'eudi';
|
|
83
54
|
/** Which surface showed the owner what they signed. A DECLARATION, not a proof
|
|
84
|
-
* — no verifier can check it; see protocol.md → Owner
|
|
55
|
+
* — no verifier can check it; see protocol.md → *Owner Signatures*. */
|
|
85
56
|
type SigningSurface = 'gatekeeper_local' | 'as_web' | 'wallet_display';
|
|
86
57
|
/**
|
|
87
|
-
* v0.
|
|
58
|
+
* v0.7 `mandate_owners` entry (protocol.md → *Mandate field: mandate_owners*).
|
|
59
|
+
* `did` is required; the remaining fields are present together when the
|
|
60
|
+
* owner co-signed and absent together when they did not (Mandate rule 7).
|
|
88
61
|
*
|
|
89
62
|
* There is deliberately NO `public_key` field, and it MUST NOT be added: the
|
|
90
63
|
* verification key is carried in the DID itself (key-bearing `did:key`), so a
|
|
91
64
|
* non-key-bearing DID fails STRUCTURALLY instead of validating against a key
|
|
92
|
-
* the AS could have substituted.
|
|
93
|
-
* hygiene; if it disagrees with the DID's key type, the DID is authoritative.
|
|
65
|
+
* the AS could have substituted.
|
|
94
66
|
*/
|
|
95
|
-
interface
|
|
96
|
-
/** The
|
|
67
|
+
interface MandateOwnerEntry {
|
|
68
|
+
/** The Mandate Owner's DID. For a co-signing entry, MUST be key-bearing. */
|
|
97
69
|
did: string;
|
|
98
70
|
/** Signature algorithm hint. The DID's multicodec wins on any disagreement. */
|
|
99
|
-
alg
|
|
71
|
+
alg?: 'EdDSA' | 'ES256';
|
|
100
72
|
/** base64url (no padding) signature over the JCS bytes of the mandate projection. */
|
|
101
|
-
signature
|
|
73
|
+
signature?: string;
|
|
102
74
|
/** When the owner signed (unix seconds). */
|
|
103
|
-
signed_at
|
|
75
|
+
signed_at?: number;
|
|
104
76
|
/** Defence-in-depth against duplicate issuance by an HONEST AS only —
|
|
105
77
|
* AS-side nonce enforcement is no defence against the AS itself. */
|
|
106
|
-
nonce
|
|
107
|
-
|
|
78
|
+
nonce?: string;
|
|
79
|
+
/** Required-present, value-open when a signature is carried: an AS MUST
|
|
80
|
+
* NOT reject an entry for a `binding` value it does not recognize. */
|
|
81
|
+
binding?: OwnerSignatureBinding;
|
|
108
82
|
signing_surface?: SigningSurface;
|
|
109
83
|
}
|
|
110
84
|
/**
|
|
111
|
-
* v0.6 Identity Assurance — a signed overlay binding a
|
|
112
|
-
* real-world identity to the
|
|
85
|
+
* v0.6 Identity Assurance — a signed overlay binding a Mandate Owner's verified
|
|
86
|
+
* real-world identity to the mandate, gated by HOW the identity was verified.
|
|
113
87
|
*
|
|
114
88
|
* Two display levels (`assurance`): `low` discloses no name; `high` MAY disclose a
|
|
115
89
|
* name. At `high`, two trust roots: `as` (the AS operator vouches — valid only
|
|
116
90
|
* within its own domain) and `external` (an external eID such as EUDI — carries the
|
|
117
|
-
* owner's own signature, AS-independent).
|
|
91
|
+
* owner's own signature, AS-independent).
|
|
118
92
|
*/
|
|
119
93
|
interface Subject {
|
|
120
|
-
/** The
|
|
94
|
+
/** The Mandate Owner DID this subject describes (matches the `mandate_owners` entry). */
|
|
121
95
|
did: string;
|
|
122
96
|
/** `low` → no name shown; `high` → the name MAY be shown. */
|
|
123
97
|
assurance: 'low' | 'high';
|
|
@@ -133,18 +107,53 @@ interface Subject {
|
|
|
133
107
|
};
|
|
134
108
|
/** When the underlying verification was performed (unix seconds). */
|
|
135
109
|
verified_at?: number;
|
|
110
|
+
}
|
|
111
|
+
/** protocol.md → *Mandate Payload (v0.7)*. */
|
|
112
|
+
interface MandatePayload {
|
|
113
|
+
mandate_id: string;
|
|
114
|
+
version: '0.7';
|
|
115
|
+
profile_id: string;
|
|
116
|
+
/** Hash of the canonical bounds string. */
|
|
117
|
+
bounds_hash: string;
|
|
118
|
+
/** Hash of the canonical scope string (sha256 of "" when scope is empty). */
|
|
119
|
+
scope_hash: string;
|
|
120
|
+
execution_context_hash: string;
|
|
121
|
+
/** `sha256` over the JCS serialization of the profile the Gatekeeper
|
|
122
|
+
* provisioned for `profile_id` — the content address of the rulebook this
|
|
123
|
+
* mandate was issued under (new in v0.7). */
|
|
124
|
+
profile_hash: string;
|
|
125
|
+
/** The Authority Server's DID — SHOULD be the `did:key` of the signing key
|
|
126
|
+
* itself (new in v0.7). */
|
|
127
|
+
issuer: string;
|
|
128
|
+
/** Exactly one entry in v0.7 (Mandate rule 7). */
|
|
129
|
+
mandate_owners: MandateOwnerEntry[];
|
|
130
|
+
gate_content_hashes: Record<string, string>;
|
|
131
|
+
commitment_mode: 'automatic' | 'review' | 'review_above_cap';
|
|
132
|
+
/** Required, together with `above_cap_approvers`, iff `commitment_mode === "review_above_cap"`. */
|
|
133
|
+
above_cap_caps?: Record<string, number>;
|
|
134
|
+
above_cap_approvers?: string[];
|
|
136
135
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* it; verifiers MAY ignore it on artifacts that carry it. Kept readable for
|
|
141
|
-
* audit of pre-0.6 artifacts only.
|
|
136
|
+
* Present iff the mandate carries an encrypted-intent disclosure object
|
|
137
|
+
* (companion spec `intent-disclosure@0.1`) — `sha256:`-prefixed hash
|
|
138
|
+
* binding `intent_ciphertext` + `approvers_frozen` into the signed payload.
|
|
142
139
|
*/
|
|
143
|
-
|
|
140
|
+
intent_disclosure_hash?: string;
|
|
141
|
+
/** Signed identity-assurance block, one entry per owner — present iff the
|
|
142
|
+
* Mandate Owner disclosed identity. */
|
|
143
|
+
subjects?: Subject[];
|
|
144
|
+
/**
|
|
145
|
+
* The owner's narrowing of the profile's `disclose_fields` list (new in
|
|
146
|
+
* v0.7) — a subset of the profile's own list. See *Ticket Disclosure Is
|
|
147
|
+
* Declared*.
|
|
148
|
+
*/
|
|
149
|
+
disclose_fields?: string[];
|
|
150
|
+
issued_at: number;
|
|
151
|
+
expires_at: number;
|
|
144
152
|
}
|
|
145
|
-
interface
|
|
146
|
-
header:
|
|
147
|
-
payload:
|
|
153
|
+
interface Mandate {
|
|
154
|
+
header: MandateHeader;
|
|
155
|
+
payload: MandatePayload;
|
|
156
|
+
/** base64url (no padding) Ed25519 signature over the JCS-canonical payload. */
|
|
148
157
|
signature: string;
|
|
149
158
|
}
|
|
150
159
|
/**
|
|
@@ -179,24 +188,13 @@ interface FieldConstraint {
|
|
|
179
188
|
requiredFor?: string[];
|
|
180
189
|
}
|
|
181
190
|
/**
|
|
182
|
-
*
|
|
183
|
-
*/
|
|
184
|
-
interface ProfileFrameField {
|
|
185
|
-
type: 'string' | 'number';
|
|
186
|
-
required: boolean;
|
|
187
|
-
description?: string;
|
|
188
|
-
constraint?: FieldConstraint;
|
|
189
|
-
enum?: string[];
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Bound enforcement semantics — how a v0.4 bound is checked.
|
|
191
|
+
* Bound enforcement semantics — how a bound is checked.
|
|
193
192
|
*
|
|
194
|
-
* Every bounds field in a
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* dispatch sites.
|
|
193
|
+
* Every bounds field in a profile declares a `boundType` so the AS and
|
|
194
|
+
* hap-core gatekeeper can dispatch on it directly, without parsing field
|
|
195
|
+
* names or guessing conventions. This is the single source of truth for
|
|
196
|
+
* "what does this bound mean at enforcement time" — if a new kind is needed,
|
|
197
|
+
* add a variant here and update the dispatch sites.
|
|
200
198
|
*
|
|
201
199
|
* See `ProfileBoundsField.boundType`.
|
|
202
200
|
*/
|
|
@@ -206,23 +204,15 @@ type BoundType =
|
|
|
206
204
|
* satisfy `execution[of] <= bound` for the current call. No cumulative
|
|
207
205
|
* tracking. Used by: amount_max, recipient_max, booking_duration_max, etc.
|
|
208
206
|
*
|
|
209
|
-
* `requiredFor`
|
|
210
|
-
*
|
|
207
|
+
* `requiredFor` closes the converse: without it, a call that does not
|
|
208
|
+
* carry `of` at all is simply skipped — a 5,000 cap refuses 6,000 but
|
|
211
209
|
* permits a call that declares no value whatsoever, which is indistinguishable
|
|
212
|
-
* from an unenforced bound.
|
|
213
|
-
* `
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* skip-on-absence behaviour is unchanged. Meaningful only on
|
|
219
|
-
* `per_transaction`; the other `BoundType` kinds have no per-call "value
|
|
220
|
-
* exposed or not" question — `cumulative_sum`/`cumulative_count` always read
|
|
221
|
-
* from the execution log regardless of what this call declares, and `enum`
|
|
222
|
-
* is a capability flag, not a runtime value — so the field does not exist
|
|
223
|
-
* on those variants and a profile that puts it there has it silently
|
|
224
|
-
* ignored by every enforcement point (TypeScript also refuses it when the
|
|
225
|
-
* profile is authored against this type rather than raw JSON).
|
|
210
|
+
* from an unenforced bound. Values are drawn from the profile's
|
|
211
|
+
* `boundsSchema.actionTypes` registry. For a listed `action_type`, an
|
|
212
|
+
* execution whose context lacks `of` — or whose value is not a finite
|
|
213
|
+
* number — MUST be refused rather than skipped. `appliesTo` MUST NOT be
|
|
214
|
+
* declared on a `per_transaction` bound (protocol.md → Bounds Schema rule 7)
|
|
215
|
+
* — it applies wherever `of` is present in the execution context.
|
|
226
216
|
*/
|
|
227
217
|
{
|
|
228
218
|
kind: 'per_transaction';
|
|
@@ -230,10 +220,12 @@ type BoundType =
|
|
|
230
220
|
requiredFor?: string[];
|
|
231
221
|
}
|
|
232
222
|
/**
|
|
233
|
-
* Cumulative sum within a time window. The
|
|
234
|
-
* of `execution[of]` across all prior
|
|
223
|
+
* Cumulative sum within a time window. The AS maintains a running sum
|
|
224
|
+
* of `execution[of]` across all prior tickets in the window; the
|
|
235
225
|
* current call is approved iff `running_sum + execution[of] <= bound`.
|
|
236
|
-
*
|
|
226
|
+
* AS-only enforcement (protocol.md → *Enforcement Authority*); the
|
|
227
|
+
* Gatekeeper MUST NOT enforce this locally. Used by: amount_daily_max,
|
|
228
|
+
* spend_monthly_max, etc.
|
|
237
229
|
*/
|
|
238
230
|
| {
|
|
239
231
|
kind: 'cumulative_sum';
|
|
@@ -241,10 +233,14 @@ type BoundType =
|
|
|
241
233
|
window: CumulativeWindow;
|
|
242
234
|
}
|
|
243
235
|
/**
|
|
244
|
-
* Cumulative count within a time window. Every qualifying
|
|
236
|
+
* Cumulative count within a time window. Every qualifying ticket
|
|
245
237
|
* counts as +1; the current call is approved iff
|
|
246
238
|
* `running_count + 1 <= bound`. No execution context field is read.
|
|
247
|
-
*
|
|
239
|
+
* AS-only enforcement. `appliesTo` is REQUIRED on a profile published
|
|
240
|
+
* under v0.7 or later (Bounds Schema rule 7) — absence would mean
|
|
241
|
+
* "governs every action type", so a forgotten declaration silently
|
|
242
|
+
* throttles every action. Used by: write_daily_max, post_monthly_max,
|
|
243
|
+
* booking_daily_max, etc.
|
|
248
244
|
*/
|
|
249
245
|
| {
|
|
250
246
|
kind: 'cumulative_count';
|
|
@@ -252,11 +248,12 @@ type BoundType =
|
|
|
252
248
|
}
|
|
253
249
|
/**
|
|
254
250
|
* String bound restricted to a fixed set of allowed values. The bound's
|
|
255
|
-
* value must be one of `values` at
|
|
251
|
+
* value must be one of `values` at mandate time. The gateway
|
|
256
252
|
* tool-proxy gates tool calls based on the stored bound (via the
|
|
257
253
|
* integration manifest's `boundField` + `requiredValue`). Not cumulated,
|
|
258
|
-
* not checked by the
|
|
259
|
-
*
|
|
254
|
+
* not checked by the AS ticket route — it's a capability flag.
|
|
255
|
+
* `appliesTo` MUST NOT be declared here (it is a capability flag, not an
|
|
256
|
+
* action-scoped limit). Used by: read_access, delete_access, archive_access.
|
|
260
257
|
*/
|
|
261
258
|
| {
|
|
262
259
|
kind: 'enum';
|
|
@@ -274,11 +271,11 @@ type BoundType =
|
|
|
274
271
|
*/
|
|
275
272
|
type FieldUnit = 'count' | 'minutes' | 'hours' | 'days' | `currency:${string}` | 'percent';
|
|
276
273
|
/**
|
|
277
|
-
* Bounds field definition within a
|
|
274
|
+
* Bounds field definition within a profile.
|
|
278
275
|
*
|
|
279
|
-
*
|
|
280
|
-
* the bound is enforced.
|
|
281
|
-
*
|
|
276
|
+
* Every non-metadata bounds field MUST declare `boundType` — an explicit
|
|
277
|
+
* declaration of how the bound is enforced. Implementations MUST fail
|
|
278
|
+
* closed on any bounds field that omits it (Bounds Schema rule 2).
|
|
282
279
|
*/
|
|
283
280
|
interface ProfileBoundsField {
|
|
284
281
|
type: 'string' | 'number';
|
|
@@ -286,26 +283,18 @@ interface ProfileBoundsField {
|
|
|
286
283
|
description?: string;
|
|
287
284
|
displayName?: string;
|
|
288
285
|
format?: 'email' | 'domain' | 'url' | 'currency';
|
|
289
|
-
/**
|
|
290
|
-
* v0.4 enforcement semantics. Required for all new profiles.
|
|
291
|
-
* Optional here only so the type stays backward compatible with v0.3
|
|
292
|
-
* profiles that predate the boundType convention — consumers must
|
|
293
|
-
* treat a missing boundType as an error when running in v0.4 mode.
|
|
294
|
-
*/
|
|
286
|
+
/** Enforcement semantics. REQUIRED on every bounds field except `profile`. */
|
|
295
287
|
boundType?: BoundType;
|
|
296
288
|
/**
|
|
297
289
|
* Which execution action types this bound governs, e.g. `["write"]`.
|
|
298
290
|
*
|
|
299
|
-
* When present, this is authoritative. When absent,
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
* `
|
|
304
|
-
* is
|
|
305
|
-
*
|
|
306
|
-
* Declaring it removes the guess. Deliberately additive: profiles migrate one
|
|
307
|
-
* at a time, and every remaining fallback is logged, so the cases still
|
|
308
|
-
* relying on the naming convention become visible instead of staying silent.
|
|
291
|
+
* When present, this is authoritative. When absent, a `cumulative_sum` bound
|
|
292
|
+
* (or any bound on a profile published before v0.7) governs every action
|
|
293
|
+
* type in the profile's `actionTypes` registry; a `cumulative_count` bound
|
|
294
|
+
* on a v0.7+ profile MUST declare it (Bounds Schema rule 7). MUST NOT be
|
|
295
|
+
* declared on a `per_transaction` bound (it applies wherever its `of`
|
|
296
|
+
* field is present in the execution context) or on an `enum` bound (a
|
|
297
|
+
* capability flag, not an action-scoped limit).
|
|
309
298
|
*/
|
|
310
299
|
appliesTo?: string[];
|
|
311
300
|
/**
|
|
@@ -328,18 +317,14 @@ interface ProfileBoundsField {
|
|
|
328
317
|
* min(value, maximum). Absent → no ceiling.
|
|
329
318
|
*/
|
|
330
319
|
maximum?: number;
|
|
331
|
-
/** @deprecated v0.4: use boundType instead. */
|
|
332
|
-
constraint?: FieldConstraint;
|
|
333
|
-
/** @deprecated v0.4: use boundType: { kind: 'enum', values: [...] }. */
|
|
334
|
-
enum?: string[];
|
|
335
320
|
}
|
|
336
321
|
/**
|
|
337
322
|
* v0.5 Content Provenance — how a profile's action content is hashed into a
|
|
338
|
-
* signed
|
|
323
|
+
* signed ticket (`contentHash`). The ephemeral-content analog of Output
|
|
339
324
|
* Provenance: it binds the *bytes* of the action rather than a location.
|
|
340
325
|
*
|
|
341
326
|
* Profile-bound and OPTIONAL. Absent → no content hash is produced (full
|
|
342
|
-
* backward compatibility). The gateway computes the hash; the
|
|
327
|
+
* backward compatibility). The gateway computes the hash; the AS only ever
|
|
343
328
|
* receives the hash, never the content, so HAP's privacy-minimal design holds.
|
|
344
329
|
*
|
|
345
330
|
* At `version:"1"` the profile declares only the *policy* — whether to bind and
|
|
@@ -399,18 +384,35 @@ interface ContentBinding {
|
|
|
399
384
|
* Absent → the binding applies to every gated action under the profile.
|
|
400
385
|
*/
|
|
401
386
|
appliesTo?: string[];
|
|
387
|
+
/**
|
|
388
|
+
* v2 only — what kind of value each bound field holds, so a verifier (or any
|
|
389
|
+
* view) can collect and show it without guessing from the field's name:
|
|
390
|
+
* - `text` — one string (an email body);
|
|
391
|
+
* - `list` — an array of strings (recipients);
|
|
392
|
+
* - `files` — the sealed fingerprints of documents sent by reference:
|
|
393
|
+
* `[{ filename, sha256 }]` — never the bytes. A verifier asks for the
|
|
394
|
+
* files themselves and computes each SHA-256 locally.
|
|
395
|
+
*
|
|
396
|
+
* A hint only: it never changes what is hashed. Keys MUST be a subset of
|
|
397
|
+
* {@link fields}; a field without an entry is unspecified (a verifier may
|
|
398
|
+
* offer a choice, as before).
|
|
399
|
+
*/
|
|
400
|
+
field_kinds?: Record<string, ContentFieldKind>;
|
|
402
401
|
}
|
|
402
|
+
/** See {@link ContentBinding.field_kinds}. */
|
|
403
|
+
type ContentFieldKind = 'text' | 'list' | 'files';
|
|
403
404
|
/**
|
|
404
|
-
*
|
|
405
|
+
* Scope field definition within a profile (renamed from `ProfileContextField`
|
|
406
|
+
* in v0.7 — protocol.md → *Migration from v0.6*, `contextSchema` → `scopeSchema`).
|
|
405
407
|
*/
|
|
406
|
-
interface
|
|
408
|
+
interface ProfileScopeField {
|
|
407
409
|
type: 'string' | 'number';
|
|
408
410
|
required: boolean;
|
|
409
411
|
description?: string;
|
|
410
412
|
displayName?: string;
|
|
411
413
|
format?: 'email' | 'domain' | 'url' | 'currency';
|
|
412
414
|
/**
|
|
413
|
-
*
|
|
415
|
+
* What this field NAMES on the read path. `counterparty` = the other
|
|
414
416
|
* party to a communication (matched against an item's participants);
|
|
415
417
|
* `resource` = the container an item belongs to (a direct attribute match —
|
|
416
418
|
* and a resource scope enforced on writes MUST also bind reads). Absent →
|
|
@@ -419,7 +421,6 @@ interface ProfileContextField {
|
|
|
419
421
|
*/
|
|
420
422
|
scopeKind?: 'counterparty' | 'resource';
|
|
421
423
|
constraint?: FieldConstraint;
|
|
422
|
-
enum?: string[];
|
|
423
424
|
}
|
|
424
425
|
/**
|
|
425
426
|
* Execution context field definition — declared source (value comes from the agent's tool call).
|
|
@@ -435,13 +436,11 @@ interface DeclaredFieldDef {
|
|
|
435
436
|
*/
|
|
436
437
|
type CumulativeWindow = 'daily' | 'weekly' | 'monthly';
|
|
437
438
|
/**
|
|
438
|
-
* Execution context field definition — cumulative source (resolved from
|
|
439
|
+
* Execution context field definition — cumulative source (resolved from the
|
|
440
|
+
* AS's ticket history; see protocol.md → *Cumulative State*).
|
|
439
441
|
*
|
|
440
|
-
* The gatekeeper resolves these by querying the execution log:
|
|
441
442
|
* - `cumulativeField`: which declared field to sum (use "_count" for plain counting)
|
|
442
443
|
* - `window`: time window for aggregation (daily, weekly, monthly)
|
|
443
|
-
*
|
|
444
|
-
* The resolved value = running total within window + current call value.
|
|
445
444
|
*/
|
|
446
445
|
interface CumulativeFieldDef {
|
|
447
446
|
source: 'cumulative';
|
|
@@ -456,35 +455,21 @@ interface CumulativeFieldDef {
|
|
|
456
455
|
*/
|
|
457
456
|
type ExecutionContextFieldDef = DeclaredFieldDef | CumulativeFieldDef;
|
|
458
457
|
/**
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*/
|
|
462
|
-
interface GateQuestion {
|
|
463
|
-
question: string;
|
|
464
|
-
required: boolean;
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* @deprecated Execution paths removed in v0.4. Kept for backward compatibility.
|
|
468
|
-
*/
|
|
469
|
-
interface ExecutionPath {
|
|
470
|
-
description: string;
|
|
471
|
-
requiredDomains?: string[];
|
|
472
|
-
ttl?: {
|
|
473
|
-
default: number;
|
|
474
|
-
max: number;
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
/**
|
|
478
|
-
* Agent Profile — defines constraint types, execution paths, gate questions,
|
|
479
|
-
* and the frame/bounds/context schemas for bounded execution.
|
|
480
|
-
*
|
|
481
|
-
* Supports both v0.3 (frameSchema) and v0.4 (boundsSchema + contextSchema).
|
|
458
|
+
* Agent Profile — defines the bounds/scope/execution-context schemas, gates,
|
|
459
|
+
* TTL policy, and retention for a bounded authority (protocol.md → *Profiles*).
|
|
482
460
|
*/
|
|
483
461
|
/**
|
|
484
462
|
* A commitment mode a person can choose when signing a mandate. (`review_above_cap`
|
|
485
463
|
* is not chosen by the signer — it follows from team caps.)
|
|
486
464
|
*/
|
|
487
465
|
type SignableCommitmentMode = 'automatic' | 'review';
|
|
466
|
+
/** protocol.md → *Owner Signatures* → "Where the requirement lives" — the
|
|
467
|
+
* profile-floor tier: `minBinding` names the weakest `OwnerSignatureBinding`
|
|
468
|
+
* this profile accepts for a co-signing owner. */
|
|
469
|
+
interface ProfileOwnerSignatureFloor {
|
|
470
|
+
required: boolean;
|
|
471
|
+
minBinding?: OwnerSignatureBinding;
|
|
472
|
+
}
|
|
488
473
|
interface AgentProfile {
|
|
489
474
|
id: string;
|
|
490
475
|
name?: string;
|
|
@@ -505,9 +490,10 @@ interface AgentProfile {
|
|
|
505
490
|
*/
|
|
506
491
|
whatsNew?: string;
|
|
507
492
|
/**
|
|
508
|
-
* Whether
|
|
509
|
-
* verifier holding the content supplies its hash and learns which
|
|
510
|
-
* bind it, without needing a
|
|
493
|
+
* Whether tickets under this profile may be looked up BY THEIR CONTENT — a
|
|
494
|
+
* verifier holding the content supplies its hash and learns which tickets
|
|
495
|
+
* bind it, without needing a ticket id. (Renamed from `receipt_lookup` in
|
|
496
|
+
* v0.7.)
|
|
511
497
|
*
|
|
512
498
|
* OFF unless declared, and that default is the point. The lookup is a
|
|
513
499
|
* confirmation oracle: given a guess at the content it says whether that
|
|
@@ -519,13 +505,8 @@ interface AgentProfile {
|
|
|
519
505
|
* Enable only when the bound content is unguessable enough that producing it
|
|
520
506
|
* is equivalent to already having it: prose, an artifact URL, a whole record
|
|
521
507
|
* payload. Never for a binding over a short value drawn from a small set.
|
|
522
|
-
*
|
|
523
|
-
* Why it must exist at all: most consequential actions cannot carry their
|
|
524
|
-
* receipt id. A released build was built before the receipt existed, a
|
|
525
|
-
* content-addressed artifact would change identity if the id were added, and
|
|
526
|
-
* a forwarded message has usually lost the footer that carried it.
|
|
527
508
|
*/
|
|
528
|
-
|
|
509
|
+
ticket_lookup?: boolean;
|
|
529
510
|
/**
|
|
530
511
|
* The commitment modes a mandate under this profile may be signed with.
|
|
531
512
|
* Absent → every mode (`automatic`, `review`), as before this field existed.
|
|
@@ -539,21 +520,14 @@ interface AgentProfile {
|
|
|
539
520
|
*/
|
|
540
521
|
commitment_modes?: readonly SignableCommitmentMode[];
|
|
541
522
|
/**
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*/
|
|
545
|
-
frameSchema?: {
|
|
546
|
-
keyOrder: string[];
|
|
547
|
-
fields: Record<string, ProfileFrameField>;
|
|
548
|
-
};
|
|
549
|
-
/**
|
|
550
|
-
* v0.4 bounds schema — defines the authorization bounds parameters.
|
|
523
|
+
* The bounds schema — the enforceable parameters (protocol.md → *Bounds
|
|
524
|
+
* Schema*).
|
|
551
525
|
*/
|
|
552
526
|
boundsSchema?: {
|
|
553
527
|
keyOrder: string[];
|
|
554
528
|
fields: Record<string, ProfileBoundsField>;
|
|
555
529
|
/**
|
|
556
|
-
*
|
|
530
|
+
* Registry of the action types this profile recognizes (e.g.
|
|
557
531
|
* `["send", "delete", "setup"]`). Every `execution.action_type` the
|
|
558
532
|
* Authority Server and gateway accept for this profile MUST be a member
|
|
559
533
|
* when the registry is declared (protocol.md → Bounds Schema, rule 2);
|
|
@@ -564,92 +538,45 @@ interface AgentProfile {
|
|
|
564
538
|
actionTypes?: readonly string[];
|
|
565
539
|
};
|
|
566
540
|
/**
|
|
567
|
-
*
|
|
568
|
-
* May be absent or empty for profiles with
|
|
541
|
+
* The scope schema — operational scoping fields that stay local (renamed
|
|
542
|
+
* from `contextSchema` in v0.7). May be absent or empty for profiles with
|
|
543
|
+
* no static scope.
|
|
569
544
|
*/
|
|
570
|
-
|
|
545
|
+
scopeSchema?: {
|
|
571
546
|
keyOrder: string[];
|
|
572
|
-
fields: Record<string,
|
|
547
|
+
fields: Record<string, ProfileScopeField>;
|
|
573
548
|
};
|
|
574
549
|
executionContextSchema: {
|
|
575
550
|
fields: Record<string, ExecutionContextFieldDef>;
|
|
576
551
|
};
|
|
577
|
-
/** @deprecated Execution paths removed in v0.4. Kept for backward compatibility. */
|
|
578
|
-
executionPaths?: Record<string, ExecutionPath>;
|
|
579
552
|
requiredGates: string[];
|
|
580
|
-
/**
|
|
581
|
-
* v0.4: no gateQuestions — intent prompt is universal, defined in the gateway UI.
|
|
582
|
-
* v0.3: profile-specific gate questions (deprecated).
|
|
583
|
-
*/
|
|
584
|
-
gateQuestions?: {
|
|
585
|
-
problem?: GateQuestion;
|
|
586
|
-
objective?: GateQuestion;
|
|
587
|
-
tradeoffs?: GateQuestion;
|
|
588
|
-
};
|
|
589
553
|
ttl: {
|
|
590
554
|
default: number;
|
|
591
555
|
max: number;
|
|
592
556
|
};
|
|
593
557
|
retention_minimum: number;
|
|
594
558
|
/**
|
|
595
|
-
*
|
|
559
|
+
* Content Provenance (OPTIONAL, profile-bound). When present, the
|
|
596
560
|
* gateway computes a `contentHash` for gated writes under this profile and
|
|
597
|
-
* passes it (hash only) to the
|
|
561
|
+
* passes it (hash only) to the AS, which signs it into the ticket. Absent
|
|
598
562
|
* → no content hash. See {@link ContentBinding}.
|
|
599
563
|
*/
|
|
600
564
|
content_binding?: ContentBinding;
|
|
601
565
|
/**
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
566
|
+
* The execution-context fields a ticket under this profile MAY disclose
|
|
567
|
+
* (new in v0.7 — protocol.md → *Ticket Disclosure Is Declared*). Absent →
|
|
568
|
+
* nothing is disclosed by default. A mandate's own `disclose_fields` (when
|
|
569
|
+
* present) MUST be a subset of this list — a mandate may narrow disclosure,
|
|
570
|
+
* never widen it.
|
|
605
571
|
*/
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
*
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
type ExecutionMappingTransform = 'join' | 'join_domains' | 'length';
|
|
615
|
-
/**
|
|
616
|
-
* Execution mapping value — how a tool argument maps to execution context field(s).
|
|
617
|
-
* - string: direct copy (argName → fieldName)
|
|
618
|
-
* - { field, divisor }: numeric division (e.g., cents ÷ 100 → EUR)
|
|
619
|
-
* - { field, transform }: array transform (e.g., join_domains)
|
|
620
|
-
* - Array form: one argument maps to multiple execution fields
|
|
621
|
-
*/
|
|
622
|
-
type ExecutionMappingValue = string | {
|
|
623
|
-
field: string;
|
|
624
|
-
divisor: number;
|
|
625
|
-
} | {
|
|
626
|
-
field: string;
|
|
627
|
-
transform: ExecutionMappingTransform;
|
|
628
|
-
} | Array<{
|
|
629
|
-
field: string;
|
|
630
|
-
divisor?: number;
|
|
631
|
-
transform?: ExecutionMappingTransform;
|
|
632
|
-
}>;
|
|
633
|
-
/**
|
|
634
|
-
* Tool gating entry — how a tool's calls map to execution context fields.
|
|
635
|
-
* Read-only tools use { category: "read" } — they require authorization
|
|
636
|
-
* but skip execution context verification.
|
|
637
|
-
*/
|
|
638
|
-
interface ProfileToolGatingEntry {
|
|
639
|
-
executionMapping: Record<string, ExecutionMappingValue>;
|
|
640
|
-
staticExecution?: Record<string, string | number>;
|
|
641
|
-
/** Read-only tools: require authorization but no execution context checks */
|
|
642
|
-
category?: 'read';
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* Profile-level tool gating configuration.
|
|
646
|
-
* - default: applied to all tools not listed in overrides
|
|
647
|
-
* - overrides: per-tool configs keyed by original MCP tool name
|
|
648
|
-
* Use { category: "read" } for read-only tools (null is deprecated)
|
|
649
|
-
*/
|
|
650
|
-
interface ProfileToolGating {
|
|
651
|
-
default: ProfileToolGatingEntry;
|
|
652
|
-
overrides?: Record<string, ProfileToolGatingEntry | null>;
|
|
572
|
+
disclose_fields?: string[];
|
|
573
|
+
/**
|
|
574
|
+
* Profile-floor requirement for an owner signature on a mandate under this
|
|
575
|
+
* profile (protocol.md → *Owner Signatures* → "Where the requirement
|
|
576
|
+
* lives"). Reserved for domains that cannot mean anything without a
|
|
577
|
+
* signature — used more freely it forks `charge` from `charge-with-cosign`.
|
|
578
|
+
*/
|
|
579
|
+
ownerSignature?: ProfileOwnerSignatureFloor;
|
|
653
580
|
}
|
|
654
581
|
/**
|
|
655
582
|
* A recorded execution — stored after gatekeeper approval for cumulative tracking.
|
|
@@ -685,51 +612,47 @@ interface ExecutionLogQuery {
|
|
|
685
612
|
*/
|
|
686
613
|
actionTypes?: readonly string[]): number;
|
|
687
614
|
}
|
|
688
|
-
/**
|
|
689
|
-
* Agent frame parameters — mixed types (strings and numbers).
|
|
690
|
-
* Keys and values come from the profile's frameSchema.
|
|
691
|
-
*/
|
|
692
|
-
type AgentFrameParams = Record<string, string | number>;
|
|
693
615
|
/**
|
|
694
616
|
* Agent bounds parameters — mixed types (strings and numbers).
|
|
695
|
-
* Keys and values come from the profile's boundsSchema
|
|
617
|
+
* Keys and values come from the profile's boundsSchema.
|
|
696
618
|
*/
|
|
697
619
|
type AgentBoundsParams = Record<string, string | number>;
|
|
698
620
|
/**
|
|
699
|
-
* Agent
|
|
700
|
-
*
|
|
621
|
+
* Agent scope parameters — mixed types (strings and numbers). Keys and
|
|
622
|
+
* values come from the profile's scopeSchema (renamed from
|
|
623
|
+
* `AgentContextParams` in v0.7).
|
|
701
624
|
*/
|
|
702
|
-
type
|
|
625
|
+
type AgentScopeParams = Record<string, string | number>;
|
|
703
626
|
/**
|
|
704
627
|
* Request to the Gatekeeper for bounded execution verification.
|
|
705
628
|
*/
|
|
706
629
|
interface GatekeeperRequest {
|
|
707
|
-
/** The
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
|
|
711
|
-
/** The agent's execution values for this specific action */
|
|
630
|
+
/** The bounds the mandate(s) committed to. */
|
|
631
|
+
bounds: AgentBoundsParams;
|
|
632
|
+
/** Mandate blobs (base64url) to verify against. */
|
|
633
|
+
mandates: string[];
|
|
634
|
+
/** The agent's execution values for this specific action. */
|
|
712
635
|
execution: Record<string, string | number>;
|
|
713
|
-
/**
|
|
714
|
-
|
|
636
|
+
/** Scope parameters (currency, action_type, allowed recipients, etc.) —
|
|
637
|
+
* enforced locally only; never sent to the Authority Server. */
|
|
638
|
+
scope?: AgentScopeParams;
|
|
715
639
|
/**
|
|
716
|
-
* Authorization path used to scope cumulative lookups in the execution log
|
|
717
|
-
*
|
|
718
|
-
*
|
|
719
|
-
* profile's boundsSchema, so an extra key there is rejected as an unknown
|
|
720
|
-
* field and breaks attestation verification outright. Cumulative checks
|
|
721
|
-
* previously read the path from the frame, where callers cannot legally put
|
|
722
|
-
* it — so it resolved to "" while the log stored real paths, no entry ever
|
|
723
|
-
* matched, and every running total read zero. The local gate existed but
|
|
724
|
-
* could never fire.
|
|
640
|
+
* Authorization path used to scope cumulative lookups in the execution log
|
|
641
|
+
* (display only — see {@link ExecutionLogQuery}; the Gatekeeper MUST NOT
|
|
642
|
+
* enforce cumulative bounds locally, protocol.md → *Enforcement Authority*).
|
|
725
643
|
*/
|
|
726
644
|
path?: string;
|
|
727
645
|
}
|
|
728
646
|
/**
|
|
729
|
-
* Structured error from Gatekeeper verification.
|
|
647
|
+
* Structured error from Gatekeeper verification. Codes are the canonical
|
|
648
|
+
* v0.7 surface ({@link HapErrorCode}, `src/errors.ts`) plus the two
|
|
649
|
+
* Gatekeeper-local-only codes that never reach the wire (`FRAME_MISMATCH`
|
|
650
|
+
* is retired with v0.3; a Gatekeeper reports `BOUNDS_HASH_MISMATCH` /
|
|
651
|
+
* `SCOPE_HASH_MISMATCH` for both its own local mismatch and the AS's wire
|
|
652
|
+
* refusal, per protocol.md → *Error Codes*, "Gatekeeper · local").
|
|
730
653
|
*/
|
|
731
654
|
interface GatekeeperError {
|
|
732
|
-
code:
|
|
655
|
+
code: HapErrorCode;
|
|
733
656
|
field?: string;
|
|
734
657
|
message: string;
|
|
735
658
|
bound?: string | number;
|
|
@@ -780,6 +703,46 @@ type GatekeeperResult = {
|
|
|
780
703
|
*/
|
|
781
704
|
declare function canonicalize(value: unknown): string;
|
|
782
705
|
|
|
706
|
+
/**
|
|
707
|
+
* base64url (RFC 4648 §5), strict — no padding, no standard-base64 characters.
|
|
708
|
+
*
|
|
709
|
+
* protocol.md → *Mandate Payload* rule 3: "Signatures MUST be encoded as
|
|
710
|
+
* base64url without padding ... Implementations MUST NOT use standard base64
|
|
711
|
+
* (which differs in `+`/`/` vs `-`/`_` and in padding) — third-party verifiers
|
|
712
|
+
* reading the spec literally will reject standard-base64 signatures."
|
|
713
|
+
*
|
|
714
|
+
* Earlier hap-core verifiers were lenient: they translated `+`/`/` back from
|
|
715
|
+
* `-`/`_` and re-added padding before decoding, so a standard-base64 signature
|
|
716
|
+
* verified exactly as well as a base64url one — silently accepting the wire
|
|
717
|
+
* format the spec forbids. v0.7 closes that: decoding a string that is not
|
|
718
|
+
* strict base64url throws, never silently repairs it.
|
|
719
|
+
*/
|
|
720
|
+
/** Encode bytes as base64url with no padding. */
|
|
721
|
+
declare function toBase64Url(bytes: Uint8Array): string;
|
|
722
|
+
/**
|
|
723
|
+
* Decode a STRICT base64url string (no padding, no `+`/`/`/`=`).
|
|
724
|
+
*
|
|
725
|
+
* @throws Error (no protocol code — the caller knows which artifact it was
|
|
726
|
+
* decoding and assigns the code) when the input carries a `+`, `/`, `=`, or
|
|
727
|
+
* any other byte outside the base64url alphabet.
|
|
728
|
+
*/
|
|
729
|
+
declare function fromBase64Url(s: string): Uint8Array;
|
|
730
|
+
|
|
731
|
+
/** The protocol version this package issues and verifies. */
|
|
732
|
+
declare const PROTOCOL_VERSION = "0.7";
|
|
733
|
+
/** The protocol versions this package can verify and enforce. */
|
|
734
|
+
declare const SUPPORTED_VERSIONS: readonly string[];
|
|
735
|
+
/**
|
|
736
|
+
* The highest version both the requester and this implementation support.
|
|
737
|
+
*
|
|
738
|
+
* @param requested `supported_versions` from the mandate request. Absent →
|
|
739
|
+
* read as `["0.6"]` (protocol.md rule 1) — which, against this package's
|
|
740
|
+
* `SUPPORTED_VERSIONS`, has no overlap and refuses honestly rather than
|
|
741
|
+
* silently assuming `0.7`.
|
|
742
|
+
* @throws HapError `VERSION_UNSUPPORTED` when no version is common to both.
|
|
743
|
+
*/
|
|
744
|
+
declare function negotiateVersion(requested: readonly string[] | undefined, supported?: readonly string[]): string;
|
|
745
|
+
|
|
783
746
|
/**
|
|
784
747
|
* Content binding — Level 2 content proof (HAP v0.5 Content Provenance).
|
|
785
748
|
*
|
|
@@ -884,6 +847,14 @@ declare function selectBoundFields(binding: ContentBinding, args: Record<string,
|
|
|
884
847
|
* {@link selectBoundFields} + {@link computeContentHash} for the common path.
|
|
885
848
|
*/
|
|
886
849
|
declare function computeFieldsContentHash(binding: ContentBinding, args: Record<string, unknown>): string;
|
|
850
|
+
/**
|
|
851
|
+
* Why a binding's `field_kinds` is malformed, or null when it is fine (or
|
|
852
|
+
* absent). For profile validation — a malformed hint is a profile error, not
|
|
853
|
+
* a call-time refusal, because it never changes what is hashed.
|
|
854
|
+
*/
|
|
855
|
+
declare function invalidFieldKindsReason(binding: ContentBinding): string | null;
|
|
856
|
+
/** The bound fields declared as `files` — sealed document fingerprints. */
|
|
857
|
+
declare function filesFields(binding: ContentBinding): string[];
|
|
887
858
|
|
|
888
859
|
/**
|
|
889
860
|
* Intent hashing + intent-disclosure binding (HAP v0.5).
|
|
@@ -950,20 +921,17 @@ interface SubjectValidation {
|
|
|
950
921
|
* - `self_declared` ⇒ `low` / `self`, no disclosed name.
|
|
951
922
|
* - `as_vouched` ⇒ `high` / `as`, `verifier` required.
|
|
952
923
|
* - `eudi` ⇒ `high` / `external`, `verifier` required — and, when the
|
|
953
|
-
* carrying
|
|
924
|
+
* carrying mandate's `mandate_owners` are supplied, a corresponding
|
|
954
925
|
* entry with `binding:"eudi"` for this subject's DID.
|
|
955
926
|
* - `disclose.name` only at `assurance:"high"`.
|
|
956
927
|
*
|
|
957
|
-
*
|
|
958
|
-
* the identity claim, not the mandate (see the field's deprecation note). The
|
|
959
|
-
* signature-bearing object is the attestation's `owner_mandates` entry, which
|
|
928
|
+
* The signature-bearing object is the mandate's `mandate_owners` entry, which
|
|
960
929
|
* is payload-level context this per-subject check cannot see on its own; pass
|
|
961
|
-
* `opts.
|
|
962
|
-
* validate subject shape alone
|
|
963
|
-
* the retired owner_signature requirement).
|
|
930
|
+
* `opts.mandateOwners` to enforce the eudi ⇒ mandate-owner-entry rule, omit it
|
|
931
|
+
* to validate subject shape alone.
|
|
964
932
|
*/
|
|
965
933
|
declare function validateSubject(subject: Subject, opts?: {
|
|
966
|
-
|
|
934
|
+
mandateOwners?: MandateOwnerEntry[];
|
|
967
935
|
}): SubjectValidation;
|
|
968
936
|
/**
|
|
969
937
|
* The footer line for a subject — the ONE place this wording lives.
|
|
@@ -991,76 +959,38 @@ declare function deriveIdentityLine(subject: Subject | undefined | null, { opera
|
|
|
991
959
|
}): string;
|
|
992
960
|
|
|
993
961
|
/**
|
|
994
|
-
*
|
|
962
|
+
* Bounds & Scope Canonicalization (protocol.md → *Bounds & Scope
|
|
963
|
+
* Canonicalization*).
|
|
995
964
|
*
|
|
996
|
-
* Agent profiles support mixed-type fields (strings and numbers).
|
|
997
965
|
* Canonical form: `key=value` records joined with LF, keys in the profile's
|
|
998
966
|
* keyOrder. Values are stringified with String(value) — the shortest
|
|
999
967
|
* round-trippable form for numbers — then percent-encoded per protocol.md
|
|
1000
968
|
* (`=`, `%`, and every byte outside printable ASCII); a value carrying a raw
|
|
1001
969
|
* LF/CR is refused, never normalized. See `canonicalRecords` below.
|
|
1002
970
|
*
|
|
1003
|
-
* v0.3
|
|
1004
|
-
* v0.
|
|
971
|
+
* v0.3's `frameSchema` / `canonicalFrame` / `computeFrameHash` are retired
|
|
972
|
+
* with the rest of v0.3 (CLAUDE.md "Decided by the owner": no backward
|
|
973
|
+
* compatibility). v0.7 renames `contextSchema` → `scopeSchema` and
|
|
974
|
+
* `AgentContextParams` → `AgentScopeParams` (protocol.md → *Migration from
|
|
975
|
+
* v0.6*); the functions below follow suit (`canonicalContext` →
|
|
976
|
+
* `canonicalScope`, `computeContextHash` → `computeScopeHash`,
|
|
977
|
+
* `validateContextParams` → `validateScopeParams`).
|
|
1005
978
|
*/
|
|
1006
979
|
|
|
1007
980
|
/**
|
|
1008
|
-
* Validates
|
|
1009
|
-
*/
|
|
1010
|
-
declare function validateFrameParams(params: AgentFrameParams, profile: AgentProfile): {
|
|
1011
|
-
valid: boolean;
|
|
1012
|
-
errors: string[];
|
|
1013
|
-
};
|
|
1014
|
-
/**
|
|
1015
|
-
* Builds the canonical frame string from parameters.
|
|
1016
|
-
* All values are converted to strings. Keys are ordered per profile's keyOrder.
|
|
1017
|
-
*
|
|
1018
|
-
* @throws Error if any field fails validation
|
|
1019
|
-
*/
|
|
1020
|
-
declare function canonicalFrame(params: AgentFrameParams, profile: AgentProfile): string;
|
|
1021
|
-
/**
|
|
1022
|
-
* Computes the frame hash from a canonical frame string.
|
|
1023
|
-
*
|
|
1024
|
-
* @returns Hash in format "sha256:<64 hex chars>"
|
|
1025
|
-
*/
|
|
1026
|
-
declare function frameHash(canonicalFrameString: string): string;
|
|
1027
|
-
/**
|
|
1028
|
-
* Convenience: builds canonical frame and computes hash in one step.
|
|
1029
|
-
*/
|
|
1030
|
-
declare function computeFrameHash(params: AgentFrameParams, profile: AgentProfile): string;
|
|
1031
|
-
/**
|
|
1032
|
-
* Validates bounds parameters against the profile's boundsSchema (v0.4).
|
|
981
|
+
* Validates bounds parameters against the profile's boundsSchema.
|
|
1033
982
|
*/
|
|
1034
983
|
declare function validateBoundsParams(params: AgentBoundsParams, profile: AgentProfile): {
|
|
1035
984
|
valid: boolean;
|
|
1036
985
|
errors: string[];
|
|
1037
986
|
};
|
|
1038
987
|
/**
|
|
1039
|
-
* Validates
|
|
988
|
+
* Validates scope parameters against the profile's scopeSchema.
|
|
1040
989
|
*/
|
|
1041
|
-
declare function
|
|
990
|
+
declare function validateScopeParams(params: AgentScopeParams, profile: AgentProfile): {
|
|
1042
991
|
valid: boolean;
|
|
1043
992
|
errors: string[];
|
|
1044
993
|
};
|
|
1045
|
-
/**
|
|
1046
|
-
* Authoring-time check for `per_transaction` bound `requiredFor` (v0.8+):
|
|
1047
|
-
* entries must name a real `action_type` from the profile's declared
|
|
1048
|
-
* `boundsSchema.actionTypes` registry, and the field is only meaningful on
|
|
1049
|
-
* `per_transaction` bounds (see `BoundType`'s doc comment for why the other
|
|
1050
|
-
* kinds don't carry it).
|
|
1051
|
-
*
|
|
1052
|
-
* This is NOT wired into `registerProfile` or any enforcement path — nothing
|
|
1053
|
-
* in hap-core validates profile JSON at load time today (the pre-existing
|
|
1054
|
-
* `requiredFor` on a context/scope constraint has the same gap), so adding
|
|
1055
|
-
* an automatic refusal here would be new behaviour beyond this change's
|
|
1056
|
-
* scope. Call it explicitly from an authoring tool, a hap-profiles CI check,
|
|
1057
|
-
* or a test, to catch a `requiredFor` entry that can never match (misspelled
|
|
1058
|
-
* action type) or is attached to a bound kind that silently ignores it.
|
|
1059
|
-
*
|
|
1060
|
-
* Returns an empty array when the profile has no boundsSchema or nothing to
|
|
1061
|
-
* check — never throws.
|
|
1062
|
-
*/
|
|
1063
|
-
declare function validateBoundsRequiredFor(profile: AgentProfile): string[];
|
|
1064
994
|
/**
|
|
1065
995
|
* Thrown when a value cannot be canonicalized at all — currently only for raw
|
|
1066
996
|
* LF/CR inside a value. Carries the protocol error code so callers can map it
|
|
@@ -1073,9 +1003,9 @@ declare function validateBoundsRequiredFor(profile: AgentProfile): string[];
|
|
|
1073
1003
|
* does not faithfully represent the input."
|
|
1074
1004
|
*/
|
|
1075
1005
|
declare class CanonicalValueError extends Error {
|
|
1076
|
-
readonly code: 'BOUNDS_INVALID_VALUE' | '
|
|
1006
|
+
readonly code: 'BOUNDS_INVALID_VALUE' | 'SCOPE_INVALID_VALUE';
|
|
1077
1007
|
readonly field: string;
|
|
1078
|
-
constructor(code: 'BOUNDS_INVALID_VALUE' | '
|
|
1008
|
+
constructor(code: 'BOUNDS_INVALID_VALUE' | 'SCOPE_INVALID_VALUE', field: string, message: string);
|
|
1079
1009
|
}
|
|
1080
1010
|
/**
|
|
1081
1011
|
* Builds the canonical bounds string from parameters.
|
|
@@ -1087,29 +1017,29 @@ declare class CanonicalValueError extends Error {
|
|
|
1087
1017
|
*/
|
|
1088
1018
|
declare function canonicalBounds(params: AgentBoundsParams, profile: AgentProfile): string;
|
|
1089
1019
|
/**
|
|
1090
|
-
* Builds the canonical
|
|
1091
|
-
* Keys are ordered per profile's
|
|
1020
|
+
* Builds the canonical scope string from parameters.
|
|
1021
|
+
* Keys are ordered per profile's scopeSchema.keyOrder; values are encoded per
|
|
1092
1022
|
* protocol.md → *Value encoding* (see `canonicalRecords`).
|
|
1093
|
-
* For empty
|
|
1023
|
+
* For empty scope (no scopeSchema or no fields), returns "".
|
|
1094
1024
|
*
|
|
1095
1025
|
* @throws Error if any field fails validation
|
|
1096
|
-
* @throws CanonicalValueError (code
|
|
1026
|
+
* @throws CanonicalValueError (code SCOPE_INVALID_VALUE) if a value carries a raw LF/CR
|
|
1097
1027
|
*/
|
|
1098
|
-
declare function
|
|
1028
|
+
declare function canonicalScope(params: AgentScopeParams, profile: AgentProfile): string;
|
|
1099
1029
|
/**
|
|
1100
|
-
* Computes the bounds hash from bounds parameters
|
|
1030
|
+
* Computes the bounds hash from bounds parameters.
|
|
1101
1031
|
*
|
|
1102
1032
|
* @returns Hash in format "sha256:<64 hex chars>"
|
|
1103
1033
|
*/
|
|
1104
1034
|
declare function computeBoundsHash(params: AgentBoundsParams, profile: AgentProfile): string;
|
|
1105
1035
|
/**
|
|
1106
|
-
* Computes the
|
|
1107
|
-
* For empty
|
|
1036
|
+
* Computes the scope hash from scope parameters.
|
|
1037
|
+
* For empty scope {}, returns the sha256 of "":
|
|
1108
1038
|
* "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
1109
1039
|
*
|
|
1110
1040
|
* @returns Hash in format "sha256:<64 hex chars>"
|
|
1111
1041
|
*/
|
|
1112
|
-
declare function
|
|
1042
|
+
declare function computeScopeHash(params: AgentScopeParams, profile: AgentProfile): string;
|
|
1113
1043
|
|
|
1114
1044
|
/**
|
|
1115
1045
|
* Which commitment modes a profile allows its mandates to be signed with
|
|
@@ -1128,248 +1058,208 @@ declare const SIGNABLE_COMMITMENT_MODES: readonly SignableCommitmentMode[];
|
|
|
1128
1058
|
declare function allowedCommitmentModes(profile: AgentProfile): readonly SignableCommitmentMode[];
|
|
1129
1059
|
/** Whether a mandate under this profile may be signed with `mode`. */
|
|
1130
1060
|
declare function isCommitmentModeAllowed(profile: AgentProfile, mode: string): boolean;
|
|
1131
|
-
/**
|
|
1132
|
-
* Authoring check for a profile's `commitment_modes` declaration — for
|
|
1133
|
-
* hap-profiles CI, an authoring tool or a test. Like `validateBoundsRequiredFor`
|
|
1134
|
-
* it is not wired into profile loading; enforcement reads the declaration
|
|
1135
|
-
* fail-closed through `allowedCommitmentModes`. Empty array = nothing to fix.
|
|
1136
|
-
*/
|
|
1137
|
-
declare function validateCommitmentModes(profile: AgentProfile): string[];
|
|
1138
|
-
|
|
1139
|
-
/**
|
|
1140
|
-
* Attestation Encoding, Decoding, and Verification
|
|
1141
|
-
*
|
|
1142
|
-
* Supports both v0.3 (frame_hash) and v0.4 (bounds_hash + context_hash).
|
|
1143
|
-
*/
|
|
1144
1061
|
|
|
1062
|
+
/** One authoring-time finding. `code` is always a canonical v0.7 code — in
|
|
1063
|
+
* practice always `PROFILE_INVALID`, since that is the only mandate-request
|
|
1064
|
+
* refusal the protocol attributes to "the profile fails validation"
|
|
1065
|
+
* (protocol.md → Error Codes). */
|
|
1066
|
+
interface ProfileValidationError {
|
|
1067
|
+
code: HapErrorCode;
|
|
1068
|
+
field?: string;
|
|
1069
|
+
message: string;
|
|
1070
|
+
}
|
|
1145
1071
|
/**
|
|
1146
|
-
*
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
* Encodes an attestation as a base64url blob (no padding).
|
|
1151
|
-
*/
|
|
1152
|
-
declare function encodeAttestationBlob(attestation: Attestation): string;
|
|
1153
|
-
/**
|
|
1154
|
-
* Computes the attestation ID (hash of the blob).
|
|
1072
|
+
* `profile_hash` — `"sha256:" + sha256(JCS(parsed))`, over the PARSED
|
|
1073
|
+
* profile document. Formatting, key order, and a trailing newline on disk
|
|
1074
|
+
* never change it (protocol.md → *Profile hash*; conformance vector set
|
|
1075
|
+
* `profile-hash.json`).
|
|
1155
1076
|
*/
|
|
1156
|
-
declare function
|
|
1077
|
+
declare function computeProfileHash(parsed: unknown): string;
|
|
1157
1078
|
/**
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
1160
|
-
*
|
|
1161
|
-
* @throws Error with code prefix if invalid
|
|
1079
|
+
* Authoring-time structural validation (protocol.md → *Bounds Schema*,
|
|
1080
|
+
* *Owner Signatures* → "Where the requirement lives", *Migration from
|
|
1081
|
+
* v0.6*). Returns `[]` for a clean profile; never throws.
|
|
1162
1082
|
*/
|
|
1163
|
-
declare function
|
|
1083
|
+
declare function validateProfile(profile: AgentProfile): ProfileValidationError[];
|
|
1164
1084
|
/**
|
|
1165
|
-
*
|
|
1085
|
+
* A mandate's `disclose_fields` MUST be a subset of the profile's own list
|
|
1086
|
+
* (protocol.md → *Mandate Payload*, conditional field `disclose_fields`: "a
|
|
1087
|
+
* subset of the profile's `disclose_fields` list"). A mandate may narrow
|
|
1088
|
+
* disclosure, never widen it.
|
|
1166
1089
|
*
|
|
1167
|
-
* @throws Error
|
|
1090
|
+
* @throws Error prefixed `MALFORMED_MANDATE:` when it is not a subset.
|
|
1168
1091
|
*/
|
|
1169
|
-
declare function
|
|
1092
|
+
declare function checkDiscloseSubset(mandateDiscloseFields: readonly string[] | undefined, profileDiscloseFields: readonly string[] | undefined): void;
|
|
1093
|
+
|
|
1170
1094
|
/**
|
|
1171
|
-
*
|
|
1095
|
+
* Mandate encoding, decoding, signing, and verification (v0.7).
|
|
1172
1096
|
*
|
|
1173
|
-
*
|
|
1097
|
+
* Renamed from `attestation.ts` (protocol.md → *Migration from v0.6*:
|
|
1098
|
+
* `header.typ: "HAP-attestation"` → `"HAP-mandate"`, `attestation_id` →
|
|
1099
|
+
* `mandate_id`). v0.3's frame-hash path and v0.4's dual frame/bounds
|
|
1100
|
+
* verification are retired with the rest of pre-v0.5 — this module issues
|
|
1101
|
+
* and verifies `version: "0.7"` mandates only (CLAUDE.md "Decided by the
|
|
1102
|
+
* owner": no backward-compat verify path for 0.5/0.6).
|
|
1174
1103
|
*/
|
|
1175
|
-
|
|
1104
|
+
|
|
1176
1105
|
/**
|
|
1177
|
-
*
|
|
1178
|
-
*
|
|
1179
|
-
*
|
|
1180
|
-
* Migration rule: if attestation has frame_hash but not bounds_hash, it is v0.3.
|
|
1106
|
+
* Decodes a base64url-encoded mandate blob.
|
|
1107
|
+
* @throws HapError `MALFORMED_MANDATE` on anything that is not a strict
|
|
1108
|
+
* base64url-encoded JSON mandate.
|
|
1181
1109
|
*/
|
|
1182
|
-
declare function
|
|
1110
|
+
declare function decodeMandateBlob(blob: string): Mandate;
|
|
1183
1111
|
/**
|
|
1184
|
-
*
|
|
1185
|
-
*
|
|
1186
|
-
* @throws Error if bounds hash doesn't match
|
|
1112
|
+
* Encodes a mandate as a base64url blob (no padding).
|
|
1187
1113
|
*/
|
|
1188
|
-
declare function
|
|
1114
|
+
declare function encodeMandateBlob(mandate: Mandate): string;
|
|
1189
1115
|
/**
|
|
1190
|
-
*
|
|
1191
|
-
*
|
|
1192
|
-
* @throws Error if context hash doesn't match
|
|
1116
|
+
* Computes the mandate blob id (hash of the blob) — renamed from
|
|
1117
|
+
* `attestationId`.
|
|
1193
1118
|
*/
|
|
1194
|
-
declare function
|
|
1119
|
+
declare function mandateBlobId(blob: string): string;
|
|
1195
1120
|
/**
|
|
1196
|
-
*
|
|
1121
|
+
* Sign a mandate payload with the Authority Server's Ed25519 private key.
|
|
1197
1122
|
*
|
|
1198
|
-
* @
|
|
1199
|
-
*
|
|
1123
|
+
* @param opts.kid Optional key id for the envelope header. When present it
|
|
1124
|
+
* MUST identify the same key as `payload.issuer` (Mandate rule 8) — this
|
|
1125
|
+
* function does not enforce that on the signer's side; `verifyMandateSignature`
|
|
1126
|
+
* enforces it on the verifier's side, where a mismatch is actually dangerous.
|
|
1200
1127
|
*/
|
|
1201
|
-
declare function
|
|
1128
|
+
declare function signMandate(payload: MandatePayload, privateKey: Uint8Array, opts?: {
|
|
1129
|
+
kid?: string;
|
|
1130
|
+
}): Promise<Mandate>;
|
|
1202
1131
|
/**
|
|
1203
|
-
*
|
|
1132
|
+
* Verifies a mandate's Ed25519 signature. The verification key is resolved
|
|
1133
|
+
* from `payload.issuer` itself — never from a key offered alongside the
|
|
1134
|
+
* artifact (protocol.md → *Ticket Verification* step 1, which states the
|
|
1135
|
+
* same rule for tickets; mandates follow it identically).
|
|
1204
1136
|
*
|
|
1205
|
-
* @
|
|
1206
|
-
*
|
|
1137
|
+
* @param opts.trustedIssuers When supplied, an `issuer` not in this list is
|
|
1138
|
+
* rejected before any cryptography runs — "a verifier MUST reject a [...]
|
|
1139
|
+
* whose `issuer` it does not trust" (protocol.md → *Ticket Verification*).
|
|
1140
|
+
* @throws Error prefixed `INVALID_SIGNATURE:` on any verification failure.
|
|
1207
1141
|
*/
|
|
1208
|
-
declare function
|
|
1209
|
-
|
|
1142
|
+
declare function verifyMandateSignature(mandate: Mandate, opts?: {
|
|
1143
|
+
trustedIssuers?: readonly string[];
|
|
1144
|
+
}): Promise<void>;
|
|
1210
1145
|
/**
|
|
1211
|
-
*
|
|
1212
|
-
*
|
|
1213
|
-
*
|
|
1214
|
-
* bounds_hash + context_hash).
|
|
1146
|
+
* Checks a mandate payload's time rules (Mandate rule 9): `expires_at` is
|
|
1147
|
+
* authoritative, with a {@link CLOCK_SKEW_SECONDS} tolerance that never
|
|
1148
|
+
* extends life past `expires_at + 300s`.
|
|
1215
1149
|
*
|
|
1216
|
-
*
|
|
1217
|
-
* 1. Resolve profile from frame
|
|
1218
|
-
* 2. Recompute frame_hash
|
|
1219
|
-
* 3. For each required domain: find attestation, verify signature, verify frame_hash, verify TTL
|
|
1220
|
-
* 4. Check bounds: max → actual <= bound, enum → actual in allowed
|
|
1221
|
-
* 5. Return { approved } or { approved: false, errors: [...] }
|
|
1222
|
-
*
|
|
1223
|
-
* v0.4 flow:
|
|
1224
|
-
* 1. Resolve profile from bounds.profile
|
|
1225
|
-
* 2. Recompute bounds_hash and context_hash
|
|
1226
|
-
* 3. For each required domain: find attestation, verify signature, verify bounds_hash + context_hash, verify TTL
|
|
1227
|
-
* 4. Check bounds (from boundsSchema), check context constraints (from contextSchema)
|
|
1228
|
-
* 5. Resolve cumulative fields, check cumulative limits
|
|
1229
|
-
* 6. Return { approved } or { approved: false, errors: [...] }
|
|
1150
|
+
* @throws Error prefixed `TTL_EXPIRED:` when expired.
|
|
1230
1151
|
*/
|
|
1231
|
-
|
|
1152
|
+
declare function checkMandateExpiry(payload: MandatePayload, now?: number): void;
|
|
1232
1153
|
/**
|
|
1233
|
-
*
|
|
1234
|
-
*
|
|
1235
|
-
*
|
|
1236
|
-
* and the optional `context` param is used for the context hash check.
|
|
1154
|
+
* Validates the structural shape of `mandate_owners` (Mandate rule 7):
|
|
1155
|
+
* exactly one entry, `did` required, and the signature fields present
|
|
1156
|
+
* together or absent together. A co-signing entry's `did` MUST be key-bearing.
|
|
1237
1157
|
*
|
|
1238
|
-
*
|
|
1239
|
-
*
|
|
1240
|
-
* @param request - The frame/bounds, attestations, execution values, and optional context
|
|
1241
|
-
* @param publicKeyHex - The SP's public key in hex (cached locally by MCP server)
|
|
1242
|
-
* @param now - Current timestamp in seconds (for testing)
|
|
1243
|
-
* @param executionLog - Optional execution log for resolving cumulative fields
|
|
1158
|
+
* @throws HapError `MALFORMED_MANDATE` on a cardinality or completeness
|
|
1159
|
+
* violation; `OWNER_SIGNATURE_INVALID` when a co-signing DID is not key-bearing.
|
|
1244
1160
|
*/
|
|
1245
|
-
declare function
|
|
1161
|
+
declare function validateMandateOwners(payload: MandatePayload): void;
|
|
1246
1162
|
/**
|
|
1247
|
-
*
|
|
1248
|
-
*
|
|
1163
|
+
* Full mandate verification: decode, check `version`, verify the AS
|
|
1164
|
+
* signature, check expiry, validate `mandate_owners` shape.
|
|
1249
1165
|
*
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
*
|
|
1253
|
-
* "orders per day" was checked only on orders, but counted every quote and send.
|
|
1254
|
-
* Consumption is partitioned by action type (*Cumulative Tracking* rule 4), as
|
|
1255
|
-
* the Authority Server already does.
|
|
1166
|
+
* Deliberately does NOT verify any `mandate_owners` co-signature — that is
|
|
1167
|
+
* `verifyOwnerSignature` in `owner-signature.ts`, a separate trust axis the
|
|
1168
|
+
* caller opts into.
|
|
1256
1169
|
*
|
|
1257
|
-
*
|
|
1258
|
-
*
|
|
1259
|
-
*
|
|
1260
|
-
* same totals the gate enforces.
|
|
1170
|
+
* @throws HapError `VERSION_UNSUPPORTED` for anything other than `"0.7"`.
|
|
1171
|
+
* @throws Error (`INVALID_SIGNATURE:` / `TTL_EXPIRED:` / `MALFORMED_MANDATE:`
|
|
1172
|
+
* prefixed, or `OWNER_SIGNATURE_INVALID` as a HapError) on any other failure.
|
|
1261
1173
|
*/
|
|
1262
|
-
declare function
|
|
1174
|
+
declare function verifyMandate(blob: string, opts?: {
|
|
1175
|
+
trustedIssuers?: readonly string[];
|
|
1176
|
+
now?: number;
|
|
1177
|
+
}): Promise<MandatePayload>;
|
|
1263
1178
|
|
|
1264
1179
|
/**
|
|
1265
|
-
*
|
|
1266
|
-
*/
|
|
1267
|
-
|
|
1268
|
-
declare function registerProfile(profileId: string, profile: AgentProfile): void;
|
|
1269
|
-
declare function getProfile(profileId: string): AgentProfile | undefined;
|
|
1270
|
-
declare function listProfiles(): string[];
|
|
1271
|
-
declare function getAllProfiles(): AgentProfile[];
|
|
1272
|
-
declare function clearProfiles(): void;
|
|
1273
|
-
|
|
1274
|
-
/**
|
|
1275
|
-
* did:key — encode/decode for KEY-BEARING DIDs (HAP v0.6).
|
|
1276
|
-
*
|
|
1277
|
-
* A real `did:key` is self-certifying: the public key IS the identifier, so
|
|
1278
|
-
* substituting the key produces a DIFFERENT DID and no key directory is ever
|
|
1279
|
-
* consulted. That property is what the Owner Mandate design stands on
|
|
1280
|
-
* (protocol.md → "Identity DIDs vs signing DIDs"): a signing DID MUST be
|
|
1281
|
-
* key-bearing, and verification uses the key carried in the identifier —
|
|
1282
|
-
* deliberately, there is no `public_key` field anywhere on the wire.
|
|
1283
|
-
*
|
|
1284
|
-
* Format (W3C did:key method, Ed25519):
|
|
1285
|
-
* did:key:z<base58btc( 0xed 0x01 ‖ 32-byte-public-key )>
|
|
1286
|
-
* `z` is the multibase prefix for base58btc; `0xed 0x01` is the multicodec
|
|
1287
|
-
* varint for ed25519-pub. The result always starts `did:key:z6Mk`.
|
|
1288
|
-
*
|
|
1289
|
-
* Scope: Ed25519 only — the one signing curve this protocol version uses.
|
|
1290
|
-
* Anything else (including the reference implementation's legacy decorative
|
|
1291
|
-
* `did:key:<uuid-fragment>` identifiers) decodes as NOT key-bearing.
|
|
1292
|
-
*/
|
|
1293
|
-
/** Encode a 32-byte Ed25519 public key as a key-bearing `did:key:z6Mk…`. */
|
|
1294
|
-
declare function encodeDidKey(publicKey: Uint8Array): string;
|
|
1295
|
-
/**
|
|
1296
|
-
* Decode a key-bearing `did:key` to its Ed25519 public key bytes.
|
|
1297
|
-
*
|
|
1298
|
-
* @throws Error prefixed `NOT_KEY_BEARING:` for anything that is not a valid
|
|
1299
|
-
* base58btc-multibase Ed25519 did:key — including the legacy decorative
|
|
1300
|
-
* `did:key:<uuid-fragment>` identifiers, other multibase prefixes, and other
|
|
1301
|
-
* key types. The error name is the point: under the mandate design, a
|
|
1302
|
-
* non-key-bearing signing DID must fail STRUCTURALLY, never fall back.
|
|
1303
|
-
*/
|
|
1304
|
-
declare function decodeDidKey(did: string): Uint8Array;
|
|
1305
|
-
/** True iff the DID decodes as a key-bearing Ed25519 did:key. Never throws. */
|
|
1306
|
-
declare function isKeyBearingDid(did: string): boolean;
|
|
1307
|
-
|
|
1308
|
-
/**
|
|
1309
|
-
* Owner Mandate Signatures (HAP v0.6) — the `HAP-mandate` projection, the
|
|
1180
|
+
* Owner Signatures (v0.7) — the `HAP-mandate-projection` object, the
|
|
1310
1181
|
* `HAP-approval` object, and their verification.
|
|
1311
1182
|
*
|
|
1183
|
+
* Renamed from `mandate.ts` (protocol.md → *Migration from v0.6*: the
|
|
1184
|
+
* owner-signed projection's `typ` moves from `"HAP-mandate"` to
|
|
1185
|
+
* `"HAP-mandate-projection"`, disambiguating it from the AS-signed mandate
|
|
1186
|
+
* itself, which now legitimately owns the name `"HAP-mandate"`).
|
|
1187
|
+
*
|
|
1312
1188
|
* The human signs BEFORE the AS does, so they cannot sign the finished
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1318
|
-
*
|
|
1319
|
-
*
|
|
1320
|
-
* Verification here requires NO trust in the AS. What it cannot do is tell
|
|
1321
|
-
* verifier WHOSE key signed — confirming the DID belongs to the
|
|
1322
|
-
* person is the out-of-band step (protocol.md → "
|
|
1323
|
-
*
|
|
1189
|
+
* mandate (`mandate_id`/`issued_at` do not exist yet). They sign a mandate
|
|
1190
|
+
* PROJECTION: a canonical object every field of which is known at approval
|
|
1191
|
+
* time and reconstructible from the finished mandate — so a verifier
|
|
1192
|
+
* rebuilds it from the mandate it already holds and checks the signature
|
|
1193
|
+
* with the key carried in the owner's DID. No side channel, no second
|
|
1194
|
+
* fetch, no key directory. See protocol.md → "Owner Signatures".
|
|
1195
|
+
*
|
|
1196
|
+
* Verification here requires NO trust in the AS. What it cannot do is tell
|
|
1197
|
+
* the verifier WHOSE key signed — confirming the DID belongs to the
|
|
1198
|
+
* expected person is the out-of-band step (protocol.md → "Verification
|
|
1199
|
+
* procedure" step 5), and it is the honest cost of cold verification.
|
|
1324
1200
|
*/
|
|
1325
1201
|
|
|
1326
|
-
/** The object the owner signs — a canonical projection of the mandate.
|
|
1202
|
+
/** The object the owner signs — a canonical projection of the mandate.
|
|
1203
|
+
* Field absence is defined, not incidental (protocol.md → *The signed
|
|
1204
|
+
* object: HAP-mandate-projection*): each conditional field is included
|
|
1205
|
+
* exactly when the mandate carries it and omitted otherwise. */
|
|
1327
1206
|
interface MandateProjection {
|
|
1328
|
-
typ: 'HAP-mandate';
|
|
1207
|
+
typ: 'HAP-mandate-projection';
|
|
1329
1208
|
/** Projection/canonicalization version. A verifier MUST pin it. */
|
|
1330
|
-
version: '0.
|
|
1209
|
+
version: '0.7';
|
|
1331
1210
|
profile_id: string;
|
|
1332
|
-
/**
|
|
1211
|
+
/** Covered deliberately: the owner signs the profile's BYTES, not only its id. */
|
|
1212
|
+
profile_hash: string;
|
|
1213
|
+
/** The signing owner's own DID. MUST equal the carrying mandate's `mandate_owners[0].did`. */
|
|
1333
1214
|
owner_did: string;
|
|
1334
1215
|
bounds_hash: string;
|
|
1335
|
-
|
|
1216
|
+
scope_hash: string;
|
|
1336
1217
|
execution_context_hash: string;
|
|
1337
1218
|
gate_content_hashes: Record<string, string>;
|
|
1338
|
-
/** Included iff the
|
|
1219
|
+
/** Included iff the mandate carries one — binds ciphertext AND the
|
|
1339
1220
|
* frozen approver set, the swap this mechanism exists to stop. */
|
|
1340
1221
|
intent_disclosure_hash?: string;
|
|
1341
1222
|
commitment_mode: string;
|
|
1223
|
+
/** Included together, iff `commitment_mode === "review_above_cap"`. */
|
|
1224
|
+
above_cap_caps?: Record<string, number>;
|
|
1225
|
+
above_cap_approvers?: string[];
|
|
1226
|
+
/** Included iff the mandate narrows the profile's disclose_fields. */
|
|
1227
|
+
disclose_fields?: string[];
|
|
1342
1228
|
/** The replay defence: the human signs how long the authority lives. */
|
|
1343
1229
|
expires_at: number;
|
|
1344
1230
|
nonce: string;
|
|
1345
1231
|
}
|
|
1346
1232
|
/** Per-action approval, signed by the owner in `review` mode. Signing a
|
|
1347
|
-
* `reject` matters as much as a `commit`: a rejection the AS can discard is
|
|
1348
|
-
* rejection that never happened. */
|
|
1233
|
+
* `reject` matters as much as a `commit`: a rejection the AS can discard is
|
|
1234
|
+
* a rejection that never happened. */
|
|
1349
1235
|
interface ApprovalObject {
|
|
1350
1236
|
typ: 'HAP-approval';
|
|
1351
|
-
version: '0.
|
|
1237
|
+
version: '0.7';
|
|
1352
1238
|
proposal_id: string;
|
|
1353
|
-
attestation_id
|
|
1239
|
+
/** Renamed from `attestation_id` in v0.7. */
|
|
1240
|
+
mandate_id: string;
|
|
1354
1241
|
decision: 'commit' | 'reject';
|
|
1355
|
-
/** What was approved: the
|
|
1242
|
+
/** What was approved: the ticket's contentHash where the profile binds
|
|
1356
1243
|
* content; otherwise sha256 over the JCS of the proposal's argument set. */
|
|
1357
1244
|
content_hash: string;
|
|
1358
1245
|
decided_at: number;
|
|
1359
1246
|
nonce: string;
|
|
1360
1247
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1248
|
+
/** Renamed from `MandateError` in v0.7 — carries a canonical {@link HapErrorCode}. */
|
|
1249
|
+
declare class OwnerSignatureError extends Error {
|
|
1250
|
+
code: HapErrorCode;
|
|
1251
|
+
constructor(code: HapErrorCode, message: string);
|
|
1364
1252
|
}
|
|
1365
1253
|
/**
|
|
1366
|
-
* Rebuild the projection a given `
|
|
1367
|
-
*
|
|
1368
|
-
* `intent_disclosure_hash`
|
|
1254
|
+
* Rebuild the projection a given `mandate_owners` entry signed, from the
|
|
1255
|
+
* mandate's own signed fields. Field absence is defined, not incidental:
|
|
1256
|
+
* `intent_disclosure_hash`, `above_cap_caps`/`above_cap_approvers`, and
|
|
1257
|
+
* `disclose_fields` are included iff the mandate carries them.
|
|
1369
1258
|
*/
|
|
1370
|
-
declare function buildMandateProjection(payload:
|
|
1371
|
-
/** The exact bytes an owner signs: RFC 8785 (JCS) canonical UTF-8.
|
|
1372
|
-
|
|
1259
|
+
declare function buildMandateProjection(payload: MandatePayload, entry: Pick<MandateOwnerEntry, 'did' | 'nonce'>): MandateProjection;
|
|
1260
|
+
/** The exact bytes an owner signs: RFC 8785 (JCS) canonical UTF-8. Renamed
|
|
1261
|
+
* from `mandateSigningBytes` in v0.7. */
|
|
1262
|
+
declare function projectionSigningBytes(projection: MandateProjection): Uint8Array;
|
|
1373
1263
|
/** The exact bytes an owner signs for a per-action approval. */
|
|
1374
1264
|
declare function approvalSigningBytes(approval: ApprovalObject): Uint8Array;
|
|
1375
1265
|
/**
|
|
@@ -1382,40 +1272,61 @@ declare function signMandateProjection(projection: MandateProjection, privateKey
|
|
|
1382
1272
|
/** Sign an approval object with a raw Ed25519 private key (`raw` binding). */
|
|
1383
1273
|
declare function signApproval(approval: ApprovalObject, privateKey: Uint8Array): Promise<string>;
|
|
1384
1274
|
/**
|
|
1385
|
-
* Verify ONE `
|
|
1275
|
+
* Verify ONE `mandate_owners` entry against the mandate that carries it.
|
|
1386
1276
|
*
|
|
1387
|
-
* Steps (protocol.md → "Verification procedure",
|
|
1388
|
-
*
|
|
1389
|
-
* JCS bytes. The DID is
|
|
1390
|
-
*
|
|
1277
|
+
* Steps (protocol.md → "Verification procedure", step 4 of 6): the entry
|
|
1278
|
+
* names this mandate's sole owner, its signing DID is key-bearing,
|
|
1279
|
+
* projection reconstruction, Ed25519 over JCS bytes. The DID is
|
|
1280
|
+
* authoritative over `alg` — a disagreement is `OWNER_SIGNATURE_INVALID`,
|
|
1281
|
+
* never a fallback to the claimed algorithm.
|
|
1391
1282
|
*
|
|
1392
|
-
* What this deliberately does NOT verify: that the DID belongs to the
|
|
1393
|
-
* the verifier expects (out-of-band, step 5) and the AS's own
|
|
1394
|
-
* the
|
|
1283
|
+
* What this deliberately does NOT verify: that the DID belongs to the
|
|
1284
|
+
* person the verifier expects (out-of-band, step 5) and the AS's own
|
|
1285
|
+
* signature over the mandate (`verifyMandateSignature`, step 1).
|
|
1395
1286
|
*/
|
|
1396
|
-
declare function
|
|
1397
|
-
/** Verify every `
|
|
1398
|
-
*
|
|
1399
|
-
*
|
|
1400
|
-
declare function
|
|
1287
|
+
declare function verifyOwnerSignature(mandate: Mandate, entry: MandateOwnerEntry): Promise<void>;
|
|
1288
|
+
/** Verify every co-signing `mandate_owners` entry a mandate carries (i.e.
|
|
1289
|
+
* every entry that carries a signature). Resolves to the verified entries;
|
|
1290
|
+
* a mandate with none resolves to `[]` — nothing to check, nothing claimed. */
|
|
1291
|
+
declare function verifyOwnerSignatures(mandate: Mandate): Promise<MandateOwnerEntry[]>;
|
|
1401
1292
|
/** Verify an approval signature against a signer's key-bearing DID. */
|
|
1402
1293
|
declare function verifyApproval(approval: ApprovalObject, signature: string, signerDid: string): Promise<void>;
|
|
1294
|
+
/**
|
|
1295
|
+
* Enforce a profile's `ownerSignature` floor (protocol.md → *Owner
|
|
1296
|
+
* Signatures* → "Where the requirement lives" — the profile-floor tier,
|
|
1297
|
+
* checkable without trusting the AS: `profile_id`/`profile_hash` are in the
|
|
1298
|
+
* signed payload AND in the owner-signed projection).
|
|
1299
|
+
*
|
|
1300
|
+
* An unknown `binding` value is treated as rank 0 (weakest) rather than
|
|
1301
|
+
* rejected outright — "an AS MUST NOT reject a mandate_owners entry solely
|
|
1302
|
+
* because it does not recognize its binding value" extends naturally to a
|
|
1303
|
+
* verifier ranking it, since crediting an unrecognized binding with
|
|
1304
|
+
* unearned strength would be the more dangerous failure mode.
|
|
1305
|
+
*
|
|
1306
|
+
* @throws OwnerSignatureError `OWNER_SIGNATURE_REQUIRED` when the floor is
|
|
1307
|
+
* unmet — "MUST fail, not warn" (protocol.md, same section).
|
|
1308
|
+
*/
|
|
1309
|
+
declare function checkOwnerSignatureRequirement(payload: MandatePayload, floor: ProfileOwnerSignatureFloor | undefined): void;
|
|
1403
1310
|
|
|
1404
1311
|
/**
|
|
1405
|
-
*
|
|
1406
|
-
* verification.
|
|
1407
|
-
*
|
|
1408
|
-
*
|
|
1312
|
+
* Mandate tickets (v0.7) — the wire type, signing, and holder-side
|
|
1313
|
+
* verification. Renamed from `receipt.ts` (protocol.md → *Mandate Tickets*:
|
|
1314
|
+
* "v0.4 introduced the object under the name execution receipt; v0.7 renames
|
|
1315
|
+
* it because the tense was wrong — a receipt follows payment, and the ticket
|
|
1316
|
+
* must come first").
|
|
1409
1317
|
*
|
|
1410
|
-
*
|
|
1411
|
-
*
|
|
1412
|
-
*
|
|
1318
|
+
* Ticket payloads are camelCase on the wire (unlike the snake_case mandate
|
|
1319
|
+
* payload) — a shipped inconsistency the spec documents rather than breaks
|
|
1320
|
+
* (protocol.md → *Migration from v0.6*, "Unchanged on purpose").
|
|
1413
1321
|
*/
|
|
1414
1322
|
|
|
1415
|
-
/** Signed
|
|
1416
|
-
interface
|
|
1323
|
+
/** Signed mandate ticket — protocol.md → *Ticket Payload Schema*. */
|
|
1324
|
+
interface TicketPayload {
|
|
1417
1325
|
id: string;
|
|
1418
|
-
|
|
1326
|
+
/** The mandate this ticket was issued under (new in v0.7). Audit and
|
|
1327
|
+
* chain-of-custody only — `boundsHash` remains the AS's lookup key. */
|
|
1328
|
+
mandateId: string;
|
|
1329
|
+
groupId: string | null;
|
|
1419
1330
|
userId?: string;
|
|
1420
1331
|
boundsHash: string;
|
|
1421
1332
|
profileId: string;
|
|
@@ -1425,35 +1336,169 @@ interface ReceiptPayload {
|
|
|
1425
1336
|
actionType: string;
|
|
1426
1337
|
executionContext: Record<string, unknown>;
|
|
1427
1338
|
cumulativeState?: Record<string, unknown>;
|
|
1428
|
-
|
|
1339
|
+
/** The mandate's plaintext bounds object, exactly as the AS enforced them
|
|
1340
|
+
* for this ticket (new in v0.7 — required). */
|
|
1341
|
+
limits: Record<string, unknown>;
|
|
1342
|
+
/** Protocol version of the ticket — the version of the mandate it was
|
|
1343
|
+
* issued under (new in v0.7). */
|
|
1344
|
+
version: string;
|
|
1345
|
+
/** The AS DID, as in the mandate (new in v0.7). */
|
|
1346
|
+
issuer: string;
|
|
1429
1347
|
timestamp: number;
|
|
1430
|
-
/**
|
|
1348
|
+
/** Hash of the bound content, computed by the Gatekeeper, copied
|
|
1431
1349
|
* verbatim by the AS. The AS never sees the content. */
|
|
1432
1350
|
contentHash?: string;
|
|
1433
|
-
/**
|
|
1351
|
+
/** The binding declaration echoed into the signed ticket, so a
|
|
1434
1352
|
* verifier knows exactly what `contentHash` covers. */
|
|
1435
1353
|
contentBinding?: Pick<ContentBinding, 'version' | 'kind' | 'fields' | 'required_fields' | 'appliesTo'>;
|
|
1436
|
-
/**
|
|
1354
|
+
/** Disclosed subset of the mandate's identity overlay. */
|
|
1437
1355
|
subjects?: Subject[];
|
|
1438
|
-
/**
|
|
1356
|
+
/** Review path: the proposal this ticket executed. */
|
|
1439
1357
|
proposalId?: string;
|
|
1440
|
-
/**
|
|
1441
|
-
*
|
|
1442
|
-
|
|
1358
|
+
/** Review path: the owner's HAP-approval signature for the executed
|
|
1359
|
+
* proposal, as the object itself — never a hash of it, which a holder
|
|
1360
|
+
* could check only by asking the AS. */
|
|
1361
|
+
approvalSignature?: {
|
|
1362
|
+
alg: 'EdDSA' | 'ES256';
|
|
1363
|
+
signature: string;
|
|
1364
|
+
nonce: string;
|
|
1365
|
+
decided_at: number;
|
|
1366
|
+
};
|
|
1443
1367
|
/** Ed25519 over the JCS-canonical payload (this field excluded), base64url. */
|
|
1444
1368
|
signature: string;
|
|
1445
1369
|
}
|
|
1446
1370
|
/**
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
|
|
1450
|
-
|
|
1371
|
+
* Sign a ticket payload with the Authority Server's Ed25519 private key —
|
|
1372
|
+
* the same key used for mandates.
|
|
1373
|
+
*/
|
|
1374
|
+
declare function signTicket(payload: Omit<TicketPayload, 'signature'>, privateKey: Uint8Array): Promise<TicketPayload>;
|
|
1375
|
+
/**
|
|
1376
|
+
* Verify a ticket's AS signature: strip `signature`, JCS-canonicalize the
|
|
1377
|
+
* rest, Ed25519-verify against the key resolved from `issuer` — never
|
|
1378
|
+
* against a key offered alongside the ticket (protocol.md → *Ticket
|
|
1379
|
+
* Verification* step 1). This is holder-side verification — it needs the
|
|
1380
|
+
* COMPLETE ticket; a redacted public view cannot be re-verified this way
|
|
1381
|
+
* (see {@link publicTicketView}).
|
|
1451
1382
|
*
|
|
1452
|
-
* Content is checked separately: where `contentHash` is present, recompute
|
|
1453
|
-
* from the held artifact via the content-binding module and compare.
|
|
1383
|
+
* Content is checked separately: where `contentHash` is present, recompute
|
|
1384
|
+
* it from the held artifact via the content-binding module and compare.
|
|
1454
1385
|
*
|
|
1386
|
+
* @param opts.trustedIssuers When supplied, an `issuer` not in this list is
|
|
1387
|
+
* rejected before any cryptography runs.
|
|
1455
1388
|
* @throws Error prefixed `INVALID_SIGNATURE:` when verification fails.
|
|
1456
1389
|
*/
|
|
1457
|
-
declare function
|
|
1390
|
+
declare function verifyTicketSignature(ticket: TicketPayload, opts?: {
|
|
1391
|
+
trustedIssuers?: readonly string[];
|
|
1392
|
+
}): Promise<void>;
|
|
1393
|
+
/**
|
|
1394
|
+
* A redacted public view of a ticket (new in v0.7 — protocol.md → *Ticket
|
|
1395
|
+
* Disclosure Is Declared*, *Ticket Verification*). Hides `userId`,
|
|
1396
|
+
* `cumulativeState`, and `limits` unconditionally, and narrows
|
|
1397
|
+
* `executionContext` to exactly the keys named by `governingDiscloseFields`
|
|
1398
|
+
* (the mandate's `disclose_fields` where present, otherwise the profile's).
|
|
1399
|
+
*
|
|
1400
|
+
* Disclose NOTHING by default: an empty or absent `governingDiscloseFields`
|
|
1401
|
+
* strips `executionContext` entirely. "The holder is the verifier" — this
|
|
1402
|
+
* view is NOT independently re-verifiable against `signature` (it is missing
|
|
1403
|
+
* signed fields); a "signature valid" indicator on a page showing only this
|
|
1404
|
+
* view is the AS re-verifying its own signature, and implementations MUST
|
|
1405
|
+
* NOT present it as independently verified.
|
|
1406
|
+
*/
|
|
1407
|
+
declare function publicTicketView(ticket: TicketPayload, governingDiscloseFields?: readonly string[]): Omit<TicketPayload, 'userId' | 'cumulativeState' | 'limits'>;
|
|
1408
|
+
|
|
1409
|
+
/**
|
|
1410
|
+
* Gatekeeper — local (Phase 1) verification for bounded execution (v0.7).
|
|
1411
|
+
*
|
|
1412
|
+
* protocol.md → *Validation Steps* (Phase 1, never reaches the wire):
|
|
1413
|
+
* 1. Resolve profile from bounds.profile
|
|
1414
|
+
* 2. Recompute bounds_hash and scope_hash
|
|
1415
|
+
* 3. For each mandate: decode, verify AS signature, verify version,
|
|
1416
|
+
* verify bounds_hash / scope_hash / profile_hash, verify expiry
|
|
1417
|
+
* 4. Check actionType registry membership
|
|
1418
|
+
* 5. Check per_transaction bounds and enum capability flags locally
|
|
1419
|
+
* (Enforcement Authority: this is what makes per_transaction
|
|
1420
|
+
* compromise-resistant — *Enforcement classes*)
|
|
1421
|
+
* 6. Check scope constraints (enum, subset, requiredFor) locally — the AS
|
|
1422
|
+
* never sees scope plaintext, so the Gatekeeper is the sole enforcer
|
|
1423
|
+
* 7. Return { approved } or { approved: false, errors: [...] }
|
|
1424
|
+
*
|
|
1425
|
+
* What this module deliberately does NOT do (protocol.md → *Enforcement
|
|
1426
|
+
* Authority*, *Migration from v0.6* semantic change 9): enforce
|
|
1427
|
+
* `cumulative_sum` / `cumulative_count` bounds. "Local cumulative
|
|
1428
|
+
* enforcement is removed, not merely deprecated: a Gatekeeper MUST NOT
|
|
1429
|
+
* refuse an execution on a cumulative bound from its own records" — only
|
|
1430
|
+
* the Authority Server's ticket history is authoritative for a running
|
|
1431
|
+
* total across every Gatekeeper exercising the bucket. v0.3/v0.4's
|
|
1432
|
+
* frame-based path is retired entirely (CLAUDE.md "Decided by the owner").
|
|
1433
|
+
*/
|
|
1434
|
+
|
|
1435
|
+
/** Options for {@link verify}. */
|
|
1436
|
+
interface VerifyOptions {
|
|
1437
|
+
/** Restricts mandate-signature verification to these Authority Server
|
|
1438
|
+
* DIDs — protocol.md → *Ticket Verification* step 1 applies identically
|
|
1439
|
+
* to mandates: "a verifier MUST reject a [...] whose issuer it does not
|
|
1440
|
+
* trust." Omitted → any issuer that resolves a valid did:key is accepted
|
|
1441
|
+
* (appropriate for a Gatekeeper that has not yet pinned an AS). */
|
|
1442
|
+
trustedIssuers?: readonly string[];
|
|
1443
|
+
now?: number;
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Verify an execution request against the mandates supplied.
|
|
1447
|
+
*
|
|
1448
|
+
* @param request bounds, mandate blobs, execution values, and optional scope
|
|
1449
|
+
*/
|
|
1450
|
+
declare function verify(request: GatekeeperRequest, opts?: VerifyOptions): Promise<GatekeeperResult>;
|
|
1451
|
+
/**
|
|
1452
|
+
* Which action types' executions count toward this cumulative bound's
|
|
1453
|
+
* running total — `undefined` means all of them. Display-only now that
|
|
1454
|
+
* local cumulative enforcement is removed (protocol.md → *Enforcement
|
|
1455
|
+
* Authority*: cumulative bounds are AS-only). Exported so a gateway's own
|
|
1456
|
+
* usage display partitions totals the same way the Authority Server does.
|
|
1457
|
+
*/
|
|
1458
|
+
declare function boundActionTypes(fieldName: string, fieldDef: ProfileBoundsField): readonly string[] | undefined;
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* Profile Registry — dynamically populated from git-hosted profile sources.
|
|
1462
|
+
*/
|
|
1463
|
+
|
|
1464
|
+
declare function registerProfile(profileId: string, profile: AgentProfile): void;
|
|
1465
|
+
declare function getProfile(profileId: string): AgentProfile | undefined;
|
|
1466
|
+
declare function listProfiles(): string[];
|
|
1467
|
+
declare function getAllProfiles(): AgentProfile[];
|
|
1468
|
+
declare function clearProfiles(): void;
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* did:key — encode/decode for KEY-BEARING DIDs (HAP v0.6).
|
|
1472
|
+
*
|
|
1473
|
+
* A real `did:key` is self-certifying: the public key IS the identifier, so
|
|
1474
|
+
* substituting the key produces a DIFFERENT DID and no key directory is ever
|
|
1475
|
+
* consulted. That property is what the Owner Mandate design stands on
|
|
1476
|
+
* (protocol.md → "Identity DIDs vs signing DIDs"): a signing DID MUST be
|
|
1477
|
+
* key-bearing, and verification uses the key carried in the identifier —
|
|
1478
|
+
* deliberately, there is no `public_key` field anywhere on the wire.
|
|
1479
|
+
*
|
|
1480
|
+
* Format (W3C did:key method, Ed25519):
|
|
1481
|
+
* did:key:z<base58btc( 0xed 0x01 ‖ 32-byte-public-key )>
|
|
1482
|
+
* `z` is the multibase prefix for base58btc; `0xed 0x01` is the multicodec
|
|
1483
|
+
* varint for ed25519-pub. The result always starts `did:key:z6Mk`.
|
|
1484
|
+
*
|
|
1485
|
+
* Scope: Ed25519 only — the one signing curve this protocol version uses.
|
|
1486
|
+
* Anything else (including the reference implementation's legacy decorative
|
|
1487
|
+
* `did:key:<uuid-fragment>` identifiers) decodes as NOT key-bearing.
|
|
1488
|
+
*/
|
|
1489
|
+
/** Encode a 32-byte Ed25519 public key as a key-bearing `did:key:z6Mk…`. */
|
|
1490
|
+
declare function encodeDidKey(publicKey: Uint8Array): string;
|
|
1491
|
+
/**
|
|
1492
|
+
* Decode a key-bearing `did:key` to its Ed25519 public key bytes.
|
|
1493
|
+
*
|
|
1494
|
+
* @throws Error prefixed `NOT_KEY_BEARING:` for anything that is not a valid
|
|
1495
|
+
* base58btc-multibase Ed25519 did:key — including the legacy decorative
|
|
1496
|
+
* `did:key:<uuid-fragment>` identifiers, other multibase prefixes, and other
|
|
1497
|
+
* key types. The error name is the point: under the mandate design, a
|
|
1498
|
+
* non-key-bearing signing DID must fail STRUCTURALLY, never fall back.
|
|
1499
|
+
*/
|
|
1500
|
+
declare function decodeDidKey(did: string): Uint8Array;
|
|
1501
|
+
/** True iff the DID decodes as a key-bearing Ed25519 did:key. Never throws. */
|
|
1502
|
+
declare function isKeyBearingDid(did: string): boolean;
|
|
1458
1503
|
|
|
1459
|
-
export { type AgentBoundsParams, type
|
|
1504
|
+
export { type AgentBoundsParams, type AgentProfile, type AgentScopeParams, type ApprovalObject, type BoundType, CanonicalValueError, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type ContentFieldKind, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type FieldConstraint, type FieldUnit, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, HapError, type HapErrorCode, MANDATE_ERROR_CODES, type Mandate, type MandateHeader, type MandateOwnerEntry, type MandatePayload, type MandateProjection, type OwnerSignatureBinding, OwnerSignatureError, PROTOCOL_VERSION, type ProfileBoundsField, type ProfileOwnerSignatureFloor, type ProfileScopeField, type ProfileValidationError, SIGNABLE_COMMITMENT_MODES, SUPPORTED_VERSIONS, type SignableCommitmentMode, type SigningSurface, type Subject, type SubjectValidation, TICKET_ERROR_CODES, type TicketPayload, type VerifyOptions, allowedCommitmentModes, approvalSigningBytes, bindingAppliesTo, boundActionTypes, buildMandateProjection, canonicalBounds, canonicalScope, canonicalize, canonicalizeText, checkDiscloseSubset, checkMandateExpiry, checkOwnerSignatureRequirement, clearProfiles, computeBoundsHash, computeContentHash, computeFieldsContentHash, computeIntentDisclosureHash, computeIntentHash, computeProfileHash, computeScopeHash, contentCanonicalBytes, decodeDidKey, decodeMandateBlob, deriveIdentityLine, encodeDidKey, encodeMandateBlob, filesFields, fromBase64Url, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, invalidFieldKindsReason, isCommitmentModeAllowed, isFieldBinding, isKeyBearingDid, listProfiles, mandateBlobId, negotiateVersion, projectionSigningBytes, publicTicketView, registerProfile, selectBoundFields, signApproval, signMandate, signMandateProjection, signTicket, toBase64Url, validateBoundsParams, validateMandateOwners, validateProfile, validateScopeParams, validateSubject, verify, verifyApproval, verifyMandate, verifyMandateSignature, verifyOwnerSignature, verifyOwnerSignatures, verifyTicketSignature };
|