@humanagencyp/hap-core 0.12.1 → 0.13.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/dist/index.d.mts CHANGED
@@ -1,23 +1,22 @@
1
1
  /**
2
- * Canonical v0.7 error codes — protocol.md → *Error Codes*.
2
+ * Canonical v0.8 error codes — protocol.md → *Error Codes*.
3
3
  *
4
4
  * "Error codes are canonical across the protocol. Implementations MUST emit
5
5
  * exactly these codes and MUST NOT alias them under different names." This
6
6
  * module is the single list both the Gatekeeper (local checks) and any AS
7
7
  * client built on this library read from, so the two cannot drift.
8
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").
9
+ * v0.8 adds codes and retires none; every v0.7 code keeps its name.
14
10
  */
15
11
  /** 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"];
12
+ 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", "COMMITMENT_MODE_NOT_ALLOWED", "GROUP_CAP_EXCEEDED", "OWNER_SIGNATURE_REQUIRED", "OWNER_SIGNATURE_INVALID"];
13
+ /** Codes an Authority Server emits refusing a ticket request, plus the
14
+ * Gatekeeper-local and verifier codes of the same table. */
15
+ 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", "APPROVAL_IMPOSSIBLE", "PROPOSAL_CONTENT_MISMATCH", "PROPOSAL_REQUIRED", "PROPOSAL_NOT_FOUND", "PROPOSAL_NOT_APPROVED", "PROPOSAL_EXPIRED", "PROPOSAL_REJECTED", "PROPOSAL_MISMATCH", "PROPOSAL_MANDATE_MISMATCH", "PROPOSAL_ALREADY_EXECUTED", "PROPOSAL_WITHDRAWN", "MALFORMED_TICKET_REQUEST", "CONTENT_BINDING_INCOMPLETE", "IDEMPOTENCY_KEY_REQUIRED", "IDEMPOTENCY_MISMATCH", "APPROVAL_SIGNATURE_REQUIRED", "APPROVAL_SIGNATURE_INVALID", "TICKET_REQUEST_MISMATCH", "POLICY_UNMET"];
16
+ /** Codes an Authority Server emits refusing a proposal submission or a decision (v0.8). */
17
+ declare const PROPOSAL_ERROR_CODES: readonly ["PROPOSAL_NOT_ENCRYPTED", "DECIDER_INVALID", "PROPOSAL_ALREADY_DECIDED", "PROPOSAL_APPROVERS_INVALID"];
19
18
  /** The full canonical surface — every code a conformant implementation may emit. */
20
- type HapErrorCode = (typeof MANDATE_ERROR_CODES)[number] | (typeof TICKET_ERROR_CODES)[number];
19
+ type HapErrorCode = (typeof MANDATE_ERROR_CODES)[number] | (typeof TICKET_ERROR_CODES)[number] | (typeof PROPOSAL_ERROR_CODES)[number];
21
20
  /**
22
21
  * A refusal carrying one of the canonical codes above. Thrown by local
23
22
  * (Gatekeeper-side, no-wire-round-trip) checks in this library; an AS client
@@ -108,10 +107,14 @@ interface Subject {
108
107
  /** When the underlying verification was performed (unix seconds). */
109
108
  verified_at?: number;
110
109
  }
