agent-passport-system 7.0.0 → 7.2.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.
Files changed (198) hide show
  1. package/SECURITY.md +11 -0
  2. package/dist/src/core/tool-integrity.d.ts +13 -0
  3. package/dist/src/core/tool-integrity.d.ts.map +1 -1
  4. package/dist/src/core/tool-integrity.js +1 -1
  5. package/dist/src/core/tool-integrity.js.map +1 -1
  6. package/dist/src/index.d.ts +52 -2
  7. package/dist/src/index.d.ts.map +1 -1
  8. package/dist/src/index.js +282 -1
  9. package/dist/src/index.js.map +1 -1
  10. package/dist/src/v2/activation/canonical.d.ts +40 -0
  11. package/dist/src/v2/activation/canonical.d.ts.map +1 -0
  12. package/dist/src/v2/activation/canonical.js +64 -0
  13. package/dist/src/v2/activation/canonical.js.map +1 -0
  14. package/dist/src/v2/activation/index.d.ts +4 -0
  15. package/dist/src/v2/activation/index.d.ts.map +1 -0
  16. package/dist/src/v2/activation/index.js +11 -0
  17. package/dist/src/v2/activation/index.js.map +1 -0
  18. package/dist/src/v2/activation/types.d.ts +268 -0
  19. package/dist/src/v2/activation/types.d.ts.map +1 -0
  20. package/dist/src/v2/activation/types.js +198 -0
  21. package/dist/src/v2/activation/types.js.map +1 -0
  22. package/dist/src/v2/activation/verify.d.ts +132 -0
  23. package/dist/src/v2/activation/verify.d.ts.map +1 -0
  24. package/dist/src/v2/activation/verify.js +406 -0
  25. package/dist/src/v2/activation/verify.js.map +1 -0
  26. package/dist/src/v2/authority-revocation/canonical.d.ts +46 -0
  27. package/dist/src/v2/authority-revocation/canonical.d.ts.map +1 -0
  28. package/dist/src/v2/authority-revocation/canonical.js +68 -0
  29. package/dist/src/v2/authority-revocation/canonical.js.map +1 -0
  30. package/dist/src/v2/authority-revocation/index.d.ts +9 -0
  31. package/dist/src/v2/authority-revocation/index.d.ts.map +1 -0
  32. package/dist/src/v2/authority-revocation/index.js +11 -0
  33. package/dist/src/v2/authority-revocation/index.js.map +1 -0
  34. package/dist/src/v2/authority-revocation/issue.d.ts +54 -0
  35. package/dist/src/v2/authority-revocation/issue.d.ts.map +1 -0
  36. package/dist/src/v2/authority-revocation/issue.js +111 -0
  37. package/dist/src/v2/authority-revocation/issue.js.map +1 -0
  38. package/dist/src/v2/authority-revocation/record.d.ts +69 -0
  39. package/dist/src/v2/authority-revocation/record.d.ts.map +1 -0
  40. package/dist/src/v2/authority-revocation/record.js +51 -0
  41. package/dist/src/v2/authority-revocation/record.js.map +1 -0
  42. package/dist/src/v2/authority-revocation/resolver.d.ts +30 -0
  43. package/dist/src/v2/authority-revocation/resolver.d.ts.map +1 -0
  44. package/dist/src/v2/authority-revocation/resolver.js +52 -0
  45. package/dist/src/v2/authority-revocation/resolver.js.map +1 -0
  46. package/dist/src/v2/authority-revocation/schema.d.ts +15 -0
  47. package/dist/src/v2/authority-revocation/schema.d.ts.map +1 -0
  48. package/dist/src/v2/authority-revocation/schema.js +107 -0
  49. package/dist/src/v2/authority-revocation/schema.js.map +1 -0
  50. package/dist/src/v2/authority-revocation/store.d.ts +55 -0
  51. package/dist/src/v2/authority-revocation/store.d.ts.map +1 -0
  52. package/dist/src/v2/authority-revocation/store.js +69 -0
  53. package/dist/src/v2/authority-revocation/store.js.map +1 -0
  54. package/dist/src/v2/authority-revocation/types.d.ts +136 -0
  55. package/dist/src/v2/authority-revocation/types.d.ts.map +1 -0
  56. package/dist/src/v2/authority-revocation/types.js +11 -0
  57. package/dist/src/v2/authority-revocation/types.js.map +1 -0
  58. package/dist/src/v2/authority-revocation/verify.d.ts +38 -0
  59. package/dist/src/v2/authority-revocation/verify.d.ts.map +1 -0
  60. package/dist/src/v2/authority-revocation/verify.js +139 -0
  61. package/dist/src/v2/authority-revocation/verify.js.map +1 -0
  62. package/dist/src/v2/authority-state/fencing.d.ts +49 -0
  63. package/dist/src/v2/authority-state/fencing.d.ts.map +1 -0
  64. package/dist/src/v2/authority-state/fencing.js +92 -0
  65. package/dist/src/v2/authority-state/fencing.js.map +1 -0
  66. package/dist/src/v2/authority-state/index.d.ts +7 -0
  67. package/dist/src/v2/authority-state/index.d.ts.map +1 -0
  68. package/dist/src/v2/authority-state/index.js +14 -0
  69. package/dist/src/v2/authority-state/index.js.map +1 -0
  70. package/dist/src/v2/authority-state/marker.d.ts +70 -0
  71. package/dist/src/v2/authority-state/marker.d.ts.map +1 -0
  72. package/dist/src/v2/authority-state/marker.js +122 -0
  73. package/dist/src/v2/authority-state/marker.js.map +1 -0
  74. package/dist/src/v2/authority-state/report.d.ts +55 -0
  75. package/dist/src/v2/authority-state/report.d.ts.map +1 -0
  76. package/dist/src/v2/authority-state/report.js +51 -0
  77. package/dist/src/v2/authority-state/report.js.map +1 -0
  78. package/dist/src/v2/authority-state/retained.d.ts +82 -0
  79. package/dist/src/v2/authority-state/retained.d.ts.map +1 -0
  80. package/dist/src/v2/authority-state/retained.js +93 -0
  81. package/dist/src/v2/authority-state/retained.js.map +1 -0
  82. package/dist/src/v2/authority-state/types.d.ts +198 -0
  83. package/dist/src/v2/authority-state/types.d.ts.map +1 -0
  84. package/dist/src/v2/authority-state/types.js +103 -0
  85. package/dist/src/v2/authority-state/types.js.map +1 -0
  86. package/dist/src/v2/authority-state/withdrawal.d.ts +80 -0
  87. package/dist/src/v2/authority-state/withdrawal.d.ts.map +1 -0
  88. package/dist/src/v2/authority-state/withdrawal.js +199 -0
  89. package/dist/src/v2/authority-state/withdrawal.js.map +1 -0
  90. package/dist/src/v2/bounds/canonical.d.ts +30 -0
  91. package/dist/src/v2/bounds/canonical.d.ts.map +1 -0
  92. package/dist/src/v2/bounds/canonical.js +69 -0
  93. package/dist/src/v2/bounds/canonical.js.map +1 -0
  94. package/dist/src/v2/bounds/evaluate.d.ts +107 -0
  95. package/dist/src/v2/bounds/evaluate.d.ts.map +1 -0
  96. package/dist/src/v2/bounds/evaluate.js +315 -0
  97. package/dist/src/v2/bounds/evaluate.js.map +1 -0
  98. package/dist/src/v2/bounds/index.d.ts +6 -0
  99. package/dist/src/v2/bounds/index.d.ts.map +1 -0
  100. package/dist/src/v2/bounds/index.js +13 -0
  101. package/dist/src/v2/bounds/index.js.map +1 -0
  102. package/dist/src/v2/bounds/purpose.d.ts +2 -0
  103. package/dist/src/v2/bounds/purpose.d.ts.map +1 -0
  104. package/dist/src/v2/bounds/purpose.js +23 -0
  105. package/dist/src/v2/bounds/purpose.js.map +1 -0
  106. package/dist/src/v2/bounds/record.d.ts +78 -0
  107. package/dist/src/v2/bounds/record.d.ts.map +1 -0
  108. package/dist/src/v2/bounds/record.js +170 -0
  109. package/dist/src/v2/bounds/record.js.map +1 -0
  110. package/dist/src/v2/bounds/types.d.ts +275 -0
  111. package/dist/src/v2/bounds/types.d.ts.map +1 -0
  112. package/dist/src/v2/bounds/types.js +124 -0
  113. package/dist/src/v2/bounds/types.js.map +1 -0
  114. package/dist/src/v2/capability-binding/binding.d.ts +67 -0
  115. package/dist/src/v2/capability-binding/binding.d.ts.map +1 -0
  116. package/dist/src/v2/capability-binding/binding.js +267 -0
  117. package/dist/src/v2/capability-binding/binding.js.map +1 -0
  118. package/dist/src/v2/capability-binding/digest.d.ts +33 -0
  119. package/dist/src/v2/capability-binding/digest.d.ts.map +1 -0
  120. package/dist/src/v2/capability-binding/digest.js +55 -0
  121. package/dist/src/v2/capability-binding/digest.js.map +1 -0
  122. package/dist/src/v2/capability-binding/identifier.d.ts +103 -0
  123. package/dist/src/v2/capability-binding/identifier.d.ts.map +1 -0
  124. package/dist/src/v2/capability-binding/identifier.js +281 -0
  125. package/dist/src/v2/capability-binding/identifier.js.map +1 -0
  126. package/dist/src/v2/capability-binding/index.d.ts +6 -0
  127. package/dist/src/v2/capability-binding/index.d.ts.map +1 -0
  128. package/dist/src/v2/capability-binding/index.js +13 -0
  129. package/dist/src/v2/capability-binding/index.js.map +1 -0
  130. package/dist/src/v2/capability-binding/pins.d.ts +30 -0
  131. package/dist/src/v2/capability-binding/pins.d.ts.map +1 -0
  132. package/dist/src/v2/capability-binding/pins.js +109 -0
  133. package/dist/src/v2/capability-binding/pins.js.map +1 -0
  134. package/dist/src/v2/capability-binding/types.d.ts +213 -0
  135. package/dist/src/v2/capability-binding/types.d.ts.map +1 -0
  136. package/dist/src/v2/capability-binding/types.js +150 -0
  137. package/dist/src/v2/capability-binding/types.js.map +1 -0
  138. package/dist/src/v2/chain-selection/index.d.ts +4 -0
  139. package/dist/src/v2/chain-selection/index.d.ts.map +1 -0
  140. package/dist/src/v2/chain-selection/index.js +11 -0
  141. package/dist/src/v2/chain-selection/index.js.map +1 -0
  142. package/dist/src/v2/chain-selection/select.d.ts +62 -0
  143. package/dist/src/v2/chain-selection/select.d.ts.map +1 -0
  144. package/dist/src/v2/chain-selection/select.js +390 -0
  145. package/dist/src/v2/chain-selection/select.js.map +1 -0
  146. package/dist/src/v2/chain-selection/types.d.ts +189 -0
  147. package/dist/src/v2/chain-selection/types.d.ts.map +1 -0
  148. package/dist/src/v2/chain-selection/types.js +81 -0
  149. package/dist/src/v2/chain-selection/types.js.map +1 -0
  150. package/dist/src/v2/lifecycle-state/index.d.ts +4 -0
  151. package/dist/src/v2/lifecycle-state/index.d.ts.map +1 -0
  152. package/dist/src/v2/lifecycle-state/index.js +10 -0
  153. package/dist/src/v2/lifecycle-state/index.js.map +1 -0
  154. package/dist/src/v2/lifecycle-state/map.d.ts +52 -0
  155. package/dist/src/v2/lifecycle-state/map.d.ts.map +1 -0
  156. package/dist/src/v2/lifecycle-state/map.js +122 -0
  157. package/dist/src/v2/lifecycle-state/map.js.map +1 -0
  158. package/dist/src/v2/lifecycle-state/state.d.ts +60 -0
  159. package/dist/src/v2/lifecycle-state/state.d.ts.map +1 -0
  160. package/dist/src/v2/lifecycle-state/state.js +157 -0
  161. package/dist/src/v2/lifecycle-state/state.js.map +1 -0
  162. package/dist/src/v2/lifecycle-state/types.d.ts +163 -0
  163. package/dist/src/v2/lifecycle-state/types.d.ts.map +1 -0
  164. package/dist/src/v2/lifecycle-state/types.js +121 -0
  165. package/dist/src/v2/lifecycle-state/types.js.map +1 -0
  166. package/dist/src/v2/receipt-core/composite.d.ts +69 -3
  167. package/dist/src/v2/receipt-core/composite.d.ts.map +1 -1
  168. package/dist/src/v2/receipt-core/composite.js +75 -9
  169. package/dist/src/v2/receipt-core/composite.js.map +1 -1
  170. package/dist/src/v2/receipt-core/predecessor.d.ts +95 -0
  171. package/dist/src/v2/receipt-core/predecessor.d.ts.map +1 -0
  172. package/dist/src/v2/receipt-core/predecessor.js +115 -0
  173. package/dist/src/v2/receipt-core/predecessor.js.map +1 -0
  174. package/dist/src/v2/status-coverage/decide.d.ts +52 -0
  175. package/dist/src/v2/status-coverage/decide.d.ts.map +1 -0
  176. package/dist/src/v2/status-coverage/decide.js +398 -0
  177. package/dist/src/v2/status-coverage/decide.js.map +1 -0
  178. package/dist/src/v2/status-coverage/index.d.ts +3 -0
  179. package/dist/src/v2/status-coverage/index.d.ts.map +1 -0
  180. package/dist/src/v2/status-coverage/index.js +10 -0
  181. package/dist/src/v2/status-coverage/index.js.map +1 -0
  182. package/dist/src/v2/status-coverage/types.d.ts +258 -0
  183. package/dist/src/v2/status-coverage/types.d.ts.map +1 -0
  184. package/dist/src/v2/status-coverage/types.js +127 -0
  185. package/dist/src/v2/status-coverage/types.js.map +1 -0
  186. package/dist/src/v2/suspension/evaluate.d.ts +88 -0
  187. package/dist/src/v2/suspension/evaluate.d.ts.map +1 -0
  188. package/dist/src/v2/suspension/evaluate.js +404 -0
  189. package/dist/src/v2/suspension/evaluate.js.map +1 -0
  190. package/dist/src/v2/suspension/index.d.ts +3 -0
  191. package/dist/src/v2/suspension/index.d.ts.map +1 -0
  192. package/dist/src/v2/suspension/index.js +10 -0
  193. package/dist/src/v2/suspension/index.js.map +1 -0
  194. package/dist/src/v2/suspension/types.d.ts +243 -0
  195. package/dist/src/v2/suspension/types.d.ts.map +1 -0
  196. package/dist/src/v2/suspension/types.js +135 -0
  197. package/dist/src/v2/suspension/types.js.map +1 -0
  198. package/package.json +10 -7
