@humanagencyp/hap-core 0.11.0 → 0.11.2

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
@@ -315,6 +315,19 @@ interface ProfileBoundsField {
315
315
  * kinds, and the same boundType can carry different units.
316
316
  */
317
317
  unit?: FieldUnit;
318
+ /**
319
+ * The largest value a mandate may set for this (numeric) bound — a ceiling
320
+ * on what the owner can grant, declared by the profile. E.g. reporting@0.2
321
+ * `read_max_age_days: { maximum: 366 }`: no reporting mandate may read back
322
+ * further than a year.
323
+ *
324
+ * Distinct from the bound itself: the bound limits each ACTION; `maximum`
325
+ * limits the MANDATE. A value above it is not a valid mandate for this
326
+ * profile — the mandate editor caps its input, the issuing Authority Server
327
+ * should refuse it, and an enforcement point that reads the bound applies
328
+ * min(value, maximum). Absent → no ceiling.
329
+ */
330
+ maximum?: number;
318
331
  /** @deprecated v0.4: use boundType instead. */
319
332
  constraint?: FieldConstraint;
320
333
  /** @deprecated v0.4: use boundType: { kind: 'enum', values: [...] }. */
@@ -467,6 +480,11 @@ interface ExecutionPath {
467
480
  *
468
481
  * Supports both v0.3 (frameSchema) and v0.4 (boundsSchema + contextSchema).
469
482
  */
483
+ /**
484
+ * A commitment mode a person can choose when signing a mandate. (`review_above_cap`
485
+ * is not chosen by the signer — it follows from team caps.)
486
+ */
487
+ type SignableCommitmentMode = 'automatic' | 'review';
470
488
  interface AgentProfile {
471
489
  id: string;
472
490
  name?: string;
@@ -508,6 +526,18 @@ interface AgentProfile {
508
526
  * a forwarded message has usually lost the footer that carried it.
509
527
  */
510
528
  receipt_lookup?: boolean;
529
+ /**
530
+ * The commitment modes a mandate under this profile may be signed with.
531
+ * Absent → every mode (`automatic`, `review`), as before this field existed.
532
+ *
533
+ * Lets a profile be review-only: every action it governs then needs a
534
+ * person's approval, whatever the signer would have picked. The Authority
535
+ * Server refuses to sign a mandate whose `commitment_mode` is not listed;
536
+ * a mandate screen offers only the listed modes. Fail-closed: a declared
537
+ * list with no valid entry allows nothing — a malformed declaration never
538
+ * widens what a profile allows. See {@link allowedCommitmentModes}.
539
+ */
540
+ commitment_modes?: readonly SignableCommitmentMode[];
511
541
  /**
512
542
  * v0.3 frame schema (deprecated, kept for backward compat).
513
543
  * Used when boundsSchema is not present.
@@ -1081,6 +1111,31 @@ declare function computeBoundsHash(params: AgentBoundsParams, profile: AgentProf
1081
1111
  */
1082
1112
  declare function computeContextHash(params: AgentContextParams, profile: AgentProfile): string;
1083
1113
 
1114
+ /**
1115
+ * Which commitment modes a profile allows its mandates to be signed with
1116
+ * (`AgentProfile.commitment_modes`). One source for the Authority Server's
1117
+ * refusal and the mandate screen's choice, so the two cannot disagree.
1118
+ */
1119
+
1120
+ /** Every mode a signer can choose, in the order a mandate screen shows them. */
1121
+ declare const SIGNABLE_COMMITMENT_MODES: readonly SignableCommitmentMode[];
1122
+ /**
1123
+ * The modes a mandate under this profile may carry, in display order.
1124
+ * No declaration → all of them. A declaration is read fail-closed: entries
1125
+ * that are not a signable mode are dropped, so a malformed list can only
1126
+ * narrow — `[]`, `"review"` (not a list) or `["reveiw"]` allow nothing.
1127
+ */
1128
+ declare function allowedCommitmentModes(profile: AgentProfile): readonly SignableCommitmentMode[];
1129
+ /** Whether a mandate under this profile may be signed with `mode`. */
1130
+ 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
+
1084
1139
  /**
1085
1140
  * Attestation Encoding, Decoding, and Verification
1086
1141
  *
@@ -1401,4 +1456,4 @@ interface ReceiptPayload {
1401
1456
  */
1402
1457
  declare function verifyReceiptSignature(receipt: ReceiptPayload, publicKeyHex: string): Promise<void>;
1403
1458
 
1404
- export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type ApprovalObject, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, CanonicalValueError, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type MandateBinding, MandateError, type MandateProjection, type OwnerMandate, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ReceiptPayload, type ResolvedDomain, type SigningSurface, type Subject, type SubjectValidation, approvalSigningBytes, attestationId, bindingAppliesTo, boundActionTypes, buildMandateProjection, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFieldsContentHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, decodeDidKey, deriveIdentityLine, encodeAttestationBlob, encodeDidKey, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isFieldBinding, isKeyBearingDid, isV4Attestation, listProfiles, mandateSigningBytes, registerProfile, selectBoundFields, signApproval, signMandateProjection, validateBoundsParams, validateBoundsRequiredFor, validateContextParams, validateFrameParams, validateSubject, verify, verifyApproval, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash, verifyOwnerMandate, verifyOwnerMandates, verifyReceiptSignature };
1459
+ export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type ApprovalObject, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, CanonicalValueError, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type MandateBinding, MandateError, type MandateProjection, type OwnerMandate, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ReceiptPayload, type ResolvedDomain, SIGNABLE_COMMITMENT_MODES, type SignableCommitmentMode, type SigningSurface, type Subject, type SubjectValidation, allowedCommitmentModes, approvalSigningBytes, attestationId, bindingAppliesTo, boundActionTypes, buildMandateProjection, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFieldsContentHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, decodeDidKey, deriveIdentityLine, encodeAttestationBlob, encodeDidKey, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isCommitmentModeAllowed, isFieldBinding, isKeyBearingDid, isV4Attestation, listProfiles, mandateSigningBytes, registerProfile, selectBoundFields, signApproval, signMandateProjection, validateBoundsParams, validateBoundsRequiredFor, validateCommitmentModes, validateContextParams, validateFrameParams, validateSubject, verify, verifyApproval, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash, verifyOwnerMandate, verifyOwnerMandates, verifyReceiptSignature };
package/dist/index.d.ts CHANGED
@@ -315,6 +315,19 @@ interface ProfileBoundsField {
315
315
  * kinds, and the same boundType can carry different units.
316
316
  */
317
317
  unit?: FieldUnit;
318
+ /**
319
+ * The largest value a mandate may set for this (numeric) bound — a ceiling
320
+ * on what the owner can grant, declared by the profile. E.g. reporting@0.2
321
+ * `read_max_age_days: { maximum: 366 }`: no reporting mandate may read back
322
+ * further than a year.
323
+ *
324
+ * Distinct from the bound itself: the bound limits each ACTION; `maximum`
325
+ * limits the MANDATE. A value above it is not a valid mandate for this
326
+ * profile — the mandate editor caps its input, the issuing Authority Server
327
+ * should refuse it, and an enforcement point that reads the bound applies
328
+ * min(value, maximum). Absent → no ceiling.
329
+ */
330
+ maximum?: number;
318
331
  /** @deprecated v0.4: use boundType instead. */
319
332
  constraint?: FieldConstraint;
320
333
  /** @deprecated v0.4: use boundType: { kind: 'enum', values: [...] }. */
@@ -467,6 +480,11 @@ interface ExecutionPath {
467
480
  *
468
481
  * Supports both v0.3 (frameSchema) and v0.4 (boundsSchema + contextSchema).
469
482
  */
483
+ /**
484
+ * A commitment mode a person can choose when signing a mandate. (`review_above_cap`
485
+ * is not chosen by the signer — it follows from team caps.)
486
+ */
487
+ type SignableCommitmentMode = 'automatic' | 'review';
470
488
  interface AgentProfile {
471
489
  id: string;
472
490
  name?: string;
@@ -508,6 +526,18 @@ interface AgentProfile {
508
526
  * a forwarded message has usually lost the footer that carried it.
509
527
  */
510
528
  receipt_lookup?: boolean;
529
+ /**
530
+ * The commitment modes a mandate under this profile may be signed with.
531
+ * Absent → every mode (`automatic`, `review`), as before this field existed.
532
+ *
533
+ * Lets a profile be review-only: every action it governs then needs a
534
+ * person's approval, whatever the signer would have picked. The Authority
535
+ * Server refuses to sign a mandate whose `commitment_mode` is not listed;
536
+ * a mandate screen offers only the listed modes. Fail-closed: a declared
537
+ * list with no valid entry allows nothing — a malformed declaration never
538
+ * widens what a profile allows. See {@link allowedCommitmentModes}.
539
+ */
540
+ commitment_modes?: readonly SignableCommitmentMode[];
511
541
  /**
512
542
  * v0.3 frame schema (deprecated, kept for backward compat).
513
543
  * Used when boundsSchema is not present.
@@ -1081,6 +1111,31 @@ declare function computeBoundsHash(params: AgentBoundsParams, profile: AgentProf
1081
1111
  */
1082
1112
  declare function computeContextHash(params: AgentContextParams, profile: AgentProfile): string;
1083
1113
 
1114
+ /**
1115
+ * Which commitment modes a profile allows its mandates to be signed with
1116
+ * (`AgentProfile.commitment_modes`). One source for the Authority Server's
1117
+ * refusal and the mandate screen's choice, so the two cannot disagree.
1118
+ */
1119
+
1120
+ /** Every mode a signer can choose, in the order a mandate screen shows them. */
1121
+ declare const SIGNABLE_COMMITMENT_MODES: readonly SignableCommitmentMode[];
1122
+ /**
1123
+ * The modes a mandate under this profile may carry, in display order.
1124
+ * No declaration → all of them. A declaration is read fail-closed: entries
1125
+ * that are not a signable mode are dropped, so a malformed list can only
1126
+ * narrow — `[]`, `"review"` (not a list) or `["reveiw"]` allow nothing.
1127
+ */
1128
+ declare function allowedCommitmentModes(profile: AgentProfile): readonly SignableCommitmentMode[];
1129
+ /** Whether a mandate under this profile may be signed with `mode`. */
1130
+ 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
+
1084
1139
  /**
1085
1140
  * Attestation Encoding, Decoding, and Verification
1086
1141
  *
@@ -1401,4 +1456,4 @@ interface ReceiptPayload {
1401
1456
  */
1402
1457
  declare function verifyReceiptSignature(receipt: ReceiptPayload, publicKeyHex: string): Promise<void>;
1403
1458
 
1404
- export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type ApprovalObject, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, CanonicalValueError, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type MandateBinding, MandateError, type MandateProjection, type OwnerMandate, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ReceiptPayload, type ResolvedDomain, type SigningSurface, type Subject, type SubjectValidation, approvalSigningBytes, attestationId, bindingAppliesTo, boundActionTypes, buildMandateProjection, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFieldsContentHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, decodeDidKey, deriveIdentityLine, encodeAttestationBlob, encodeDidKey, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isFieldBinding, isKeyBearingDid, isV4Attestation, listProfiles, mandateSigningBytes, registerProfile, selectBoundFields, signApproval, signMandateProjection, validateBoundsParams, validateBoundsRequiredFor, validateContextParams, validateFrameParams, validateSubject, verify, verifyApproval, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash, verifyOwnerMandate, verifyOwnerMandates, verifyReceiptSignature };
1459
+ export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type ApprovalObject, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, CanonicalValueError, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type MandateBinding, MandateError, type MandateProjection, type OwnerMandate, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ReceiptPayload, type ResolvedDomain, SIGNABLE_COMMITMENT_MODES, type SignableCommitmentMode, type SigningSurface, type Subject, type SubjectValidation, allowedCommitmentModes, approvalSigningBytes, attestationId, bindingAppliesTo, boundActionTypes, buildMandateProjection, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFieldsContentHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, decodeDidKey, deriveIdentityLine, encodeAttestationBlob, encodeDidKey, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isCommitmentModeAllowed, isFieldBinding, isKeyBearingDid, isV4Attestation, listProfiles, mandateSigningBytes, registerProfile, selectBoundFields, signApproval, signMandateProjection, validateBoundsParams, validateBoundsRequiredFor, validateCommitmentModes, validateContextParams, validateFrameParams, validateSubject, verify, verifyApproval, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash, verifyOwnerMandate, verifyOwnerMandates, verifyReceiptSignature };
package/dist/index.js CHANGED
@@ -33,6 +33,8 @@ __export(index_exports, {
33
33
  CanonicalValueError: () => CanonicalValueError,
34
34
  ContentBindingError: () => ContentBindingError,
35
35
  MandateError: () => MandateError,
36
+ SIGNABLE_COMMITMENT_MODES: () => SIGNABLE_COMMITMENT_MODES,
37
+ allowedCommitmentModes: () => allowedCommitmentModes,
36
38
  approvalSigningBytes: () => approvalSigningBytes,
37
39
  attestationId: () => attestationId,
38
40
  bindingAppliesTo: () => bindingAppliesTo,
@@ -62,6 +64,7 @@ __export(index_exports, {
62
64
  getAllProfiles: () => getAllProfiles,
63
65
  getProfile: () => getProfile,
64
66
  intentDisclosureCanonicalBytes: () => intentDisclosureCanonicalBytes,
67
+ isCommitmentModeAllowed: () => isCommitmentModeAllowed,
65
68
  isFieldBinding: () => isFieldBinding,
66
69
  isKeyBearingDid: () => isKeyBearingDid,
67
70
  isV4Attestation: () => isV4Attestation,
@@ -73,6 +76,7 @@ __export(index_exports, {
73
76
  signMandateProjection: () => signMandateProjection,
74
77
  validateBoundsParams: () => validateBoundsParams,
75
78
  validateBoundsRequiredFor: () => validateBoundsRequiredFor,
79
+ validateCommitmentModes: () => validateCommitmentModes,
76
80
  validateContextParams: () => validateContextParams,
77
81
  validateFrameParams: () => validateFrameParams,
78
82
  validateSubject: () => validateSubject,
@@ -478,6 +482,33 @@ function computeContextHash(params, profile) {
478
482
  return `sha256:${hash}`;
479
483
  }
480
484
 
485
+ // src/commitment-modes.ts
486
+ var SIGNABLE_COMMITMENT_MODES = ["review", "automatic"];
487
+ var isSignable = (v) => typeof v === "string" && SIGNABLE_COMMITMENT_MODES.includes(v);
488
+ function allowedCommitmentModes(profile) {
489
+ const declared = profile.commitment_modes;
490
+ if (declared === void 0) return SIGNABLE_COMMITMENT_MODES;
491
+ if (!Array.isArray(declared)) return [];
492
+ return SIGNABLE_COMMITMENT_MODES.filter((m) => declared.includes(m));
493
+ }
494
+ function isCommitmentModeAllowed(profile, mode) {
495
+ return isSignable(mode) && allowedCommitmentModes(profile).includes(mode);
496
+ }
497
+ function validateCommitmentModes(profile) {
498
+ const declared = profile.commitment_modes;
499
+ if (declared === void 0) return [];
500
+ if (!Array.isArray(declared)) return ["commitment_modes must be a list of modes."];
501
+ const errors = [];
502
+ if (declared.length === 0) errors.push("commitment_modes is empty \u2014 no mandate could ever be signed under this profile.");
503
+ const seen = /* @__PURE__ */ new Set();
504
+ for (const m of declared) {
505
+ if (!isSignable(m)) errors.push(`commitment_modes: "${String(m)}" is not a mode a signer can choose (${SIGNABLE_COMMITMENT_MODES.join(", ")}).`);
506
+ if (seen.has(m)) errors.push(`commitment_modes: "${String(m)}" appears twice.`);
507
+ seen.add(m);
508
+ }
509
+ return errors;
510
+ }
511
+
481
512
  // src/attestation.ts
482
513
  var import_crypto4 = require("crypto");
483
514
  var ed = __toESM(require("@noble/ed25519"));
@@ -1276,6 +1307,8 @@ async function verifyReceiptSignature(receipt, publicKeyHex) {
1276
1307
  CanonicalValueError,
1277
1308
  ContentBindingError,
1278
1309
  MandateError,
1310
+ SIGNABLE_COMMITMENT_MODES,
1311
+ allowedCommitmentModes,
1279
1312
  approvalSigningBytes,
1280
1313
  attestationId,
1281
1314
  bindingAppliesTo,
@@ -1305,6 +1338,7 @@ async function verifyReceiptSignature(receipt, publicKeyHex) {
1305
1338
  getAllProfiles,
1306
1339
  getProfile,
1307
1340
  intentDisclosureCanonicalBytes,
1341
+ isCommitmentModeAllowed,
1308
1342
  isFieldBinding,
1309
1343
  isKeyBearingDid,
1310
1344
  isV4Attestation,
@@ -1316,6 +1350,7 @@ async function verifyReceiptSignature(receipt, publicKeyHex) {
1316
1350
  signMandateProjection,
1317
1351
  validateBoundsParams,
1318
1352
  validateBoundsRequiredFor,
1353
+ validateCommitmentModes,
1319
1354
  validateContextParams,
1320
1355
  validateFrameParams,
1321
1356
  validateSubject,
package/dist/index.mjs CHANGED
@@ -386,6 +386,33 @@ function computeContextHash(params, profile) {
386
386
  return `sha256:${hash}`;
387
387
  }
388
388
 
389
+ // src/commitment-modes.ts
390
+ var SIGNABLE_COMMITMENT_MODES = ["review", "automatic"];
391
+ var isSignable = (v) => typeof v === "string" && SIGNABLE_COMMITMENT_MODES.includes(v);
392
+ function allowedCommitmentModes(profile) {
393
+ const declared = profile.commitment_modes;
394
+ if (declared === void 0) return SIGNABLE_COMMITMENT_MODES;
395
+ if (!Array.isArray(declared)) return [];
396
+ return SIGNABLE_COMMITMENT_MODES.filter((m) => declared.includes(m));
397
+ }
398
+ function isCommitmentModeAllowed(profile, mode) {
399
+ return isSignable(mode) && allowedCommitmentModes(profile).includes(mode);
400
+ }
401
+ function validateCommitmentModes(profile) {
402
+ const declared = profile.commitment_modes;
403
+ if (declared === void 0) return [];
404
+ if (!Array.isArray(declared)) return ["commitment_modes must be a list of modes."];
405
+ const errors = [];
406
+ if (declared.length === 0) errors.push("commitment_modes is empty \u2014 no mandate could ever be signed under this profile.");
407
+ const seen = /* @__PURE__ */ new Set();
408
+ for (const m of declared) {
409
+ if (!isSignable(m)) errors.push(`commitment_modes: "${String(m)}" is not a mode a signer can choose (${SIGNABLE_COMMITMENT_MODES.join(", ")}).`);
410
+ if (seen.has(m)) errors.push(`commitment_modes: "${String(m)}" appears twice.`);
411
+ seen.add(m);
412
+ }
413
+ return errors;
414
+ }
415
+
389
416
  // src/attestation.ts
390
417
  import { createHash as createHash4 } from "crypto";
391
418
  import * as ed from "@noble/ed25519";
@@ -1183,6 +1210,8 @@ export {
1183
1210
  CanonicalValueError,
1184
1211
  ContentBindingError,
1185
1212
  MandateError,
1213
+ SIGNABLE_COMMITMENT_MODES,
1214
+ allowedCommitmentModes,
1186
1215
  approvalSigningBytes,
1187
1216
  attestationId,
1188
1217
  bindingAppliesTo,
@@ -1212,6 +1241,7 @@ export {
1212
1241
  getAllProfiles,
1213
1242
  getProfile,
1214
1243
  intentDisclosureCanonicalBytes,
1244
+ isCommitmentModeAllowed,
1215
1245
  isFieldBinding,
1216
1246
  isKeyBearingDid,
1217
1247
  isV4Attestation,
@@ -1223,6 +1253,7 @@ export {
1223
1253
  signMandateProjection,
1224
1254
  validateBoundsParams,
1225
1255
  validateBoundsRequiredFor,
1256
+ validateCommitmentModes,
1226
1257
  validateContextParams,
1227
1258
  validateFrameParams,
1228
1259
  validateSubject,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanagencyp/hap-core",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Core types, cryptographic primitives, and verification logic for the Human Agency Protocol",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Which commitment modes a profile allows its mandates to be signed with
3
+ * (`AgentProfile.commitment_modes`). One source for the Authority Server's
4
+ * refusal and the mandate screen's choice, so the two cannot disagree.
5
+ */
6
+ import type { AgentProfile, SignableCommitmentMode } from './types';
7
+
8
+ /** Every mode a signer can choose, in the order a mandate screen shows them. */
9
+ export const SIGNABLE_COMMITMENT_MODES: readonly SignableCommitmentMode[] = ['review', 'automatic'];
10
+
11
+ const isSignable = (v: unknown): v is SignableCommitmentMode =>
12
+ typeof v === 'string' && (SIGNABLE_COMMITMENT_MODES as readonly string[]).includes(v);
13
+
14
+ /**
15
+ * The modes a mandate under this profile may carry, in display order.
16
+ * No declaration → all of them. A declaration is read fail-closed: entries
17
+ * that are not a signable mode are dropped, so a malformed list can only
18
+ * narrow — `[]`, `"review"` (not a list) or `["reveiw"]` allow nothing.
19
+ */
20
+ export function allowedCommitmentModes(profile: AgentProfile): readonly SignableCommitmentMode[] {
21
+ const declared = (profile as { commitment_modes?: unknown }).commitment_modes;
22
+ if (declared === undefined) return SIGNABLE_COMMITMENT_MODES;
23
+ if (!Array.isArray(declared)) return [];
24
+ return SIGNABLE_COMMITMENT_MODES.filter((m) => declared.includes(m));
25
+ }
26
+
27
+ /** Whether a mandate under this profile may be signed with `mode`. */
28
+ export function isCommitmentModeAllowed(profile: AgentProfile, mode: string): boolean {
29
+ return isSignable(mode) && allowedCommitmentModes(profile).includes(mode);
30
+ }
31
+
32
+ /**
33
+ * Authoring check for a profile's `commitment_modes` declaration — for
34
+ * hap-profiles CI, an authoring tool or a test. Like `validateBoundsRequiredFor`
35
+ * it is not wired into profile loading; enforcement reads the declaration
36
+ * fail-closed through `allowedCommitmentModes`. Empty array = nothing to fix.
37
+ */
38
+ export function validateCommitmentModes(profile: AgentProfile): string[] {
39
+ const declared = (profile as { commitment_modes?: unknown }).commitment_modes;
40
+ if (declared === undefined) return [];
41
+ if (!Array.isArray(declared)) return ['commitment_modes must be a list of modes.'];
42
+ const errors: string[] = [];
43
+ if (declared.length === 0) errors.push('commitment_modes is empty — no mandate could ever be signed under this profile.');
44
+ const seen = new Set<unknown>();
45
+ for (const m of declared) {
46
+ if (!isSignable(m)) errors.push(`commitment_modes: "${String(m)}" is not a mode a signer can choose (${SIGNABLE_COMMITMENT_MODES.join(', ')}).`);
47
+ if (seen.has(m)) errors.push(`commitment_modes: "${String(m)}" appears twice.`);
48
+ seen.add(m);
49
+ }
50
+ return errors;
51
+ }
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export * from './content-binding';
10
10
  export * from './intent-disclosure';
11
11
  export * from './identity';
12
12
  export * from './frame';
13
+ export * from './commitment-modes';
13
14
  export * from './attestation';
14
15
  export * from './gatekeeper';
15
16
  export * from './profiles';
package/src/types.ts CHANGED
@@ -320,6 +320,19 @@ export interface ProfileBoundsField {
320
320
  * kinds, and the same boundType can carry different units.
321
321
  */
322
322
  unit?: FieldUnit;
323
+ /**
324
+ * The largest value a mandate may set for this (numeric) bound — a ceiling
325
+ * on what the owner can grant, declared by the profile. E.g. reporting@0.2
326
+ * `read_max_age_days: { maximum: 366 }`: no reporting mandate may read back
327
+ * further than a year.
328
+ *
329
+ * Distinct from the bound itself: the bound limits each ACTION; `maximum`
330
+ * limits the MANDATE. A value above it is not a valid mandate for this
331
+ * profile — the mandate editor caps its input, the issuing Authority Server
332
+ * should refuse it, and an enforcement point that reads the bound applies
333
+ * min(value, maximum). Absent → no ceiling.
334
+ */
335
+ maximum?: number;
323
336
  /** @deprecated v0.4: use boundType instead. */
324
337
  constraint?: FieldConstraint;
325
338
  /** @deprecated v0.4: use boundType: { kind: 'enum', values: [...] }. */
@@ -479,6 +492,12 @@ export interface ExecutionPath {
479
492
  *
480
493
  * Supports both v0.3 (frameSchema) and v0.4 (boundsSchema + contextSchema).
481
494
  */
495
+ /**
496
+ * A commitment mode a person can choose when signing a mandate. (`review_above_cap`
497
+ * is not chosen by the signer — it follows from team caps.)
498
+ */
499
+ export type SignableCommitmentMode = 'automatic' | 'review';
500
+
482
501
  export interface AgentProfile {
483
502
  id: string;
484
503
  name?: string;
@@ -523,6 +542,19 @@ export interface AgentProfile {
523
542
  */
524
543
  receipt_lookup?: boolean;
525
544
 
545
+ /**
546
+ * The commitment modes a mandate under this profile may be signed with.
547
+ * Absent → every mode (`automatic`, `review`), as before this field existed.
548
+ *
549
+ * Lets a profile be review-only: every action it governs then needs a
550
+ * person's approval, whatever the signer would have picked. The Authority
551
+ * Server refuses to sign a mandate whose `commitment_mode` is not listed;
552
+ * a mandate screen offers only the listed modes. Fail-closed: a declared
553
+ * list with no valid entry allows nothing — a malformed declaration never
554
+ * widens what a profile allows. See {@link allowedCommitmentModes}.
555
+ */
556
+ commitment_modes?: readonly SignableCommitmentMode[];
557
+
526
558
  /**
527
559
  * v0.3 frame schema (deprecated, kept for backward compat).
528
560
  * Used when boundsSchema is not present.