@humanagencyp/hap-core 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -610,7 +610,21 @@ interface ExecutionLogQuery {
610
610
  * Sum a field's values within a time window for a given profile.
611
611
  * Use field="_count" to count executions instead of summing a field.
612
612
  */
613
- sumByWindow(profileId: string, path: string, field: string, window: CumulativeWindow, now?: number): number;
613
+ sumByWindow(profileId: string, path: string, field: string, window: CumulativeWindow, now?: number,
614
+ /**
615
+ * Only count executions whose `execution.action_type` is one of these.
616
+ * Omitted → every execution counts (a bound that governs all action types).
617
+ *
618
+ * Consumption is partitioned by action type (*Cumulative Tracking* rule 4):
619
+ * a bound counts only the action types it governs. Without the filter a
620
+ * profile with several action types feeds every bound the combined total —
621
+ * sales' "orders per day" counted quotes and sends too, and its daily order
622
+ * value summed quote values. Use {@link boundActionTypes} to derive it.
623
+ *
624
+ * Implementations SHOULD count an entry that carries no `action_type`
625
+ * against every filter: an unknown action is not evidence of headroom.
626
+ */
627
+ actionTypes?: readonly string[]): number;
614
628
  }
615
629
  /**
616
630
  * Agent frame parameters — mixed types (strings and numbers).
@@ -1126,6 +1140,23 @@ declare function verifyAttestationV4(blob: string, publicKeyHex: string, expecte
1126
1140
  * @param executionLog - Optional execution log for resolving cumulative fields
1127
1141
  */
1128
1142
  declare function verify(request: GatekeeperRequest, publicKeyHex: string, now?: number, executionLog?: ExecutionLogQuery): Promise<GatekeeperResult>;
1143
+ /**
1144
+ * Which action types' executions count toward this cumulative bound's running
1145
+ * total. `undefined` means all of them.
1146
+ *
1147
+ * `boundGovernsAction` decides whether a bound applies to the current call; this
1148
+ * decides what the bound's total is made of. Both are needed: selecting the right
1149
+ * bound and then feeding it the profile's combined total is still wrong — sales'
1150
+ * "orders per day" was checked only on orders, but counted every quote and send.
1151
+ * Consumption is partitioned by action type (*Cumulative Tracking* rule 4), as
1152
+ * the Authority Server already does.
1153
+ *
1154
+ * Same answer, same order of preference as `boundGovernsAction`: the declared
1155
+ * `appliesTo`; else, for a count bound named for an action (`delete_daily_max`),
1156
+ * that action; else every action type. Exported so display surfaces report the
1157
+ * same totals the gate enforces.
1158
+ */
1159
+ declare function boundActionTypes(fieldName: string, fieldDef: ProfileBoundsField): readonly string[] | undefined;
1129
1160
 
1130
1161
  /**
1131
1162
  * Profile Registry — dynamically populated from git-hosted profile sources.
@@ -1322,4 +1353,4 @@ interface ReceiptPayload {
1322
1353
  */
1323
1354
  declare function verifyReceiptSignature(receipt: ReceiptPayload, publicKeyHex: string): Promise<void>;
1324
1355
 
1325
- 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, 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, validateContextParams, validateFrameParams, validateSubject, verify, verifyApproval, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash, verifyOwnerMandate, verifyOwnerMandates, verifyReceiptSignature };
1356
+ 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, validateContextParams, validateFrameParams, validateSubject, verify, verifyApproval, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash, verifyOwnerMandate, verifyOwnerMandates, verifyReceiptSignature };
package/dist/index.d.ts CHANGED
@@ -610,7 +610,21 @@ interface ExecutionLogQuery {
610
610
  * Sum a field's values within a time window for a given profile.
611
611
  * Use field="_count" to count executions instead of summing a field.
612
612
  */
613
- sumByWindow(profileId: string, path: string, field: string, window: CumulativeWindow, now?: number): number;
613
+ sumByWindow(profileId: string, path: string, field: string, window: CumulativeWindow, now?: number,
614
+ /**
615
+ * Only count executions whose `execution.action_type` is one of these.
616
+ * Omitted → every execution counts (a bound that governs all action types).
617
+ *
618
+ * Consumption is partitioned by action type (*Cumulative Tracking* rule 4):
619
+ * a bound counts only the action types it governs. Without the filter a
620
+ * profile with several action types feeds every bound the combined total —
621
+ * sales' "orders per day" counted quotes and sends too, and its daily order
622
+ * value summed quote values. Use {@link boundActionTypes} to derive it.
623
+ *
624
+ * Implementations SHOULD count an entry that carries no `action_type`
625
+ * against every filter: an unknown action is not evidence of headroom.
626
+ */
627
+ actionTypes?: readonly string[]): number;
614
628
  }