@@ -0,0 +1,315 @@
1
+ // Copyright (c) 2026 Tymofii Pidlisnyi
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // PROPOSED, OPT-IN. Assessing fulfilment records and evaluating a bound's state.
5
+ //
6
+ // Pure. No clock, no I/O, no network. Every instant compared here is supplied by the
7
+ // caller, and every resolver is the caller's. Not required by draft-pidlisnyi-aps-03; see
8
+ // ./types.ts for the specification position.
9
+ import { lifecycleState, notEstablished } from '../lifecycle-state/state.js';
10
+ import { verifyAuthorityBoundFulfilmentSignature } from './canonical.js';
11
+ import { AUTHORITY_BOUND_FULFILMENT_TYPE, AUTHORITY_BOUND_TYPE, ATTESTOR_ROLE_ANSWERS, BOUND_KINDS, } from './types.js';
12
+ /** Thrown when an input this module cannot read at all is passed. A malformed BOUND is a
13
+ * programming error, not a verdict: the module was asked a question about nothing. A
14
+ * malformed FULFILMENT RECORD is different and never throws, because a record somebody
15
+ * else wrote is data, and the right answer about it is a rejection with a reason code. */
16
+ export class AuthorityBoundError extends Error {
17
+ code;
18
+ constructor(code, message) {
19
+ super(message);
20
+ this.name = 'AuthorityBoundError';
21
+ this.code = code;
22
+ }
23
+ }
24
+ const CANONICAL_UNSIGNED_INTEGER = /^(0|[1-9][0-9]*)$/;
25
+ const DELEGATION_ID = /^sha256:[0-9a-f]{64}$/;
26
+ const SIGNATURE_HEX = /^[0-9a-f]{128}$/;
27
+ export function isBoundKind(value) {
28
+ return typeof value === 'string' && BOUND_KINDS.includes(value);
29
+ }
30
+ export function isAttestorRoleAnswer(value) {
31
+ return (typeof value === 'string' && ATTESTOR_ROLE_ANSWERS.includes(value));
32
+ }
33
+ /** Shape check for a bound declaration. Throws, for the reason `AuthorityBoundError`
34
+ * documents. */
35
+ export function assertAuthorityBound(bound) {
36
+ if (bound === null || typeof bound !== 'object') {
37
+ throw new AuthorityBoundError('BOUND_MALFORMED', 'bound is not an object');
38
+ }
39
+ if (bound.record_type !== AUTHORITY_BOUND_TYPE) {
40
+ throw new AuthorityBoundError('BOUND_RECORD_TYPE_UNSUPPORTED', `record_type must be ${AUTHORITY_BOUND_TYPE}`);
41
+ }
42
+ if (typeof bound.bound_id !== 'string' || bound.bound_id.length === 0) {
43
+ throw new AuthorityBoundError('BOUND_MALFORMED', 'bound_id must be a non-empty string');
44
+ }
45
+ if (typeof bound.delegation_id !== 'string' || !DELEGATION_ID.test(bound.delegation_id)) {
46
+ throw new AuthorityBoundError('BOUND_MALFORMED', 'delegation_id must be sha256:<64 lowercase hex>');
47
+ }
48
+ if (!isBoundKind(bound.kind)) {
49
+ throw new AuthorityBoundError('BOUND_KIND_UNSUPPORTED', `kind must be one of ${BOUND_KINDS.join(', ')}`);
50
+ }
51
+ if (typeof bound.value !== 'string' || bound.value.length === 0) {
52
+ throw new AuthorityBoundError('BOUND_MALFORMED', 'value must be a non-empty string');
53
+ }
54
+ if (bound.kind !== 'purpose' && !CANONICAL_UNSIGNED_INTEGER.test(bound.value)) {
55
+ throw new AuthorityBoundError('BOUND_VALUE_NONCANONICAL', `a ${bound.kind} bound's value must be a canonical unsigned decimal integer`);
56
+ }
57
+ if (!Array.isArray(bound.fulfilment_attestor_roles)) {
58
+ throw new AuthorityBoundError('BOUND_MALFORMED', 'fulfilment_attestor_roles must be an array, empty if nobody but the issuer may attest');
59
+ }
60
+ for (const role of bound.fulfilment_attestor_roles) {
61
+ if (typeof role !== 'string' || role.length === 0) {
62
+ throw new AuthorityBoundError('BOUND_MALFORMED', 'fulfilment_attestor_roles must contain only non-empty strings');
63
+ }
64
+ }
65
+ }
66
+ /** True only for a record this module can read far enough to assess. A record that fails
67
+ * here is rejected with `FULFILMENT_SCHEMA_INVALID`, never thrown on. */
68
+ function readableFulfilment(record) {
69
+ return (record !== null &&
70
+ typeof record === 'object' &&
71
+ record.record_type === AUTHORITY_BOUND_FULFILMENT_TYPE &&
72
+ typeof record.bound_id === 'string' &&
73
+ record.bound_id.length > 0 &&
74
+ typeof record.delegation_id === 'string' &&
75
+ DELEGATION_ID.test(record.delegation_id) &&
76
+ typeof record.attestor === 'string' &&
77
+ record.attestor.length > 0 &&
78
+ typeof record.verification_method === 'string' &&
79
+ record.verification_method.length > 0 &&
80
+ typeof record.attested_at === 'string' &&
81
+ record.attested_at.length > 0 &&
82
+ (record.outcome === 'fulfilled' || record.outcome === 'not_fulfilled') &&
83
+ typeof record.reason_code === 'string' &&
84
+ record.reason_code.length > 0 &&
85
+ typeof record.signature === 'string' &&
86
+ SIGNATURE_HEX.test(record.signature));
87
+ }
88
+ function assessment(attestor, attestedAt, accepted, reasonCode, extra = {}) {
89
+ const out = { attestor, attested_at: attestedAt, accepted, reason_code: reasonCode };
90
+ if (extra.role_answer !== undefined)
91
+ out.role_answer = extra.role_answer;
92
+ if (extra.missing !== undefined)
93
+ out.missing = Object.freeze([...extra.missing]);
94
+ return Object.freeze(out);
95
+ }
96
+ /**
97
+ * Assess ONE fulfilment record against one bound.
98
+ *
99
+ * The order of the checks is the substance of this function, not an implementation detail.
100
+ * Standing is asked FIRST and authenticity SECOND, and each produces its own code, because
101
+ * "authenticated by somebody who may not say this" and "not authenticated at all" are
102
+ * different failures and collapsing them loses the distinction CAND-01 turns on. A valid
103
+ * signature establishes who signed. It does not establish that the signer was allowed to
104
+ * make this statement.
105
+ *
106
+ * `FULFILMENT_OUTCOME_NOT_FULFILLED` carries no `missing` limbs, and that is deliberate: a
107
+ * record from a party with standing saying the purpose was NOT met is a conclusion the
108
+ * verifier reached, not a gap in its evidence. Every other rejection names a limb.
109
+ *
110
+ * Concept source: aeoess/agent-authority-lifecycle, invariant candidate CAND-01 and
111
+ * invariant candidate BROAD-L7. Proposed.
112
+ */
113
+ export function assessFulfilment(input) {
114
+ assertAuthorityBound(input.bound);
115
+ const { bound, record, atInstant } = input;
116
+ if (bound.kind !== 'purpose') {
117
+ return assessment('', '', false, 'FULFILMENT_NOT_APPLICABLE_TO_KIND', {
118
+ missing: ['coverage'],
119
+ });
120
+ }
121
+ if (!readableFulfilment(record)) {
122
+ return assessment('', '', false, 'FULFILMENT_SCHEMA_INVALID', { missing: ['source'] });
123
+ }
124
+ const attestor = record.attestor;
125
+ const attestedAt = record.attested_at;
126
+ // The record has to be about THIS bound on THIS delegation. A coverage gap: the claim does
127
+ // not state that it covers what the verdict needed.
128
+ if (record.bound_id !== bound.bound_id || record.delegation_id !== bound.delegation_id) {
129
+ return assessment(attestor, attestedAt, false, 'FULFILMENT_NOT_BOUND_TO_BOUND', {
130
+ missing: ['coverage'],
131
+ });
132
+ }
133
+ if (attestedAt > atInstant) {
134
+ return assessment(attestor, attestedAt, false, 'FULFILMENT_NOT_YET_ATTESTED', {
135
+ missing: ['coverage'],
136
+ });
137
+ }
138
+ const roleAnswer = input.resolveAttestorRole(attestor, bound.fulfilment_attestor_roles, atInstant);
139
+ if (!isAttestorRoleAnswer(roleAnswer)) {
140
+ return assessment(attestor, attestedAt, false, 'FULFILMENT_ATTESTOR_ROLE_UNKNOWN', {
141
+ missing: ['source'],
142
+ });
143
+ }
144
+ if (roleAnswer === 'unknown') {
145
+ return assessment(attestor, attestedAt, false, 'FULFILMENT_ATTESTOR_ROLE_UNKNOWN', {
146
+ role_answer: roleAnswer,
147
+ missing: ['source'],
148
+ });
149
+ }
150
+ if (roleAnswer === 'does_not_hold_role') {
151
+ return assessment(attestor, attestedAt, false, 'FULFILMENT_ATTESTOR_WITHOUT_STANDING', {
152
+ role_answer: roleAnswer,
153
+ missing: ['source'],
154
+ });
155
+ }
156
+ const publicKey = input.resolveVerificationKey(attestor, record.verification_method, attestedAt);
157
+ if (typeof publicKey !== 'string' || publicKey.length === 0) {
158
+ return assessment(attestor, attestedAt, false, 'FULFILMENT_KEY_UNRESOLVED', {
159
+ role_answer: roleAnswer,
160
+ missing: ['source'],
161
+ });
162
+ }
163
+ if (!verifyAuthorityBoundFulfilmentSignature(record, publicKey)) {
164
+ return assessment(attestor, attestedAt, false, 'FULFILMENT_SIGNATURE_INVALID', {
165
+ role_answer: roleAnswer,
166
+ missing: ['source'],
167
+ });
168
+ }
169
+ if (record.outcome !== 'fulfilled') {
170
+ return assessment(attestor, attestedAt, false, 'FULFILMENT_OUTCOME_NOT_FULFILLED', {
171
+ role_answer: roleAnswer,
172
+ });
173
+ }
174
+ return assessment(attestor, attestedAt, true, 'FULFILMENT_ACCEPTED', {
175
+ role_answer: roleAnswer,
176
+ });
177
+ }
178
+ function decimal(value, fallback) {
179
+ const raw = value ?? fallback;
180
+ if (!CANONICAL_UNSIGNED_INTEGER.test(raw)) {
181
+ throw new AuthorityBoundError('COUNTER_NONCANONICAL', `expected a canonical unsigned decimal integer, got ${JSON.stringify(raw)}`);
182
+ }
183
+ return BigInt(raw);
184
+ }
185
+ function lifecycleFor(state, reasonCode, missing) {
186
+ if (state === 'not_established')
187
+ return notEstablished(missing, reasonCode);
188
+ return lifecycleState({
189
+ verdict: state === 'exhausted' ? 'invalid' : 'valid',
190
+ reason_code: reasonCode,
191
+ });
192
+ }
193
+ /**
194
+ * Evaluate one bound's state at one instant.
195
+ *
196
+ * THE RESOLUTION RULE, and why it is this way round. For a purpose bound:
197
+ *
198
+ * 1. If any fulfilment record was ACCEPTED, the state is `exhausted`.
199
+ * 2. Otherwise, if any record was rejected on an EVIDENTIAL ground (signature, standing,
200
+ * unresolved role, unresolved key, unreadable shape), the state is `not_established`.
201
+ * 3. Otherwise the state is `not_reached`.
202
+ *
203
+ * Step 1 comes before step 2 on purpose, and it is invariant CAND-02's rule made
204
+ * executable: an exhaustion that WAS established is never downgraded by a later claim
205
+ * nobody could authenticate. A later finding is a new record about an earlier one, never a
206
+ * rewrite of it. Because the rule is a fold over the whole set rather than a running
207
+ * mutation, the verdict is the same whatever order the records arrive in, which is what
208
+ * makes it safe for a caller to keep them in an unordered store.
209
+ *
210
+ * Step 3 reads `not_reached` from an empty record set, and that is the one reading here
211
+ * worth arguing with. It is a positive claim: the bound is declared, no evidence reaches
212
+ * it, and under the applicable authority model a purpose that nobody has recorded as met is
213
+ * a purpose that is not met. A model whose default runs the other way (absence of a
214
+ * periodic fulfilment report is itself the trigger) is a model this function cannot
215
+ * express, and a caller in that position should report `not_established` with a `source`
216
+ * limb rather than passing an empty set and reading the answer as a finding. CAND-01's
217
+ * carve-out for a declared default on absence is the reason the disclaimer is here and not
218
+ * a silent assumption.
219
+ *
220
+ * A `use_count` bound needs no records at all: the ADMISSION reaches it, so `consumed`
221
+ * against `value` is the whole computation. A `budget` bound is answered by the ledger's
222
+ * counter, per draft-03 section 3.4, verbatim: "Signatures establish static limits; they do
223
+ * not establish the current cumulative total."
224
+ *
225
+ * Reported ALONGSIDE a chain result, never merged into it, and never in place of it. A
226
+ * grant can be exhausted while its chain still verifies `valid`, which is the whole point:
227
+ * exhaustion is invisible to chain verification. A grant can also be expired AND exhausted,
228
+ * and then the chain result says `EXPIRED` and this says `exhaustion` and neither
229
+ * overwrites the other. Invariant L10.
230
+ *
231
+ * Concept source: aeoess/agent-authority-lifecycle, invariant L10 and invariant candidates
232
+ * CAND-01 and CAND-02. All proposed.
233
+ */
234
+ export function evaluateBound(input) {
235
+ assertAuthorityBound(input.bound);
236
+ const { bound } = input;
237
+ if (typeof input.atInstant !== 'string' || input.atInstant.length === 0) {
238
+ throw new AuthorityBoundError('INSTANT_MISSING', 'atInstant must be a non-empty string');
239
+ }
240
+ const assessments = [];
241
+ let state;
242
+ let reasonCode;
243
+ let remaining;
244
+ const missing = [];
245
+ if (bound.kind === 'purpose') {
246
+ const records = input.fulfilments ?? [];
247
+ if (records.length > 0) {
248
+ if (typeof input.resolveAttestorRole !== 'function') {
249
+ throw new AuthorityBoundError('ROLE_RESOLVER_MISSING', 'a purpose bound with fulfilment records needs resolveAttestorRole');
250
+ }
251
+ if (typeof input.resolveVerificationKey !== 'function') {
252
+ throw new AuthorityBoundError('KEY_RESOLVER_MISSING', 'a purpose bound with fulfilment records needs resolveVerificationKey');
253
+ }
254
+ }
255
+ const resolveAttestorRole = input.resolveAttestorRole ?? (() => 'unknown');
256
+ const resolveVerificationKey = input.resolveVerificationKey ?? (() => null);
257
+ for (const record of records) {
258
+ assessments.push(assessFulfilment({
259
+ bound,
260
+ record,
261
+ atInstant: input.atInstant,
262
+ resolveAttestorRole,
263
+ resolveVerificationKey,
264
+ }));
265
+ }
266
+ const accepted = assessments.some(a => a.accepted);
267
+ const unestablished = assessments.filter(a => !a.accepted && a.missing !== undefined);
268
+ if (accepted) {
269
+ state = 'exhausted';
270
+ reasonCode = 'PURPOSE_EXHAUSTED';
271
+ }
272
+ else if (unestablished.length > 0) {
273
+ state = 'not_established';
274
+ reasonCode = 'BOUND_STATE_NOT_ESTABLISHED';
275
+ for (const a of unestablished) {
276
+ for (const gap of a.missing ?? [])
277
+ if (!missing.includes(gap))
278
+ missing.push(gap);
279
+ }
280
+ }
281
+ else {
282
+ state = 'not_reached';
283
+ reasonCode = 'BOUND_NOT_REACHED';
284
+ }
285
+ }
286
+ else if (bound.kind === 'use_count') {
287
+ const limit = decimal(bound.value, '0');
288
+ const consumed = decimal(input.consumed, '0');
289
+ const left = consumed >= limit ? 0n : limit - consumed;
290
+ remaining = left.toString(10);
291
+ state = consumed >= limit ? 'exhausted' : 'not_reached';
292
+ reasonCode = state === 'exhausted' ? 'USE_COUNT_EXHAUSTED' : 'BOUND_NOT_REACHED';
293
+ }
294
+ else {
295
+ const ceiling = decimal(bound.value, '0');
296
+ const committed = decimal(input.budgetCounter?.committed, '0');
297
+ const reserved = decimal(input.budgetCounter?.reserved, '0');
298
+ state = committed + reserved >= ceiling ? 'exhausted' : 'not_reached';
299
+ reasonCode = state === 'exhausted' ? 'BUDGET_EXHAUSTED' : 'BOUND_NOT_REACHED';
300
+ }
301
+ const ending = state === 'exhausted' ? 'exhaustion' : null;
302
+ const result = {
303
+ bound_id: bound.bound_id,
304
+ kind: bound.kind,
305
+ bound_state: state,
306
+ reason_code: reasonCode,
307
+ ending,
308
+ lifecycle: lifecycleFor(state, reasonCode, missing.length > 0 ? missing : ['source']),
309
+ fulfilments: Object.freeze([...assessments]),
310
+ };
311
+ if (remaining !== undefined)
312
+ result.remaining = remaining;
313
+ return Object.freeze(result);
314
+ }
315
+ //# sourceMappingURL=evaluate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../../../src/v2/bounds/evaluate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export * from './types.js';
2
+ export * from './canonical.js';
3
+ export * from './evaluate.js';
4
+ export * from './record.js';
5
+ export * from './purpose.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/v2/bounds/index.ts"],"names":[],"mappings":"AAQA,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA"}
@@ -0,0 +1,13 @@
1
+ // Copyright (c) 2026 Tymofii Pidlisnyi
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // PROPOSED, OPT-IN. Non-time bounds on a grant: purpose, use count and budget.
5
+ // Nothing here is required by draft-pidlisnyi-aps-03 and nothing here changes any existing
6
+ // exported behaviour. Concept source: aeoess/agent-authority-lifecycle, invariant L10 and
7
+ // invariant candidates CAND-01 and CAND-02. See ./types.ts.
8
+ export * from './types.js';
9
+ export * from './canonical.js';
10
+ export * from './evaluate.js';
11
+ export * from './record.js';
12
+ export * from './purpose.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/v2/bounds/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,sCAAsC;AACtC,EAAE;AACF,+EAA+E;AAC/E,2FAA2F;AAC3F,0FAA0F;AAC1F,4DAA4D;AAE5D,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { isPurposePermitted, purposeCategory } from '../../core/data-lifecycle.js';
2
+ //# sourceMappingURL=purpose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"purpose.d.ts","sourceRoot":"","sources":["../../../../src/v2/bounds/purpose.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA"}
@@ -0,0 +1,23 @@
1
+ // Copyright (c) 2026 Tymofii Pidlisnyi
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // PROPOSED module, but these two functions are NOT new and NOT proposed. They are the
5
+ // hierarchical purpose helpers that have shipped in `src/core/data-lifecycle.ts` since
6
+ // before this module existed, re-exported here so that both reference SDKs agree on where
7
+ // purpose membership lives.
8
+ //
9
+ // WHY THE RE-EXPORT. The Python SDK had no port of either function. Adding them there under
10
+ // a `data_lifecycle` module, to hold two functions, would have put the same primitive at two
11
+ // unrelated paths in the two languages, which becomes a cross-language test annoyance the
12
+ // first time a vector references it. Both SDKs now expose them from the bounds module, and
13
+ // the TypeScript original stays exported from its old path too, so nothing that imports it
14
+ // today changes.
15
+ //
16
+ // PURPOSE MEMBERSHIP IS NOT PURPOSE EXHAUSTION, and the distinction is the reason this file
17
+ // carries a comment at all. `isPurposePermitted` answers whether a requested purpose falls
18
+ // inside a set of allowed purposes. It answers `true` for the second compressor purchase
19
+ // exactly as it does for the first. Membership can therefore never decide exhaustion, and a
20
+ // boundary that checks only membership is the defective implementation the purpose-bound
21
+ // case corpus exists to catch. `evaluateBound` is the exhaustion question.
22
+ export { isPurposePermitted, purposeCategory } from '../../core/data-lifecycle.js';
23
+ //# sourceMappingURL=purpose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"purpose.js","sourceRoot":"","sources":["../../../../src/v2/bounds/purpose.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,sCAAsC;AACtC,EAAE;AACF,sFAAsF;AACtF,uFAAuF;AACvF,0FAA0F;AAC1F,4BAA4B;AAC5B,EAAE;AACF,4FAA4F;AAC5F,6FAA6F;AAC7F,0FAA0F;AAC1F,2FAA2F;AAC3F,2FAA2F;AAC3F,iBAAiB;AACjB,EAAE;AACF,4FAA4F;AAC5F,2FAA2F;AAC3F,yFAAyF;AACzF,4FAA4F;AAC5F,yFAAyF;AACzF,2EAA2E;AAE3E,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA"}
@@ -0,0 +1,78 @@
1
+ import { type AuthorityBoundFulfilment, type AuthorityExhaustion, type BoundEvaluation } from './types.js';
2
+ export interface IssueFulfilmentInput {
3
+ readonly bound_id: string;
4
+ readonly delegation_id: string;
5
+ readonly attestor: string;
6
+ readonly verification_method: string;
7
+ /** Canonical UTC-millisecond time, supplied by the caller. Issuance never reads a clock,
8
+ * so the same inputs produce the same bytes on every run, which is what makes the
9
+ * signature reproducible in a test and in a conformance vector. */
10
+ readonly attested_at: string;
11
+ readonly outcome: 'fulfilled' | 'not_fulfilled';
12
+ readonly reason_code: string;
13
+ /** Omitted from the record entirely when not supplied, never written as null. */
14
+ readonly detail?: string;
15
+ }
16
+ /**
17
+ * Mint a signed fulfilment attestation.
18
+ *
19
+ * What this function does NOT establish: that the holder of `privateKey` is in fact
20
+ * `attestor`, that `verification_method` is one of that party's keys, or that the attestor
21
+ * holds any fulfilment-attestor role. All three are a resolver's answer, not a local one.
22
+ * `assessFulfilment` makes those checks, and it asks about standing BEFORE it asks about the
23
+ * signature, because a record can be perfectly authentic and still come from somebody with
24
+ * no standing to make the statement.
25
+ *
26
+ * Proposed. Concept source: aeoess/agent-authority-lifecycle.
27
+ */
28
+ export declare function issueAuthorityBoundFulfilment(input: IssueFulfilmentInput, privateKeyHex: string): AuthorityBoundFulfilment;
29
+ export interface IssueExhaustionInput {
30
+ readonly boundary: string;
31
+ readonly verification_method: string;
32
+ /** Canonical UTC-millisecond time the finding was made. Never read from a clock. */
33
+ readonly found_at: string;
34
+ readonly detail?: string;
35
+ }
36
+ /**
37
+ * Mint the OPTIONAL signed exhaustion record for a `BoundEvaluation` that reached
38
+ * `exhausted`.
39
+ *
40
+ * REFUSES on any other state, and the refusal is the design. A record that says "I found
41
+ * this bound exhausted" when the evaluation said `not_established` would be a boundary
42
+ * asserting a finding it did not make, which is precisely what CAND-01 forbids. There is no
43
+ * flag to override it.
44
+ *
45
+ * `evidence` is derived from the evaluation's own accepted fulfilment records, and it is
46
+ * EMPTY for a `use_count` or `budget` bound. That emptiness is honest rather than
47
+ * incomplete: the basis for those two is the boundary's own ledger, which is the one thing
48
+ * an outside verifier cannot check, and a placeholder there would hide the gap. The same
49
+ * limit applies to the whole record, and ./types.ts states it: the record attests the
50
+ * boundary's finding, not the state of the world, on the model draft-03 section 5.3.3 uses
51
+ * for an action result.
52
+ *
53
+ * Proposed. Concept source: aeoess/agent-authority-lifecycle, invariant L10.
54
+ */
55
+ export declare function issueAuthorityExhaustion(evaluation: BoundEvaluation, delegationId: string, input: IssueExhaustionInput, privateKeyHex: string): AuthorityExhaustion;
56
+ export type AuthorityExhaustionFailureCode = 'SCHEMA_INVALID' | 'UNSUPPORTED_RECORD_TYPE' | 'ID_MISMATCH' | 'KEY_UNRESOLVED' | 'SIGNATURE_INVALID';
57
+ export interface AuthorityExhaustionVerification {
58
+ /** `valid` or `invalid` only. This function answers a question about ONE record's bytes
59
+ * and its signer, which is genuinely two sided, so it does not borrow the lifecycle
60
+ * vocabulary. What the record means for a grant's state is `evaluateBound`'s question,
61
+ * and a verified record is evidence toward it, never a substitute for it. */
62
+ readonly status: 'valid' | 'invalid';
63
+ readonly failures: readonly AuthorityExhaustionFailureCode[];
64
+ }
65
+ /**
66
+ * Verify an exhaustion record's bytes, identifier and signature.
67
+ *
68
+ * A `valid` answer here means: the record recomputes its own identifier, and the key
69
+ * resolved for its `boundary` at its `found_at` signed it. It does NOT mean the bound is
70
+ * exhausted. It means the named boundary said so, at that time, over those bytes. Treating
71
+ * the two as the same thing is the substitution draft-03's own security considerations warn
72
+ * about for receipts generally, verbatim: "Verifiers MUST treat receipts as evidence of what
73
+ * was attested, not as proof of what is true."
74
+ *
75
+ * Proposed.
76
+ */
77
+ export declare function verifyAuthorityExhaustion(record: AuthorityExhaustion, resolveVerificationKey: (boundary: string, verificationMethod: string, foundAt: string) => string | null): AuthorityExhaustionVerification;
78
+ //# sourceMappingURL=record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../../../src/v2/bounds/record.ts"],"names":[],"mappings":"AAgBA,OAAO,EAIL,KAAK,wBAAwB,EAE7B,KAAK,mBAAmB,EAExB,KAAK,eAAe,EAErB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAA;IACpC;;wEAEoE;IACpE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,eAAe,CAAA;IAC/C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,oBAAoB,EAC3B,aAAa,EAAE,MAAM,GACpB,wBAAwB,CAsC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAA;IACpC,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,eAAe,EAC3B,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,oBAAoB,EAC3B,aAAa,EAAE,MAAM,GACpB,mBAAmB,CAiDrB;AAED,MAAM,MAAM,8BAA8B,GACtC,gBAAgB,GAChB,yBAAyB,GACzB,aAAa,GACb,gBAAgB,GAChB,mBAAmB,CAAA;AAEvB,MAAM,WAAW,+BAA+B;IAC9C;;;kFAG8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,QAAQ,EAAE,SAAS,8BAA8B,EAAE,CAAA;CAC7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,mBAAmB,EAC3B,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,kBAAkB,EAAE,MAAM,EAC1B,OAAO,EAAE,MAAM,KACZ,MAAM,GAAG,IAAI,GACjB,+BAA+B,CAqDjC"}
@@ -0,0 +1,170 @@
1
+ // Copyright (c) 2026 Tymofii Pidlisnyi
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ //
4
+ // PROPOSED, OPT-IN. Issuing and verifying the two records this module declares.
5
+ //
6
+ // Neither record is required by draft-pidlisnyi-aps-03 and neither is an `aps:` record type.
7
+ // See ./types.ts for what each one attests and, more importantly, what it does not.
8
+ import { computeAuthorityExhaustionIdForWrite, computeAuthorityExhaustionId, signAuthorityBoundFulfilment, signAuthorityExhaustion, verifyAuthorityExhaustionSignature, } from './canonical.js';
9
+ import { AuthorityBoundError } from './evaluate.js';
10
+ import { AUTHORITY_BOUND_FULFILMENT_TYPE, AUTHORITY_EXHAUSTION_TYPE, BOUND_KINDS, } from './types.js';
11
+ /**
12
+ * Mint a signed fulfilment attestation.
13
+ *
14
+ * What this function does NOT establish: that the holder of `privateKey` is in fact
15
+ * `attestor`, that `verification_method` is one of that party's keys, or that the attestor
16
+ * holds any fulfilment-attestor role. All three are a resolver's answer, not a local one.
17
+ * `assessFulfilment` makes those checks, and it asks about standing BEFORE it asks about the
18
+ * signature, because a record can be perfectly authentic and still come from somebody with
19
+ * no standing to make the statement.
20
+ *
21
+ * Proposed. Concept source: aeoess/agent-authority-lifecycle.
22
+ */
23
+ export function issueAuthorityBoundFulfilment(input, privateKeyHex) {
24
+ for (const field of [
25
+ 'bound_id',
26
+ 'delegation_id',
27
+ 'attestor',
28
+ 'verification_method',
29
+ 'attested_at',
30
+ 'reason_code',
31
+ ]) {
32
+ const value = input[field];
33
+ if (typeof value !== 'string' || value.length === 0) {
34
+ throw new AuthorityBoundError('FULFILMENT_ISSUE_MALFORMED', `${field} must be a non-empty string`);
35
+ }
36
+ }
37
+ if (input.outcome !== 'fulfilled' && input.outcome !== 'not_fulfilled') {
38
+ throw new AuthorityBoundError('FULFILMENT_ISSUE_MALFORMED', 'outcome must be fulfilled or not_fulfilled');
39
+ }
40
+ const body = Object.freeze({
41
+ record_type: AUTHORITY_BOUND_FULFILMENT_TYPE,
42
+ bound_id: input.bound_id,
43
+ delegation_id: input.delegation_id,
44
+ attestor: input.attestor,
45
+ verification_method: input.verification_method,
46
+ attested_at: input.attested_at,
47
+ outcome: input.outcome,
48
+ reason_code: input.reason_code,
49
+ ...(input.detail !== undefined ? { detail: input.detail } : {}),
50
+ });
51
+ return Object.freeze({
52
+ ...body,
53
+ signature: signAuthorityBoundFulfilment(body, privateKeyHex),
54
+ });
55
+ }
56
+ /**
57
+ * Mint the OPTIONAL signed exhaustion record for a `BoundEvaluation` that reached
58
+ * `exhausted`.
59
+ *
60
+ * REFUSES on any other state, and the refusal is the design. A record that says "I found
61
+ * this bound exhausted" when the evaluation said `not_established` would be a boundary
62
+ * asserting a finding it did not make, which is precisely what CAND-01 forbids. There is no
63
+ * flag to override it.
64
+ *
65
+ * `evidence` is derived from the evaluation's own accepted fulfilment records, and it is
66
+ * EMPTY for a `use_count` or `budget` bound. That emptiness is honest rather than
67
+ * incomplete: the basis for those two is the boundary's own ledger, which is the one thing
68
+ * an outside verifier cannot check, and a placeholder there would hide the gap. The same
69
+ * limit applies to the whole record, and ./types.ts states it: the record attests the
70
+ * boundary's finding, not the state of the world, on the model draft-03 section 5.3.3 uses
71
+ * for an action result.
72
+ *
73
+ * Proposed. Concept source: aeoess/agent-authority-lifecycle, invariant L10.
74
+ */
75
+ export function issueAuthorityExhaustion(evaluation, delegationId, input, privateKeyHex) {
76
+ if (evaluation === null || typeof evaluation !== 'object') {
77
+ throw new AuthorityBoundError('EXHAUSTION_ISSUE_MALFORMED', 'evaluation is not an object');
78
+ }
79
+ if (evaluation.bound_state !== 'exhausted') {
80
+ throw new AuthorityBoundError('EXHAUSTION_STATE_NOT_EXHAUSTED', `an exhaustion record can only be minted for bound_state exhausted, not ${String(evaluation.bound_state)}`);
81
+ }
82
+ for (const field of ['boundary', 'verification_method', 'found_at']) {
83
+ const value = input[field];
84
+ if (typeof value !== 'string' || value.length === 0) {
85
+ throw new AuthorityBoundError('EXHAUSTION_ISSUE_MALFORMED', `${field} must be a non-empty string`);
86
+ }
87
+ }
88
+ if (!BOUND_KINDS.includes(evaluation.kind)) {
89
+ throw new AuthorityBoundError('EXHAUSTION_ISSUE_MALFORMED', `kind must be one of ${BOUND_KINDS.join(', ')}`);
90
+ }
91
+ const body = Object.freeze({
92
+ record_type: AUTHORITY_EXHAUSTION_TYPE,
93
+ bound_id: evaluation.bound_id,
94
+ delegation_id: delegationId,
95
+ kind: evaluation.kind,
96
+ boundary: input.boundary,
97
+ verification_method: input.verification_method,
98
+ found_at: input.found_at,
99
+ reason_code: evaluation.reason_code,
100
+ evidence: Object.freeze(evaluation.fulfilments
101
+ .filter(a => a.accepted)
102
+ .map(a => Object.freeze({ attestor: a.attestor, attested_at: a.attested_at }))),
103
+ ...(input.detail !== undefined ? { detail: input.detail } : {}),
104
+ });
105
+ const exhaustionId = computeAuthorityExhaustionIdForWrite(body);
106
+ const unsigned = Object.freeze({ ...body, exhaustion_id: exhaustionId });
107
+ return Object.freeze({
108
+ ...unsigned,
109
+ signature: signAuthorityExhaustion(unsigned, privateKeyHex),
110
+ });
111
+ }
112
+ /**
113
+ * Verify an exhaustion record's bytes, identifier and signature.
114
+ *
115
+ * A `valid` answer here means: the record recomputes its own identifier, and the key
116
+ * resolved for its `boundary` at its `found_at` signed it. It does NOT mean the bound is
117
+ * exhausted. It means the named boundary said so, at that time, over those bytes. Treating
118
+ * the two as the same thing is the substitution draft-03's own security considerations warn
119
+ * about for receipts generally, verbatim: "Verifiers MUST treat receipts as evidence of what
120
+ * was attested, not as proof of what is true."
121
+ *
122
+ * Proposed.
123
+ */
124
+ export function verifyAuthorityExhaustion(record, resolveVerificationKey) {
125
+ const failures = [];
126
+ const invalid = () => Object.freeze({ status: 'invalid', failures: Object.freeze([...failures]) });
127
+ if (record === null || typeof record !== 'object') {
128
+ failures.push('SCHEMA_INVALID');
129
+ return invalid();
130
+ }
131
+ if (record.record_type !== AUTHORITY_EXHAUSTION_TYPE) {
132
+ failures.push('UNSUPPORTED_RECORD_TYPE');
133
+ return invalid();
134
+ }
135
+ for (const field of [
136
+ 'bound_id',
137
+ 'delegation_id',
138
+ 'boundary',
139
+ 'verification_method',
140
+ 'found_at',
141
+ 'reason_code',
142
+ 'exhaustion_id',
143
+ 'signature',
144
+ ]) {
145
+ if (typeof record[field] !== 'string' || record[field].length === 0) {
146
+ failures.push('SCHEMA_INVALID');
147
+ return invalid();
148
+ }
149
+ }
150
+ if (!Array.isArray(record.evidence) || !BOUND_KINDS.includes(record.kind)) {
151
+ failures.push('SCHEMA_INVALID');
152
+ return invalid();
153
+ }
154
+ const { exhaustion_id: claimedId, signature: _signature, ...body } = record;
155
+ if (computeAuthorityExhaustionId(body) !== claimedId) {
156
+ failures.push('ID_MISMATCH');
157
+ return invalid();
158
+ }
159
+ const publicKey = resolveVerificationKey(record.boundary, record.verification_method, record.found_at);
160
+ if (typeof publicKey !== 'string' || publicKey.length === 0) {
161
+ failures.push('KEY_UNRESOLVED');
162
+ return invalid();
163
+ }
164
+ if (!verifyAuthorityExhaustionSignature(record, publicKey)) {
165
+ failures.push('SIGNATURE_INVALID');
166
+ return invalid();
167
+ }
168
+ return Object.freeze({ status: 'valid', failures: Object.freeze([]) });
169
+ }
170
+ //# sourceMappingURL=record.js.map