@suveren/gateway 0.19.5 → 0.20.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/content/integrations/crm.json +1 -1
- package/content/integrations/deploy-github.json +1 -1
- package/content/integrations/erp.json +1 -1
- package/content/integrations/mail.json +1 -1
- package/content/integrations/records.json +1 -1
- package/dist/control-plane/index.mjs +21 -13
- package/dist/mcp-server/{chunk-ZRRZFGRL.mjs → chunk-APIQIEUA.mjs} +39 -27
- package/dist/mcp-server/http.mjs +308 -97
- package/dist/mcp-server/report-verify-cli.mjs +1 -1
- package/dist/ui/assets/index-1gCMlbLl.js +116 -0
- package/dist/ui/index.html +1 -1
- package/node_modules/@hap/core/README.md +92 -29
- package/node_modules/@hap/core/dist/index.d.mts +614 -593
- package/node_modules/@hap/core/dist/index.d.ts +614 -593
- package/node_modules/@hap/core/dist/index.js +774 -749
- package/node_modules/@hap/core/dist/index.mjs +744 -724
- package/node_modules/@hap/core/package.json +1 -1
- package/node_modules/@hap/core/src/base64url.ts +39 -0
- package/node_modules/@hap/core/src/commitment-modes.ts +0 -21
- package/node_modules/@hap/core/src/errors.ts +76 -0
- package/node_modules/@hap/core/src/frame.ts +47 -178
- package/node_modules/@hap/core/src/gatekeeper.ts +219 -600
- package/node_modules/@hap/core/src/identity.ts +9 -12
- package/node_modules/@hap/core/src/index.ts +8 -4
- package/node_modules/@hap/core/src/mandate.ts +177 -166
- package/node_modules/@hap/core/src/owner-signature.ts +282 -0
- package/node_modules/@hap/core/src/profile.ts +234 -0
- package/node_modules/@hap/core/src/ticket.ts +148 -0
- package/node_modules/@hap/core/src/types.ts +191 -344
- package/node_modules/@hap/core/src/versions.ts +47 -0
- package/package.json +2 -2
- package/profiles/calendar/0.6.profile.json +191 -0
- package/profiles/charge/0.6.profile.json +211 -0
- package/profiles/customers/0.9.profile.json +182 -0
- package/profiles/delegation/0.3.profile.json +118 -0
- package/profiles/deploy/0.11.profile.json +177 -0
- package/profiles/email/0.8.profile.json +226 -0
- package/profiles/index.json +12 -1
- package/profiles/publish/0.6.profile.json +187 -0
- package/profiles/purchase/0.7.profile.json +235 -0
- package/profiles/records/0.6.profile.json +118 -0
- package/profiles/reporting/0.3.profile.json +98 -0
- package/profiles/sales/0.4.profile.json +291 -0
- package/dist/ui/assets/index-Du68zJhH.js +0 -116
- package/node_modules/@hap/core/src/attestation.ts +0 -173
- package/node_modules/@hap/core/src/receipt.ts +0 -74
|
@@ -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
|
/**
|
|
@@ -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
|
|
@@ -401,16 +386,17 @@ interface ContentBinding {
|
|
|
401
386
|
appliesTo?: string[];
|
|
402
387
|
}
|
|
403
388
|
/**
|
|
404
|
-
*
|
|
389
|
+
* Scope field definition within a profile (renamed from `ProfileContextField`
|
|
390
|
+
* in v0.7 — protocol.md → *Migration from v0.6*, `contextSchema` → `scopeSchema`).
|
|
405
391
|
*/
|
|
406
|
-
interface
|
|
392
|
+
interface ProfileScopeField {
|
|
407
393
|
type: 'string' | 'number';
|
|
408
394
|
required: boolean;
|
|
409
395
|
description?: string;
|
|
410
396
|
displayName?: string;
|
|
411
397
|
format?: 'email' | 'domain' | 'url' | 'currency';
|
|
412
398
|
/**
|
|
413
|
-
*
|
|
399
|
+
* What this field NAMES on the read path. `counterparty` = the other
|
|
414
400
|
* party to a communication (matched against an item's participants);
|
|
415
401
|
* `resource` = the container an item belongs to (a direct attribute match —
|
|
416
402
|
* and a resource scope enforced on writes MUST also bind reads). Absent →
|
|
@@ -419,7 +405,6 @@ interface ProfileContextField {
|
|
|
419
405
|
*/
|
|
420
406
|
scopeKind?: 'counterparty' | 'resource';
|
|
421
407
|
constraint?: FieldConstraint;
|
|
422
|
-
enum?: string[];
|
|
423
408
|
}
|
|
424
409
|
/**
|
|
425
410
|
* Execution context field definition — declared source (value comes from the agent's tool call).
|
|
@@ -435,13 +420,11 @@ interface DeclaredFieldDef {
|
|
|
435
420
|
*/
|
|
436
421
|
type CumulativeWindow = 'daily' | 'weekly' | 'monthly';
|
|
437
422
|
/**
|
|
438
|
-
* 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*).
|
|
439
425
|
*
|
|
440
|
-
* The gatekeeper resolves these by querying the execution log:
|
|
441
426
|
* - `cumulativeField`: which declared field to sum (use "_count" for plain counting)
|
|
442
427
|
* - `window`: time window for aggregation (daily, weekly, monthly)
|
|
443
|
-
*
|
|
444
|
-
* The resolved value = running total within window + current call value.
|
|
445
428
|
*/
|
|
446
429
|
interface CumulativeFieldDef {
|
|
447
430
|
source: 'cumulative';
|
|
@@ -456,35 +439,21 @@ interface CumulativeFieldDef {
|
|
|
456
439
|
*/
|
|
457
440
|
type ExecutionContextFieldDef = DeclaredFieldDef | CumulativeFieldDef;
|
|
458
441
|
/**
|
|
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).
|
|
442
|
+
* Agent Profile — defines the bounds/scope/execution-context schemas, gates,
|
|
443
|
+
* TTL policy, and retention for a bounded authority (protocol.md → *Profiles*).
|
|
482
444
|
*/
|
|
483
445
|
/**
|
|
484
446
|
* A commitment mode a person can choose when signing a mandate. (`review_above_cap`
|
|
485
447
|
* is not chosen by the signer — it follows from team caps.)
|
|
486
448
|
*/
|
|
487
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
|
+
}
|
|
488
457
|
interface AgentProfile {
|
|
489
458
|
id: string;
|
|
490
459
|
name?: string;
|
|
@@ -505,9 +474,10 @@ interface AgentProfile {
|
|
|
505
474
|
*/
|
|
506
475
|
whatsNew?: string;
|
|
507
476
|
/**
|
|
508
|
-
* Whether
|
|
509
|
-
* verifier holding the content supplies its hash and learns which
|
|
510
|
-
* 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.)
|
|
511
481
|
*
|
|
512
482
|
* OFF unless declared, and that default is the point. The lookup is a
|
|
513
483
|
* confirmation oracle: given a guess at the content it says whether that
|
|
@@ -519,13 +489,8 @@ interface AgentProfile {
|
|
|
519
489
|
* Enable only when the bound content is unguessable enough that producing it
|
|
520
490
|
* is equivalent to already having it: prose, an artifact URL, a whole record
|
|
521
491
|
* 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
492
|
*/
|
|
528
|
-
|
|
493
|
+
ticket_lookup?: boolean;
|
|
529
494
|
/**
|
|
530
495
|
* The commitment modes a mandate under this profile may be signed with.
|
|
531
496
|
* Absent → every mode (`automatic`, `review`), as before this field existed.
|
|
@@ -539,21 +504,14 @@ interface AgentProfile {
|
|
|
539
504
|
*/
|
|
540
505
|
commitment_modes?: readonly SignableCommitmentMode[];
|
|
541
506
|
/**
|
|
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.
|
|
507
|
+
* The bounds schema — the enforceable parameters (protocol.md → *Bounds
|
|
508
|
+
* Schema*).
|
|
551
509
|
*/
|
|
552
510
|
boundsSchema?: {
|
|
553
511
|
keyOrder: string[];
|
|
554
512
|
fields: Record<string, ProfileBoundsField>;
|
|
555
513
|
/**
|
|
556
|
-
*
|
|
514
|
+
* Registry of the action types this profile recognizes (e.g.
|
|
557
515
|
* `["send", "delete", "setup"]`). Every `execution.action_type` the
|
|
558
516
|
* Authority Server and gateway accept for this profile MUST be a member
|
|
559
517
|
* when the registry is declared (protocol.md → Bounds Schema, rule 2);
|
|
@@ -564,92 +522,45 @@ interface AgentProfile {
|
|
|
564
522
|
actionTypes?: readonly string[];
|
|
565
523
|
};
|
|
566
524
|
/**
|
|
567
|
-
*
|
|
568
|
-
* 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.
|
|
569
528
|
*/
|
|
570
|
-
|
|
529
|
+
scopeSchema?: {
|
|
571
530
|
keyOrder: string[];
|
|
572
|
-
fields: Record<string,
|
|
531
|
+
fields: Record<string, ProfileScopeField>;
|
|
573
532
|
};
|
|
574
533
|
executionContextSchema: {
|
|
575
534
|
fields: Record<string, ExecutionContextFieldDef>;
|
|
576
535
|
};
|
|
577
|
-
/** @deprecated Execution paths removed in v0.4. Kept for backward compatibility. */
|
|
578
|
-
executionPaths?: Record<string, ExecutionPath>;
|
|
579
536
|
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
537
|
ttl: {
|
|
590
538
|
default: number;
|
|
591
539
|
max: number;
|
|
592
540
|
};
|
|
593
541
|
retention_minimum: number;
|
|
594
542
|
/**
|
|
595
|
-
*
|
|
543
|
+
* Content Provenance (OPTIONAL, profile-bound). When present, the
|
|
596
544
|
* gateway computes a `contentHash` for gated writes under this profile and
|
|
597
|
-
* passes it (hash only) to the
|
|
545
|
+
* passes it (hash only) to the AS, which signs it into the ticket. Absent
|
|
598
546
|
* → no content hash. See {@link ContentBinding}.
|
|
599
547
|
*/
|
|
600
548
|
content_binding?: ContentBinding;
|
|
601
549
|
/**
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
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.
|
|
605
555
|
*/
|
|
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>;
|
|
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;
|
|
653
564
|
}
|
|
654
565
|
/**
|
|
655
566
|
* A recorded execution — stored after gatekeeper approval for cumulative tracking.
|
|
@@ -685,51 +596,47 @@ interface ExecutionLogQuery {
|
|
|
685
596
|
*/
|
|
686
597
|
actionTypes?: readonly string[]): number;
|
|
687
598
|
}
|
|
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
599
|
/**
|
|
694
600
|
* Agent bounds parameters — mixed types (strings and numbers).
|
|
695
|
-
* Keys and values come from the profile's boundsSchema
|
|
601
|
+
* Keys and values come from the profile's boundsSchema.
|
|
696
602
|
*/
|
|
697
603
|
type AgentBoundsParams = Record<string, string | number>;
|
|
698
604
|
/**
|
|
699
|
-
* Agent
|
|
700
|
-
*
|
|
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).
|
|
701
608
|
*/
|
|
702
|
-
type
|
|
609
|
+
type AgentScopeParams = Record<string, string | number>;
|
|
703
610
|
/**
|
|
704
611
|
* Request to the Gatekeeper for bounded execution verification.
|
|
705
612
|
*/
|
|
706
613
|
interface GatekeeperRequest {
|
|
707
|
-
/** The
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
|
|
711
|
-
/** 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. */
|
|
712
619
|
execution: Record<string, string | number>;
|
|
713
|
-
/**
|
|
714
|
-
|
|
620
|
+
/** Scope parameters (currency, action_type, allowed recipients, etc.) —
|
|
621
|
+
* enforced locally only; never sent to the Authority Server. */
|
|
622
|
+
scope?: AgentScopeParams;
|
|
715
623
|
/**
|
|
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.
|
|
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*).
|
|
725
627
|
*/
|
|
726
628
|
path?: string;
|
|
727
629
|
}
|
|
728
630
|
/**
|
|
729
|
-
* 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").
|
|
730
637
|
*/
|
|
731
638
|
interface GatekeeperError {
|
|
732
|
-
code:
|
|
639
|
+
code: HapErrorCode;
|
|
733
640
|
field?: string;
|
|
734
641
|
message: string;
|
|
735
642
|
bound?: string | number;
|
|
@@ -780,6 +687,46 @@ type GatekeeperResult = {
|
|
|
780
687
|
*/
|
|
781
688
|
declare function canonicalize(value: unknown): string;
|
|
782
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
|
+
|
|
783
730
|
/**
|
|
784
731
|
* Content binding — Level 2 content proof (HAP v0.5 Content Provenance).
|
|
785
732
|
*
|
|
@@ -950,20 +897,17 @@ interface SubjectValidation {
|
|
|
950
897
|
* - `self_declared` ⇒ `low` / `self`, no disclosed name.
|
|
951
898
|
* - `as_vouched` ⇒ `high` / `as`, `verifier` required.
|
|
952
899
|
* - `eudi` ⇒ `high` / `external`, `verifier` required — and, when the
|
|
953
|
-
* carrying
|
|
900
|
+
* carrying mandate's `mandate_owners` are supplied, a corresponding
|
|
954
901
|
* entry with `binding:"eudi"` for this subject's DID.
|
|
955
902
|
* - `disclose.name` only at `assurance:"high"`.
|
|
956
903
|
*
|
|
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
|
|
904
|
+
* The signature-bearing object is the mandate's `mandate_owners` entry, which
|
|
960
905
|
* 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).
|
|
906
|
+
* `opts.mandateOwners` to enforce the eudi ⇒ mandate-owner-entry rule, omit it
|
|
907
|
+
* to validate subject shape alone.
|
|
964
908
|
*/
|
|
965
909
|
declare function validateSubject(subject: Subject, opts?: {
|
|
966
|
-
|
|
910
|
+
mandateOwners?: MandateOwnerEntry[];
|
|
967
911
|
}): SubjectValidation;
|
|
968
912
|
/**
|
|
969
913
|
* The footer line for a subject — the ONE place this wording lives.
|
|
@@ -991,76 +935,38 @@ declare function deriveIdentityLine(subject: Subject | undefined | null, { opera
|
|
|
991
935
|
}): string;
|
|
992
936
|
|
|
993
937
|
/**
|
|
994
|
-
*
|
|
938
|
+
* Bounds & Scope Canonicalization (protocol.md → *Bounds & Scope
|
|
939
|
+
* Canonicalization*).
|
|
995
940
|
*
|
|
996
|
-
* Agent profiles support mixed-type fields (strings and numbers).
|
|
997
941
|
* Canonical form: `key=value` records joined with LF, keys in the profile's
|
|
998
942
|
* keyOrder. Values are stringified with String(value) — the shortest
|
|
999
943
|
* round-trippable form for numbers — then percent-encoded per protocol.md
|
|
1000
944
|
* (`=`, `%`, and every byte outside printable ASCII); a value carrying a raw
|
|
1001
945
|
* LF/CR is refused, never normalized. See `canonicalRecords` below.
|
|
1002
946
|
*
|
|
1003
|
-
* v0.3
|
|
1004
|
-
* 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`).
|
|
1005
954
|
*/
|
|
1006
955
|
|
|
1007
956
|
/**
|
|
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).
|
|
957
|
+
* Validates bounds parameters against the profile's boundsSchema.
|
|
1033
958
|
*/
|
|
1034
959
|
declare function validateBoundsParams(params: AgentBoundsParams, profile: AgentProfile): {
|
|
1035
960
|
valid: boolean;
|
|
1036
961
|
errors: string[];
|
|
1037
962
|
};
|
|
1038
963
|
/**
|
|
1039
|
-
* Validates
|
|
964
|
+
* Validates scope parameters against the profile's scopeSchema.
|
|
1040
965
|
*/
|
|
1041
|
-
declare function
|
|
966
|
+
declare function validateScopeParams(params: AgentScopeParams, profile: AgentProfile): {
|
|
1042
967
|
valid: boolean;
|
|
1043
968
|
errors: string[];
|
|
1044
969
|
};
|
|
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
970
|
/**
|
|
1065
971
|
* Thrown when a value cannot be canonicalized at all — currently only for raw
|
|
1066
972
|
* LF/CR inside a value. Carries the protocol error code so callers can map it
|
|
@@ -1073,9 +979,9 @@ declare function validateBoundsRequiredFor(profile: AgentProfile): string[];
|
|
|
1073
979
|
* does not faithfully represent the input."
|
|
1074
980
|
*/
|
|
1075
981
|
declare class CanonicalValueError extends Error {
|
|
1076
|
-
readonly code: 'BOUNDS_INVALID_VALUE' | '
|
|
982
|
+
readonly code: 'BOUNDS_INVALID_VALUE' | 'SCOPE_INVALID_VALUE';
|
|
1077
983
|
readonly field: string;
|
|
1078
|
-
constructor(code: 'BOUNDS_INVALID_VALUE' | '
|
|
984
|
+
constructor(code: 'BOUNDS_INVALID_VALUE' | 'SCOPE_INVALID_VALUE', field: string, message: string);
|
|
1079
985
|
}
|
|
1080
986
|
/**
|
|
1081
987
|
* Builds the canonical bounds string from parameters.
|
|
@@ -1087,29 +993,29 @@ declare class CanonicalValueError extends Error {
|
|
|
1087
993
|
*/
|
|
1088
994
|
declare function canonicalBounds(params: AgentBoundsParams, profile: AgentProfile): string;
|
|
1089
995
|
/**
|
|
1090
|
-
* Builds the canonical
|
|
1091
|
-
* 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
|
|
1092
998
|
* protocol.md → *Value encoding* (see `canonicalRecords`).
|
|
1093
|
-
* For empty
|
|
999
|
+
* For empty scope (no scopeSchema or no fields), returns "".
|
|
1094
1000
|
*
|
|
1095
1001
|
* @throws Error if any field fails validation
|
|
1096
|
-
* @throws CanonicalValueError (code
|
|
1002
|
+
* @throws CanonicalValueError (code SCOPE_INVALID_VALUE) if a value carries a raw LF/CR
|
|
1097
1003
|
*/
|
|
1098
|
-
declare function
|
|
1004
|
+
declare function canonicalScope(params: AgentScopeParams, profile: AgentProfile): string;
|
|
1099
1005
|
/**
|
|
1100
|
-
* Computes the bounds hash from bounds parameters
|
|
1006
|
+
* Computes the bounds hash from bounds parameters.
|
|
1101
1007
|
*
|
|
1102
1008
|
* @returns Hash in format "sha256:<64 hex chars>"
|
|
1103
1009
|
*/
|
|
1104
1010
|
declare function computeBoundsHash(params: AgentBoundsParams, profile: AgentProfile): string;
|
|
1105
1011
|
/**
|
|
1106
|
-
* Computes the
|
|
1107
|
-
* For empty
|
|
1012
|
+
* Computes the scope hash from scope parameters.
|
|
1013
|
+
* For empty scope {}, returns the sha256 of "":
|
|
1108
1014
|
* "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
1109
1015
|
*
|
|
1110
1016
|
* @returns Hash in format "sha256:<64 hex chars>"
|
|
1111
1017
|
*/
|
|
1112
|
-
declare function
|
|
1018
|
+
declare function computeScopeHash(params: AgentScopeParams, profile: AgentProfile): string;
|
|
1113
1019
|
|
|
1114
1020
|
/**
|
|
1115
1021
|
* Which commitment modes a profile allows its mandates to be signed with
|
|
@@ -1128,248 +1034,208 @@ declare const SIGNABLE_COMMITMENT_MODES: readonly SignableCommitmentMode[];
|
|
|
1128
1034
|
declare function allowedCommitmentModes(profile: AgentProfile): readonly SignableCommitmentMode[];
|
|
1129
1035
|
/** Whether a mandate under this profile may be signed with `mode`. */
|
|
1130
1036
|
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
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
|
+
}
|
|
1145
1047
|
/**
|
|
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).
|
|
1155
|
-
*/
|
|
1156
|
-
declare function attestationId(blob: string): string;
|
|
1157
|
-
/**
|
|
1158
|
-
* Verifies an attestation signature using the SP public key.
|
|
1159
|
-
*
|
|
1160
|
-
* @returns true if valid
|
|
1161
|
-
* @throws Error with code prefix if invalid
|
|
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`).
|
|
1162
1052
|
*/
|
|
1163
|
-
declare function
|
|
1053
|
+
declare function computeProfileHash(parsed: unknown): string;
|
|
1164
1054
|
/**
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
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.
|
|
1168
1058
|
*/
|
|
1169
|
-
declare function
|
|
1059
|
+
declare function validateProfile(profile: AgentProfile): ProfileValidationError[];
|
|
1170
1060
|
/**
|
|
1171
|
-
*
|
|
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.
|
|
1172
1065
|
*
|
|
1173
|
-
* @throws Error
|
|
1066
|
+
* @throws Error prefixed `MALFORMED_MANDATE:` when it is not a subset.
|
|
1174
1067
|
*/
|
|
1175
|
-
declare function
|
|
1068
|
+
declare function checkDiscloseSubset(mandateDiscloseFields: readonly string[] | undefined, profileDiscloseFields: readonly string[] | undefined): void;
|
|
1069
|
+
|
|
1176
1070
|
/**
|
|
1177
|
-
*
|
|
1178
|
-
* or v0.3 (has frame_hash).
|
|
1071
|
+
* Mandate encoding, decoding, signing, and verification (v0.7).
|
|
1179
1072
|
*
|
|
1180
|
-
*
|
|
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).
|
|
1181
1079
|
*/
|
|
1182
|
-
|
|
1080
|
+
|
|
1183
1081
|
/**
|
|
1184
|
-
*
|
|
1185
|
-
*
|
|
1186
|
-
*
|
|
1082
|
+
* Decodes a base64url-encoded mandate blob.
|
|
1083
|
+
* @throws HapError `MALFORMED_MANDATE` on anything that is not a strict
|
|
1084
|
+
* base64url-encoded JSON mandate.
|
|
1187
1085
|
*/
|
|
1188
|
-
declare function
|
|
1086
|
+
declare function decodeMandateBlob(blob: string): Mandate;
|
|
1189
1087
|
/**
|
|
1190
|
-
*
|
|
1191
|
-
*
|
|
1192
|
-
* @throws Error if context hash doesn't match
|
|
1088
|
+
* Encodes a mandate as a base64url blob (no padding).
|
|
1193
1089
|
*/
|
|
1194
|
-
declare function
|
|
1090
|
+
declare function encodeMandateBlob(mandate: Mandate): string;
|
|
1195
1091
|
/**
|
|
1196
|
-
*
|
|
1197
|
-
*
|
|
1198
|
-
* @returns The decoded attestation payload
|
|
1199
|
-
* @throws Error on any validation failure
|
|
1092
|
+
* Computes the mandate blob id (hash of the blob) — renamed from
|
|
1093
|
+
* `attestationId`.
|
|
1200
1094
|
*/
|
|
1201
|
-
declare function
|
|
1095
|
+
declare function mandateBlobId(blob: string): string;
|
|
1202
1096
|
/**
|
|
1203
|
-
*
|
|
1097
|
+
* Sign a mandate payload with the Authority Server's Ed25519 private key.
|
|
1204
1098
|
*
|
|
1205
|
-
* @
|
|
1206
|
-
*
|
|
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.
|
|
1207
1103
|
*/
|
|
1208
|
-
declare function
|
|
1209
|
-
|
|
1104
|
+
declare function signMandate(payload: MandatePayload, privateKey: Uint8Array, opts?: {
|
|
1105
|
+
kid?: string;
|
|
1106
|
+
}): Promise<Mandate>;
|
|
1210
1107
|
/**
|
|
1211
|
-
*
|
|
1212
|
-
*
|
|
1213
|
-
*
|
|
1214
|
-
*
|
|
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).
|
|
1215
1112
|
*
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1218
|
-
*
|
|
1219
|
-
*
|
|
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: [...] }
|
|
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.
|
|
1230
1117
|
*/
|
|
1231
|
-
|
|
1118
|
+
declare function verifyMandateSignature(mandate: Mandate, opts?: {
|
|
1119
|
+
trustedIssuers?: readonly string[];
|
|
1120
|
+
}): Promise<void>;
|
|
1232
1121
|
/**
|
|
1233
|
-
*
|
|
1234
|
-
*
|
|
1235
|
-
*
|
|
1236
|
-
* and the optional `context` param is used for the context hash check.
|
|
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`.
|
|
1237
1125
|
*
|
|
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
|
|
1126
|
+
* @throws Error prefixed `TTL_EXPIRED:` when expired.
|
|
1244
1127
|
*/
|
|
1245
|
-
declare function
|
|
1128
|
+
declare function checkMandateExpiry(payload: MandatePayload, now?: number): void;
|
|
1246
1129
|
/**
|
|
1247
|
-
*
|
|
1248
|
-
*
|
|
1249
|
-
*
|
|
1250
|
-
* `boundGovernsAction` decides whether a bound applies to the current call; this
|
|
1251
|
-
* decides what the bound's total is made of. Both are needed: selecting the right
|
|
1252
|
-
* bound and then feeding it the profile's combined total is still wrong — sales'
|
|
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.
|
|
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.
|
|
1256
1133
|
*
|
|
1257
|
-
*
|
|
1258
|
-
* `
|
|
1259
|
-
* that action; else every action type. Exported so display surfaces report the
|
|
1260
|
-
* same totals the gate enforces.
|
|
1261
|
-
*/
|
|
1262
|
-
declare function boundActionTypes(fieldName: string, fieldDef: ProfileBoundsField): readonly string[] | undefined;
|
|
1263
|
-
|
|
1264
|
-
/**
|
|
1265
|
-
* Profile Registry — dynamically populated from git-hosted profile sources.
|
|
1134
|
+
* @throws HapError `MALFORMED_MANDATE` on a cardinality or completeness
|
|
1135
|
+
* violation; `OWNER_SIGNATURE_INVALID` when a co-signing DID is not key-bearing.
|
|
1266
1136
|
*/
|
|
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
|
-
|
|
1137
|
+
declare function validateMandateOwners(payload: MandatePayload): void;
|
|
1274
1138
|
/**
|
|
1275
|
-
*
|
|
1139
|
+
* Full mandate verification: decode, check `version`, verify the AS
|
|
1140
|
+
* signature, check expiry, validate `mandate_owners` shape.
|
|
1276
1141
|
*
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1279
|
-
*
|
|
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.
|
|
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.
|
|
1297
1145
|
*
|
|
1298
|
-
* @throws
|
|
1299
|
-
*
|
|
1300
|
-
* `
|
|
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.
|
|
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.
|
|
1303
1149
|
*/
|
|
1304
|
-
declare function
|
|
1305
|
-
|
|
1306
|
-
|
|
1150
|
+
declare function verifyMandate(blob: string, opts?: {
|
|
1151
|
+
trustedIssuers?: readonly string[];
|
|
1152
|
+
now?: number;
|
|
1153
|
+
}): Promise<MandatePayload>;
|
|
1307
1154
|
|
|
1308
1155
|
/**
|
|
1309
|
-
* Owner
|
|
1156
|
+
* Owner Signatures (v0.7) — the `HAP-mandate-projection` object, the
|
|
1310
1157
|
* `HAP-approval` object, and their verification.
|
|
1311
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
|
+
*
|
|
1312
1164
|
* 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
|
-
*
|
|
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.
|
|
1324
1176
|
*/
|
|
1325
1177
|
|
|
1326
|
-
/** 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. */
|
|
1327
1182
|
interface MandateProjection {
|
|
1328
|
-
typ: 'HAP-mandate';
|
|
1183
|
+
typ: 'HAP-mandate-projection';
|
|
1329
1184
|
/** Projection/canonicalization version. A verifier MUST pin it. */
|
|
1330
|
-
version: '0.
|
|
1185
|
+
version: '0.7';
|
|
1331
1186
|
profile_id: string;
|
|
1332
|
-
/**
|
|
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`. */
|
|
1333
1190
|
owner_did: string;
|
|
1334
1191
|
bounds_hash: string;
|
|
1335
|
-
|
|
1192
|
+
scope_hash: string;
|
|
1336
1193
|
execution_context_hash: string;
|
|
1337
1194
|
gate_content_hashes: Record<string, string>;
|
|
1338
|
-
/** Included iff the
|
|
1195
|
+
/** Included iff the mandate carries one — binds ciphertext AND the
|
|
1339
1196
|
* frozen approver set, the swap this mechanism exists to stop. */
|
|
1340
1197
|
intent_disclosure_hash?: string;
|
|
1341
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[];
|
|
1342
1204
|
/** The replay defence: the human signs how long the authority lives. */
|
|
1343
1205
|
expires_at: number;
|
|
1344
1206
|
nonce: string;
|
|
1345
1207
|
}
|
|
1346
1208
|
/** 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. */
|
|
1209
|
+
* `reject` matters as much as a `commit`: a rejection the AS can discard is
|
|
1210
|
+
* a rejection that never happened. */
|
|
1349
1211
|
interface ApprovalObject {
|
|
1350
1212
|
typ: 'HAP-approval';
|
|
1351
|
-
version: '0.
|
|
1213
|
+
version: '0.7';
|
|
1352
1214
|
proposal_id: string;
|
|
1353
|
-
attestation_id
|
|
1215
|
+
/** Renamed from `attestation_id` in v0.7. */
|
|
1216
|
+
mandate_id: string;
|
|
1354
1217
|
decision: 'commit' | 'reject';
|
|
1355
|
-
/** What was approved: the
|
|
1218
|
+
/** What was approved: the ticket's contentHash where the profile binds
|
|
1356
1219
|
* content; otherwise sha256 over the JCS of the proposal's argument set. */
|
|
1357
1220
|
content_hash: string;
|
|
1358
1221
|
decided_at: number;
|
|
1359
1222
|
nonce: string;
|
|
1360
1223
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
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);
|
|
1364
1228
|
}
|
|
1365
1229
|
/**
|
|
1366
|
-
* Rebuild the projection a given `
|
|
1367
|
-
*
|
|
1368
|
-
* `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.
|
|
1369
1234
|
*/
|
|
1370
|
-
declare function buildMandateProjection(payload:
|
|
1371
|
-
/** The exact bytes an owner signs: RFC 8785 (JCS) canonical UTF-8.
|
|
1372
|
-
|
|
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;
|
|
1373
1239
|
/** The exact bytes an owner signs for a per-action approval. */
|
|
1374
1240
|
declare function approvalSigningBytes(approval: ApprovalObject): Uint8Array;
|
|
1375
1241
|
/**
|
|
@@ -1382,40 +1248,61 @@ declare function signMandateProjection(projection: MandateProjection, privateKey
|
|
|
1382
1248
|
/** Sign an approval object with a raw Ed25519 private key (`raw` binding). */
|
|
1383
1249
|
declare function signApproval(approval: ApprovalObject, privateKey: Uint8Array): Promise<string>;
|
|
1384
1250
|
/**
|
|
1385
|
-
* Verify ONE `
|
|
1251
|
+
* Verify ONE `mandate_owners` entry against the mandate that carries it.
|
|
1386
1252
|
*
|
|
1387
|
-
* Steps (protocol.md → "Verification procedure",
|
|
1388
|
-
*
|
|
1389
|
-
* JCS bytes. The DID is
|
|
1390
|
-
*
|
|
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.
|
|
1391
1258
|
*
|
|
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
|
|
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).
|
|
1395
1262
|
*/
|
|
1396
|
-
declare function
|
|
1397
|
-
/** Verify every `
|
|
1398
|
-
*
|
|
1399
|
-
*
|
|
1400
|
-
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[]>;
|
|
1401
1268
|
/** Verify an approval signature against a signer's key-bearing DID. */
|
|
1402
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;
|
|
1403
1286
|
|
|
1404
1287
|
/**
|
|
1405
|
-
*
|
|
1406
|
-
* verification.
|
|
1407
|
-
*
|
|
1408
|
-
*
|
|
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").
|
|
1409
1293
|
*
|
|
1410
|
-
*
|
|
1411
|
-
*
|
|
1412
|
-
*
|
|
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").
|
|
1413
1297
|
*/
|
|
1414
1298
|
|
|
1415
|
-
/** Signed
|
|
1416
|
-
interface
|
|
1299
|
+
/** Signed mandate ticket — protocol.md → *Ticket Payload Schema*. */
|
|
1300
|
+
interface TicketPayload {
|
|
1417
1301
|
id: string;
|
|
1418
|
-
|
|
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;
|
|
1419
1306
|
userId?: string;
|
|
1420
1307
|
boundsHash: string;
|
|
1421
1308
|
profileId: string;
|
|
@@ -1425,35 +1312,169 @@ interface ReceiptPayload {
|
|
|
1425
1312
|
actionType: string;
|
|
1426
1313
|
executionContext: Record<string, unknown>;
|
|
1427
1314
|
cumulativeState?: Record<string, unknown>;
|
|
1428
|
-
|
|
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;
|
|
1429
1323
|
timestamp: number;
|
|
1430
|
-
/**
|
|
1324
|
+
/** Hash of the bound content, computed by the Gatekeeper, copied
|
|
1431
1325
|
* verbatim by the AS. The AS never sees the content. */
|
|
1432
1326
|
contentHash?: string;
|
|
1433
|
-
/**
|
|
1327
|
+
/** The binding declaration echoed into the signed ticket, so a
|
|
1434
1328
|
* verifier knows exactly what `contentHash` covers. */
|
|
1435
1329
|
contentBinding?: Pick<ContentBinding, 'version' | 'kind' | 'fields' | 'required_fields' | 'appliesTo'>;
|
|
1436
|
-
/**
|
|
1330
|
+
/** Disclosed subset of the mandate's identity overlay. */
|
|
1437
1331
|
subjects?: Subject[];
|
|
1438
|
-
/**
|
|
1332
|
+
/** Review path: the proposal this ticket executed. */
|
|
1439
1333
|
proposalId?: string;
|
|
1440
|
-
/**
|
|
1441
|
-
*
|
|
1442
|
-
|
|
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
|
+
};
|
|
1443
1343
|
/** Ed25519 over the JCS-canonical payload (this field excluded), base64url. */
|
|
1444
1344
|
signature: string;
|
|
1445
1345
|
}
|
|
1446
1346
|
/**
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1449
|
-
|
|
1450
|
-
|
|
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}).
|
|
1451
1358
|
*
|
|
1452
|
-
* Content is checked separately: where `contentHash` is present, recompute
|
|
1453
|
-
* 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.
|
|
1454
1361
|
*
|
|
1362
|
+
* @param opts.trustedIssuers When supplied, an `issuer` not in this list is
|
|
1363
|
+
* rejected before any cryptography runs.
|
|
1455
1364
|
* @throws Error prefixed `INVALID_SIGNATURE:` when verification fails.
|
|
1456
1365
|
*/
|
|
1457
|
-
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;
|
|
1458
1479
|
|
|
1459
|
-
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 };
|