615
629
  /**
616
630
  * Agent frame parameters — mixed types (strings and numbers).
@@ -1126,6 +1140,23 @@ declare function verifyAttestationV4(blob: string, publicKeyHex: string, expecte
1126
1140
  * @param executionLog - Optional execution log for resolving cumulative fields
1127
1141
  */
1128
1142
  declare function verify(request: GatekeeperRequest, publicKeyHex: string, now?: number, executionLog?: ExecutionLogQuery): Promise<GatekeeperResult>;
1143
+ /**
1144
+ * Which action types' executions count toward this cumulative bound's running
1145
+ * total. `undefined` means all of them.
1146
+ *
1147
+ * `boundGovernsAction` decides whether a bound applies to the current call; this
1148
+ * decides what the bound's total is made of. Both are needed: selecting the right
1149
+ * bound and then feeding it the profile's combined total is still wrong — sales'
1150
+ * "orders per day" was checked only on orders, but counted every quote and send.
1151
+ * Consumption is partitioned by action type (*Cumulative Tracking* rule 4), as
1152
+ * the Authority Server already does.
1153
+ *
1154
+ * Same answer, same order of preference as `boundGovernsAction`: the declared
1155
+ * `appliesTo`; else, for a count bound named for an action (`delete_daily_max`),
1156
+ * that action; else every action type. Exported so display surfaces report the
1157
+ * same totals the gate enforces.
1158
+ */
1159
+ declare function boundActionTypes(fieldName: string, fieldDef: ProfileBoundsField): readonly string[] | undefined;
1129
1160
 
1130
1161
  /**
1131
1162
  * Profile Registry — dynamically populated from git-hosted profile sources.
@@ -1322,4 +1353,4 @@ interface ReceiptPayload {
1322
1353
  */
1323
1354
  declare function verifyReceiptSignature(receipt: ReceiptPayload, publicKeyHex: string): Promise<void>;
1324
1355
 
