@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/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
44
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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).
|
|
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`).
|
|
47
48
|
*/
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* v0.6 (Identity Assurance) — optional signed overlay carrying the verified
|
|
51
|
-
* real-world identity of the Decision Owner(s), one entry per owner. Present
|
|
52
|
-
* only when identity is disclosed; an attestation with no `subjects` renders
|
|
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.
|
|
66
|
-
*/
|
|
67
|
-
commitment_mode?: 'automatic' | 'review' | 'review_above_cap';
|
|
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[];
|
|
135
|
+
/**
|
|
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.
|
|
139
|
+
*/
|
|
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[];
|
|
136
144
|
/**
|
|
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.
|
|
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*.
|
|
142
148
|
*/
|
|
143
|
-
|
|
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
|
/**
|
|
@@ -315,18 +304,27 @@ interface ProfileBoundsField {
|
|
|
315
304
|
* kinds, and the same boundType can carry different units.
|
|
316
305
|
*/
|
|
317
306
|
unit?: FieldUnit;
|
|
318
|
-
/**
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
307
|
+
/**
|
|
308
|
+
* The largest value a mandate may set for this (numeric) bound — a ceiling
|
|
309
|
+
* on what the owner can grant, declared by the profile. E.g. reporting@0.2
|
|
310
|
+
* `read_max_age_days: { maximum: 366 }`: no reporting mandate may read back
|
|
311
|
+
* further than a year.
|
|
312
|
+
*
|
|
313
|
+
* Distinct from the bound itself: the bound limits each ACTION; `maximum`
|
|
314
|
+
* limits the MANDATE. A value above it is not a valid mandate for this
|
|
315
|
+
* profile — the mandate editor caps its input, the issuing Authority Server
|
|
316
|
+
* should refuse it, and an enforcement point that reads the bound applies
|
|
317
|
+
* min(value, maximum). Absent → no ceiling.
|
|
318
|
+
*/
|
|
319
|
+
maximum?: number;
|
|
322
320
|
}
|
|
323
321
|
/**
|
|
324
322
|
* v0.5 Content Provenance — how a profile's action content is hashed into a
|
|
325
|
-
* signed
|
|
323
|
+
* signed ticket (`contentHash`). The ephemeral-content analog of Output
|
|
326
324
|
* Provenance: it binds the *bytes* of the action rather than a location.
|
|
327
325
|
*
|
|
328
326
|
* Profile-bound and OPTIONAL. Absent → no content hash is produced (full
|
|
329
|
-
* backward compatibility). The gateway computes the hash; the
|
|
327
|
+
* backward compatibility). The gateway computes the hash; the AS only ever
|
|
330
328
|
* receives the hash, never the content, so HAP's privacy-minimal design holds.
|
|
331
329
|
*
|
|
332
330
|
* At `version:"1"` the profile declares only the *policy* — whether to bind and
|
|
@@ -388,16 +386,17 @@ interface ContentBinding {
|
|
|
388
386
|
appliesTo?: string[];
|
|
389
387
|
}
|
|
390
388
|
/**
|
|
391
|
-
*
|
|
389
|
+
* Scope field definition within a profile (renamed from `ProfileContextField`
|
|
390
|
+
* in v0.7 — protocol.md → *Migration from v0.6*, `contextSchema` → `scopeSchema`).
|
|
392
391
|
*/
|
|
393
|
-
interface
|
|
392
|
+
interface ProfileScopeField {
|
|
394
393
|
type: 'string' | 'number';
|
|
395
394
|
required: boolean;
|
|
396
395
|
description?: string;
|
|
397
396
|
displayName?: string;
|
|
398
397
|
format?: 'email' | 'domain' | 'url' | 'currency';
|
|
399
398
|
/**
|
|
400
|
-
*
|
|
399
|
+
* What this field NAMES on the read path. `counterparty` = the other
|
|
401
400
|
* party to a communication (matched against an item's participants);
|
|
402
401
|
* `resource` = the container an item belongs to (a direct attribute match —
|
|
403
402
|
* and a resource scope enforced on writes MUST also bind reads). Absent →
|
|
@@ -406,7 +405,6 @@ interface ProfileContextField {
|
|
|
406
405
|
*/
|
|
407
406
|
scopeKind?: 'counterparty' | 'resource';
|
|
408
407
|
constraint?: FieldConstraint;
|
|
409
|
-
enum?: string[];
|
|
410
408
|
}
|
|
411
409
|
/**
|
|
412
410
|
* Execution context field definition — declared source (value comes from the agent's tool call).
|
|
@@ -422,13 +420,11 @@ interface DeclaredFieldDef {
|
|
|
422
420
|
*/
|
|
423
421
|
type CumulativeWindow = 'daily' | 'weekly' | 'monthly';
|
|
424
422
|
/**
|
|
425
|
-
* Execution context field definition — cumulative source (resolved from
|
|
423
|
+
* Execution context field definition — cumulative source (resolved from the
|
|
424
|
+
* AS's ticket history; see protocol.md → *Cumulative State*).
|
|
426
425
|
*
|
|
427
|
-
* The gatekeeper resolves these by querying the execution log:
|
|
428
426
|
* - `cumulativeField`: which declared field to sum (use "_count" for plain counting)
|
|
429
427
|
* - `window`: time window for aggregation (daily, weekly, monthly)
|
|
430
|
-
*
|
|
431
|
-
* The resolved value = running total within window + current call value.
|
|
432
428
|
*/
|
|
433
429
|
interface CumulativeFieldDef {
|
|
434
430
|
source: 'cumulative';
|
|
@@ -443,35 +439,21 @@ interface CumulativeFieldDef {
|
|
|
443
439
|
*/
|
|
444
440
|
type ExecutionContextFieldDef = DeclaredFieldDef | CumulativeFieldDef;
|
|
445
441
|
/**
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*/
|
|
449
|
-
interface GateQuestion {
|
|
450
|
-
question: string;
|
|
451
|
-
required: boolean;
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* @deprecated Execution paths removed in v0.4. Kept for backward compatibility.
|
|
455
|
-
*/
|
|
456
|
-
interface ExecutionPath {
|
|
457
|
-
description: string;
|
|
458
|
-
requiredDomains?: string[];
|
|
459
|
-
ttl?: {
|
|
460
|
-
default: number;
|
|
461
|
-
max: number;
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Agent Profile — defines constraint types, execution paths, gate questions,
|
|
466
|
-
* and the frame/bounds/context schemas for bounded execution.
|
|
467
|
-
*
|
|
468
|
-
* Supports both v0.3 (frameSchema) and v0.4 (boundsSchema + contextSchema).
|
|
442
|
+
* Agent Profile — defines the bounds/scope/execution-context schemas, gates,
|
|
443
|
+
* TTL policy, and retention for a bounded authority (protocol.md → *Profiles*).
|
|
469
444
|
*/
|
|
470
445
|
/**
|
|
471
446
|
* A commitment mode a person can choose when signing a mandate. (`review_above_cap`
|
|
472
447
|
* is not chosen by the signer — it follows from team caps.)
|
|
473
448
|
*/
|
|
474
449
|
type SignableCommitmentMode = 'automatic' | 'review';
|
|
450
|
+
/** protocol.md → *Owner Signatures* → "Where the requirement lives" — the
|
|
451
|
+
* profile-floor tier: `minBinding` names the weakest `OwnerSignatureBinding`
|
|
452
|
+
* this profile accepts for a co-signing owner. */
|
|
453
|
+
interface ProfileOwnerSignatureFloor {
|
|
454
|
+
required: boolean;
|
|
455
|
+
minBinding?: OwnerSignatureBinding;
|
|
456
|
+
}
|
|
475
457
|
interface AgentProfile {
|
|
476
458
|
id: string;
|
|
477
459
|
name?: string;
|
|
@@ -492,9 +474,10 @@ interface AgentProfile {
|
|
|
492
474
|
*/
|
|
493
475
|
whatsNew?: string;
|
|
494
476
|
/**
|
|
495
|
-
* Whether
|
|
496
|
-
* verifier holding the content supplies its hash and learns which
|
|
497
|
-
* bind it, without needing a
|
|
477
|
+
* Whether tickets under this profile may be looked up BY THEIR CONTENT — a
|
|
478
|
+
* verifier holding the content supplies its hash and learns which tickets
|
|
479
|
+
* bind it, without needing a ticket id. (Renamed from `receipt_lookup` in
|
|
480
|
+
* v0.7.)
|
|
498
481
|
*
|
|
499
482
|
* OFF unless declared, and that default is the point. The lookup is a
|
|
500
483
|
* confirmation oracle: given a guess at the content it says whether that
|
|
@@ -506,13 +489,8 @@ interface AgentProfile {
|
|
|
506
489
|
* Enable only when the bound content is unguessable enough that producing it
|
|
507
490
|
* is equivalent to already having it: prose, an artifact URL, a whole record
|
|
508
491
|
* payload. Never for a binding over a short value drawn from a small set.
|
|
509
|
-
*
|
|
510
|
-
* Why it must exist at all: most consequential actions cannot carry their
|
|
511
|
-
* receipt id. A released build was built before the receipt existed, a
|
|
512
|
-
* content-addressed artifact would change identity if the id were added, and
|
|
513
|
-
* a forwarded message has usually lost the footer that carried it.
|
|
514
492
|
*/
|
|
515
|
-
|
|
493
|
+
ticket_lookup?: boolean;
|
|
516
494
|
/**
|
|
517
495
|
* The commitment modes a mandate under this profile may be signed with.
|
|
518
496
|
* Absent → every mode (`automatic`, `review`), as before this field existed.
|
|
@@ -526,21 +504,14 @@ interface AgentProfile {
|
|
|
526
504
|
*/
|
|
527
505
|
commitment_modes?: readonly SignableCommitmentMode[];
|
|
528
506
|
/**
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*/
|
|
532
|
-
frameSchema?: {
|
|
533
|
-
keyOrder: string[];
|
|
534
|
-
fields: Record<string, ProfileFrameField>;
|
|
535
|
-
};
|
|
536
|
-
/**
|
|
537
|
-
* v0.4 bounds schema — defines the authorization bounds parameters.
|
|
507
|
+
* The bounds schema — the enforceable parameters (protocol.md → *Bounds
|
|
508
|
+
* Schema*).
|
|
538
509
|
*/
|
|
539
510
|
boundsSchema?: {
|
|
540
511
|
keyOrder: string[];
|
|
541
512
|
fields: Record<string, ProfileBoundsField>;
|
|
542
513
|
/**
|
|
543
|
-
*
|
|
514
|
+
* Registry of the action types this profile recognizes (e.g.
|
|
544
515
|
* `["send", "delete", "setup"]`). Every `execution.action_type` the
|
|
545
516
|
* Authority Server and gateway accept for this profile MUST be a member
|
|
546
517
|
* when the registry is declared (protocol.md → Bounds Schema, rule 2);
|
|
@@ -551,92 +522,45 @@ interface AgentProfile {
|
|
|
551
522
|
actionTypes?: readonly string[];
|
|
552
523
|
};
|
|
553
524
|
/**
|
|
554
|
-
*
|
|
555
|
-
* May be absent or empty for profiles with
|
|
525
|
+
* The scope schema — operational scoping fields that stay local (renamed
|
|
526
|
+
* from `contextSchema` in v0.7). May be absent or empty for profiles with
|
|
527
|
+
* no static scope.
|
|
556
528
|
*/
|
|
557
|
-
|
|
529
|
+
scopeSchema?: {
|
|
558
530
|
keyOrder: string[];
|
|
559
|
-
fields: Record<string,
|
|
531
|
+
fields: Record<string, ProfileScopeField>;
|
|
560
532
|
};
|
|
561
533
|
executionContextSchema: {
|
|
562
534
|
fields: Record<string, ExecutionContextFieldDef>;
|
|
563
535
|
};
|
|
564
|
-
/** @deprecated Execution paths removed in v0.4. Kept for backward compatibility. */
|
|
565
|
-
executionPaths?: Record<string, ExecutionPath>;
|
|
566
536
|
requiredGates: string[];
|
|
567
|
-
/**
|
|
568
|
-
* v0.4: no gateQuestions — intent prompt is universal, defined in the gateway UI.
|
|
569
|
-
* v0.3: profile-specific gate questions (deprecated).
|
|
570
|
-
*/
|
|
571
|
-
gateQuestions?: {
|
|
572
|
-
problem?: GateQuestion;
|
|
573
|
-
objective?: GateQuestion;
|
|
574
|
-
tradeoffs?: GateQuestion;
|
|
575
|
-
};
|
|
576
537
|
ttl: {
|
|
577
538
|
default: number;
|
|
578
539
|
max: number;
|
|
579
540
|
};
|
|
580
541
|
retention_minimum: number;
|
|
581
542
|
/**
|
|
582
|
-
*
|
|
543
|
+
* Content Provenance (OPTIONAL, profile-bound). When present, the
|
|
583
544
|
* gateway computes a `contentHash` for gated writes under this profile and
|
|
584
|
-
* passes it (hash only) to the
|
|
545
|
+
* passes it (hash only) to the AS, which signs it into the ticket. Absent
|
|
585
546
|
* → no content hash. See {@link ContentBinding}.
|
|
586
547
|
*/
|
|
587
548
|
content_binding?: ContentBinding;
|
|
588
549
|
/**
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
550
|
+
* The execution-context fields a ticket under this profile MAY disclose
|
|
551
|
+
* (new in v0.7 — protocol.md → *Ticket Disclosure Is Declared*). Absent →
|
|
552
|
+
* nothing is disclosed by default. A mandate's own `disclose_fields` (when
|
|
553
|
+
* present) MUST be a subset of this list — a mandate may narrow disclosure,
|
|
554
|
+
* never widen it.
|
|
592
555
|
*/
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
*
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
type ExecutionMappingTransform = 'join' | 'join_domains' | 'length';
|
|
602
|
-
/**
|
|
603
|
-
* Execution mapping value — how a tool argument maps to execution context field(s).
|
|
604
|
-
* - string: direct copy (argName → fieldName)
|
|
605
|
-
* - { field, divisor }: numeric division (e.g., cents ÷ 100 → EUR)
|
|
606
|
-
* - { field, transform }: array transform (e.g., join_domains)
|
|
607
|
-
* - Array form: one argument maps to multiple execution fields
|
|
608
|
-
*/
|
|
609
|
-
type ExecutionMappingValue = string | {
|
|
610
|
-
field: string;
|
|
611
|
-
divisor: number;
|
|
612
|
-
} | {
|
|
613
|
-
field: string;
|
|
614
|
-
transform: ExecutionMappingTransform;
|
|
615
|
-
} | Array<{
|
|
616
|
-
field: string;
|
|
617
|
-
divisor?: number;
|
|
618
|
-
transform?: ExecutionMappingTransform;
|
|
619
|
-
}>;
|
|
620
|
-
/**
|
|
621
|
-
* Tool gating entry — how a tool's calls map to execution context fields.
|
|
622
|
-
* Read-only tools use { category: "read" } — they require authorization
|
|
623
|
-
* but skip execution context verification.
|
|
624
|
-
*/
|
|
625
|
-
interface ProfileToolGatingEntry {
|
|
626
|
-
executionMapping: Record<string, ExecutionMappingValue>;
|
|
627
|
-
staticExecution?: Record<string, string | number>;
|
|
628
|
-
/** Read-only tools: require authorization but no execution context checks */
|
|
629
|
-
category?: 'read';
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* Profile-level tool gating configuration.
|
|
633
|
-
* - default: applied to all tools not listed in overrides
|
|
634
|
-
* - overrides: per-tool configs keyed by original MCP tool name
|
|
635
|
-
* Use { category: "read" } for read-only tools (null is deprecated)
|
|
636
|
-
*/
|
|
637
|
-
interface ProfileToolGating {
|
|
638
|
-
default: ProfileToolGatingEntry;
|
|
639
|
-
overrides?: Record<string, ProfileToolGatingEntry | null>;
|
|
556
|
+
disclose_fields?: string[];
|
|
557
|
+
/**
|
|
558
|
+
* Profile-floor requirement for an owner signature on a mandate under this
|
|
559
|
+
* profile (protocol.md → *Owner Signatures* → "Where the requirement
|
|
560
|
+
* lives"). Reserved for domains that cannot mean anything without a
|
|
561
|
+
* signature — used more freely it forks `charge` from `charge-with-cosign`.
|
|
562
|
+
*/
|
|
563
|
+
ownerSignature?: ProfileOwnerSignatureFloor;
|
|
640
564
|
}
|
|
641
565
|
/**
|
|
642
566
|
* A recorded execution — stored after gatekeeper approval for cumulative tracking.
|
|
@@ -672,51 +596,47 @@ interface ExecutionLogQuery {
|
|
|
672
596
|
*/
|
|
673
597
|
actionTypes?: readonly string[]): number;
|
|
674
598
|
}
|
|
675
|
-
/**
|
|
676
|
-
* Agent frame parameters — mixed types (strings and numbers).
|
|
677
|
-
* Keys and values come from the profile's frameSchema.
|
|
678
|
-
*/
|
|
679
|
-
type AgentFrameParams = Record<string, string | number>;
|
|
680
599
|
/**
|
|
681
600
|
* Agent bounds parameters — mixed types (strings and numbers).
|
|
682
|
-
* Keys and values come from the profile's boundsSchema
|
|
601
|
+
* Keys and values come from the profile's boundsSchema.
|
|
683
602
|
*/
|
|
684
603
|
type AgentBoundsParams = Record<string, string | number>;
|
|
685
604
|
/**
|
|
686
|
-
* Agent
|
|
687
|
-
*
|
|
605
|
+
* Agent scope parameters — mixed types (strings and numbers). Keys and
|
|
606
|
+
* values come from the profile's scopeSchema (renamed from
|
|
607
|
+
* `AgentContextParams` in v0.7).
|
|
688
608
|
*/
|
|
689
|
-
type
|
|
609
|
+
type AgentScopeParams = Record<string, string | number>;
|
|
690
610
|
/**
|
|
691
611
|
* Request to the Gatekeeper for bounded execution verification.
|
|
692
612
|
*/
|
|
693
613
|
interface GatekeeperRequest {
|
|
694
|
-
/** The
|
|
695
|
-
|
|
696
|
-
/**
|
|
697
|
-
|
|
698
|
-
/** The agent's execution values for this specific action */
|
|
614
|
+
/** The bounds the mandate(s) committed to. */
|
|
615
|
+
bounds: AgentBoundsParams;
|
|
616
|
+
/** Mandate blobs (base64url) to verify against. */
|
|
617
|
+
mandates: string[];
|
|
618
|
+
/** The agent's execution values for this specific action. */
|
|
699
619
|
execution: Record<string, string | number>;
|
|
700
|
-
/**
|
|
701
|
-
|
|
620
|
+
/** Scope parameters (currency, action_type, allowed recipients, etc.) —
|
|
621
|
+
* enforced locally only; never sent to the Authority Server. */
|
|
622
|
+
scope?: AgentScopeParams;
|
|
702
623
|
/**
|
|
703
|
-
* Authorization path used to scope cumulative lookups in the execution log
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
* profile's boundsSchema, so an extra key there is rejected as an unknown
|
|
707
|
-
* field and breaks attestation verification outright. Cumulative checks
|
|
708
|
-
* previously read the path from the frame, where callers cannot legally put
|
|
709
|
-
* it — so it resolved to "" while the log stored real paths, no entry ever
|
|
710
|
-
* matched, and every running total read zero. The local gate existed but
|
|
711
|
-
* could never fire.
|
|
624
|
+
* Authorization path used to scope cumulative lookups in the execution log
|
|
625
|
+
* (display only — see {@link ExecutionLogQuery}; the Gatekeeper MUST NOT
|
|
626
|
+
* enforce cumulative bounds locally, protocol.md → *Enforcement Authority*).
|
|
712
627
|
*/
|
|
713
628
|
path?: string;
|
|
714
629
|
}
|
|
715
630
|
/**
|
|
716
|
-
* Structured error from Gatekeeper verification.
|
|
631
|
+
* Structured error from Gatekeeper verification. Codes are the canonical
|
|
632
|
+
* v0.7 surface ({@link HapErrorCode}, `src/errors.ts`) plus the two
|
|
633
|
+
* Gatekeeper-local-only codes that never reach the wire (`FRAME_MISMATCH`
|
|
634
|
+
* is retired with v0.3; a Gatekeeper reports `BOUNDS_HASH_MISMATCH` /
|
|
635
|
+
* `SCOPE_HASH_MISMATCH` for both its own local mismatch and the AS's wire
|
|
636
|
+
* refusal, per protocol.md → *Error Codes*, "Gatekeeper · local").
|
|
717
637
|
*/
|
|
718
638
|
interface GatekeeperError {
|
|
719
|
-
code:
|
|
639
|
+
code: HapErrorCode;
|
|
720
640
|
field?: string;
|
|
721
641
|
message: string;
|
|
722
642
|
bound?: string | number;
|
|
@@ -767,6 +687,46 @@ type GatekeeperResult = {
|
|
|
767
687
|
*/
|
|
768
688
|
declare function canonicalize(value: unknown): string;
|
|
769
689
|
|
|
690
|
+
/**
|
|
691
|
+
* base64url (RFC 4648 §5), strict — no padding, no standard-base64 characters.
|
|
692
|
+
*
|
|
693
|
+
* protocol.md → *Mandate Payload* rule 3: "Signatures MUST be encoded as
|
|
694
|
+
* base64url without padding ... Implementations MUST NOT use standard base64
|
|
695
|
+
* (which differs in `+`/`/` vs `-`/`_` and in padding) — third-party verifiers
|
|
696
|
+
* reading the spec literally will reject standard-base64 signatures."
|
|
697
|
+
*
|
|
698
|
+
* Earlier hap-core verifiers were lenient: they translated `+`/`/` back from
|
|
699
|
+
* `-`/`_` and re-added padding before decoding, so a standard-base64 signature
|
|
700
|
+
* verified exactly as well as a base64url one — silently accepting the wire
|
|
701
|
+
* format the spec forbids. v0.7 closes that: decoding a string that is not
|
|
702
|
+
* strict base64url throws, never silently repairs it.
|
|
703
|
+
*/
|
|
704
|
+
/** Encode bytes as base64url with no padding. */
|
|
705
|
+
declare function toBase64Url(bytes: Uint8Array): string;
|
|
706
|
+
/**
|
|
707
|
+
* Decode a STRICT base64url string (no padding, no `+`/`/`/`=`).
|
|
708
|
+
*
|
|
709
|
+
* @throws Error (no protocol code — the caller knows which artifact it was
|
|
710
|
+
* decoding and assigns the code) when the input carries a `+`, `/`, `=`, or
|
|
711
|
+
* any other byte outside the base64url alphabet.
|
|
712
|
+
*/
|
|
713
|
+
declare function fromBase64Url(s: string): Uint8Array;
|
|
714
|
+
|
|
715
|
+
/** The protocol version this package issues and verifies. */
|
|
716
|
+
declare const PROTOCOL_VERSION = "0.7";
|
|
717
|
+
/** The protocol versions this package can verify and enforce. */
|
|
718
|
+
declare const SUPPORTED_VERSIONS: readonly string[];
|
|
719
|
+
/**
|
|
720
|
+
* The highest version both the requester and this implementation support.
|
|
721
|
+
*
|
|
722
|
+
* @param requested `supported_versions` from the mandate request. Absent →
|
|
723
|
+
* read as `["0.6"]` (protocol.md rule 1) — which, against this package's
|
|
724
|
+
* `SUPPORTED_VERSIONS`, has no overlap and refuses honestly rather than
|
|
725
|
+
* silently assuming `0.7`.
|
|
726
|
+
* @throws HapError `VERSION_UNSUPPORTED` when no version is common to both.
|
|
727
|
+
*/
|
|
728
|
+
declare function negotiateVersion(requested: readonly string[] | undefined, supported?: readonly string[]): string;
|
|
729
|
+
|
|
770
730
|
/**
|
|
771
731
|
* Content binding — Level 2 content proof (HAP v0.5 Content Provenance).
|
|
772
732
|
*
|
|
@@ -937,20 +897,17 @@ interface SubjectValidation {
|
|
|
937
897
|
* - `self_declared` ⇒ `low` / `self`, no disclosed name.
|
|
938
898
|
* - `as_vouched` ⇒ `high` / `as`, `verifier` required.
|
|
939
899
|
* - `eudi` ⇒ `high` / `external`, `verifier` required — and, when the
|
|
940
|
-
* carrying
|
|
900
|
+
* carrying mandate's `mandate_owners` are supplied, a corresponding
|
|
941
901
|
* entry with `binding:"eudi"` for this subject's DID.
|
|
942
902
|
* - `disclose.name` only at `assurance:"high"`.
|
|
943
903
|
*
|
|
944
|
-
*
|
|
945
|
-
* the identity claim, not the mandate (see the field's deprecation note). The
|
|
946
|
-
* signature-bearing object is the attestation's `owner_mandates` entry, which
|
|
904
|
+
* The signature-bearing object is the mandate's `mandate_owners` entry, which
|
|
947
905
|
* is payload-level context this per-subject check cannot see on its own; pass
|
|
948
|
-
* `opts.
|
|
949
|
-
* validate subject shape alone
|
|
950
|
-
* the retired owner_signature requirement).
|
|
906
|
+
* `opts.mandateOwners` to enforce the eudi ⇒ mandate-owner-entry rule, omit it
|
|
907
|
+
* to validate subject shape alone.
|
|
951
908
|
*/
|
|
952
909
|
declare function validateSubject(subject: Subject, opts?: {
|
|
953
|
-
|
|
910
|
+
mandateOwners?: MandateOwnerEntry[];
|
|
954
911
|
}): SubjectValidation;
|
|
955
912
|
/**
|
|
956
913
|
* The footer line for a subject — the ONE place this wording lives.
|
|
@@ -978,76 +935,38 @@ declare function deriveIdentityLine(subject: Subject | undefined | null, { opera
|
|
|
978
935
|
}): string;
|
|
979
936
|
|
|
980
937
|
/**
|
|
981
|
-
*
|
|
938
|
+
* Bounds & Scope Canonicalization (protocol.md → *Bounds & Scope
|
|
939
|
+
* Canonicalization*).
|
|
982
940
|
*
|
|
983
|
-
* Agent profiles support mixed-type fields (strings and numbers).
|
|
984
941
|
* Canonical form: `key=value` records joined with LF, keys in the profile's
|
|
985
942
|
* keyOrder. Values are stringified with String(value) — the shortest
|
|
986
943
|
* round-trippable form for numbers — then percent-encoded per protocol.md
|
|
987
944
|
* (`=`, `%`, and every byte outside printable ASCII); a value carrying a raw
|
|
988
945
|
* LF/CR is refused, never normalized. See `canonicalRecords` below.
|
|
989
946
|
*
|
|
990
|
-
* v0.3
|
|
991
|
-
* v0.
|
|
947
|
+
* v0.3's `frameSchema` / `canonicalFrame` / `computeFrameHash` are retired
|
|
948
|
+
* with the rest of v0.3 (CLAUDE.md "Decided by the owner": no backward
|
|
949
|
+
* compatibility). v0.7 renames `contextSchema` → `scopeSchema` and
|
|
950
|
+
* `AgentContextParams` → `AgentScopeParams` (protocol.md → *Migration from
|
|
951
|
+
* v0.6*); the functions below follow suit (`canonicalContext` →
|
|
952
|
+
* `canonicalScope`, `computeContextHash` → `computeScopeHash`,
|
|
953
|
+
* `validateContextParams` → `validateScopeParams`).
|
|
992
954
|
*/
|
|
993
955
|
|
|
994
956
|
/**
|
|
995
|
-
* Validates
|
|
996
|
-
*/
|
|
997
|
-
declare function validateFrameParams(params: AgentFrameParams, profile: AgentProfile): {
|
|
998
|
-
valid: boolean;
|
|
999
|
-
errors: string[];
|
|
1000
|
-
};
|
|
1001
|
-
/**
|
|
1002
|
-
* Builds the canonical frame string from parameters.
|
|
1003
|
-
* All values are converted to strings. Keys are ordered per profile's keyOrder.
|
|
1004
|
-
*
|
|
1005
|
-
* @throws Error if any field fails validation
|
|
1006
|
-
*/
|
|
1007
|
-
declare function canonicalFrame(params: AgentFrameParams, profile: AgentProfile): string;
|
|
1008
|
-
/**
|
|
1009
|
-
* Computes the frame hash from a canonical frame string.
|
|
1010
|
-
*
|
|
1011
|
-
* @returns Hash in format "sha256:<64 hex chars>"
|
|
1012
|
-
*/
|
|
1013
|
-
declare function frameHash(canonicalFrameString: string): string;
|
|
1014
|
-
/**
|
|
1015
|
-
* Convenience: builds canonical frame and computes hash in one step.
|
|
1016
|
-
*/
|
|
1017
|
-
declare function computeFrameHash(params: AgentFrameParams, profile: AgentProfile): string;
|
|
1018
|
-
/**
|
|
1019
|
-
* Validates bounds parameters against the profile's boundsSchema (v0.4).
|
|
957
|
+
* Validates bounds parameters against the profile's boundsSchema.
|
|
1020
958
|
*/
|
|
1021
959
|
declare function validateBoundsParams(params: AgentBoundsParams, profile: AgentProfile): {
|
|
1022
960
|
valid: boolean;
|
|
1023
961
|
errors: string[];
|
|
1024
962
|
};
|
|
1025
963
|
/**
|
|
1026
|
-
* Validates
|
|
964
|
+
* Validates scope parameters against the profile's scopeSchema.
|
|
1027
965
|
*/
|
|
1028
|
-
declare function
|
|
966
|
+
declare function validateScopeParams(params: AgentScopeParams, profile: AgentProfile): {
|
|
1029
967
|
valid: boolean;
|
|
1030
968
|
errors: string[];
|
|
1031
969
|
};
|
|
1032
|
-
/**
|
|
1033
|
-
* Authoring-time check for `per_transaction` bound `requiredFor` (v0.8+):
|
|
1034
|
-
* entries must name a real `action_type` from the profile's declared
|
|
1035
|
-
* `boundsSchema.actionTypes` registry, and the field is only meaningful on
|
|
1036
|
-
* `per_transaction` bounds (see `BoundType`'s doc comment for why the other
|
|
1037
|
-
* kinds don't carry it).
|
|
1038
|
-
*
|
|
1039
|
-
* This is NOT wired into `registerProfile` or any enforcement path — nothing
|
|
1040
|
-
* in hap-core validates profile JSON at load time today (the pre-existing
|
|
1041
|
-
* `requiredFor` on a context/scope constraint has the same gap), so adding
|
|
1042
|
-
* an automatic refusal here would be new behaviour beyond this change's
|
|
1043
|
-
* scope. Call it explicitly from an authoring tool, a hap-profiles CI check,
|
|
1044
|
-
* or a test, to catch a `requiredFor` entry that can never match (misspelled
|
|
1045
|
-
* action type) or is attached to a bound kind that silently ignores it.
|
|
1046
|
-
*
|
|
1047
|
-
* Returns an empty array when the profile has no boundsSchema or nothing to
|
|
1048
|
-
* check — never throws.
|
|
1049
|
-
*/
|
|
1050
|
-
declare function validateBoundsRequiredFor(profile: AgentProfile): string[];
|
|
1051
970
|
/**
|
|
1052
971
|
* Thrown when a value cannot be canonicalized at all — currently only for raw
|
|
1053
972
|
* LF/CR inside a value. Carries the protocol error code so callers can map it
|
|
@@ -1060,9 +979,9 @@ declare function validateBoundsRequiredFor(profile: AgentProfile): string[];
|
|
|
1060
979
|
* does not faithfully represent the input."
|
|
1061
980
|
*/
|
|
1062
981
|
declare class CanonicalValueError extends Error {
|
|
1063
|
-
readonly code: 'BOUNDS_INVALID_VALUE' | '
|
|
982
|
+
readonly code: 'BOUNDS_INVALID_VALUE' | 'SCOPE_INVALID_VALUE';
|
|
1064
983
|
readonly field: string;
|
|
1065
|
-
constructor(code: 'BOUNDS_INVALID_VALUE' | '
|
|
984
|
+
constructor(code: 'BOUNDS_INVALID_VALUE' | 'SCOPE_INVALID_VALUE', field: string, message: string);
|
|
1066
985
|
}
|
|
1067
986
|
/**
|
|
1068
987
|
* Builds the canonical bounds string from parameters.
|
|
@@ -1074,29 +993,29 @@ declare class CanonicalValueError extends Error {
|
|
|
1074
993
|
*/
|
|
1075
994
|
declare function canonicalBounds(params: AgentBoundsParams, profile: AgentProfile): string;
|
|
1076
995
|
/**
|
|
1077
|
-
* Builds the canonical
|
|
1078
|
-
* Keys are ordered per profile's
|
|
996
|
+
* Builds the canonical scope string from parameters.
|
|
997
|
+
* Keys are ordered per profile's scopeSchema.keyOrder; values are encoded per
|
|
1079
998
|
* protocol.md → *Value encoding* (see `canonicalRecords`).
|
|
1080
|
-
* For empty
|
|
999
|
+
* For empty scope (no scopeSchema or no fields), returns "".
|
|
1081
1000
|
*
|
|
1082
1001
|
* @throws Error if any field fails validation
|
|
1083
|
-
* @throws CanonicalValueError (code
|
|
1002
|
+
* @throws CanonicalValueError (code SCOPE_INVALID_VALUE) if a value carries a raw LF/CR
|
|
1084
1003
|
*/
|
|
1085
|
-
declare function
|
|
1004
|
+
declare function canonicalScope(params: AgentScopeParams, profile: AgentProfile): string;
|
|
1086
1005
|
/**
|
|
1087
|
-
* Computes the bounds hash from bounds parameters
|
|
1006
|
+
* Computes the bounds hash from bounds parameters.
|
|
1088
1007
|
*
|
|
1089
1008
|
* @returns Hash in format "sha256:<64 hex chars>"
|
|
1090
1009
|
*/
|
|
1091
1010
|
declare function computeBoundsHash(params: AgentBoundsParams, profile: AgentProfile): string;
|
|
1092
1011
|
/**
|
|
1093
|
-
* Computes the
|
|
1094
|
-
* For empty
|
|
1012
|
+
* Computes the scope hash from scope parameters.
|
|
1013
|
+
* For empty scope {}, returns the sha256 of "":
|
|
1095
1014
|
* "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
1096
1015
|
*
|
|
1097
1016
|
* @returns Hash in format "sha256:<64 hex chars>"
|
|
1098
1017
|
*/
|
|
1099
|
-
declare function
|
|
1018
|
+
declare function computeScopeHash(params: AgentScopeParams, profile: AgentProfile): string;
|
|
1100
1019
|
|
|
1101
1020
|
/**
|
|
1102
1021
|
* Which commitment modes a profile allows its mandates to be signed with
|
|
@@ -1115,248 +1034,208 @@ declare const SIGNABLE_COMMITMENT_MODES: readonly SignableCommitmentMode[];
|
|
|
1115
1034
|
declare function allowedCommitmentModes(profile: AgentProfile): readonly SignableCommitmentMode[];
|
|
1116
1035
|
/** Whether a mandate under this profile may be signed with `mode`. */
|
|
1117
1036
|
declare function isCommitmentModeAllowed(profile: AgentProfile, mode: string): boolean;
|
|
1118
|
-
/**
|
|
1119
|
-
* Authoring check for a profile's `commitment_modes` declaration — for
|
|
1120
|
-
* hap-profiles CI, an authoring tool or a test. Like `validateBoundsRequiredFor`
|
|
1121
|
-
* it is not wired into profile loading; enforcement reads the declaration
|
|
1122
|
-
* fail-closed through `allowedCommitmentModes`. Empty array = nothing to fix.
|
|
1123
|
-
*/
|
|
1124
|
-
declare function validateCommitmentModes(profile: AgentProfile): string[];
|
|
1125
1037
|
|
|
1038
|
+
/** One authoring-time finding. `code` is always a canonical v0.7 code — in
|
|
1039
|
+
* practice always `PROFILE_INVALID`, since that is the only mandate-request
|
|
1040
|
+
* refusal the protocol attributes to "the profile fails validation"
|
|
1041
|
+
* (protocol.md → Error Codes). */
|
|
1042
|
+
interface ProfileValidationError {
|
|
1043
|
+
code: HapErrorCode;
|
|
1044
|
+
field?: string;
|
|
1045
|
+
message: string;
|
|
1046
|
+
}
|
|
1126
1047
|
/**
|
|
1127
|
-
*
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
/**
|
|
1133
|
-
* Decodes a base64url-encoded attestation blob.
|
|
1134
|
-
*/
|
|
1135
|
-
declare function decodeAttestationBlob(blob: string): Attestation;
|
|
1136
|
-
/**
|
|
1137
|
-
* Encodes an attestation as a base64url blob (no padding).
|
|
1138
|
-
*/
|
|
1139
|
-
declare function encodeAttestationBlob(attestation: Attestation): string;
|
|
1140
|
-
/**
|
|
1141
|
-
* Computes the attestation ID (hash of the blob).
|
|
1048
|
+
* `profile_hash` — `"sha256:" + sha256(JCS(parsed))`, over the PARSED
|
|
1049
|
+
* profile document. Formatting, key order, and a trailing newline on disk
|
|
1050
|
+
* never change it (protocol.md → *Profile hash*; conformance vector set
|
|
1051
|
+
* `profile-hash.json`).
|
|
1142
1052
|
*/
|
|
1143
|
-
declare function
|
|
1053
|
+
declare function computeProfileHash(parsed: unknown): string;
|
|
1144
1054
|
/**
|
|
1145
|
-
*
|
|
1146
|
-
*
|
|
1147
|
-
*
|
|
1148
|
-
* @throws Error with code prefix if invalid
|
|
1055
|
+
* Authoring-time structural validation (protocol.md → *Bounds Schema*,
|
|
1056
|
+
* *Owner Signatures* → "Where the requirement lives", *Migration from
|
|
1057
|
+
* v0.6*). Returns `[]` for a clean profile; never throws.
|
|
1149
1058
|
*/
|
|
1150
|
-
declare function
|
|
1059
|
+
declare function validateProfile(profile: AgentProfile): ProfileValidationError[];
|
|
1151
1060
|
/**
|
|
1152
|
-
*
|
|
1061
|
+
* A mandate's `disclose_fields` MUST be a subset of the profile's own list
|
|
1062
|
+
* (protocol.md → *Mandate Payload*, conditional field `disclose_fields`: "a
|
|
1063
|
+
* subset of the profile's `disclose_fields` list"). A mandate may narrow
|
|
1064
|
+
* disclosure, never widen it.
|
|
1153
1065
|
*
|
|
1154
|
-
* @throws Error
|
|
1066
|
+
* @throws Error prefixed `MALFORMED_MANDATE:` when it is not a subset.
|
|
1155
1067
|
*/
|
|
1156
|
-
declare function
|
|
1068
|
+
declare function checkDiscloseSubset(mandateDiscloseFields: readonly string[] | undefined, profileDiscloseFields: readonly string[] | undefined): void;
|
|
1069
|
+
|
|
1157
1070
|
/**
|
|
1158
|
-
*
|
|
1071
|
+
* Mandate encoding, decoding, signing, and verification (v0.7).
|
|
1159
1072
|
*
|
|
1160
|
-
*
|
|
1073
|
+
* Renamed from `attestation.ts` (protocol.md → *Migration from v0.6*:
|
|
1074
|
+
* `header.typ: "HAP-attestation"` → `"HAP-mandate"`, `attestation_id` →
|
|
1075
|
+
* `mandate_id`). v0.3's frame-hash path and v0.4's dual frame/bounds
|
|
1076
|
+
* verification are retired with the rest of pre-v0.5 — this module issues
|
|
1077
|
+
* and verifies `version: "0.7"` mandates only (CLAUDE.md "Decided by the
|
|
1078
|
+
* owner": no backward-compat verify path for 0.5/0.6).
|
|
1161
1079
|
*/
|
|
1162
|
-
|
|
1080
|
+
|
|
1163
1081
|
/**
|
|
1164
|
-
*
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
1167
|
-
* Migration rule: if attestation has frame_hash but not bounds_hash, it is v0.3.
|
|
1082
|
+
* Decodes a base64url-encoded mandate blob.
|
|
1083
|
+
* @throws HapError `MALFORMED_MANDATE` on anything that is not a strict
|
|
1084
|
+
* base64url-encoded JSON mandate.
|
|
1168
1085
|
*/
|
|
1169
|
-
declare function
|
|
1086
|
+
declare function decodeMandateBlob(blob: string): Mandate;
|
|
1170
1087
|
/**
|
|
1171
|
-
*
|
|
1172
|
-
*
|
|
1173
|
-
* @throws Error if bounds hash doesn't match
|
|
1088
|
+
* Encodes a mandate as a base64url blob (no padding).
|
|
1174
1089
|
*/
|
|
1175
|
-
declare function
|
|
1090
|
+
declare function encodeMandateBlob(mandate: Mandate): string;
|
|
1176
1091
|
/**
|
|
1177
|
-
*
|
|
1178
|
-
*
|
|
1179
|
-
* @throws Error if context hash doesn't match
|
|
1092
|
+
* Computes the mandate blob id (hash of the blob) — renamed from
|
|
1093
|
+
* `attestationId`.
|
|
1180
1094
|
*/
|
|
1181
|
-
declare function
|
|
1095
|
+
declare function mandateBlobId(blob: string): string;
|
|
1182
1096
|
/**
|
|
1183
|
-
*
|
|
1097
|
+
* Sign a mandate payload with the Authority Server's Ed25519 private key.
|
|
1184
1098
|
*
|
|
1185
|
-
* @
|
|
1186
|
-
*
|
|
1099
|
+
* @param opts.kid Optional key id for the envelope header. When present it
|
|
1100
|
+
* MUST identify the same key as `payload.issuer` (Mandate rule 8) — this
|
|
1101
|
+
* function does not enforce that on the signer's side; `verifyMandateSignature`
|
|
1102
|
+
* enforces it on the verifier's side, where a mismatch is actually dangerous.
|
|
1187
1103
|
*/
|
|
1188
|
-
declare function
|
|
1104
|
+
declare function signMandate(payload: MandatePayload, privateKey: Uint8Array, opts?: {
|
|
1105
|
+
kid?: string;
|
|
1106
|
+
}): Promise<Mandate>;
|
|
1189
1107
|
/**
|
|
1190
|
-
*
|
|
1108
|
+
* Verifies a mandate's Ed25519 signature. The verification key is resolved
|
|
1109
|
+
* from `payload.issuer` itself — never from a key offered alongside the
|
|
1110
|
+
* artifact (protocol.md → *Ticket Verification* step 1, which states the
|
|
1111
|
+
* same rule for tickets; mandates follow it identically).
|
|
1191
1112
|
*
|
|
1192
|
-
* @
|
|
1193
|
-
*
|
|
1113
|
+
* @param opts.trustedIssuers When supplied, an `issuer` not in this list is
|
|
1114
|
+
* rejected before any cryptography runs — "a verifier MUST reject a [...]
|
|
1115
|
+
* whose `issuer` it does not trust" (protocol.md → *Ticket Verification*).
|
|
1116
|
+
* @throws Error prefixed `INVALID_SIGNATURE:` on any verification failure.
|
|
1194
1117
|
*/
|
|
1195
|
-
declare function
|
|
1196
|
-
|
|
1118
|
+
declare function verifyMandateSignature(mandate: Mandate, opts?: {
|
|
1119
|
+
trustedIssuers?: readonly string[];
|
|
1120
|
+
}): Promise<void>;
|
|
1197
1121
|
/**
|
|
1198
|
-
*
|
|
1199
|
-
*
|
|
1200
|
-
*
|
|
1201
|
-
* bounds_hash + context_hash).
|
|
1202
|
-
*
|
|
1203
|
-
* v0.3 flow (§8.6):
|
|
1204
|
-
* 1. Resolve profile from frame
|
|
1205
|
-
* 2. Recompute frame_hash
|
|
1206
|
-
* 3. For each required domain: find attestation, verify signature, verify frame_hash, verify TTL
|
|
1207
|
-
* 4. Check bounds: max → actual <= bound, enum → actual in allowed
|
|
1208
|
-
* 5. Return { approved } or { approved: false, errors: [...] }
|
|
1122
|
+
* Checks a mandate payload's time rules (Mandate rule 9): `expires_at` is
|
|
1123
|
+
* authoritative, with a {@link CLOCK_SKEW_SECONDS} tolerance that never
|
|
1124
|
+
* extends life past `expires_at + 300s`.
|
|
1209
1125
|
*
|
|
1210
|
-
*
|
|
1211
|
-
* 1. Resolve profile from bounds.profile
|
|
1212
|
-
* 2. Recompute bounds_hash and context_hash
|
|
1213
|
-
* 3. For each required domain: find attestation, verify signature, verify bounds_hash + context_hash, verify TTL
|
|
1214
|
-
* 4. Check bounds (from boundsSchema), check context constraints (from contextSchema)
|
|
1215
|
-
* 5. Resolve cumulative fields, check cumulative limits
|
|
1216
|
-
* 6. Return { approved } or { approved: false, errors: [...] }
|
|
1126
|
+
* @throws Error prefixed `TTL_EXPIRED:` when expired.
|
|
1217
1127
|
*/
|
|
1218
|
-
|
|
1128
|
+
declare function checkMandateExpiry(payload: MandatePayload, now?: number): void;
|
|
1219
1129
|
/**
|
|
1220
|
-
*
|
|
1221
|
-
*
|
|
1222
|
-
*
|
|
1223
|
-
* and the optional `context` param is used for the context hash check.
|
|
1224
|
-
*
|
|
1225
|
-
* For v0.3 profiles (have frameSchema only), existing logic is used unchanged.
|
|
1130
|
+
* Validates the structural shape of `mandate_owners` (Mandate rule 7):
|
|
1131
|
+
* exactly one entry, `did` required, and the signature fields present
|
|
1132
|
+
* together or absent together. A co-signing entry's `did` MUST be key-bearing.
|
|
1226
1133
|
*
|
|
1227
|
-
* @
|
|
1228
|
-
*
|
|
1229
|
-
* @param now - Current timestamp in seconds (for testing)
|
|
1230
|
-
* @param executionLog - Optional execution log for resolving cumulative fields
|
|
1134
|
+
* @throws HapError `MALFORMED_MANDATE` on a cardinality or completeness
|
|
1135
|
+
* violation; `OWNER_SIGNATURE_INVALID` when a co-signing DID is not key-bearing.
|
|
1231
1136
|
*/
|
|
1232
|
-
declare function
|
|
1233
|
-
/**
|
|
1234
|
-
* Which action types' executions count toward this cumulative bound's running
|
|
1235
|
-
* total. `undefined` means all of them.
|
|
1236
|
-
*
|
|
1237
|
-
* `boundGovernsAction` decides whether a bound applies to the current call; this
|
|
1238
|
-
* decides what the bound's total is made of. Both are needed: selecting the right
|
|
1239
|
-
* bound and then feeding it the profile's combined total is still wrong — sales'
|
|
1240
|
-
* "orders per day" was checked only on orders, but counted every quote and send.
|
|
1241
|
-
* Consumption is partitioned by action type (*Cumulative Tracking* rule 4), as
|
|
1242
|
-
* the Authority Server already does.
|
|
1243
|
-
*
|
|
1244
|
-
* Same answer, same order of preference as `boundGovernsAction`: the declared
|
|
1245
|
-
* `appliesTo`; else, for a count bound named for an action (`delete_daily_max`),
|
|
1246
|
-
* that action; else every action type. Exported so display surfaces report the
|
|
1247
|
-
* same totals the gate enforces.
|
|
1248
|
-
*/
|
|
1249
|
-
declare function boundActionTypes(fieldName: string, fieldDef: ProfileBoundsField): readonly string[] | undefined;
|
|
1250
|
-
|
|
1251
|
-
/**
|
|
1252
|
-
* Profile Registry — dynamically populated from git-hosted profile sources.
|
|
1253
|
-
*/
|
|
1254
|
-
|
|
1255
|
-
declare function registerProfile(profileId: string, profile: AgentProfile): void;
|
|
1256
|
-
declare function getProfile(profileId: string): AgentProfile | undefined;
|
|
1257
|
-
declare function listProfiles(): string[];
|
|
1258
|
-
declare function getAllProfiles(): AgentProfile[];
|
|
1259
|
-
declare function clearProfiles(): void;
|
|
1260
|
-
|
|
1137
|
+
declare function validateMandateOwners(payload: MandatePayload): void;
|
|
1261
1138
|
/**
|
|
1262
|
-
*
|
|
1139
|
+
* Full mandate verification: decode, check `version`, verify the AS
|
|
1140
|
+
* signature, check expiry, validate `mandate_owners` shape.
|
|
1263
1141
|
*
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
1267
|
-
* (protocol.md → "Identity DIDs vs signing DIDs"): a signing DID MUST be
|
|
1268
|
-
* key-bearing, and verification uses the key carried in the identifier —
|
|
1269
|
-
* deliberately, there is no `public_key` field anywhere on the wire.
|
|
1142
|
+
* Deliberately does NOT verify any `mandate_owners` co-signature — that is
|
|
1143
|
+
* `verifyOwnerSignature` in `owner-signature.ts`, a separate trust axis the
|
|
1144
|
+
* caller opts into.
|
|
1270
1145
|
*
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
* `
|
|
1274
|
-
* varint for ed25519-pub. The result always starts `did:key:z6Mk`.
|
|
1275
|
-
*
|
|
1276
|
-
* Scope: Ed25519 only — the one signing curve this protocol version uses.
|
|
1277
|
-
* Anything else (including the reference implementation's legacy decorative
|
|
1278
|
-
* `did:key:<uuid-fragment>` identifiers) decodes as NOT key-bearing.
|
|
1146
|
+
* @throws HapError `VERSION_UNSUPPORTED` for anything other than `"0.7"`.
|
|
1147
|
+
* @throws Error (`INVALID_SIGNATURE:` / `TTL_EXPIRED:` / `MALFORMED_MANDATE:`
|
|
1148
|
+
* prefixed, or `OWNER_SIGNATURE_INVALID` as a HapError) on any other failure.
|
|
1279
1149
|
*/
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
*
|
|
1285
|
-
* @throws Error prefixed `NOT_KEY_BEARING:` for anything that is not a valid
|
|
1286
|
-
* base58btc-multibase Ed25519 did:key — including the legacy decorative
|
|
1287
|
-
* `did:key:<uuid-fragment>` identifiers, other multibase prefixes, and other
|
|
1288
|
-
* key types. The error name is the point: under the mandate design, a
|
|
1289
|
-
* non-key-bearing signing DID must fail STRUCTURALLY, never fall back.
|
|
1290
|
-
*/
|
|
1291
|
-
declare function decodeDidKey(did: string): Uint8Array;
|
|
1292
|
-
/** True iff the DID decodes as a key-bearing Ed25519 did:key. Never throws. */
|
|
1293
|
-
declare function isKeyBearingDid(did: string): boolean;
|
|
1150
|
+
declare function verifyMandate(blob: string, opts?: {
|
|
1151
|
+
trustedIssuers?: readonly string[];
|
|
1152
|
+
now?: number;
|
|
1153
|
+
}): Promise<MandatePayload>;
|
|
1294
1154
|
|
|
1295
1155
|
/**
|
|
1296
|
-
* Owner
|
|
1156
|
+
* Owner Signatures (v0.7) — the `HAP-mandate-projection` object, the
|
|
1297
1157
|
* `HAP-approval` object, and their verification.
|
|
1298
1158
|
*
|
|
1159
|
+
* Renamed from `mandate.ts` (protocol.md → *Migration from v0.6*: the
|
|
1160
|
+
* owner-signed projection's `typ` moves from `"HAP-mandate"` to
|
|
1161
|
+
* `"HAP-mandate-projection"`, disambiguating it from the AS-signed mandate
|
|
1162
|
+
* itself, which now legitimately owns the name `"HAP-mandate"`).
|
|
1163
|
+
*
|
|
1299
1164
|
* The human signs BEFORE the AS does, so they cannot sign the finished
|
|
1300
|
-
*
|
|
1301
|
-
*
|
|
1302
|
-
*
|
|
1303
|
-
*
|
|
1304
|
-
*
|
|
1305
|
-
*
|
|
1306
|
-
*
|
|
1307
|
-
* Verification here requires NO trust in the AS. What it cannot do is tell
|
|
1308
|
-
* verifier WHOSE key signed — confirming the DID belongs to the
|
|
1309
|
-
* person is the out-of-band step (protocol.md → "
|
|
1310
|
-
*
|
|
1165
|
+
* mandate (`mandate_id`/`issued_at` do not exist yet). They sign a mandate
|
|
1166
|
+
* PROJECTION: a canonical object every field of which is known at approval
|
|
1167
|
+
* time and reconstructible from the finished mandate — so a verifier
|
|
1168
|
+
* rebuilds it from the mandate it already holds and checks the signature
|
|
1169
|
+
* with the key carried in the owner's DID. No side channel, no second
|
|
1170
|
+
* fetch, no key directory. See protocol.md → "Owner Signatures".
|
|
1171
|
+
*
|
|
1172
|
+
* Verification here requires NO trust in the AS. What it cannot do is tell
|
|
1173
|
+
* the verifier WHOSE key signed — confirming the DID belongs to the
|
|
1174
|
+
* expected person is the out-of-band step (protocol.md → "Verification
|
|
1175
|
+
* procedure" step 5), and it is the honest cost of cold verification.
|
|
1311
1176
|
*/
|
|
1312
1177
|
|
|
1313
|
-
/** The object the owner signs — a canonical projection of the mandate.
|
|
1178
|
+
/** The object the owner signs — a canonical projection of the mandate.
|
|
1179
|
+
* Field absence is defined, not incidental (protocol.md → *The signed
|
|
1180
|
+
* object: HAP-mandate-projection*): each conditional field is included
|
|
1181
|
+
* exactly when the mandate carries it and omitted otherwise. */
|
|
1314
1182
|
interface MandateProjection {
|
|
1315
|
-
typ: 'HAP-mandate';
|
|
1183
|
+
typ: 'HAP-mandate-projection';
|
|
1316
1184
|
/** Projection/canonicalization version. A verifier MUST pin it. */
|
|
1317
|
-
version: '0.
|
|
1185
|
+
version: '0.7';
|
|
1318
1186
|
profile_id: string;
|
|
1319
|
-
/**
|
|
1187
|
+
/** Covered deliberately: the owner signs the profile's BYTES, not only its id. */
|
|
1188
|
+
profile_hash: string;
|
|
1189
|
+
/** The signing owner's own DID. MUST equal the carrying mandate's `mandate_owners[0].did`. */
|
|
1320
1190
|
owner_did: string;
|
|
1321
1191
|
bounds_hash: string;
|
|
1322
|
-
|
|
1192
|
+
scope_hash: string;
|
|
1323
1193
|
execution_context_hash: string;
|
|
1324
1194
|
gate_content_hashes: Record<string, string>;
|
|
1325
|
-
/** Included iff the
|
|
1195
|
+
/** Included iff the mandate carries one — binds ciphertext AND the
|
|
1326
1196
|
* frozen approver set, the swap this mechanism exists to stop. */
|
|
1327
1197
|
intent_disclosure_hash?: string;
|
|
1328
1198
|
commitment_mode: string;
|
|
1199
|
+
/** Included together, iff `commitment_mode === "review_above_cap"`. */
|
|
1200
|
+
above_cap_caps?: Record<string, number>;
|
|
1201
|
+
above_cap_approvers?: string[];
|
|
1202
|
+
/** Included iff the mandate narrows the profile's disclose_fields. */
|
|
1203
|
+
disclose_fields?: string[];
|
|
1329
1204
|
/** The replay defence: the human signs how long the authority lives. */
|
|
1330
1205
|
expires_at: number;
|
|
1331
1206
|
nonce: string;
|
|
1332
1207
|
}
|
|
1333
1208
|
/** Per-action approval, signed by the owner in `review` mode. Signing a
|
|
1334
|
-
* `reject` matters as much as a `commit`: a rejection the AS can discard is
|
|
1335
|
-
* rejection that never happened. */
|
|
1209
|
+
* `reject` matters as much as a `commit`: a rejection the AS can discard is
|
|
1210
|
+
* a rejection that never happened. */
|
|
1336
1211
|
interface ApprovalObject {
|
|
1337
1212
|
typ: 'HAP-approval';
|
|
1338
|
-
version: '0.
|
|
1213
|
+
version: '0.7';
|
|
1339
1214
|
proposal_id: string;
|
|
1340
|
-
attestation_id
|
|
1215
|
+
/** Renamed from `attestation_id` in v0.7. */
|
|
1216
|
+
mandate_id: string;
|
|
1341
1217
|
decision: 'commit' | 'reject';
|
|
1342
|
-
/** What was approved: the
|
|
1218
|
+
/** What was approved: the ticket's contentHash where the profile binds
|
|
1343
1219
|
* content; otherwise sha256 over the JCS of the proposal's argument set. */
|
|
1344
1220
|
content_hash: string;
|
|
1345
1221
|
decided_at: number;
|
|
1346
1222
|
nonce: string;
|
|
1347
1223
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1224
|
+
/** Renamed from `MandateError` in v0.7 — carries a canonical {@link HapErrorCode}. */
|
|
1225
|
+
declare class OwnerSignatureError extends Error {
|
|
1226
|
+
code: HapErrorCode;
|
|
1227
|
+
constructor(code: HapErrorCode, message: string);
|
|
1351
1228
|
}
|
|
1352
1229
|
/**
|
|
1353
|
-
* Rebuild the projection a given `
|
|
1354
|
-
*
|
|
1355
|
-
* `intent_disclosure_hash`
|
|
1230
|
+
* Rebuild the projection a given `mandate_owners` entry signed, from the
|
|
1231
|
+
* mandate's own signed fields. Field absence is defined, not incidental:
|
|
1232
|
+
* `intent_disclosure_hash`, `above_cap_caps`/`above_cap_approvers`, and
|
|
1233
|
+
* `disclose_fields` are included iff the mandate carries them.
|
|
1356
1234
|
*/
|
|
1357
|
-
declare function buildMandateProjection(payload:
|
|
1358
|
-
/** The exact bytes an owner signs: RFC 8785 (JCS) canonical UTF-8.
|
|
1359
|
-
|
|
1235
|
+
declare function buildMandateProjection(payload: MandatePayload, entry: Pick<MandateOwnerEntry, 'did' | 'nonce'>): MandateProjection;
|
|
1236
|
+
/** The exact bytes an owner signs: RFC 8785 (JCS) canonical UTF-8. Renamed
|
|
1237
|
+
* from `mandateSigningBytes` in v0.7. */
|
|
1238
|
+
declare function projectionSigningBytes(projection: MandateProjection): Uint8Array;
|
|
1360
1239
|
/** The exact bytes an owner signs for a per-action approval. */
|
|
1361
1240
|
declare function approvalSigningBytes(approval: ApprovalObject): Uint8Array;
|
|
1362
1241
|
/**
|
|
@@ -1369,40 +1248,61 @@ declare function signMandateProjection(projection: MandateProjection, privateKey
|
|
|
1369
1248
|
/** Sign an approval object with a raw Ed25519 private key (`raw` binding). */
|
|
1370
1249
|
declare function signApproval(approval: ApprovalObject, privateKey: Uint8Array): Promise<string>;
|
|
1371
1250
|
/**
|
|
1372
|
-
* Verify ONE `
|
|
1251
|
+
* Verify ONE `mandate_owners` entry against the mandate that carries it.
|
|
1373
1252
|
*
|
|
1374
|
-
* Steps (protocol.md → "Verification procedure",
|
|
1375
|
-
*
|
|
1376
|
-
* JCS bytes. The DID is
|
|
1377
|
-
*
|
|
1253
|
+
* Steps (protocol.md → "Verification procedure", step 4 of 6): the entry
|
|
1254
|
+
* names this mandate's sole owner, its signing DID is key-bearing,
|
|
1255
|
+
* projection reconstruction, Ed25519 over JCS bytes. The DID is
|
|
1256
|
+
* authoritative over `alg` — a disagreement is `OWNER_SIGNATURE_INVALID`,
|
|
1257
|
+
* never a fallback to the claimed algorithm.
|
|
1378
1258
|
*
|
|
1379
|
-
* What this deliberately does NOT verify: that the DID belongs to the
|
|
1380
|
-
* the verifier expects (out-of-band, step 5) and the AS's own
|
|
1381
|
-
* the
|
|
1259
|
+
* What this deliberately does NOT verify: that the DID belongs to the
|
|
1260
|
+
* person the verifier expects (out-of-band, step 5) and the AS's own
|
|
1261
|
+
* signature over the mandate (`verifyMandateSignature`, step 1).
|
|
1382
1262
|
*/
|
|
1383
|
-
declare function
|
|
1384
|
-
/** Verify every `
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1387
|
-
declare function
|
|
1263
|
+
declare function verifyOwnerSignature(mandate: Mandate, entry: MandateOwnerEntry): Promise<void>;
|
|
1264
|
+
/** Verify every co-signing `mandate_owners` entry a mandate carries (i.e.
|
|
1265
|
+
* every entry that carries a signature). Resolves to the verified entries;
|
|
1266
|
+
* a mandate with none resolves to `[]` — nothing to check, nothing claimed. */
|
|
1267
|
+
declare function verifyOwnerSignatures(mandate: Mandate): Promise<MandateOwnerEntry[]>;
|
|
1388
1268
|
/** Verify an approval signature against a signer's key-bearing DID. */
|
|
1389
1269
|
declare function verifyApproval(approval: ApprovalObject, signature: string, signerDid: string): Promise<void>;
|
|
1270
|
+
/**
|
|
1271
|
+
* Enforce a profile's `ownerSignature` floor (protocol.md → *Owner
|
|
1272
|
+
* Signatures* → "Where the requirement lives" — the profile-floor tier,
|
|
1273
|
+
* checkable without trusting the AS: `profile_id`/`profile_hash` are in the
|
|
1274
|
+
* signed payload AND in the owner-signed projection).
|
|
1275
|
+
*
|
|
1276
|
+
* An unknown `binding` value is treated as rank 0 (weakest) rather than
|
|
1277
|
+
* rejected outright — "an AS MUST NOT reject a mandate_owners entry solely
|
|
1278
|
+
* because it does not recognize its binding value" extends naturally to a
|
|
1279
|
+
* verifier ranking it, since crediting an unrecognized binding with
|
|
1280
|
+
* unearned strength would be the more dangerous failure mode.
|
|
1281
|
+
*
|
|
1282
|
+
* @throws OwnerSignatureError `OWNER_SIGNATURE_REQUIRED` when the floor is
|
|
1283
|
+
* unmet — "MUST fail, not warn" (protocol.md, same section).
|
|
1284
|
+
*/
|
|
1285
|
+
declare function checkOwnerSignatureRequirement(payload: MandatePayload, floor: ProfileOwnerSignatureFloor | undefined): void;
|
|
1390
1286
|
|
|
1391
1287
|
/**
|
|
1392
|
-
*
|
|
1393
|
-
* verification.
|
|
1394
|
-
*
|
|
1395
|
-
*
|
|
1288
|
+
* Mandate tickets (v0.7) — the wire type, signing, and holder-side
|
|
1289
|
+
* verification. Renamed from `receipt.ts` (protocol.md → *Mandate Tickets*:
|
|
1290
|
+
* "v0.4 introduced the object under the name execution receipt; v0.7 renames
|
|
1291
|
+
* it because the tense was wrong — a receipt follows payment, and the ticket
|
|
1292
|
+
* must come first").
|
|
1396
1293
|
*
|
|
1397
|
-
*
|
|
1398
|
-
*
|
|
1399
|
-
*
|
|
1294
|
+
* Ticket payloads are camelCase on the wire (unlike the snake_case mandate
|
|
1295
|
+
* payload) — a shipped inconsistency the spec documents rather than breaks
|
|
1296
|
+
* (protocol.md → *Migration from v0.6*, "Unchanged on purpose").
|
|
1400
1297
|
*/
|
|
1401
1298
|
|
|
1402
|
-
/** Signed
|
|
1403
|
-
interface
|
|
1299
|
+
/** Signed mandate ticket — protocol.md → *Ticket Payload Schema*. */
|
|
1300
|
+
interface TicketPayload {
|
|
1404
1301
|
id: string;
|
|
1405
|
-
|
|
1302
|
+
/** The mandate this ticket was issued under (new in v0.7). Audit and
|
|
1303
|
+
* chain-of-custody only — `boundsHash` remains the AS's lookup key. */
|
|
1304
|
+
mandateId: string;
|
|
1305
|
+
groupId: string | null;
|
|
1406
1306
|
userId?: string;
|
|
1407
1307
|
boundsHash: string;
|
|
1408
1308
|
profileId: string;
|
|
@@ -1412,35 +1312,169 @@ interface ReceiptPayload {
|
|
|
1412
1312
|
actionType: string;
|
|
1413
1313
|
executionContext: Record<string, unknown>;
|
|
1414
1314
|
cumulativeState?: Record<string, unknown>;
|
|
1415
|
-
|
|
1315
|
+
/** The mandate's plaintext bounds object, exactly as the AS enforced them
|
|
1316
|
+
* for this ticket (new in v0.7 — required). */
|
|
1317
|
+
limits: Record<string, unknown>;
|
|
1318
|
+
/** Protocol version of the ticket — the version of the mandate it was
|
|
1319
|
+
* issued under (new in v0.7). */
|
|
1320
|
+
version: string;
|
|
1321
|
+
/** The AS DID, as in the mandate (new in v0.7). */
|
|
1322
|
+
issuer: string;
|
|
1416
1323
|
timestamp: number;
|
|
1417
|
-
/**
|
|
1324
|
+
/** Hash of the bound content, computed by the Gatekeeper, copied
|
|
1418
1325
|
* verbatim by the AS. The AS never sees the content. */
|
|
1419
1326
|
contentHash?: string;
|
|
1420
|
-
/**
|
|
1327
|
+
/** The binding declaration echoed into the signed ticket, so a
|
|
1421
1328
|
* verifier knows exactly what `contentHash` covers. */
|
|
1422
1329
|
contentBinding?: Pick<ContentBinding, 'version' | 'kind' | 'fields' | 'required_fields' | 'appliesTo'>;
|
|
1423
|
-
/**
|
|
1330
|
+
/** Disclosed subset of the mandate's identity overlay. */
|
|
1424
1331
|
subjects?: Subject[];
|
|
1425
|
-
/**
|
|
1332
|
+
/** Review path: the proposal this ticket executed. */
|
|
1426
1333
|
proposalId?: string;
|
|
1427
|
-
/**
|
|
1428
|
-
*
|
|
1429
|
-
|
|
1334
|
+
/** Review path: the owner's HAP-approval signature for the executed
|
|
1335
|
+
* proposal, as the object itself — never a hash of it, which a holder
|
|
1336
|
+
* could check only by asking the AS. */
|
|
1337
|
+
approvalSignature?: {
|
|
1338
|
+
alg: 'EdDSA' | 'ES256';
|
|
1339
|
+
signature: string;
|
|
1340
|
+
nonce: string;
|
|
1341
|
+
decided_at: number;
|
|
1342
|
+
};
|
|
1430
1343
|
/** Ed25519 over the JCS-canonical payload (this field excluded), base64url. */
|
|
1431
1344
|
signature: string;
|
|
1432
1345
|
}
|
|
1433
1346
|
/**
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
1436
|
-
|
|
1437
|
-
|
|
1347
|
+
* Sign a ticket payload with the Authority Server's Ed25519 private key —
|
|
1348
|
+
* the same key used for mandates.
|
|
1349
|
+
*/
|
|
1350
|
+
declare function signTicket(payload: Omit<TicketPayload, 'signature'>, privateKey: Uint8Array): Promise<TicketPayload>;
|
|
1351
|
+
/**
|
|
1352
|
+
* Verify a ticket's AS signature: strip `signature`, JCS-canonicalize the
|
|
1353
|
+
* rest, Ed25519-verify against the key resolved from `issuer` — never
|
|
1354
|
+
* against a key offered alongside the ticket (protocol.md → *Ticket
|
|
1355
|
+
* Verification* step 1). This is holder-side verification — it needs the
|
|
1356
|
+
* COMPLETE ticket; a redacted public view cannot be re-verified this way
|
|
1357
|
+
* (see {@link publicTicketView}).
|
|
1438
1358
|
*
|
|
1439
|
-
* Content is checked separately: where `contentHash` is present, recompute
|
|
1440
|
-
* from the held artifact via the content-binding module and compare.
|
|
1359
|
+
* Content is checked separately: where `contentHash` is present, recompute
|
|
1360
|
+
* it from the held artifact via the content-binding module and compare.
|
|
1441
1361
|
*
|
|
1362
|
+
* @param opts.trustedIssuers When supplied, an `issuer` not in this list is
|
|
1363
|
+
* rejected before any cryptography runs.
|
|
1442
1364
|
* @throws Error prefixed `INVALID_SIGNATURE:` when verification fails.
|
|
1443
1365
|
*/
|
|
1444
|
-
declare function
|
|
1366
|
+
declare function verifyTicketSignature(ticket: TicketPayload, opts?: {
|
|
1367
|
+
trustedIssuers?: readonly string[];
|
|
1368
|
+
}): Promise<void>;
|
|
1369
|
+
/**
|
|
1370
|
+
* A redacted public view of a ticket (new in v0.7 — protocol.md → *Ticket
|
|
1371
|
+
* Disclosure Is Declared*, *Ticket Verification*). Hides `userId`,
|
|
1372
|
+
* `cumulativeState`, and `limits` unconditionally, and narrows
|
|
1373
|
+
* `executionContext` to exactly the keys named by `governingDiscloseFields`
|
|
1374
|
+
* (the mandate's `disclose_fields` where present, otherwise the profile's).
|
|
1375
|
+
*
|
|
1376
|
+
* Disclose NOTHING by default: an empty or absent `governingDiscloseFields`
|
|
1377
|
+
* strips `executionContext` entirely. "The holder is the verifier" — this
|
|
1378
|
+
* view is NOT independently re-verifiable against `signature` (it is missing
|
|
1379
|
+
* signed fields); a "signature valid" indicator on a page showing only this
|
|
1380
|
+
* view is the AS re-verifying its own signature, and implementations MUST
|
|
1381
|
+
* NOT present it as independently verified.
|
|
1382
|
+
*/
|
|
1383
|
+
declare function publicTicketView(ticket: TicketPayload, governingDiscloseFields?: readonly string[]): Omit<TicketPayload, 'userId' | 'cumulativeState' | 'limits'>;
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* Gatekeeper — local (Phase 1) verification for bounded execution (v0.7).
|
|
1387
|
+
*
|
|
1388
|
+
* protocol.md → *Validation Steps* (Phase 1, never reaches the wire):
|
|
1389
|
+
* 1. Resolve profile from bounds.profile
|
|
1390
|
+
* 2. Recompute bounds_hash and scope_hash
|
|
1391
|
+
* 3. For each mandate: decode, verify AS signature, verify version,
|
|
1392
|
+
* verify bounds_hash / scope_hash / profile_hash, verify expiry
|
|
1393
|
+
* 4. Check actionType registry membership
|
|
1394
|
+
* 5. Check per_transaction bounds and enum capability flags locally
|
|
1395
|
+
* (Enforcement Authority: this is what makes per_transaction
|
|
1396
|
+
* compromise-resistant — *Enforcement classes*)
|
|
1397
|
+
* 6. Check scope constraints (enum, subset, requiredFor) locally — the AS
|
|
1398
|
+
* never sees scope plaintext, so the Gatekeeper is the sole enforcer
|
|
1399
|
+
* 7. Return { approved } or { approved: false, errors: [...] }
|
|
1400
|
+
*
|
|
1401
|
+
* What this module deliberately does NOT do (protocol.md → *Enforcement
|
|
1402
|
+
* Authority*, *Migration from v0.6* semantic change 9): enforce
|
|
1403
|
+
* `cumulative_sum` / `cumulative_count` bounds. "Local cumulative
|
|
1404
|
+
* enforcement is removed, not merely deprecated: a Gatekeeper MUST NOT
|
|
1405
|
+
* refuse an execution on a cumulative bound from its own records" — only
|
|
1406
|
+
* the Authority Server's ticket history is authoritative for a running
|
|
1407
|
+
* total across every Gatekeeper exercising the bucket. v0.3/v0.4's
|
|
1408
|
+
* frame-based path is retired entirely (CLAUDE.md "Decided by the owner").
|
|
1409
|
+
*/
|
|
1410
|
+
|
|
1411
|
+
/** Options for {@link verify}. */
|
|
1412
|
+
interface VerifyOptions {
|
|
1413
|
+
/** Restricts mandate-signature verification to these Authority Server
|
|
1414
|
+
* DIDs — protocol.md → *Ticket Verification* step 1 applies identically
|
|
1415
|
+
* to mandates: "a verifier MUST reject a [...] whose issuer it does not
|
|
1416
|
+
* trust." Omitted → any issuer that resolves a valid did:key is accepted
|
|
1417
|
+
* (appropriate for a Gatekeeper that has not yet pinned an AS). */
|
|
1418
|
+
trustedIssuers?: readonly string[];
|
|
1419
|
+
now?: number;
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Verify an execution request against the mandates supplied.
|
|
1423
|
+
*
|
|
1424
|
+
* @param request bounds, mandate blobs, execution values, and optional scope
|
|
1425
|
+
*/
|
|
1426
|
+
declare function verify(request: GatekeeperRequest, opts?: VerifyOptions): Promise<GatekeeperResult>;
|
|
1427
|
+
/**
|
|
1428
|
+
* Which action types' executions count toward this cumulative bound's
|
|
1429
|
+
* running total — `undefined` means all of them. Display-only now that
|
|
1430
|
+
* local cumulative enforcement is removed (protocol.md → *Enforcement
|
|
1431
|
+
* Authority*: cumulative bounds are AS-only). Exported so a gateway's own
|
|
1432
|
+
* usage display partitions totals the same way the Authority Server does.
|
|
1433
|
+
*/
|
|
1434
|
+
declare function boundActionTypes(fieldName: string, fieldDef: ProfileBoundsField): readonly string[] | undefined;
|
|
1435
|
+
|
|
1436
|
+
/**
|
|
1437
|
+
* Profile Registry — dynamically populated from git-hosted profile sources.
|
|
1438
|
+
*/
|
|
1439
|
+
|
|
1440
|
+
declare function registerProfile(profileId: string, profile: AgentProfile): void;
|
|
1441
|
+
declare function getProfile(profileId: string): AgentProfile | undefined;
|
|
1442
|
+
declare function listProfiles(): string[];
|
|
1443
|
+
declare function getAllProfiles(): AgentProfile[];
|
|
1444
|
+
declare function clearProfiles(): void;
|
|
1445
|
+
|
|
1446
|
+
/**
|
|
1447
|
+
* did:key — encode/decode for KEY-BEARING DIDs (HAP v0.6).
|
|
1448
|
+
*
|
|
1449
|
+
* A real `did:key` is self-certifying: the public key IS the identifier, so
|
|
1450
|
+
* substituting the key produces a DIFFERENT DID and no key directory is ever
|
|
1451
|
+
* consulted. That property is what the Owner Mandate design stands on
|
|
1452
|
+
* (protocol.md → "Identity DIDs vs signing DIDs"): a signing DID MUST be
|
|
1453
|
+
* key-bearing, and verification uses the key carried in the identifier —
|
|
1454
|
+
* deliberately, there is no `public_key` field anywhere on the wire.
|
|
1455
|
+
*
|
|
1456
|
+
* Format (W3C did:key method, Ed25519):
|
|
1457
|
+
* did:key:z<base58btc( 0xed 0x01 ‖ 32-byte-public-key )>
|
|
1458
|
+
* `z` is the multibase prefix for base58btc; `0xed 0x01` is the multicodec
|
|
1459
|
+
* varint for ed25519-pub. The result always starts `did:key:z6Mk`.
|
|
1460
|
+
*
|
|
1461
|
+
* Scope: Ed25519 only — the one signing curve this protocol version uses.
|
|
1462
|
+
* Anything else (including the reference implementation's legacy decorative
|
|
1463
|
+
* `did:key:<uuid-fragment>` identifiers) decodes as NOT key-bearing.
|
|
1464
|
+
*/
|
|
1465
|
+
/** Encode a 32-byte Ed25519 public key as a key-bearing `did:key:z6Mk…`. */
|
|
1466
|
+
declare function encodeDidKey(publicKey: Uint8Array): string;
|
|
1467
|
+
/**
|
|
1468
|
+
* Decode a key-bearing `did:key` to its Ed25519 public key bytes.
|
|
1469
|
+
*
|
|
1470
|
+
* @throws Error prefixed `NOT_KEY_BEARING:` for anything that is not a valid
|
|
1471
|
+
* base58btc-multibase Ed25519 did:key — including the legacy decorative
|
|
1472
|
+
* `did:key:<uuid-fragment>` identifiers, other multibase prefixes, and other
|
|
1473
|
+
* key types. The error name is the point: under the mandate design, a
|
|
1474
|
+
* non-key-bearing signing DID must fail STRUCTURALLY, never fall back.
|
|
1475
|
+
*/
|
|
1476
|
+
declare function decodeDidKey(did: string): Uint8Array;
|
|
1477
|
+
/** True iff the DID decodes as a key-bearing Ed25519 did:key. Never throws. */
|
|
1478
|
+
declare function isKeyBearingDid(did: string): boolean;
|
|
1445
1479
|
|
|
1446
|
-
export { type AgentBoundsParams, type
|
|
1480
|
+
export { type AgentBoundsParams, type AgentProfile, type AgentScopeParams, type ApprovalObject, type BoundType, CanonicalValueError, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, 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, fromBase64Url, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, 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 };
|