111
- /** protocol.md → *Mandate Payload (v0.7)*. */
110
+ /** The protocol version an artifact was issued under. This package verifies
111
+ * each under its own rules (protocol.md → *Version negotiation*, rule 4). */
112
+ type ProtocolVersion = '0.8' | '0.7';
113
+ /** protocol.md → *Mandate Payload (v0.8)*; a `"0.7"` payload is the same shape
114
+ * without `above_cap_approvals` and without the v0.8 owner-signed fields. */
112
115
  interface MandatePayload {
113
116
  mandate_id: string;
114
- version: '0.7';
117
+ version: ProtocolVersion;
115
118
  profile_id: string;
116
119
  /** Hash of the canonical bounds string. */
117
120
  bounds_hash: string;
@@ -128,10 +131,16 @@ interface MandatePayload {
128
131
  /** Exactly one entry in v0.7 (Mandate rule 7). */
129
132
  mandate_owners: MandateOwnerEntry[];
130
133
  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"`. */
134
+ commitment_mode: CommitmentMode;
135
+ /** Present together with `above_cap_approvers` and (v0.8) `above_cap_approvals`:
136
+ * under `review_above_cap`, or `review` in a group with approval-kind caps
137
+ * the owner's mandate can reach (protocol.md → *Group roles*, rule 7). */
133
138
  above_cap_caps?: Record<string, number>;
134
139
  above_cap_approvers?: string[];
140
+ /** v0.8 — how many distinct `above_cap_approvers`, other than the Mandate
141
+ * Owner, must approve. Integer ≥ 1. Present exactly when
142
+ * `above_cap_approvers` is (Mandate rule 10). A v0.7 mandate carries none. */
143
+ above_cap_approvals?: number;
135
144
  /**
136
145
  * Present iff the mandate carries an encrypted-intent disclosure object
137
146
  * (companion spec `intent-disclosure@0.1`) — `sha256:`-prefixed hash
@@ -317,6 +326,15 @@ interface ProfileBoundsField {
317
326
  * min(value, maximum). Absent → no ceiling.
318
327
  */
319
328
  maximum?: number;
329
+ /**
330
+ * v0.8 (Bounds Schema rule 11) — a bound only the Gatekeeper enforces, such
331
+ * as a read window whose actions produce no ticket. The Authority Server
332
+ * still verifies the value at issuance (`maximum`, enum membership, any
333
+ * group ceiling) and never reads it at ticket time. A group cap on such a
334
+ * bound is always hard, and the signed value is the ceiling (*Group
335
+ * ceilings*).
336
+ */
337
+ enforcedBy?: 'gatekeeper';
320
338
  }
321
339
  /**
322
340
  * v0.5 Content Provenance — how a profile's action content is hashed into a
@@ -463,6 +481,8 @@ type ExecutionContextFieldDef = DeclaredFieldDef | CumulativeFieldDef;
463
481
  * is not chosen by the signer — it follows from team caps.)
464
482
  */
465
483
  type SignableCommitmentMode = 'automatic' | 'review';
484
+ /** Every commitment mode a signed mandate can carry. */
485
+ type CommitmentMode = SignableCommitmentMode | 'review_above_cap';
466
486
  /** protocol.md → *Owner Signatures* → "Where the requirement lives" — the
467
487
  * profile-floor tier: `minBinding` names the weakest `OwnerSignatureBinding`
468
488
  * this profile accepts for a co-signing owner. */
@@ -517,8 +537,13 @@ interface AgentProfile {
517
537
  * a mandate screen offers only the listed modes. Fail-closed: a declared
518
538
  * list with no valid entry allows nothing — a malformed declaration never
519
539
  * widens what a profile allows. See {@link allowedCommitmentModes}.
540
+ *
541
+ * v0.8 (Commitment Modes, rule 8): `review_above_cap` is admitted if and
542
+ * only if `automatic` is listed; an entry `review_above_cap` in the list is
543
+ * ignored, and a list whose only entry is it admits nothing and is
544
+ * malformed (`PROFILE_INVALID`).
520
545
  */
521
- commitment_modes?: readonly SignableCommitmentMode[];
546
+ commitment_modes?: readonly CommitmentMode[];
522
547
  /**
523
548
  * The bounds schema — the enforceable parameters (protocol.md → *Bounds
524
549
  * Schema*).
@@ -668,39 +693,6 @@ type GatekeeperResult = {
668
693
  errors: GatekeeperError[];
669
694
  };
670
695
 
671
- /**
672
- * JCS — JSON Canonicalization (RFC 8785) for signing.
673
- *
674
- * Both attestation and receipt signatures are computed over a canonical byte
675
- * serialization of the payload, so that two independent implementations (an AS,
676
- * a gateway, an external verifier — in any language) agree on exactly which
677
- * bytes were signed regardless of how they happened to construct the object.
678
- *
679
- * `JSON.stringify` is NOT sufficient: it preserves key *insertion* order, so the
680
- * same logical payload built with keys in a different order serializes to
681
- * different bytes and the signature fails to verify. Canonicalization removes
682
- * that dependency by sorting keys.
683
- *
684
- * This is RFC 8785-compatible and matches the v0.5 spec's normative signing
685
- * canonicalization (core.md "Signing Canonicalization"):
686
- * 1. UTF-8.
687
- * 2. Object keys sorted (RFC 8785 sorts by UTF-16 code units — exactly what
688
- * JavaScript's default String comparison does, so `Object.keys().sort()` is
689
- * correct here).
690
- * 3. No insignificant whitespace.
691
- * 4. Numbers in the shortest round-trippable form — the ECMAScript
692
- * Number-to-String algorithm, which `JSON.stringify(number)` produces and
693
- * RFC 8785 adopts verbatim.
694
- * 5. Strings escaped per RFC 8259; non-ASCII passed through as UTF-8 (this is
695
- * `JSON.stringify`'s behaviour for strings).
696
- * 6. Array order preserved.
697
- *
698
- * Because it relies only on `JSON.stringify` for leaves plus `Object.keys`,
699
- * `Array`, and `String` sort — all of which are environment-independent in
700
- * JavaScript — Node and the browser produce byte-identical output. The
701
- * conformance test pins a (payload → canonical bytes) vector that any other
702
- * implementation can check against.
703
- */
704
696
  declare function canonicalize(value: unknown): string;
705
697
 
706
698
  /**
@@ -728,20 +720,31 @@ declare function toBase64Url(bytes: Uint8Array): string;
728
720
  */
729
721
  declare function fromBase64Url(s: string): Uint8Array;
730
722
 
731
- /** The protocol version this package issues and verifies. */
732
- declare const PROTOCOL_VERSION = "0.7";
733
- /** The protocol versions this package can verify and enforce. */
723
+ /** The protocol version this package issues. */
724
+ declare const PROTOCOL_VERSION = "0.8";
725
+ /** Every version this package verifies and enforces, newest first. */
734
726
  declare const SUPPORTED_VERSIONS: readonly string[];
727
+ /** The versions an Authority Server built on this package issues. */
728
+ declare const ISSUABLE_VERSIONS: readonly string[];
729
+ /** A protocol version this package knows the rules of. */
730
+ type SupportedVersion = '0.8' | '0.7';
731
+ declare function isSupportedVersion(v: unknown): v is SupportedVersion;
735
732
  /**
736
- * The highest version both the requester and this implementation support.
733
+ * The highest version both the requester and the issuer support.
737
734
  *
738
735
  * @param requested `supported_versions` from the mandate request. Absent →
739
- * read as `["0.6"]` (protocol.md rule 1) — which, against this package's
740
- * `SUPPORTED_VERSIONS`, has no overlap and refuses honestly rather than
741
- * silently assuming `0.7`.
736
+ * read as `["0.6"]` (protocol.md rule 1), which no issuer here supports, so
737
+ * the refusal is honest rather than a silent assumption.
738
+ * @param supported What the issuer is willing to issue. Default: v0.8 only.
742
739
  * @throws HapError `VERSION_UNSUPPORTED` when no version is common to both.
743
740
  */
744
741
  declare function negotiateVersion(requested: readonly string[] | undefined, supported?: readonly string[]): string;
742
+ /**
743
+ * Refuse an artifact of a version this package cannot verify (rule 4: an
744
+ * artifact is read under its own version, never re-interpreted).
745
+ * @throws HapError `VERSION_UNSUPPORTED`
746
+ */
747
+ declare function assertSupportedVersion(version: unknown, what: string): asserts version is SupportedVersion;
745
748
 
746
749
  /**
747
750
  * Content binding — Level 2 content proof (HAP v0.5 Content Provenance).
@@ -1043,20 +1046,29 @@ declare function computeScopeHash(params: AgentScopeParams, profile: AgentProfil
1043
1046
 
1044
1047
  /**
1045
1048
  * Which commitment modes a profile allows its mandates to be signed with
1046
- * (`AgentProfile.commitment_modes`). One source for the Authority Server's
1047
- * refusal and the mandate screen's choice, so the two cannot disagree.
1049
+ * (`AgentProfile.commitment_modes`; protocol.md → *Commitment Modes*, rule
1050
+ * 8). One source for the Authority Server's refusal and the mandate screen's
1051
+ * choice, so the two cannot disagree.
1048
1052
  */
1049
1053
 
1050
1054
  /** Every mode a signer can choose, in the order a mandate screen shows them. */
1051
1055
  declare const SIGNABLE_COMMITMENT_MODES: readonly SignableCommitmentMode[];
1056
+ /** Every mode a signed mandate can carry. */
1057
+ declare const COMMITMENT_MODES: readonly CommitmentMode[];
1052
1058
  /**
1053
- * The modes a mandate under this profile may carry, in display order.
1054
- * No declaration → all of them. A declaration is read fail-closed: entries
1055
- * that are not a signable mode are dropped, so a malformed list can only
1056
- * narrow — `[]`, `"review"` (not a list) or `["reveiw"]` allow nothing.
1059
+ * The signable modes a mandate under this profile may carry, in display
1060
+ * order. No declaration → all of them. A declaration is read fail-closed:
1061
+ * entries that are not a signable mode are dropped (`review_above_cap` is
1062
+ * ignored by rule 8 — it follows from `automatic`), so a malformed list can
1063
+ * only narrow — `[]`, `"review"` (not a list) or `["reveiw"]` allow nothing.
1057
1064
  */
1058
1065
  declare function allowedCommitmentModes(profile: AgentProfile): readonly SignableCommitmentMode[];
1059
- /** Whether a mandate under this profile may be signed with `mode`. */
1066
+ /**
1067
+ * Whether a mandate under this profile may be SIGNED with `mode` (rule 8):
1068
+ * `automatic` and `review` where listed; `review_above_cap` if and only if
1069
+ * `automatic` is admitted. The Authority Server refuses any other with
1070
+ * `COMMITMENT_MODE_NOT_ALLOWED`.
1071
+ */
1060
1072
  declare function isCommitmentModeAllowed(profile: AgentProfile, mode: string): boolean;
1061
1073
 
1062
1074
  /** One authoring-time finding. `code` is always a canonical v0.7 code — in
@@ -1097,9 +1109,10 @@ declare function checkDiscloseSubset(mandateDiscloseFields: readonly string[] |
1097
1109
  * Renamed from `attestation.ts` (protocol.md → *Migration from v0.6*:
1098
1110
  * `header.typ: "HAP-attestation"` → `"HAP-mandate"`, `attestation_id` →
1099
1111
  * `mandate_id`). v0.3's frame-hash path and v0.4's dual frame/bounds
1100
- * verification are retired with the rest of pre-v0.5 — this module issues
1101
- * and verifies `version: "0.7"` mandates only (CLAUDE.md "Decided by the
1102
- * owner": no backward-compat verify path for 0.5/0.6).
1112
+ * verification are retired with the rest of pre-v0.5. This module issues
1113
+ * `version: "0.8"` mandates and verifies `"0.8"` and `"0.7"` ones, each
1114
+ * under its own rules (protocol.md → *Version negotiation*, rule 4): v0.8
1115
+ * is a re-grant release, and a v0.7 artifact stays verifiable as evidence.
1103
1116
  */
1104
1117
 
1105
1118
  /**
@@ -1159,15 +1172,25 @@ declare function checkMandateExpiry(payload: MandatePayload, now?: number): void
1159
1172
  * violation; `OWNER_SIGNATURE_INVALID` when a co-signing DID is not key-bearing.
1160
1173
  */
1161
1174
  declare function validateMandateOwners(payload: MandatePayload): void;
1175
+ /**
1176
+ * The above-cap fields travel together (Mandate rule 10; *Group roles*,
1177
+ * rule 7). Under v0.8 `above_cap_approvals` is present exactly when
1178
+ * `above_cap_approvers` is, an integer ≥ 1; under v0.7 there is no count and
1179
+ * caps + approvers are present exactly under `review_above_cap`.
1180
+ *
1181
+ * @throws HapError `MALFORMED_MANDATE`
1182
+ */
1183
+ declare function validateAboveCapFields(payload: MandatePayload): void;
1162
1184
  /**
1163
1185
  * Full mandate verification: decode, check `version`, verify the AS
1164
- * signature, check expiry, validate `mandate_owners` shape.
1186
+ * signature, check expiry, validate `mandate_owners` shape and the above-cap
1187
+ * fields.
1165
1188
  *
1166
1189
  * Deliberately does NOT verify any `mandate_owners` co-signature — that is
1167
1190
  * `verifyOwnerSignature` in `owner-signature.ts`, a separate trust axis the
1168
1191
  * caller opts into.
1169
1192
  *
1170
- * @throws HapError `VERSION_UNSUPPORTED` for anything other than `"0.7"`.
1193
+ * @throws HapError `VERSION_UNSUPPORTED` for a version outside {@link SUPPORTED_VERSIONS}.
1171
1194
  * @throws Error (`INVALID_SIGNATURE:` / `TTL_EXPIRED:` / `MALFORMED_MANDATE:`
1172
1195
  * prefixed, or `OWNER_SIGNATURE_INVALID` as a HapError) on any other failure.
1173
1196
  */
@@ -1177,13 +1200,9 @@ declare function verifyMandate(blob: string, opts?: {
1177
1200
  }): Promise<MandatePayload>;
1178
1201
 
1179
1202
  /**
1180
- * Owner Signatures (v0.7) — the `HAP-mandate-projection` object, the
1181
- * `HAP-approval` object, and their verification.
1182
- *
1183
- * Renamed from `mandate.ts` (protocol.md → *Migration from v0.6*: the
1184
- * owner-signed projection's `typ` moves from `"HAP-mandate"` to
1185
- * `"HAP-mandate-projection"`, disambiguating it from the AS-signed mandate
1186
- * itself, which now legitimately owns the name `"HAP-mandate"`).
1203
+ * Owner Signatures — the `HAP-mandate-projection` object, the
1204
+ * `HAP-approval` object, and their verification (v0.8; v0.7 artifacts
1205
+ * verified under their own field lists).
1187
1206
  *
1188
1207
  * The human signs BEFORE the AS does, so they cannot sign the finished
1189
1208
  * mandate (`mandate_id`/`issued_at` do not exist yet). They sign a mandate
@@ -1193,6 +1212,13 @@ declare function verifyMandate(blob: string, opts?: {
1193
1212
  * with the key carried in the owner's DID. No side channel, no second
1194
1213
  * fetch, no key directory. See protocol.md → "Owner Signatures".
1195
1214
  *
1215
+ * v0.8 adds to the projection what a compromised AS could otherwise restate:
1216
+ * `binding` and `signing_surface` (how the owner signed), `subjects_disclosed`
1217
+ * (whether the name is attached), and `above_cap_approvals` (how many
1218
+ * approvers). The approval gains `approver_did` (who decided) and the
1219
+ * operation it is for (`action`, `action_type`), and always signs the
1220
+ * proposal's content fingerprint beside the bound subset.
1221
+ *
1196
1222
  * Verification here requires NO trust in the AS. What it cannot do is tell
1197
1223
  * the verifier WHOSE key signed — confirming the DID belongs to the
1198
1224
  * expected person is the out-of-band step (protocol.md → "Verification
@@ -1206,7 +1232,7 @@ declare function verifyMandate(blob: string, opts?: {
1206
1232
  interface MandateProjection {
1207
1233
  typ: 'HAP-mandate-projection';
1208
1234
  /** Projection/canonicalization version. A verifier MUST pin it. */
1209
- version: '0.7';
1235
+ version: ProtocolVersion;
1210
1236
  profile_id: string;
1211
1237
  /** Covered deliberately: the owner signs the profile's BYTES, not only its id. */
1212
1238
  profile_hash: string;
@@ -1219,29 +1245,46 @@ interface MandateProjection {
1219
1245
  /** Included iff the mandate carries one — binds ciphertext AND the
1220
1246
  * frozen approver set, the swap this mechanism exists to stop. */
1221
1247
  intent_disclosure_hash?: string;
1222
- commitment_mode: string;
1223
- /** Included together, iff `commitment_mode === "review_above_cap"`. */
1248
+ commitment_mode: CommitmentMode;
1249
+ /** Included together, iff the mandate carries them (v0.8: under
1250
+ * `review_above_cap`, or `review` in a group with approval-kind caps;
1251
+ * v0.7: under `review_above_cap`). */
1224
1252
  above_cap_caps?: Record<string, number>;
1225
1253
  above_cap_approvers?: string[];
1254
+ /** v0.8 only, with the other two. */
1255
+ above_cap_approvals?: number;
1226
1256
  /** Included iff the mandate narrows the profile's disclose_fields. */
1227
1257
  disclose_fields?: string[];
1228
1258
  /** The replay defence: the human signs how long the authority lives. */
1229
1259
  expires_at: number;
1230
1260
  nonce: string;
1261
+ /** v0.8, always present: MUST equal the `mandate_owners` entry's values. */
1262
+ binding?: OwnerSignatureBinding | string;
1263
+ signing_surface?: SigningSurface | string;
1264
+ /** v0.8, always present: `true` exactly when the mandate carries a
1265
+ * `subjects` entry for the owner. */
1266
+ subjects_disclosed?: boolean;
1231
1267
  }
1232
- /** Per-action approval, signed by the owner in `review` mode. Signing a
1233
- * `reject` matters as much as a `commit`: a rejection the AS can discard is
1234
- * a rejection that never happened. */
1268
+ /** Per-action approval, signed by the decider. Signing a `reject` matters as
1269
+ * much as a `commit`: a rejection the AS can discard is a rejection that
1270
+ * never happened. */
1235
1271
  interface ApprovalObject {
1236
1272
  typ: 'HAP-approval';
1237
- version: '0.7';
1273
+ version: ProtocolVersion;
1274
+ /** v0.8 — who decided: the Mandate Owner in `review`, or an above-cap approver. */
1275
+ approver_did?: string;
1238
1276
  proposal_id: string;
1239
- /** Renamed from `attestation_id` in v0.7. */
1240
1277
  mandate_id: string;
1278
+ /** v0.8 — the operation the arguments are for, exactly as the ticket carries them. */
1279
+ action?: string;
1280
+ action_type?: string;
1241
1281
  decision: 'commit' | 'reject';
1242
- /** What was approved: the ticket's contentHash where the profile binds
1243
- * content; otherwise sha256 over the JCS of the proposal's argument set. */
1282
+ /** The ticket's contentHash where a content binding applies to the action;
1283
+ * otherwise equal to `proposal_content_hash` (v0.8) / the JCS hash of the
1284
+ * proposal's arguments (v0.7). */
1244
1285
  content_hash: string;
1286
+ /** v0.8, always present — the proposal's content fingerprint. */
1287
+ proposal_content_hash?: string;
1245
1288
  decided_at: number;
1246
1289
  nonce: string;
1247
1290
  }
@@ -1252,15 +1295,13 @@ declare class OwnerSignatureError extends Error {
1252
1295
  }
1253
1296
  /**
1254
1297
  * Rebuild the projection a given `mandate_owners` entry signed, from the
1255
- * mandate's own signed fields. Field absence is defined, not incidental:
1256
- * `intent_disclosure_hash`, `above_cap_caps`/`above_cap_approvers`, and
1257
- * `disclose_fields` are included iff the mandate carries them.
1298
+ * mandate's own signed fields, under the mandate's own `version`. Field
1299
+ * absence is defined, not incidental.
1258
1300
  */
1259
- declare function buildMandateProjection(payload: MandatePayload, entry: Pick<MandateOwnerEntry, 'did' | 'nonce'>): MandateProjection;
1260
- /** The exact bytes an owner signs: RFC 8785 (JCS) canonical UTF-8. Renamed
1261
- * from `mandateSigningBytes` in v0.7. */
1301
+ declare function buildMandateProjection(payload: MandatePayload, entry: Pick<MandateOwnerEntry, 'did' | 'nonce' | 'binding' | 'signing_surface'>): MandateProjection;
1302
+ /** The exact bytes an owner signs: RFC 8785 (JCS) canonical UTF-8. */
1262
1303
  declare function projectionSigningBytes(projection: MandateProjection): Uint8Array;
1263
- /** The exact bytes an owner signs for a per-action approval. */
1304
+ /** The exact bytes a decider signs for a per-action approval. */
1264
1305
  declare function approvalSigningBytes(approval: ApprovalObject): Uint8Array;
1265
1306
  /**
1266
1307
  * Sign a mandate projection with a raw Ed25519 private key — the `raw`
@@ -1291,35 +1332,45 @@ declare function verifyOwnerSignature(mandate: Mandate, entry: MandateOwnerEntry
1291
1332
  declare function verifyOwnerSignatures(mandate: Mandate): Promise<MandateOwnerEntry[]>;
1292
1333
  /** Verify an approval signature against a signer's key-bearing DID. */
1293
1334
  declare function verifyApproval(approval: ApprovalObject, signature: string, signerDid: string): Promise<void>;
1335
+ /** Signature-assurance ranking for `OwnerSignatureBinding`, weakest first —
1336
+ * protocol.md → *Owner Signatures* → "binding" table ordering (raw < webauthn < eudi).
1337
+ * A binding the verifier does not know ranks as `raw` (satisfies no minimum
1338
+ * above it). */
1339
+ declare const BINDING_RANK: Record<OwnerSignatureBinding, number>;
1340
+ declare function bindingRank(binding: string | undefined): number;
1294
1341
  /**
1295
1342
  * Enforce a profile's `ownerSignature` floor (protocol.md → *Owner
1296
1343
  * Signatures* → "Where the requirement lives" — the profile-floor tier,
1297
1344
  * checkable without trusting the AS: `profile_id`/`profile_hash` are in the
1298
1345
  * signed payload AND in the owner-signed projection).
1299
1346
  *
1300
- * An unknown `binding` value is treated as rank 0 (weakest) rather than
1301
- * rejected outright — "an AS MUST NOT reject a mandate_owners entry solely
1302
- * because it does not recognize its binding value" extends naturally to a
1303
- * verifier ranking it, since crediting an unrecognized binding with
1304
- * unearned strength would be the more dangerous failure mode.
1305
- *
1306
1347
  * @throws OwnerSignatureError `OWNER_SIGNATURE_REQUIRED` when the floor is
1307
1348
  * unmet — "MUST fail, not warn" (protocol.md, same section).
1308
1349
  */
1309
1350
  declare function checkOwnerSignatureRequirement(payload: MandatePayload, floor: ProfileOwnerSignatureFloor | undefined): void;
1310
1351
 
1311
1352
  /**
1312
- * Mandate tickets (v0.7) — the wire type, signing, and holder-side
1313
- * verification. Renamed from `receipt.ts` (protocol.md → *Mandate Tickets*:
1314
- * "v0.4 introduced the object under the name execution receipt; v0.7 renames
1315
- * it because the tense was wrong — a receipt follows payment, and the ticket
1316
- * must come first").
1353
+ * Mandate tickets — the wire type, signing, holder-side verification, and
1354
+ * (v0.8) the Gatekeeper's check that a ticket answers the request it sent.
1317
1355
  *
1318
1356
  * Ticket payloads are camelCase on the wire (unlike the snake_case mandate
1319
1357
  * payload) — a shipped inconsistency the spec documents rather than breaks
1320
1358
  * (protocol.md → *Migration from v0.6*, "Unchanged on purpose").
1321
1359
  */
1322
1360
 
1361
+ /** Clock-skew tolerance and freshness window (Mandate rule 9; Ticket rule 4). */
1362
+ declare const TICKET_FRESHNESS_SECONDS = 300;
1363
+ /** One verified approval carried by a ticket. */
1364
+ interface ApprovalSignatureEntry {
1365
+ alg: 'EdDSA' | 'ES256';
1366
+ signature: string;
1367
+ nonce: string;
1368
+ decided_at: number;
1369
+ }
1370
+ /** v0.8 — an above-cap approver's entry in `approvalSignatures`. */
1371
+ interface ApproverSignatureEntry extends ApprovalSignatureEntry {
1372
+ approver_did: string;
1373
+ }
1323
1374
  /** Signed mandate ticket — protocol.md → *Ticket Payload Schema*. */
1324
1375
  interface TicketPayload {
1325
1376
  id: string;
@@ -1345,6 +1396,10 @@ interface TicketPayload {
1345
1396
  /** The AS DID, as in the mandate (new in v0.7). */
1346
1397
  issuer: string;
1347
1398
  timestamp: number;
1399
+ /** v0.8 — copied verbatim from the request, so the ticket names the single
1400
+ * request it answers. Present exactly when the request carried one
1401
+ * (synchronous path). */
1402
+ idempotencyKey?: string;
1348
1403
  /** Hash of the bound content, computed by the Gatekeeper, copied
1349
1404
  * verbatim by the AS. The AS never sees the content. */
1350
1405
  contentHash?: string;
@@ -1355,15 +1410,15 @@ interface TicketPayload {
1355
1410
  subjects?: Subject[];
1356
1411
  /** Review path: the proposal this ticket executed. */
1357
1412
  proposalId?: string;
1413
+ /** v0.8, review path: the proposal's content fingerprint, copied verbatim
1414
+ * from the proposal the AS compared it against. */
1415
+ proposalContentHash?: string;
1358
1416
  /** Review path: the owner's HAP-approval signature for the executed
1359
1417
  * proposal, as the object itself — never a hash of it, which a holder
1360
1418
  * could check only by asking the AS. */
1361
- approvalSignature?: {
1362
- alg: 'EdDSA' | 'ES256';
1363
- signature: string;
1364
- nonce: string;
1365
- decided_at: number;
1366
- };
1419
+ approvalSignature?: ApprovalSignatureEntry;
1420
+ /** v0.8, above-cap path: one entry per approver signature the AS verified. */
1421
+ approvalSignatures?: ApproverSignatureEntry[];
1367
1422
  /** Ed25519 over the JCS-canonical payload (this field excluded), base64url. */
1368
1423
  signature: string;
1369
1424
  }
@@ -1372,42 +1427,431 @@ interface TicketPayload {
1372
1427
  * the same key used for mandates.
1373
1428
  */
1374
1429
  declare function signTicket(payload: Omit<TicketPayload, 'signature'>, privateKey: Uint8Array): Promise<TicketPayload>;
1430
+ /**
1431
+ * The structural rules that keep a ticket, a projection and a mandate
1432
+ * payload apart (protocol.md → *Ticket Payload Schema*, "Signature"): a
1433
+ * ticket verifier MUST require `id`, `mandateId`, `actionType`, `timestamp`
1434
+ * and `issuer`, MUST reject an object carrying `typ` (a projection can never
1435
+ * be accepted as a ticket), and MUST reject `mandate_id` or `issued_at`.
1436
+ * @throws HapError `INVALID_SIGNATURE`
1437
+ */
1438
+ declare function assertTicketShape(ticket: Record<string, unknown>): void;
1375
1439
  /**
1376
1440
  * Verify a ticket's AS signature: strip `signature`, JCS-canonicalize the
1377
1441
  * rest, Ed25519-verify against the key resolved from `issuer` — never
1378
1442
  * against a key offered alongside the ticket (protocol.md → *Ticket
1379
1443
  * Verification* step 1). This is holder-side verification — it needs the
1380
1444
  * COMPLETE ticket; a redacted public view cannot be re-verified this way
1381
- * (see {@link publicTicketView}).
1445
+ * (see `ticket-projection.ts` for the view that can).
1382
1446
  *
1383
1447
  * Content is checked separately: where `contentHash` is present, recompute
1384
1448
  * it from the held artifact via the content-binding module and compare.
1385
1449
  *
1386
1450
  * @param opts.trustedIssuers When supplied, an `issuer` not in this list is
1387
1451
  * rejected before any cryptography runs.
1388
- * @throws Error prefixed `INVALID_SIGNATURE:` when verification fails.
1452
+ * @throws HapError `INVALID_SIGNATURE` when verification fails;
1453
+ * `VERSION_UNSUPPORTED` for a version this package cannot read.
1389
1454
  */
1390
1455
  declare function verifyTicketSignature(ticket: TicketPayload, opts?: {
1391
1456
  trustedIssuers?: readonly string[];
1392
1457
  }): Promise<void>;
1458
+ /** The fields of a ticket request a Gatekeeper compares with the ticket it
1459
+ * receives (protocol.md → *Ticket Payload Schema*, rule 4). */
1460
+ interface TicketRequestEcho {
1461
+ profileId: string;
1462
+ actionType: string;
1463
+ boundsHash: string;
1464
+ executionContext: Record<string, unknown>;
1465
+ mandateId?: string;
1466
+ idempotencyKey?: string;
1467
+ contentHash?: string;
1468
+ contentBinding?: TicketPayload['contentBinding'];
1469
+ proposalId?: string;
1470
+ proposalContentHash?: string;
1471
+ }
1393
1472
  /**
1394
- * A redacted public view of a ticket (new in v0.7 — protocol.md → *Ticket
1395
- * Disclosure Is Declared*, *Ticket Verification*). Hides `userId`,
1396
- * `cumulativeState`, and `limits` unconditionally, and narrows
1397
- * `executionContext` to exactly the keys named by `governingDiscloseFields`
1398
- * (the mandate's `disclose_fields` where present, otherwise the profile's).
1473
+ * v0.8 — the Gatekeeper's half of "a ticket names the request it answers":
1474
+ * every echoed field equals what was sent, the `idempotencyKey` it sent is
1475
+ * the one the ticket carries, `mandateId` and `version` are the verified
1476
+ * mandate's, and a fresh ticket's `timestamp` lies within
1477
+ * {@link TICKET_FRESHNESS_SECONDS}. A replay (`idempotent: true`, unsigned)
1478
+ * is exempt from freshness only; the caller passes `replay: true` and then
1479
+ * applies *One execution per ticket*, rule 2.
1399
1480
  *
1400
- * Disclose NOTHING by default: an empty or absent `governingDiscloseFields`
1401
- * strips `executionContext` entirely. "The holder is the verifier" — this
1402
- * view is NOT independently re-verifiable against `signature` (it is missing
1403
- * signed fields); a "signature valid" indicator on a page showing only this
1404
- * view is the AS re-verifying its own signature, and implementations MUST
1405
- * NOT present it as independently verified.
1481
+ * Does NOT verify the signature — call {@link verifyTicketSignature} first.
1482
+ * @throws HapError `TICKET_REQUEST_MISMATCH`
1483
+ */
1484
+ declare function verifyTicketAgainstRequest(ticket: TicketPayload, request: TicketRequestEcho, opts?: {
1485
+ mandate?: Mandate;
1486
+ now?: number;
1487
+ replay?: boolean;
1488
+ }): void;
1489
+ /**
1490
+ * Rebuild the `HAP-approval` a decider signed, from the ticket and the
1491
+ * mandate it names (protocol.md → *Ticket Payload Schema*, `approvalSignature`):
1492
+ * `typ` and `version`; `proposal_id` and `mandate_id` from `proposalId` and
1493
+ * `mandateId`; `action` and `action_type` (v0.8) from `action` and
1494
+ * `actionType`; `proposal_content_hash` from `proposalContentHash`;
1495
+ * `content_hash` from `contentHash` where a binding applies to this action
1496
+ * (the AS copies one only then), else `proposalContentHash`; `decision`
1497
+ * `"commit"`; `approver_did` as given; `decided_at` and `nonce` from the
1498
+ * entry.
1499
+ */
1500
+ declare function reconstructApproval(ticket: TicketPayload, entry: ApprovalSignatureEntry, approverDid: string): ApprovalObject;
1501
+ /** What a ticket's approvals, once verified, establish. */
1502
+ interface VerifiedApprovals {
1503
+ /** The Mandate Owner's DID, when `approvalSignature` verified. */
1504
+ owner?: string;
1505
+ /** Distinct above-cap approver DIDs whose `approvalSignatures` entries verified. */
1506
+ approvers: string[];
1507
+ }
1508
+ /**
1509
+ * Verify every approval a ticket carries against the mandate it names
1510
+ * (protocol.md → *Ticket Payload Schema*, rule 4): `approvalSignature`
1511
+ * under the Mandate Owner's DID; each `approvalSignatures` entry under its
1512
+ * `approver_did`, which MUST be in the signed `above_cap_approvers` and
1513
+ * MUST NOT be the Mandate Owner (the proposer never counts). Whether the
1514
+ * verified set is ENOUGH is {@link approvalsSufficient}, so a verifier can
1515
+ * report what it found and a policy can decide.
1516
+ *
1517
+ * @throws HapError `APPROVAL_SIGNATURE_INVALID`
1518
+ */
1519
+ declare function verifyTicketApprovals(ticket: TicketPayload, mandate: Mandate): Promise<VerifiedApprovals>;
1520
+ /**
1521
+ * Whether the verified approvals satisfy what the mandate requires for a
1522
+ * decision above a cap: at least `above_cap_approvals` distinct approvers
1523
+ * (v0.7: every signed approver), plus the owner's own approval in `review`.
1524
+ */
1525
+ declare function approvalsSufficient(mandate: Mandate, verified: VerifiedApprovals, aboveCap: boolean): boolean;
1526
+ /**
1527
+ * A redacted public view of a ticket (protocol.md → *Ticket Disclosure Is
1528
+ * Declared*). Hides `userId`, `cumulativeState`, and `limits`, and narrows
1529
+ * `executionContext` to the governing `disclose_fields`. NOT independently
1530
+ * re-verifiable — since v0.8 a verifiable public view is the separately
1531
+ * signed `HAP-ticket-projection` (`ticket-projection.ts`); a view that is
1532
+ * not one MUST NOT be presented as independently verified.
1406
1533
  */
1407
1534
  declare function publicTicketView(ticket: TicketPayload, governingDiscloseFields?: readonly string[]): Omit<TicketPayload, 'userId' | 'cumulativeState' | 'limits'>;
1408
1535
 
1409
1536
  /**
1410
- * Gatekeeper — local (Phase 1) verification for bounded execution (v0.7).
1537
+ * Public ticket projection — `HAP-ticket-projection` (protocol.md → *Public
1538
+ * ticket projection*, new in v0.8, specification-led).
1539
+ *
1540
+ * A ticket is one signature over every field, so a redacted view cannot be
1541
+ * re-verified. Where a ticket is shown to parties other than its holder —
1542
+ * a public page, a machine verifier at an effector, an auditor — the AS
1543
+ * serves a public-only subset, separately signed by the same key, so the
1544
+ * public view verifies on its own. It never widens what the ticket
1545
+ * discloses: `executionContext` carries only the governing
1546
+ * `disclose_fields`, and `userId`, `cumulativeState`, `limits` and `action`
1547
+ * never appear.
1548
+ */
1549
+
1550
+ /** The AS may not let a projection be accepted as authorization later than
1551
+ * this after the ticket's timestamp (rule 3). */
1552
+ declare const PROJECTION_MAX_VALIDITY_SECONDS = 86400;
1553
+ interface TicketProjection {
1554
+ typ: 'HAP-ticket-projection';
1555
+ version: string;
1556
+ id: string;
1557
+ mandateId: string;
1558
+ issuer: string;
1559
+ timestamp: number;
1560
+ /** Set by the AS at projection time; governs acceptance as authorization, not evidence. */
1561
+ valid_until: number;
1562
+ profileId: string;
1563
+ actionType: string;
1564
+ boundsHash: string;
1565
+ contentHash?: string;
1566
+ contentBinding?: TicketPayload['contentBinding'];
1567
+ subjects?: Subject[];
1568
+ /** Exactly the governing `disclose_fields` the ticket carries; absent when none. */
1569
+ executionContext?: Record<string, unknown>;
1570
+ /** The mandate's `mandate_owners` entry, present exactly when it carries a signature. */
1571
+ owner?: MandateOwnerEntry;
1572
+ signature: string;
1573
+ }
1574
+ /**
1575
+ * Build the projection of a ticket (rule 1). `discloseFields` is the
1576
+ * governing list — the mandate's `disclose_fields` where present, else the
1577
+ * profile's; nothing when neither declares one.
1578
+ */
1579
+ declare function buildTicketProjection(ticket: TicketPayload, mandate: Mandate, opts: {
1580
+ validUntil: number;
1581
+ discloseFields?: readonly string[];
1582
+ }): Omit<TicketProjection, 'signature'>;
1583
+ /** Sign a projection with the key that signed its ticket (rule 2). */
1584
+ declare function signTicketProjection(projection: Omit<TicketProjection, 'signature'>, privateKey: Uint8Array): Promise<TicketProjection>;
1585
+ /**
1586
+ * Verify a projection on its own: `typ` required, signature under the key
1587
+ * `issuer` names. With `asAuthorization`, a projection past `valid_until`
1588
+ * is refused (rule 3); as evidence it is not.
1589
+ * @throws HapError `INVALID_SIGNATURE`, `POLICY_UNMET` (validity window)
1590
+ */
1591
+ declare function verifyTicketProjection(projection: TicketProjection, opts?: {
1592
+ trustedIssuers?: readonly string[];
1593
+ now?: number;
1594
+ asAuthorization?: boolean;
1595
+ }): Promise<void>;
1596
+ /**
1597
+ * Verify the projection's `owner` entry against the mandate obtained through
1598
+ * the third-party verification API (rule 4): the entry is the mandate's
1599
+ * own, and its signature verifies over the projection rebuilt from the
1600
+ * mandate. The projection alone cannot establish this.
1601
+ * @throws HapError `OWNER_SIGNATURE_INVALID`
1602
+ */
1603
+ declare function verifyProjectionOwner(projection: TicketProjection, mandate: Mandate): Promise<void>;
1604
+
1605
+ /**
1606
+ * Verifier policy — `HAP-verifier-policy` (protocol.md → *Owner Signatures*
1607
+ * → *Verifier policy*, new in v0.8, specification-led).
1608
+ *
1609
+ * The relying party's own, declarative statement of what it demands of a
1610
+ * ticket (or projection) before acting on or trusting it. Every present
1611
+ * line is a conjunct; an unmet line is a refusal naming the line and its
1612
+ * code; absent lines impose nothing. The policy is never sent to the
1613
+ * Authority Server.
1614
+ */
1615
+
1616
+ interface VerifierPolicy {
1617
+ typ: 'HAP-verifier-policy';
1618
+ version: string;
1619
+ trusted_issuers?: string[];
1620
+ min_binding?: OwnerSignatureBinding | string;
1621
+ required_owners?: string[];
1622
+ profile_hashes?: Record<string, string>;
1623
+ action_types?: string[];
1624
+ freshness_seconds?: number;
1625
+ required_fields?: string[];
1626
+ require_approval_signature?: boolean;
1627
+ }
1628
+ /** A refusal naming the policy line that failed. */
1629
+ declare class PolicyError extends HapError {
1630
+ readonly line: keyof VerifierPolicy;
1631
+ constructor(line: keyof VerifierPolicy, code: HapErrorCode, message: string);
1632
+ }
1633
+ interface PolicyInput {
1634
+ /** The complete ticket, where the relying party holds it. */
1635
+ ticket?: TicketPayload;
1636
+ /** Or the projection, where only the public view was served. */
1637
+ projection?: TicketProjection;
1638
+ /** The mandate the artifact chains to, where the policy needs it
1639
+ * (`min_binding`, `required_owners`, `profile_hashes`, `require_approval_signature`). */
1640
+ mandate?: Mandate;
1641
+ now?: number;
1642
+ /** The relying party acts on the artifact (freshness applies to `valid_until`). */
1643
+ asAuthorization?: boolean;
1644
+ /** Whether the ticket's action exceeded an approval-kind cap — known to a
1645
+ * party that holds the mandate's caps and the execution context. */
1646
+ aboveCap?: boolean;
1647
+ }
1648
+ /**
1649
+ * Evaluate a policy (protocol.md → *Verifier policy*, "Evaluation"). The
1650
+ * artifact's own signature is verified first under `trusted_issuers` where
1651
+ * present, else under any resolvable issuer.
1652
+ * @throws PolicyError with the line and the code the table assigns.
1653
+ */
1654
+ declare function evaluateVerifierPolicy(policy: VerifierPolicy, input: PolicyInput): Promise<void>;
1655
+
1656
+ /**
1657
+ * Proposal content — what the Authority Server receives of a proposal
1658
+ * (protocol.md → *Proposals — what the Authority Server receives*, v0.8).
1659
+ *
1660
+ * The content is the action's argument object, serialized as RFC 8785 JCS.
1661
+ * Its fingerprint, `proposalContentHash`, is what the AS compares, what the
1662
+ * ticket carries and what every `HAP-approval` signs. The content itself is
1663
+ * encrypted to its readers — the Mandate Owner and every signed decider —
1664
+ * with exactly the construction rule 3 pins byte for byte:
1665
+ *
1666
+ * - a fresh 32-byte content key (CEK) per proposal;
1667
+ * - AES-256-GCM under the CEK, random 12-byte IV, no associated data,
1668
+ * 16-byte tag; `ciphertext` = IV ‖ GCM ciphertext ‖ tag;
1669
+ * - per reader, HPKE (RFC 9180) base mode, DHKEM(X25519, HKDF-SHA256),
1670
+ * HKDF-SHA256, AES-256-GCM, empty `info`, the CEK sealed single-shot with
1671
+ * empty associated data; `enc` the 32-byte encapsulated key, `ct` the
1672
+ * 48-byte sealed CEK;
1673
+ * - every binary value base64url without padding; `encrypted_keys` keyed by
1674
+ * the reader's DID. The reader key is an X25519 key distinct from the
1675
+ * DID's signing key.
1676
+ *
1677
+ * The conformance vector `proposal-encryption.json` fixes the CEK, the IV
1678
+ * and the ephemeral keys so the output is reproducible; real use draws all
1679
+ * three at random, and the deterministic hooks exist for that test only.
1680
+ */
1681
+ interface ProposalContentEnvelope {
1682
+ ciphertext: string;
1683
+ encrypted_keys: Record<string, {
1684
+ ct: string;
1685
+ enc: string;
1686
+ }>;
1687
+ readers: string[];
1688
+ unreadable: string[];
1689
+ }
1690
+ interface ReaderKeyPair {
1691
+ /** Raw X25519 private key, 32 bytes. */
1692
+ privateKey: Uint8Array;
1693
+ /** Raw X25519 public key, 32 bytes. */
1694
+ publicKey: Uint8Array;
1695
+ }
1696
+ /** The content's JCS bytes, as a string (rule 1). */
1697
+ declare function proposalContentJcs(args: Record<string, unknown>): string;
1698
+ /** `proposalContentHash` — `"sha256:" + hex(sha256(JCS(content)))` (rule 4). */
1699
+ declare function computeProposalContentHash(args: Record<string, unknown>): string;
1700
+ /** A fresh reader key pair (X25519). */
1701
+ declare function generateReaderKeyPair(): Promise<ReaderKeyPair>;
1702
+ /** A reader key pair derived from input keying material (RFC 9180
1703
+ * DeriveKeyPair) — deterministic; for tests and vectors. */
1704
+ declare function deriveReaderKeyPair(ikm: Uint8Array): Promise<ReaderKeyPair>;
1705
+ interface EncryptOptions {
1706
+ /** Signed deciders for whom no reader key exists; listed, notified, unable to decide. */
1707
+ unreadable?: string[];
1708
+ /** Deterministic inputs — TESTS AND VECTORS ONLY. */
1709
+ deterministic?: {
1710
+ contentKey: Uint8Array;
1711
+ iv: Uint8Array;
1712
+ /** Per reader DID: the ephemeral key material for its HPKE wrap. */
1713
+ ephemeralIkm: Record<string, Uint8Array>;
1714
+ };
1715
+ }
1716
+ /**
1717
+ * Encrypt a proposal's content to its readers and compute its fingerprint.
1718
+ * @param readers Each reader's DID and raw X25519 reader public key.
1719
+ */
1720
+ declare function encryptProposalContent(args: Record<string, unknown>, readers: ReadonlyArray<{
1721
+ did: string;
1722
+ publicKey: Uint8Array;
1723
+ }>, opts?: EncryptOptions): Promise<{
1724
+ content: ProposalContentEnvelope;
1725
+ proposalContentHash: string;
1726
+ }>;
1727
+ /**
1728
+ * Decrypt a proposal's content as one reader. A reader whose HPKE open or
1729
+ * GCM decryption fails MUST treat the proposal as unreadable — never as
1730
+ * empty or partial content (rule 3); this throws rather than returning.
1731
+ * @returns the argument object and its JCS, so the caller recomputes the
1732
+ * fingerprint and refuses on a mismatch (rule 8).
1733
+ */
1734
+ declare function decryptProposalContent(content: ProposalContentEnvelope, readerDid: string, readerPrivateKey: Uint8Array): Promise<{
1735
+ arguments: Record<string, unknown>;
1736
+ contentJcs: string;
1737
+ proposalContentHash: string;
1738
+ }>;
1739
+ /**
1740
+ * The reader-set rules an Authority Server applies at submission (rule 2):
1741
+ * `readers` ∪ `unreadable` equals the owner plus the signed deciders;
1742
+ * `readers` equals the key set of `encrypted_keys`.
1743
+ * @throws HapError `PROPOSAL_APPROVERS_INVALID` | `PROPOSAL_NOT_ENCRYPTED`
1744
+ */
1745
+ declare function checkProposalReaders(content: ProposalContentEnvelope, expected: {
1746
+ ownerDid: string;
1747
+ deciders: readonly string[];
1748
+ }): void;
1749
+
1750
+ /**
1751
+ * Pure checks both enforcement points apply around issuance and execution
1752
+ * (v0.8). An Authority Server composes them into its stage order
1753
+ * (protocol.md → *Mandate Issuance & Validation*, *Ticket Issuance*); a
1754
+ * Gatekeeper applies the execution-side ones locally (*Validation Steps*).
1755
+ * They live here so the two cannot disagree about what a value means.
1756
+ */
1757
+
1758
+ /**
1759
+ * `execution_context_hash` — sha256 over the JCS of the profile's
1760
+ * `executionContextSchema`, or of `{}` when the profile has none (protocol.md
1761
+ * → *Execution Context Schema*, stated in v0.8).
1762
+ */
1763
+ declare function computeExecutionContextHash(profile: Pick<AgentProfile, 'executionContextSchema'>): string;
1764
+ /** The execution-context fields some bound of the profile reads
1765
+ * (`boundType.of` of every `per_transaction` and `cumulative_sum` bound). */
1766
+ declare function boundReadFields(profile: AgentProfile): Set<string>;
1767
+ /**
1768
+ * Build the wire execution context (*Validation Steps*, step 11; *Ticket
1769
+ * Request Schema*, rules 7–8): only what a bound reads travels. `action_type`
1770
+ * travels as the request's own `actionType`, scope values never travel,
1771
+ * `_`-prefixed keys are advisory metadata. A `null` value is no value.
1772
+ */
1773
+ declare function wireExecutionContext(execution: Record<string, unknown>, profile: AgentProfile): Record<string, unknown>;
1774
+ /**
1775
+ * Every value present for a field some bound reads MUST be a finite,
1776
+ * non-negative number (*Ticket Issuance*, stage 7; *Validation Steps*, step
1777
+ * 8). `null` counts as absent. A `_`-prefixed key is refused.
1778
+ * @throws HapError `MALFORMED_TICKET_REQUEST`
1779
+ */
1780
+ declare function checkExecutionValues(execution: Record<string, unknown>, profile: AgentProfile): void;
1781
+ /** Whether a cumulative bound governs this action type: its `appliesTo`
1782
+ * names it, or it has none (Bounds Schema rule 7). */
1783
+ declare function boundGoverns(def: ProfileBoundsField, actionType: string): boolean;
1784
+ /**
1785
+ * The `cumulative_sum` bounds that govern this action type and whose field is
1786
+ * absent from the execution context (*Ticket Issuance*, stage 7): each is a
1787
+ * `BOUND_EXCEEDED`, unless a committed above-cap approval covers the action.
1788
+ */
1789
+ declare function missingSumFields(execution: Record<string, unknown>, profile: AgentProfile, actionType: string): Array<{
1790
+ bound: string;
1791
+ field: string;
1792
+ }>;
1793
+ /**
1794
+ * Bounds values against the profile at issuance (*Mandate Issuance*, stage
1795
+ * 4): a number where the field is numeric (`"100"` is refused), no higher
1796
+ * than a declared `maximum`, and an `enum` value in its `values`.
1797
+ * @throws HapError `BOUNDS_INVALID_VALUE`
1798
+ */
1799
+ declare function checkBoundsValues(bounds: AgentBoundsParams, profile: AgentProfile): void;
1800
+ /** A group's caps for one profile, as the AS holds them. */
1801
+ interface GroupCaps {
1802
+ /** Caps a proposal may lift (`approval` kind) — numeric per_transaction fields only. */
1803
+ approval?: Record<string, number>;
1804
+ /** Hard caps: refused at signing, never routed. Numeric or enum values. */
1805
+ hard?: Record<string, number | string>;
1806
+ }
1807
+ /**
1808
+ * Whether a bound value exceeds a cap. For an `enum` bound the cap is one of
1809
+ * its values, and a value that comes EARLIER in the profile's `values` order
1810
+ * (more permissive) exceeds it (Bounds Schema rule 10; *Group ceilings*).
1811
+ */
1812
+ declare function exceedsCap(value: string | number, cap: string | number, def: ProfileBoundsField): boolean;
1813
+ /**
1814
+ * Hard group ceilings at signing (*Group ceilings*; *Group roles*, rule 5):
1815
+ * a cap on a bound the Gatekeeper alone enforces (`enforcedBy: "gatekeeper"`)
1816
+ * or on an `enum` bound is always hard, as is every cap in `hard`; a bound
1817
+ * above one, or an optional bound omitted where a cap exists, is refused.
1818
+ * Caps in `approval` on a cumulative bound are hard too (Commitment Modes,
1819
+ * rule 7). An omitted REQUIRED bound is `MALFORMED_MANDATE`, checked
1820
+ * elsewhere.
1821
+ * @throws HapError `GROUP_CAP_EXCEEDED`
1822
+ */
1823
+ declare function checkGroupCeilings(bounds: AgentBoundsParams, profile: AgentProfile, caps: GroupCaps): void;
1824
+ /**
1825
+ * The above-cap configuration a mandate request or a group supplies
1826
+ * (*Commitment Modes*, rule 7; *Group roles*, rules 2 and 4; Mandate rule
1827
+ * 10): caps name numeric `per_transaction` bounds of the profile and never
1828
+ * `profile`; approvers are key-bearing DIDs; outside a group the count is an
1829
+ * integer ≥ 1 and no greater than the approvers other than the owner.
1830
+ * @throws HapError `ABOVE_CAP_CONFIG_INVALID`
1831
+ */
1832
+ declare function validateAboveCapConfig(config: {
1833
+ caps?: Record<string, number>;
1834
+ approvers?: readonly string[];
1835
+ approvals?: number;
1836
+ }, profile: AgentProfile, opts: {
1837
+ ownerDid: string;
1838
+ inGroup: boolean;
1839
+ }): void;
1840
+ /**
1841
+ * Whether a request exceeds an approval-kind cap of the mandate, or — v0.8 —
1842
+ * the field the cap reads is absent (an action of unknown amount is above
1843
+ * the cap). Returns the offending caps.
1844
+ */
1845
+ declare function capsExceeded(execution: Record<string, unknown>, mandate: Pick<MandatePayload, 'above_cap_caps'>, profile: AgentProfile): Array<{
1846
+ bound: string;
1847
+ field: string;
1848
+ cap: number;
1849
+ value: unknown;
1850
+ }>;
1851
+
1852
+ /**
1853
+ * Gatekeeper — local (Phase 1) verification for bounded execution (v0.8;
1854
+ * v0.7 mandates verified under their own rules).
1411
1855
  *
1412
1856
  * protocol.md → *Validation Steps* (Phase 1, never reaches the wire):
1413
1857
  * 1. Resolve profile from bounds.profile
@@ -1501,4 +1945,4 @@ declare function decodeDidKey(did: string): Uint8Array;
1501
1945
  /** True iff the DID decodes as a key-bearing Ed25519 did:key. Never throws. */
1502
1946
  declare function isKeyBearingDid(did: string): boolean;
1503
1947
 
1504
- export { type AgentBoundsParams, type AgentProfile, type AgentScopeParams, type ApprovalObject, type BoundType, CanonicalValueError, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type ContentFieldKind, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type FieldConstraint, type FieldUnit, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, HapError, type HapErrorCode, MANDATE_ERROR_CODES, type Mandate, type MandateHeader, type MandateOwnerEntry, type MandatePayload, type MandateProjection, type OwnerSignatureBinding, OwnerSignatureError, PROTOCOL_VERSION, type ProfileBoundsField, type ProfileOwnerSignatureFloor, type ProfileScopeField, type ProfileValidationError, SIGNABLE_COMMITMENT_MODES, SUPPORTED_VERSIONS, type SignableCommitmentMode, type SigningSurface, type Subject, type SubjectValidation, TICKET_ERROR_CODES, type TicketPayload, type VerifyOptions, allowedCommitmentModes, approvalSigningBytes, bindingAppliesTo, boundActionTypes, buildMandateProjection, canonicalBounds, canonicalScope, canonicalize, canonicalizeText, checkDiscloseSubset, checkMandateExpiry, checkOwnerSignatureRequirement, clearProfiles, computeBoundsHash, computeContentHash, computeFieldsContentHash, computeIntentDisclosureHash, computeIntentHash, computeProfileHash, computeScopeHash, contentCanonicalBytes, decodeDidKey, decodeMandateBlob, deriveIdentityLine, encodeDidKey, encodeMandateBlob, filesFields, fromBase64Url, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, invalidFieldKindsReason, isCommitmentModeAllowed, isFieldBinding, isKeyBearingDid, listProfiles, mandateBlobId, negotiateVersion, projectionSigningBytes, publicTicketView, registerProfile, selectBoundFields, signApproval, signMandate, signMandateProjection, signTicket, toBase64Url, validateBoundsParams, validateMandateOwners, validateProfile, validateScopeParams, validateSubject, verify, verifyApproval, verifyMandate, verifyMandateSignature, verifyOwnerSignature, verifyOwnerSignatures, verifyTicketSignature };
1948
+ export { type AgentBoundsParams, type AgentProfile, type AgentScopeParams, type ApprovalObject, type ApprovalSignatureEntry, type ApproverSignatureEntry, BINDING_RANK, type BoundType, COMMITMENT_MODES, CanonicalValueError, type CommitmentMode, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type ContentFieldKind, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type EncryptOptions, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type FieldConstraint, type FieldUnit, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type GroupCaps, HapError, type HapErrorCode, ISSUABLE_VERSIONS, MANDATE_ERROR_CODES, type Mandate, type MandateHeader, type MandateOwnerEntry, type MandatePayload, type MandateProjection, type OwnerSignatureBinding, OwnerSignatureError, PROJECTION_MAX_VALIDITY_SECONDS, PROPOSAL_ERROR_CODES, PROTOCOL_VERSION, PolicyError, type PolicyInput, type ProfileBoundsField, type ProfileOwnerSignatureFloor, type ProfileScopeField, type ProfileValidationError, type ProposalContentEnvelope, type ProtocolVersion, type ReaderKeyPair, SIGNABLE_COMMITMENT_MODES, SUPPORTED_VERSIONS, type SignableCommitmentMode, type SigningSurface, type Subject, type SubjectValidation, type SupportedVersion, TICKET_ERROR_CODES, TICKET_FRESHNESS_SECONDS, type TicketPayload, type TicketProjection, type TicketRequestEcho, type VerifiedApprovals, type VerifierPolicy, type VerifyOptions, allowedCommitmentModes, approvalSigningBytes, approvalsSufficient, assertSupportedVersion, assertTicketShape, bindingAppliesTo, bindingRank, boundActionTypes, boundGoverns, boundReadFields, buildMandateProjection, buildTicketProjection, canonicalBounds, canonicalScope, canonicalize, canonicalizeText, capsExceeded, checkBoundsValues, checkDiscloseSubset, checkExecutionValues, checkGroupCeilings, checkMandateExpiry, checkOwnerSignatureRequirement, checkProposalReaders, clearProfiles, computeBoundsHash, computeContentHash, computeExecutionContextHash, computeFieldsContentHash, computeIntentDisclosureHash, computeIntentHash, computeProfileHash, computeProposalContentHash, computeScopeHash, contentCanonicalBytes, decodeDidKey, decodeMandateBlob, decryptProposalContent, deriveIdentityLine, deriveReaderKeyPair, encodeDidKey, encodeMandateBlob, encryptProposalContent, evaluateVerifierPolicy, exceedsCap, filesFields, fromBase64Url, generateReaderKeyPair, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, invalidFieldKindsReason, isCommitmentModeAllowed, isFieldBinding, isKeyBearingDid, isSupportedVersion, listProfiles, mandateBlobId, missingSumFields, negotiateVersion, projectionSigningBytes, proposalContentJcs, publicTicketView, reconstructApproval, registerProfile, selectBoundFields, signApproval, signMandate, signMandateProjection, signTicket, signTicketProjection, toBase64Url, validateAboveCapConfig, validateAboveCapFields, validateBoundsParams, validateMandateOwners, validateProfile, validateScopeParams, validateSubject, verify, verifyApproval, verifyMandate, verifyMandateSignature, verifyOwnerSignature, verifyOwnerSignatures, verifyProjectionOwner, verifyTicketAgainstRequest, verifyTicketApprovals, verifyTicketProjection, verifyTicketSignature, wireExecutionContext };