1325
- 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, 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, validateContextParams, validateFrameParams, validateSubject, verify, verifyApproval, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash, verifyOwnerMandate, verifyOwnerMandates, verifyReceiptSignature };
1356
+ 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, validateContextParams, validateFrameParams, validateSubject, verify, verifyApproval, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash, verifyOwnerMandate, verifyOwnerMandates, verifyReceiptSignature };
package/dist/index.js CHANGED
@@ -36,6 +36,7 @@ __export(index_exports, {
36
36
  approvalSigningBytes: () => approvalSigningBytes,
37
37
  attestationId: () => attestationId,
38
38
  bindingAppliesTo: () => bindingAppliesTo,
39
+ boundActionTypes: () => boundActionTypes,
39
40
  buildMandateProjection: () => buildMandateProjection,
40
41
  canonicalBounds: () => canonicalBounds,
41
42
  canonicalContext: () => canonicalContext,
@@ -785,6 +786,14 @@ function boundGovernsAction(fieldName, fieldDef, bt, actionType) {
785
786
  const matches = prefix === actionType || prefix.startsWith("transaction");
786
787
  return !namedForAnAction || matches;
787
788
  }
789
+ function boundActionTypes(fieldName, fieldDef) {
790
+ if (fieldDef.appliesTo) return fieldDef.appliesTo;
791
+ const bt = fieldDef.boundType;
792
+ if (!bt || bt.kind !== "cumulative_count") return void 0;
793
+ const prefix = fieldName.replace(/_(?:daily|monthly|weekly)_max$/, "");
794
+ if (prefix === fieldName || prefix.startsWith("transaction")) return void 0;
795
+ return [prefix];
796
+ }
788
797
  function checkBoundsV4(request, profile, executionLog, now) {
789
798
  const errors = [];
790
799
  if (!profile.boundsSchema) return errors;
@@ -845,7 +854,14 @@ function checkBoundsV4(request, profile, executionLog, now) {
845
854
  break;
846
855
  }
847
856
  if (!executionLog) break;
848
- const runningTotal = executionLog.sumByWindow(profileId, path, bt.of, bt.window, now);
857
+ const runningTotal = executionLog.sumByWindow(
858
+ profileId,
859
+ path,
860
+ bt.of,
861
+ bt.window,
862
+ now,
863
+ boundActionTypes(fieldName, fieldDef)
864
+ );
849
865
  const currentRaw = request.execution[bt.of];
850
866
  const current = typeof currentRaw === "number" ? currentRaw : currentRaw !== void 0 ? Number(currentRaw) : 0;
851
867
  const total = runningTotal + current;
@@ -872,7 +888,14 @@ function checkBoundsV4(request, profile, executionLog, now) {
872
888
  break;
873
889
  }
874
890
  if (!executionLog) break;
875
- const runningCount = executionLog.sumByWindow(profileId, path, "_count", bt.window, now);
891
+ const runningCount = executionLog.sumByWindow(
892
+ profileId,
893
+ path,
894
+ "_count",
895
+ bt.window,
896
+ now,
897
+ boundActionTypes(fieldName, fieldDef)
898
+ );
876
899
  const total = runningCount + 1;
877
900
  if (total > boundValue) {
878
901
  errors.push({
@@ -1207,6 +1230,7 @@ async function verifyReceiptSignature(receipt, publicKeyHex) {
1207
1230
  approvalSigningBytes,
1208
1231
  attestationId,
1209
1232
  bindingAppliesTo,
1233
+ boundActionTypes,
1210
1234
  buildMandateProjection,
1211
1235
  canonicalBounds,
1212
1236
  canonicalContext,
package/dist/index.mjs CHANGED
@@ -695,6 +695,14 @@ function boundGovernsAction(fieldName, fieldDef, bt, actionType) {
695
695
  const matches = prefix === actionType || prefix.startsWith("transaction");
696
696
  return !namedForAnAction || matches;
697
697
  }
698
+ function boundActionTypes(fieldName, fieldDef) {
699
+ if (fieldDef.appliesTo) return fieldDef.appliesTo;
700
+ const bt = fieldDef.boundType;
701
+ if (!bt || bt.kind !== "cumulative_count") return void 0;
702
+ const prefix = fieldName.replace(/_(?:daily|monthly|weekly)_max$/, "");
703
+ if (prefix === fieldName || prefix.startsWith("transaction")) return void 0;
704
+ return [prefix];
705
+ }
698
706
  function checkBoundsV4(request, profile, executionLog, now) {
699
707
  const errors = [];
700
708
  if (!profile.boundsSchema) return errors;
@@ -755,7 +763,14 @@ function checkBoundsV4(request, profile, executionLog, now) {
755
763
  break;
756
764
  }
757
765
  if (!executionLog) break;
758
- const runningTotal = executionLog.sumByWindow(profileId, path, bt.of, bt.window, now);
766
+ const runningTotal = executionLog.sumByWindow(
767
+ profileId,
768
+ path,
769
+ bt.of,
770
+ bt.window,
771
+ now,
772
+ boundActionTypes(fieldName, fieldDef)
773
+ );
759
774
  const currentRaw = request.execution[bt.of];
760
775
  const current = typeof currentRaw === "number" ? currentRaw : currentRaw !== void 0 ? Number(currentRaw) : 0;
761
776
  const total = runningTotal + current;
@@ -782,7 +797,14 @@ function checkBoundsV4(request, profile, executionLog, now) {
782
797
  break;
783
798
  }
784
799
  if (!executionLog) break;
785
- const runningCount = executionLog.sumByWindow(profileId, path, "_count", bt.window, now);
800
+ const runningCount = executionLog.sumByWindow(
801
+ profileId,
802
+ path,
803
+ "_count",
804
+ bt.window,
805
+ now,
806
+ boundActionTypes(fieldName, fieldDef)
807
+ );
786
808
  const total = runningCount + 1;
787
809
  if (total > boundValue) {
788
810
  errors.push({
@@ -1116,6 +1138,7 @@ export {
1116
1138
  approvalSigningBytes,
1117
1139
  attestationId,
1118
1140
  bindingAppliesTo,
1141
+ boundActionTypes,
1119
1142
  buildMandateProjection,
1120
1143
  canonicalBounds,
1121
1144
  canonicalContext,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanagencyp/hap-core",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Core types, cryptographic primitives, and verification logic for the Human Agency Protocol",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/gatekeeper.ts CHANGED
@@ -469,6 +469,36 @@ function boundGovernsAction(
469
469
  return !namedForAnAction || matches;
470
470
  }
471
471
 
472
+ /**
473
+ * Which action types' executions count toward this cumulative bound's running
474
+ * total. `undefined` means all of them.
475
+ *
476
+ * `boundGovernsAction` decides whether a bound applies to the current call; this
477
+ * decides what the bound's total is made of. Both are needed: selecting the right
478
+ * bound and then feeding it the profile's combined total is still wrong — sales'
479
+ * "orders per day" was checked only on orders, but counted every quote and send.
480
+ * Consumption is partitioned by action type (*Cumulative Tracking* rule 4), as
481
+ * the Authority Server already does.
482
+ *
483
+ * Same answer, same order of preference as `boundGovernsAction`: the declared
484
+ * `appliesTo`; else, for a count bound named for an action (`delete_daily_max`),
485
+ * that action; else every action type. Exported so display surfaces report the
486
+ * same totals the gate enforces.
487
+ */
488
+ export function boundActionTypes(
489
+ fieldName: string,
490
+ fieldDef: ProfileBoundsField,
491
+ ): readonly string[] | undefined {
492
+ if (fieldDef.appliesTo) return fieldDef.appliesTo;
493
+
494
+ const bt = fieldDef.boundType;
495
+ if (!bt || bt.kind !== 'cumulative_count') return undefined;
496
+
497
+ const prefix = fieldName.replace(/_(?:daily|monthly|weekly)_max$/, '');
498
+ if (prefix === fieldName || prefix.startsWith('transaction')) return undefined;
499
+ return [prefix];
500
+ }
501
+
472
502
  function checkBoundsV4(
473
503
  request: GatekeeperRequest,
474
504
  profile: AgentProfile,
@@ -552,7 +582,9 @@ function checkBoundsV4(
552
582
  }
553
583
  if (!executionLog) break;
554
584
 
555
- const runningTotal = executionLog.sumByWindow(profileId, path, bt.of, bt.window, now);
585
+ const runningTotal = executionLog.sumByWindow(
586
+ profileId, path, bt.of, bt.window, now, boundActionTypes(fieldName, fieldDef),
587
+ );
556
588
  const currentRaw = request.execution[bt.of];
557
589
  const current = typeof currentRaw === 'number'
558
590
  ? currentRaw
@@ -584,7 +616,9 @@ function checkBoundsV4(
584
616
  }
585
617
  if (!executionLog) break;
586
618
 
587
- const runningCount = executionLog.sumByWindow(profileId, path, '_count', bt.window, now);
619
+ const runningCount = executionLog.sumByWindow(
620
+ profileId, path, '_count', bt.window, now, boundActionTypes(fieldName, fieldDef),
621
+ );
588
622
  const total = runningCount + 1;
589
623
 
590
624
  if (total > boundValue) {
package/src/types.ts CHANGED
@@ -636,7 +636,27 @@ export interface ExecutionLogQuery {
636
636
  * Sum a field's values within a time window for a given profile.
637
637
  * Use field="_count" to count executions instead of summing a field.
638
638
  */
639
- sumByWindow(profileId: string, path: string, field: string, window: CumulativeWindow, now?: number): number;
639
+ sumByWindow(
640
+ profileId: string,
641
+ path: string,
642
+ field: string,
643
+ window: CumulativeWindow,
644
+ now?: number,
645
+ /**
646
+ * Only count executions whose `execution.action_type` is one of these.
647
+ * Omitted → every execution counts (a bound that governs all action types).
648
+ *
649
+ * Consumption is partitioned by action type (*Cumulative Tracking* rule 4):
650
+ * a bound counts only the action types it governs. Without the filter a
651
+ * profile with several action types feeds every bound the combined total —
652
+ * sales' "orders per day" counted quotes and sends too, and its daily order
653
+ * value summed quote values. Use {@link boundActionTypes} to derive it.
654
+ *
655
+ * Implementations SHOULD count an entry that carries no `action_type`
656
+ * against every filter: an unknown action is not evidence of headroom.
657
+ */
658
+ actionTypes?: readonly string[],
659
+ ): number;
640
660
  }
641
661
 
642
662
  // ─── Frame Types ─────────────────────────────────────────────────────────────