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.
- package/SECURITY.md +11 -0
- package/dist/src/core/tool-integrity.d.ts +13 -0
- package/dist/src/core/tool-integrity.d.ts.map +1 -1
- package/dist/src/core/tool-integrity.js +1 -1
- package/dist/src/core/tool-integrity.js.map +1 -1
- package/dist/src/index.d.ts +52 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +282 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/v2/activation/canonical.d.ts +40 -0
- package/dist/src/v2/activation/canonical.d.ts.map +1 -0
- package/dist/src/v2/activation/canonical.js +64 -0
- package/dist/src/v2/activation/canonical.js.map +1 -0
- package/dist/src/v2/activation/index.d.ts +4 -0
- package/dist/src/v2/activation/index.d.ts.map +1 -0
- package/dist/src/v2/activation/index.js +11 -0
- package/dist/src/v2/activation/index.js.map +1 -0
- package/dist/src/v2/activation/types.d.ts +268 -0
- package/dist/src/v2/activation/types.d.ts.map +1 -0
- package/dist/src/v2/activation/types.js +198 -0
- package/dist/src/v2/activation/types.js.map +1 -0
- package/dist/src/v2/activation/verify.d.ts +132 -0
- package/dist/src/v2/activation/verify.d.ts.map +1 -0
- package/dist/src/v2/activation/verify.js +406 -0
- package/dist/src/v2/activation/verify.js.map +1 -0
- package/dist/src/v2/authority-revocation/canonical.d.ts +46 -0
- package/dist/src/v2/authority-revocation/canonical.d.ts.map +1 -0
- package/dist/src/v2/authority-revocation/canonical.js +68 -0
- package/dist/src/v2/authority-revocation/canonical.js.map +1 -0
- package/dist/src/v2/authority-revocation/index.d.ts +9 -0
- package/dist/src/v2/authority-revocation/index.d.ts.map +1 -0
- package/dist/src/v2/authority-revocation/index.js +11 -0
- package/dist/src/v2/authority-revocation/index.js.map +1 -0
- package/dist/src/v2/authority-revocation/issue.d.ts +54 -0
- package/dist/src/v2/authority-revocation/issue.d.ts.map +1 -0
- package/dist/src/v2/authority-revocation/issue.js +111 -0
- package/dist/src/v2/authority-revocation/issue.js.map +1 -0
- package/dist/src/v2/authority-revocation/record.d.ts +69 -0
- package/dist/src/v2/authority-revocation/record.d.ts.map +1 -0
- package/dist/src/v2/authority-revocation/record.js +51 -0
- package/dist/src/v2/authority-revocation/record.js.map +1 -0
- package/dist/src/v2/authority-revocation/resolver.d.ts +30 -0
- package/dist/src/v2/authority-revocation/resolver.d.ts.map +1 -0
- package/dist/src/v2/authority-revocation/resolver.js +52 -0
- package/dist/src/v2/authority-revocation/resolver.js.map +1 -0
- package/dist/src/v2/authority-revocation/schema.d.ts +15 -0
- package/dist/src/v2/authority-revocation/schema.d.ts.map +1 -0
- package/dist/src/v2/authority-revocation/schema.js +107 -0
- package/dist/src/v2/authority-revocation/schema.js.map +1 -0
- package/dist/src/v2/authority-revocation/store.d.ts +55 -0
- package/dist/src/v2/authority-revocation/store.d.ts.map +1 -0
- package/dist/src/v2/authority-revocation/store.js +69 -0
- package/dist/src/v2/authority-revocation/store.js.map +1 -0
- package/dist/src/v2/authority-revocation/types.d.ts +136 -0
- package/dist/src/v2/authority-revocation/types.d.ts.map +1 -0
- package/dist/src/v2/authority-revocation/types.js +11 -0
- package/dist/src/v2/authority-revocation/types.js.map +1 -0
- package/dist/src/v2/authority-revocation/verify.d.ts +38 -0
- package/dist/src/v2/authority-revocation/verify.d.ts.map +1 -0
- package/dist/src/v2/authority-revocation/verify.js +139 -0
- package/dist/src/v2/authority-revocation/verify.js.map +1 -0
- package/dist/src/v2/authority-state/fencing.d.ts +49 -0
- package/dist/src/v2/authority-state/fencing.d.ts.map +1 -0
- package/dist/src/v2/authority-state/fencing.js +92 -0
- package/dist/src/v2/authority-state/fencing.js.map +1 -0
- package/dist/src/v2/authority-state/index.d.ts +7 -0
- package/dist/src/v2/authority-state/index.d.ts.map +1 -0
- package/dist/src/v2/authority-state/index.js +14 -0
- package/dist/src/v2/authority-state/index.js.map +1 -0
- package/dist/src/v2/authority-state/marker.d.ts +70 -0
- package/dist/src/v2/authority-state/marker.d.ts.map +1 -0
- package/dist/src/v2/authority-state/marker.js +122 -0
- package/dist/src/v2/authority-state/marker.js.map +1 -0
- package/dist/src/v2/authority-state/report.d.ts +55 -0
- package/dist/src/v2/authority-state/report.d.ts.map +1 -0
- package/dist/src/v2/authority-state/report.js +51 -0
- package/dist/src/v2/authority-state/report.js.map +1 -0
- package/dist/src/v2/authority-state/retained.d.ts +82 -0
- package/dist/src/v2/authority-state/retained.d.ts.map +1 -0
- package/dist/src/v2/authority-state/retained.js +93 -0
- package/dist/src/v2/authority-state/retained.js.map +1 -0
- package/dist/src/v2/authority-state/types.d.ts +198 -0
- package/dist/src/v2/authority-state/types.d.ts.map +1 -0
- package/dist/src/v2/authority-state/types.js +103 -0
- package/dist/src/v2/authority-state/types.js.map +1 -0
- package/dist/src/v2/authority-state/withdrawal.d.ts +80 -0
- package/dist/src/v2/authority-state/withdrawal.d.ts.map +1 -0
- package/dist/src/v2/authority-state/withdrawal.js +199 -0
- package/dist/src/v2/authority-state/withdrawal.js.map +1 -0
- package/dist/src/v2/bounds/canonical.d.ts +30 -0
- package/dist/src/v2/bounds/canonical.d.ts.map +1 -0
- package/dist/src/v2/bounds/canonical.js +69 -0
- package/dist/src/v2/bounds/canonical.js.map +1 -0
- package/dist/src/v2/bounds/evaluate.d.ts +107 -0
- package/dist/src/v2/bounds/evaluate.d.ts.map +1 -0
- package/dist/src/v2/bounds/evaluate.js +315 -0
- package/dist/src/v2/bounds/evaluate.js.map +1 -0
- package/dist/src/v2/bounds/index.d.ts +6 -0
- package/dist/src/v2/bounds/index.d.ts.map +1 -0
- package/dist/src/v2/bounds/index.js +13 -0
- package/dist/src/v2/bounds/index.js.map +1 -0
- package/dist/src/v2/bounds/purpose.d.ts +2 -0
- package/dist/src/v2/bounds/purpose.d.ts.map +1 -0
- package/dist/src/v2/bounds/purpose.js +23 -0
- package/dist/src/v2/bounds/purpose.js.map +1 -0
- package/dist/src/v2/bounds/record.d.ts +78 -0
- package/dist/src/v2/bounds/record.d.ts.map +1 -0
- package/dist/src/v2/bounds/record.js +170 -0
- package/dist/src/v2/bounds/record.js.map +1 -0
- package/dist/src/v2/bounds/types.d.ts +275 -0
- package/dist/src/v2/bounds/types.d.ts.map +1 -0
- package/dist/src/v2/bounds/types.js +124 -0
- package/dist/src/v2/bounds/types.js.map +1 -0
- package/dist/src/v2/capability-binding/binding.d.ts +67 -0
- package/dist/src/v2/capability-binding/binding.d.ts.map +1 -0
- package/dist/src/v2/capability-binding/binding.js +267 -0
- package/dist/src/v2/capability-binding/binding.js.map +1 -0
- package/dist/src/v2/capability-binding/digest.d.ts +33 -0
- package/dist/src/v2/capability-binding/digest.d.ts.map +1 -0
- package/dist/src/v2/capability-binding/digest.js +55 -0
- package/dist/src/v2/capability-binding/digest.js.map +1 -0
- package/dist/src/v2/capability-binding/identifier.d.ts +103 -0
- package/dist/src/v2/capability-binding/identifier.d.ts.map +1 -0
- package/dist/src/v2/capability-binding/identifier.js +281 -0
- package/dist/src/v2/capability-binding/identifier.js.map +1 -0
- package/dist/src/v2/capability-binding/index.d.ts +6 -0
- package/dist/src/v2/capability-binding/index.d.ts.map +1 -0
- package/dist/src/v2/capability-binding/index.js +13 -0
- package/dist/src/v2/capability-binding/index.js.map +1 -0
- package/dist/src/v2/capability-binding/pins.d.ts +30 -0
- package/dist/src/v2/capability-binding/pins.d.ts.map +1 -0
- package/dist/src/v2/capability-binding/pins.js +109 -0
- package/dist/src/v2/capability-binding/pins.js.map +1 -0
- package/dist/src/v2/capability-binding/types.d.ts +213 -0
- package/dist/src/v2/capability-binding/types.d.ts.map +1 -0
- package/dist/src/v2/capability-binding/types.js +150 -0
- package/dist/src/v2/capability-binding/types.js.map +1 -0
- package/dist/src/v2/chain-selection/index.d.ts +4 -0
- package/dist/src/v2/chain-selection/index.d.ts.map +1 -0
- package/dist/src/v2/chain-selection/index.js +11 -0
- package/dist/src/v2/chain-selection/index.js.map +1 -0
- package/dist/src/v2/chain-selection/select.d.ts +62 -0
- package/dist/src/v2/chain-selection/select.d.ts.map +1 -0
- package/dist/src/v2/chain-selection/select.js +390 -0
- package/dist/src/v2/chain-selection/select.js.map +1 -0
- package/dist/src/v2/chain-selection/types.d.ts +189 -0
- package/dist/src/v2/chain-selection/types.d.ts.map +1 -0
- package/dist/src/v2/chain-selection/types.js +81 -0
- package/dist/src/v2/chain-selection/types.js.map +1 -0
- package/dist/src/v2/lifecycle-state/index.d.ts +4 -0
- package/dist/src/v2/lifecycle-state/index.d.ts.map +1 -0
- package/dist/src/v2/lifecycle-state/index.js +10 -0
- package/dist/src/v2/lifecycle-state/index.js.map +1 -0
- package/dist/src/v2/lifecycle-state/map.d.ts +52 -0
- package/dist/src/v2/lifecycle-state/map.d.ts.map +1 -0
- package/dist/src/v2/lifecycle-state/map.js +122 -0
- package/dist/src/v2/lifecycle-state/map.js.map +1 -0
- package/dist/src/v2/lifecycle-state/state.d.ts +60 -0
- package/dist/src/v2/lifecycle-state/state.d.ts.map +1 -0
- package/dist/src/v2/lifecycle-state/state.js +157 -0
- package/dist/src/v2/lifecycle-state/state.js.map +1 -0
- package/dist/src/v2/lifecycle-state/types.d.ts +163 -0
- package/dist/src/v2/lifecycle-state/types.d.ts.map +1 -0
- package/dist/src/v2/lifecycle-state/types.js +121 -0
- package/dist/src/v2/lifecycle-state/types.js.map +1 -0
- package/dist/src/v2/receipt-core/composite.d.ts +69 -3
- package/dist/src/v2/receipt-core/composite.d.ts.map +1 -1
- package/dist/src/v2/receipt-core/composite.js +75 -9
- package/dist/src/v2/receipt-core/composite.js.map +1 -1
- package/dist/src/v2/receipt-core/predecessor.d.ts +95 -0
- package/dist/src/v2/receipt-core/predecessor.d.ts.map +1 -0
- package/dist/src/v2/receipt-core/predecessor.js +115 -0
- package/dist/src/v2/receipt-core/predecessor.js.map +1 -0
- package/dist/src/v2/status-coverage/decide.d.ts +52 -0
- package/dist/src/v2/status-coverage/decide.d.ts.map +1 -0
- package/dist/src/v2/status-coverage/decide.js +398 -0
- package/dist/src/v2/status-coverage/decide.js.map +1 -0
- package/dist/src/v2/status-coverage/index.d.ts +3 -0
- package/dist/src/v2/status-coverage/index.d.ts.map +1 -0
- package/dist/src/v2/status-coverage/index.js +10 -0
- package/dist/src/v2/status-coverage/index.js.map +1 -0
- package/dist/src/v2/status-coverage/types.d.ts +258 -0
- package/dist/src/v2/status-coverage/types.d.ts.map +1 -0
- package/dist/src/v2/status-coverage/types.js +127 -0
- package/dist/src/v2/status-coverage/types.js.map +1 -0
- package/dist/src/v2/suspension/evaluate.d.ts +88 -0
- package/dist/src/v2/suspension/evaluate.d.ts.map +1 -0
- package/dist/src/v2/suspension/evaluate.js +404 -0
- package/dist/src/v2/suspension/evaluate.js.map +1 -0
- package/dist/src/v2/suspension/index.d.ts +3 -0
- package/dist/src/v2/suspension/index.d.ts.map +1 -0
- package/dist/src/v2/suspension/index.js +10 -0
- package/dist/src/v2/suspension/index.js.map +1 -0
- package/dist/src/v2/suspension/types.d.ts +243 -0
- package/dist/src/v2/suspension/types.d.ts.map +1 -0
- package/dist/src/v2/suspension/types.js +135 -0
- package/dist/src/v2/suspension/types.js.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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
|