agent-passport-system 7.1.0 → 7.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/SECURITY.md +2 -2
- 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 +28 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +242 -0
- 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-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/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 +8 -8
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// Copyright (c) 2026 Tymofii Pidlisnyi
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// PROPOSED, OPT-IN. Deciding whether an activation condition is established at an instant.
|
|
5
|
+
// See ./types.ts for the specification position and for the three parameters this module
|
|
6
|
+
// refuses to default.
|
|
7
|
+
import { parseRfc3339 } from '../../core/rfc3339.js';
|
|
8
|
+
import { verify as verifyEd25519 } from '../../crypto/keys.js';
|
|
9
|
+
import { mapAuthorityValidationToLifecycle } from '../lifecycle-state/map.js';
|
|
10
|
+
import { lifecycleState } from '../lifecycle-state/state.js';
|
|
11
|
+
import { activationAttestationSignatureInput } from './canonical.js';
|
|
12
|
+
import { ACTIVATION_ATTESTATION_TYPE, ACTIVATION_CONDITION_TYPE, ACTIVATION_GAPS_BY_REASON, ACTIVATION_INSTANT_BASES, ActivationError, } from './types.js';
|
|
13
|
+
/** How far through the checks a rejected attestation got. When no record was accepted, the
|
|
14
|
+
* reported code is the reason of the record that got FURTHEST, so the verdict names the
|
|
15
|
+
* closest thing to usable evidence that was presented. Ties break on `attestation_id`
|
|
16
|
+
* ascending, which makes the answer independent of presentation order. */
|
|
17
|
+
const REJECTION_RANK = Object.freeze({
|
|
18
|
+
ATTESTATION_RECORD_TYPE_NOT_ACCEPTED: 1,
|
|
19
|
+
ATTESTATION_SIGNATURE_UNVERIFIED: 2,
|
|
20
|
+
ATTESTATION_ATTESTOR_BINDING_MISMATCH: 3,
|
|
21
|
+
ATTESTATION_ATTESTOR_ROLE_UNKNOWN: 4,
|
|
22
|
+
ATTESTATION_ROLE_CLAIM_CONFLICT: 5,
|
|
23
|
+
ATTESTATION_ATTESTOR_ROLE_MISMATCH: 6,
|
|
24
|
+
ATTESTATION_CONDITION_MISMATCH: 7,
|
|
25
|
+
ATTESTATION_UNKNOWN_ASSERTION: 8,
|
|
26
|
+
ATTESTATION_INSTANT_MALFORMED: 9,
|
|
27
|
+
ATTESTATION_DOES_NOT_REACH_ACTION: 10,
|
|
28
|
+
// Verdict-level codes never rank: they are not rejections of a record.
|
|
29
|
+
ACTIVATION_ESTABLISHED: 0,
|
|
30
|
+
CONDITION_DATE_NOT_REACHED: 0,
|
|
31
|
+
CONDITION_ESTABLISHED_NOT_YET_OCCURRED: 0,
|
|
32
|
+
CONDITION_FIRST_OCCURRED_AFTER_ACTION: 0,
|
|
33
|
+
CONDITION_EVIDENCE_CONFLICT: 0,
|
|
34
|
+
ACTIVATION_THRESHOLD_NOT_MET: 0,
|
|
35
|
+
NO_ATTESTATION_PRESENTED: 0,
|
|
36
|
+
CONDITION_DELEGATION_MISMATCH: 0,
|
|
37
|
+
});
|
|
38
|
+
function assertInstant(value, label) {
|
|
39
|
+
const parsed = parseRfc3339(value);
|
|
40
|
+
if (!parsed.ok) {
|
|
41
|
+
throw new ActivationError('INSTANT_MALFORMED', `${label} is not an RFC 3339 instant with an offset (${parsed.reason})`);
|
|
42
|
+
}
|
|
43
|
+
return parsed.ms;
|
|
44
|
+
}
|
|
45
|
+
/** Shape rules, each with the reason it exists. A condition that breaks one is a programming
|
|
46
|
+
* error at the caller, not a verdict about evidence, so this throws.
|
|
47
|
+
*
|
|
48
|
+
* Exported so a caller can validate a condition it received before reaching a boundary. */
|
|
49
|
+
export function validateActivationCondition(condition) {
|
|
50
|
+
if (condition === null || typeof condition !== 'object') {
|
|
51
|
+
throw new ActivationError('CONDITION_MALFORMED', 'condition is not an object');
|
|
52
|
+
}
|
|
53
|
+
if (condition.record_type !== ACTIVATION_CONDITION_TYPE) {
|
|
54
|
+
throw new ActivationError('CONDITION_RECORD_TYPE_UNKNOWN', `condition record_type must be ${ACTIVATION_CONDITION_TYPE}`);
|
|
55
|
+
}
|
|
56
|
+
for (const field of ['condition_id', 'delegation_id']) {
|
|
57
|
+
if (typeof condition[field] !== 'string' || condition[field].length === 0) {
|
|
58
|
+
throw new ActivationError('CONDITION_MALFORMED', `condition ${field} must be a non-empty string`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (condition.condition_type === 'date') {
|
|
62
|
+
assertInstant(condition.activation_date, 'condition activation_date');
|
|
63
|
+
return condition;
|
|
64
|
+
}
|
|
65
|
+
if (condition.condition_type === 'recorded_event') {
|
|
66
|
+
for (const field of ['event_type', 'event_id']) {
|
|
67
|
+
if (typeof condition[field] !== 'string' || condition[field].length === 0) {
|
|
68
|
+
throw new ActivationError('CONDITION_MALFORMED', `condition ${field} must be a non-empty string`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const roles = condition.required_attestor_roles;
|
|
72
|
+
if (!Array.isArray(roles) || roles.length === 0) {
|
|
73
|
+
// A condition naming no accepted source has not stated what it accepts. Accepting
|
|
74
|
+
// anything would make the role check decorative, and refusing everything would be a
|
|
75
|
+
// fail-closed ruling the text does not state. Refuse the CONDITION instead.
|
|
76
|
+
throw new ActivationError('CONDITION_ROLES_REQUIRED', 'required_attestor_roles must name at least one role');
|
|
77
|
+
}
|
|
78
|
+
for (const role of roles) {
|
|
79
|
+
if (typeof role !== 'string' || role.length === 0) {
|
|
80
|
+
throw new ActivationError('CONDITION_ROLES_REQUIRED', 'required_attestor_roles must contain only non-empty strings');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!Number.isInteger(condition.threshold) || condition.threshold < 1) {
|
|
84
|
+
throw new ActivationError('CONDITION_THRESHOLD_INVALID', 'threshold must be an integer of at least 1, and this module declares no default');
|
|
85
|
+
}
|
|
86
|
+
if (!ACTIVATION_INSTANT_BASES.includes(condition.instant_basis)) {
|
|
87
|
+
throw new ActivationError('CONDITION_INSTANT_BASIS_REQUIRED', `instant_basis must be one of ${ACTIVATION_INSTANT_BASES.join(', ')}, and this module declares no default`);
|
|
88
|
+
}
|
|
89
|
+
return condition;
|
|
90
|
+
}
|
|
91
|
+
throw new ActivationError('CONDITION_TYPE_UNKNOWN', 'condition_type must be date or recorded_event');
|
|
92
|
+
}
|
|
93
|
+
function classify(attestation, condition, actionMs, input) {
|
|
94
|
+
const usesOwnPreimage = input.attestationPreimage === undefined;
|
|
95
|
+
const accepted = input.acceptedAttestationRecordTypes;
|
|
96
|
+
// 1. Record type. A record whose type the model has not declared it accepts for this
|
|
97
|
+
// condition is not evidence, however well it verifies.
|
|
98
|
+
if (usesOwnPreimage) {
|
|
99
|
+
if (attestation.record_type !== ACTIVATION_ATTESTATION_TYPE) {
|
|
100
|
+
return { ok: false, reason: 'ATTESTATION_RECORD_TYPE_NOT_ACCEPTED' };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (!accepted.includes(attestation.record_type)) {
|
|
104
|
+
return { ok: false, reason: 'ATTESTATION_RECORD_TYPE_NOT_ACCEPTED' };
|
|
105
|
+
}
|
|
106
|
+
// 2. Signature, over the record's own bytes, under the key authorized at `attested_at`.
|
|
107
|
+
if (typeof attestation.attested_at !== 'string') {
|
|
108
|
+
return { ok: false, reason: 'ATTESTATION_INSTANT_MALFORMED' };
|
|
109
|
+
}
|
|
110
|
+
if (!parseRfc3339(attestation.attested_at).ok) {
|
|
111
|
+
return { ok: false, reason: 'ATTESTATION_INSTANT_MALFORMED' };
|
|
112
|
+
}
|
|
113
|
+
if (typeof attestation.verification_method !== 'string' ||
|
|
114
|
+
typeof attestation.signature !== 'string' ||
|
|
115
|
+
typeof attestation.attestor !== 'string') {
|
|
116
|
+
return { ok: false, reason: 'ATTESTATION_SIGNATURE_UNVERIFIED' };
|
|
117
|
+
}
|
|
118
|
+
const resolved = input.resolveVerificationKey(attestation.attestor, attestation.verification_method, attestation.attested_at);
|
|
119
|
+
// A resolver failure and a missing key are the same finding here: no accepted source
|
|
120
|
+
// produced a key, so the record is not usable evidence. The granular key-resolution
|
|
121
|
+
// outcomes belong to chain verification, which reports them on its own result.
|
|
122
|
+
if (typeof resolved !== 'string' || resolved.length === 0) {
|
|
123
|
+
return { ok: false, reason: 'ATTESTATION_SIGNATURE_UNVERIFIED' };
|
|
124
|
+
}
|
|
125
|
+
const preimage = usesOwnPreimage
|
|
126
|
+
? activationAttestationSignatureInput(attestation)
|
|
127
|
+
: input.attestationPreimage(attestation);
|
|
128
|
+
let signatureOk = false;
|
|
129
|
+
try {
|
|
130
|
+
signatureOk = verifyEd25519(preimage, attestation.signature, resolved);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
signatureOk = false;
|
|
134
|
+
}
|
|
135
|
+
if (!signatureOk)
|
|
136
|
+
return { ok: false, reason: 'ATTESTATION_SIGNATURE_UNVERIFIED' };
|
|
137
|
+
// 3. The verification method has to belong to the attestor the body names, or a valid
|
|
138
|
+
// signature says nothing about who attested.
|
|
139
|
+
if (!attestation.verification_method.startsWith(`${attestation.attestor}#`)) {
|
|
140
|
+
return { ok: false, reason: 'ATTESTATION_ATTESTOR_BINDING_MISMATCH' };
|
|
141
|
+
}
|
|
142
|
+
// 4. The attestor's own role claim, checked AGAINST the resolver. Never believed.
|
|
143
|
+
if (typeof attestation.attestor_role === 'string' && attestation.attestor_role.length > 0) {
|
|
144
|
+
const claimed = input.resolveAttestorRole(attestation.attestor, attestation.attestor_role, attestation.attested_at);
|
|
145
|
+
if (claimed === 'unknown')
|
|
146
|
+
return { ok: false, reason: 'ATTESTATION_ATTESTOR_ROLE_UNKNOWN' };
|
|
147
|
+
if (claimed !== 'holds')
|
|
148
|
+
return { ok: false, reason: 'ATTESTATION_ROLE_CLAIM_CONFLICT' };
|
|
149
|
+
}
|
|
150
|
+
// 5. Does the attestor hold a role the condition requires? A source the model does not
|
|
151
|
+
// accept for THIS condition is not evidence in either direction: it cannot establish the
|
|
152
|
+
// condition and it cannot establish that the condition was unmet.
|
|
153
|
+
let holdsRequired = false;
|
|
154
|
+
let anyUnknown = false;
|
|
155
|
+
for (const role of condition.required_attestor_roles) {
|
|
156
|
+
const standing = input.resolveAttestorRole(attestation.attestor, role, attestation.attested_at);
|
|
157
|
+
if (standing === 'holds') {
|
|
158
|
+
holdsRequired = true;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
if (standing === 'unknown')
|
|
162
|
+
anyUnknown = true;
|
|
163
|
+
}
|
|
164
|
+
if (!holdsRequired) {
|
|
165
|
+
return {
|
|
166
|
+
ok: false,
|
|
167
|
+
reason: anyUnknown
|
|
168
|
+
? 'ATTESTATION_ATTESTOR_ROLE_UNKNOWN'
|
|
169
|
+
: 'ATTESTATION_ATTESTOR_ROLE_MISMATCH',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// 6. Condition binding.
|
|
173
|
+
if (attestation.condition_id !== condition.condition_id ||
|
|
174
|
+
attestation.event_type !== condition.event_type ||
|
|
175
|
+
attestation.event_id !== condition.event_id) {
|
|
176
|
+
return { ok: false, reason: 'ATTESTATION_CONDITION_MISMATCH' };
|
|
177
|
+
}
|
|
178
|
+
// 7. What it establishes, measured on the instant the CONDITION declares governs.
|
|
179
|
+
if (attestation.assertion === 'condition_occurred') {
|
|
180
|
+
const raw = condition.instant_basis === 'attestation_written'
|
|
181
|
+
? attestation.attested_at
|
|
182
|
+
: attestation.occurred_at;
|
|
183
|
+
if (typeof raw !== 'string')
|
|
184
|
+
return { ok: false, reason: 'ATTESTATION_UNKNOWN_ASSERTION' };
|
|
185
|
+
const parsed = parseRfc3339(raw);
|
|
186
|
+
if (!parsed.ok)
|
|
187
|
+
return { ok: false, reason: 'ATTESTATION_INSTANT_MALFORMED' };
|
|
188
|
+
return {
|
|
189
|
+
ok: true,
|
|
190
|
+
finding: parsed.ms <= actionMs ? 'occurred_by_action' : 'occurred_after_action',
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (attestation.assertion === 'condition_not_occurred_through') {
|
|
194
|
+
const raw = attestation.not_occurred_through;
|
|
195
|
+
if (typeof raw !== 'string')
|
|
196
|
+
return { ok: false, reason: 'ATTESTATION_UNKNOWN_ASSERTION' };
|
|
197
|
+
const parsed = parseRfc3339(raw);
|
|
198
|
+
if (!parsed.ok)
|
|
199
|
+
return { ok: false, reason: 'ATTESTATION_INSTANT_MALFORMED' };
|
|
200
|
+
// A negative that stops short of the action instant says nothing about the interval
|
|
201
|
+
// between where it stops and the action. That is the coverage limb, not a source problem.
|
|
202
|
+
if (parsed.ms < actionMs)
|
|
203
|
+
return { ok: false, reason: 'ATTESTATION_DOES_NOT_REACH_ACTION' };
|
|
204
|
+
return { ok: true, finding: 'not_occurred_through_action' };
|
|
205
|
+
}
|
|
206
|
+
return { ok: false, reason: 'ATTESTATION_UNKNOWN_ASSERTION' };
|
|
207
|
+
}
|
|
208
|
+
function notEstablishedFor(code) {
|
|
209
|
+
return lifecycleState({
|
|
210
|
+
verdict: 'not_established',
|
|
211
|
+
reason_code: code,
|
|
212
|
+
missing: ACTIVATION_GAPS_BY_REASON[code] ?? ['source'],
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Decide whether an activation condition is established for one action at one instant.
|
|
217
|
+
*
|
|
218
|
+
* Three verdicts are reachable and `invalid` is not one of them:
|
|
219
|
+
*
|
|
220
|
+
* | situation | verdict |
|
|
221
|
+
* |--------------------------------------------------------------|---------------------|
|
|
222
|
+
* | condition established at or before the action instant | `valid` |
|
|
223
|
+
* | established as not met at that instant, or first met after it | `not_yet_effective` |
|
|
224
|
+
* | the verifier cannot tell | `not_established` |
|
|
225
|
+
*
|
|
226
|
+
* NOT ESTABLISHED IS NOT THE NEGATION OF THE CLAIM. It says the verifier could not reach a
|
|
227
|
+
* conclusion and names which establishment limb was missing. `not_yet_effective` says the
|
|
228
|
+
* verifier DID reach a conclusion and it was negative; its remedy is to wait, where
|
|
229
|
+
* `not_established`'s remedy is a better source. Collapsing the two throws away a decidable
|
|
230
|
+
* answer the verifier already had.
|
|
231
|
+
*
|
|
232
|
+
* NO RETROACTIVE ACTIVATION, KEYED ON THE CONDITION'S OWN INSTANT. A record putting the
|
|
233
|
+
* condition's first occurrence after the action instant leaves that action
|
|
234
|
+
* `not_yet_effective`, and the SAME record establishes the condition for any later action.
|
|
235
|
+
* What the rule keys on is the condition's instant, not the instant someone wrote the record:
|
|
236
|
+
* learning on Thursday that a condition was met on Monday is the normal case for any model
|
|
237
|
+
* built around an after-the-fact determination, and under
|
|
238
|
+
* `instant_basis: 'condition_occurrence'` such a record establishes the condition. A model
|
|
239
|
+
* that reads it the other way says so with `instant_basis: 'attestation_written'`.
|
|
240
|
+
*
|
|
241
|
+
* ORDER OF RESOLUTION, and why contradiction sits above acceptance:
|
|
242
|
+
*
|
|
243
|
+
* 1. two accepted records that disagree `not_established`, `CONDITION_EVIDENCE_CONFLICT`
|
|
244
|
+
* 2. `threshold` records say occurred by it `valid`
|
|
245
|
+
* 3. `threshold` records say not occurred `not_yet_effective`
|
|
246
|
+
* 4. `threshold` records say occurred after `not_yet_effective`
|
|
247
|
+
* 5. some accepted, fewer than `threshold` `not_established`, `ACTIVATION_THRESHOLD_NOT_MET`
|
|
248
|
+
* 6. none accepted `not_established`, furthest rejection reason
|
|
249
|
+
*
|
|
250
|
+
* Contradiction is evaluated on PRESENCE, not on counts. Two acceptable records that disagree
|
|
251
|
+
* leave the verifier unable to tell which holds, and neither is discarded in favour of the
|
|
252
|
+
* other: preferring the later record, or the negative one, or a majority, would each be a
|
|
253
|
+
* precedence rule the concept text does not state.
|
|
254
|
+
*
|
|
255
|
+
* This function reads no clock and makes no network call. `actionInstant` is a parameter.
|
|
256
|
+
*
|
|
257
|
+
* Proposed. Concept source: aeoess/agent-authority-lifecycle, invariant candidates CAND-04
|
|
258
|
+
* and CAND-13 (activation half) and BROAD-L7. Not required by draft-pidlisnyi-aps-03.
|
|
259
|
+
*/
|
|
260
|
+
export function verifyActivation(input) {
|
|
261
|
+
if (input === null || typeof input !== 'object') {
|
|
262
|
+
throw new ActivationError('INPUT_MALFORMED', 'input is not an object');
|
|
263
|
+
}
|
|
264
|
+
if (typeof input.resolveAttestorRole !== 'function') {
|
|
265
|
+
throw new ActivationError('ROLE_RESOLVER_REQUIRED', 'resolveAttestorRole is required: role standing is resolved outside the record, always');
|
|
266
|
+
}
|
|
267
|
+
if (typeof input.resolveVerificationKey !== 'function') {
|
|
268
|
+
throw new ActivationError('KEY_RESOLVER_REQUIRED', 'resolveVerificationKey is required');
|
|
269
|
+
}
|
|
270
|
+
if (input.attestationPreimage !== undefined) {
|
|
271
|
+
const accepted = input.acceptedAttestationRecordTypes;
|
|
272
|
+
if (!Array.isArray(accepted) || accepted.length === 0) {
|
|
273
|
+
throw new ActivationError('ACCEPTED_RECORD_TYPES_REQUIRED', 'acceptedAttestationRecordTypes must name at least one type when attestationPreimage is supplied');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (typeof input.delegationId !== 'string' || input.delegationId.length === 0) {
|
|
277
|
+
throw new ActivationError('DELEGATION_ID_REQUIRED', 'delegationId must be a non-empty string');
|
|
278
|
+
}
|
|
279
|
+
const condition = validateActivationCondition(input.condition);
|
|
280
|
+
const actionMs = assertInstant(input.actionInstant, 'actionInstant');
|
|
281
|
+
const presented = input.attestations ?? [];
|
|
282
|
+
if (condition.delegation_id !== input.delegationId) {
|
|
283
|
+
return Object.freeze({
|
|
284
|
+
state: notEstablishedFor('CONDITION_DELEGATION_MISMATCH'),
|
|
285
|
+
condition_id: condition.condition_id,
|
|
286
|
+
condition_type: condition.condition_type,
|
|
287
|
+
findings: Object.freeze([]),
|
|
288
|
+
rejections: Object.freeze([]),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
// A date condition needs no evidence at all. The verifier reads the date off the condition
|
|
292
|
+
// and compares it with the action instant, so an unreached date is a KNOWN negative and
|
|
293
|
+
// never an unknown one. Presented attestations are not consulted.
|
|
294
|
+
if (condition.condition_type === 'date') {
|
|
295
|
+
const reached = assertInstant(condition.activation_date, 'condition activation_date') <= actionMs;
|
|
296
|
+
return Object.freeze({
|
|
297
|
+
state: reached
|
|
298
|
+
? lifecycleState({ verdict: 'valid', reason_code: 'ACTIVATION_ESTABLISHED' })
|
|
299
|
+
: lifecycleState({
|
|
300
|
+
verdict: 'not_yet_effective',
|
|
301
|
+
reason_code: 'CONDITION_DATE_NOT_REACHED',
|
|
302
|
+
}),
|
|
303
|
+
condition_id: condition.condition_id,
|
|
304
|
+
condition_type: condition.condition_type,
|
|
305
|
+
findings: Object.freeze([]),
|
|
306
|
+
rejections: Object.freeze([]),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
const findings = [];
|
|
310
|
+
const rejections = [];
|
|
311
|
+
const counts = {
|
|
312
|
+
occurred_by_action: 0,
|
|
313
|
+
occurred_after_action: 0,
|
|
314
|
+
not_occurred_through_action: 0,
|
|
315
|
+
};
|
|
316
|
+
for (const attestation of presented) {
|
|
317
|
+
if (attestation === null || typeof attestation !== 'object') {
|
|
318
|
+
throw new ActivationError('ATTESTATION_MALFORMED', 'an attestation is not an object');
|
|
319
|
+
}
|
|
320
|
+
const id = typeof attestation.attestation_id === 'string' ? attestation.attestation_id : '';
|
|
321
|
+
const attestor = typeof attestation.attestor === 'string' ? attestation.attestor : '';
|
|
322
|
+
const outcome = classify(attestation, condition, actionMs, input);
|
|
323
|
+
if (outcome.ok) {
|
|
324
|
+
counts[outcome.finding] += 1;
|
|
325
|
+
findings.push(Object.freeze({ attestation_id: id, attestor, finding: outcome.finding }));
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
rejections.push(Object.freeze({ attestation_id: id, attestor, reason_code: outcome.reason }));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const frozen = {
|
|
332
|
+
condition_id: condition.condition_id,
|
|
333
|
+
condition_type: condition.condition_type,
|
|
334
|
+
findings: Object.freeze([...findings]),
|
|
335
|
+
rejections: Object.freeze([...rejections]),
|
|
336
|
+
};
|
|
337
|
+
if (counts.occurred_by_action > 0 && counts.not_occurred_through_action > 0) {
|
|
338
|
+
return Object.freeze({ ...frozen, state: notEstablishedFor('CONDITION_EVIDENCE_CONFLICT') });
|
|
339
|
+
}
|
|
340
|
+
if (counts.occurred_by_action >= condition.threshold) {
|
|
341
|
+
return Object.freeze({
|
|
342
|
+
...frozen,
|
|
343
|
+
state: lifecycleState({ verdict: 'valid', reason_code: 'ACTIVATION_ESTABLISHED' }),
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (counts.not_occurred_through_action >= condition.threshold) {
|
|
347
|
+
return Object.freeze({
|
|
348
|
+
...frozen,
|
|
349
|
+
state: lifecycleState({
|
|
350
|
+
verdict: 'not_yet_effective',
|
|
351
|
+
reason_code: 'CONDITION_ESTABLISHED_NOT_YET_OCCURRED',
|
|
352
|
+
}),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
if (counts.occurred_after_action >= condition.threshold) {
|
|
356
|
+
return Object.freeze({
|
|
357
|
+
...frozen,
|
|
358
|
+
state: lifecycleState({
|
|
359
|
+
verdict: 'not_yet_effective',
|
|
360
|
+
reason_code: 'CONDITION_FIRST_OCCURRED_AFTER_ACTION',
|
|
361
|
+
}),
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
if (findings.length > 0) {
|
|
365
|
+
return Object.freeze({ ...frozen, state: notEstablishedFor('ACTIVATION_THRESHOLD_NOT_MET') });
|
|
366
|
+
}
|
|
367
|
+
if (rejections.length === 0) {
|
|
368
|
+
return Object.freeze({ ...frozen, state: notEstablishedFor('NO_ATTESTATION_PRESENTED') });
|
|
369
|
+
}
|
|
370
|
+
const furthest = [...rejections].sort((a, b) => {
|
|
371
|
+
const rank = REJECTION_RANK[b.reason_code] - REJECTION_RANK[a.reason_code];
|
|
372
|
+
return rank !== 0 ? rank : a.attestation_id.localeCompare(b.attestation_id);
|
|
373
|
+
})[0];
|
|
374
|
+
return Object.freeze({ ...frozen, state: notEstablishedFor(furthest.reason_code) });
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Report an activation result ALONGSIDE a chain result, never merged into it.
|
|
378
|
+
*
|
|
379
|
+
* Two stages, in order, and the order is the point:
|
|
380
|
+
*
|
|
381
|
+
* 1. Chain verification decides whether the grant is valid at the action instant. Its
|
|
382
|
+
* four-value result is returned untouched in `chain`.
|
|
383
|
+
* 2. Activation is asked ONLY when the chain is valid. A grant that does not verify is not a
|
|
384
|
+
* grant that is waiting on a condition, and reporting `not_yet_effective` for a revoked
|
|
385
|
+
* or unparented grant would say the remedy is to wait when it is not.
|
|
386
|
+
*
|
|
387
|
+
* This is also where CAND-13's activation half lands. A pre-committed replacement grant is an
|
|
388
|
+
* ordinary grant with an ordinary condition, so it needs no separate machinery here, and the
|
|
389
|
+
* ordering is what keeps L1 intact: if the pre-committing instrument is revoked, the
|
|
390
|
+
* replacement's chain is invalid and no activation evidence can make it exercisable. Nothing
|
|
391
|
+
* in this module can turn an invalid chain into a `valid` lifecycle verdict.
|
|
392
|
+
*
|
|
393
|
+
* `activation` may be `null`, which is how a caller says no activation module ran. The
|
|
394
|
+
* composite then carries only the chain's own reading.
|
|
395
|
+
*
|
|
396
|
+
* Proposed. Concept source: aeoess/agent-authority-lifecycle, invariant L1 and invariant
|
|
397
|
+
* candidates CAND-04 and CAND-13.
|
|
398
|
+
*/
|
|
399
|
+
export function composeActivation(chain, activation, options = {}) {
|
|
400
|
+
const mapped = mapAuthorityValidationToLifecycle(chain, options.mapping ?? {});
|
|
401
|
+
if (chain.state !== 'valid' || activation === null) {
|
|
402
|
+
return Object.freeze({ chain, lifecycle: mapped });
|
|
403
|
+
}
|
|
404
|
+
return Object.freeze({ chain, lifecycle: activation.state });
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../../../src/v2/activation/verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { FencedWrite, FencedWriteOutcome, StateMarker } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* An authority-state publication log with a fencing gate on every write.
|
|
4
|
+
*
|
|
5
|
+
* Generic in the payload on purpose: this class orders writes and stores nothing about
|
|
6
|
+
* authority itself. The payload is whatever the deployment publishes as a state view, a
|
|
7
|
+
* store handle, a snapshot identifier or a record set. The SDK has no opinion about it.
|
|
8
|
+
*
|
|
9
|
+
* The gate, in one line each:
|
|
10
|
+
*
|
|
11
|
+
* - a token BELOW the highest already accepted is refused, `stale_fencing_token`
|
|
12
|
+
* - a token EQUAL to it is accepted, because equal has not gone backwards: the same holder
|
|
13
|
+
* is retrying, and the retry is idempotent
|
|
14
|
+
* - a token ABOVE it is accepted, and so is a token the log has never seen before, as long
|
|
15
|
+
* as it does not go backwards. The gate fences state that is behind, not state that
|
|
16
|
+
* merely differs
|
|
17
|
+
* - a token counted in a DIFFERENT SCOPE from the log's is refused,
|
|
18
|
+
* `fencing_scope_mismatch`, and an absent or malformed token is refused
|
|
19
|
+
* `fencing_token_unreadable`. Two markers from different scopes order nothing, and a
|
|
20
|
+
* write path that cannot order a token is not fenced. Neither refusal is in any source;
|
|
21
|
+
* both are this module's choice, and they are kept apart because an unreadable token is
|
|
22
|
+
* a different finding from a backwards one
|
|
23
|
+
*
|
|
24
|
+
* A refused write changes nothing: not the published payload, not the highest token. That
|
|
25
|
+
* is the property the whole class exists for, and a caller reads `accepted` to learn which
|
|
26
|
+
* of the two happened rather than comparing payloads.
|
|
27
|
+
*
|
|
28
|
+
* NOT DURABLE, NOT CONCURRENT, NOT A LOCK SERVICE. This holds one token and one payload in
|
|
29
|
+
* this process's heap. It acquires no lock, contacts no lock service, and its check-then-set
|
|
30
|
+
* is one synchronous statement sequence, which is safe on this runtime and is a property of
|
|
31
|
+
* the runtime rather than a pattern a durable store may copy. The same warning
|
|
32
|
+
* `InMemoryAuthorityRevocationStore` carries applies here for the same reason.
|
|
33
|
+
*/
|
|
34
|
+
export declare class FencedAuthorityStateLog<T> {
|
|
35
|
+
private highest;
|
|
36
|
+
private payload;
|
|
37
|
+
private written;
|
|
38
|
+
/** The highest token accepted so far, or `null` when nothing has been published. */
|
|
39
|
+
highestToken(): StateMarker | null;
|
|
40
|
+
/** What is published now. `undefined` when no write has landed, which a caller must tell
|
|
41
|
+
* apart from a write that landed carrying `undefined`; `hasPublished()` is how. */
|
|
42
|
+
published(): T | undefined;
|
|
43
|
+
hasPublished(): boolean;
|
|
44
|
+
/** Submit a write. See the class doc for the four outcomes. Never throws for a malformed
|
|
45
|
+
* token: an unreadable token is one that cannot be shown to have moved forward, so it is
|
|
46
|
+
* refused. */
|
|
47
|
+
write(write: FencedWrite<T>): FencedWriteOutcome<T>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=fencing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fencing.d.ts","sourceRoot":"","sources":["../../../../src/v2/authority-state/fencing.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,uBAAuB,CAAC,CAAC;IACpC,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,OAAO,CAAQ;IAEvB,oFAAoF;IACpF,YAAY,IAAI,WAAW,GAAG,IAAI,CAEjC;IAED;wFACoF;IACpF,SAAS,IAAI,CAAC,GAAG,SAAS,CAEzB;IAED,YAAY,IAAI,OAAO,CAEtB;IAED;;mBAEe;IACf,KAAK,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAmBlD;CACF"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Copyright (c) 2026 Tymofii Pidlisnyi
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// PROPOSED, OPT-IN. Fencing on the authority-state WRITE path.
|
|
5
|
+
// See ./types.ts for the specification position. Proposed.
|
|
6
|
+
//
|
|
7
|
+
// WHY THIS IS A SEPARATE AXIS FROM THE READ PATH. A read-side high-water mark protects one
|
|
8
|
+
// verifier that has already seen the newer state. It does nothing for a verifier that has
|
|
9
|
+
// seen nothing, reading a published view that a stale writer republished underneath it. A
|
|
10
|
+
// partition that resolves the wrong way rolls authority back with no restore involved, and
|
|
11
|
+
// the read path never sees a regression because the published state simply IS the old
|
|
12
|
+
// state. So the check has to exist where the write lands.
|
|
13
|
+
//
|
|
14
|
+
// The rule implemented here is the one Kleppmann's "How to do distributed locking" states:
|
|
15
|
+
// the storage server takes an active role in checking tokens and rejects any write on which
|
|
16
|
+
// the token has gone backwards. `OPEN-QUESTIONS.md` names authority rollback as open and
|
|
17
|
+
// names an append-only revocation log a restore must replay as the other candidate
|
|
18
|
+
// mechanism in the same sentence. That one is NOT modelled here. Neither is specified.
|
|
19
|
+
import { compareStateMarker, sameScope } from './marker.js';
|
|
20
|
+
/**
|
|
21
|
+
* An authority-state publication log with a fencing gate on every write.
|
|
22
|
+
*
|
|
23
|
+
* Generic in the payload on purpose: this class orders writes and stores nothing about
|
|
24
|
+
* authority itself. The payload is whatever the deployment publishes as a state view, a
|
|
25
|
+
* store handle, a snapshot identifier or a record set. The SDK has no opinion about it.
|
|
26
|
+
*
|
|
27
|
+
* The gate, in one line each:
|
|
28
|
+
*
|
|
29
|
+
* - a token BELOW the highest already accepted is refused, `stale_fencing_token`
|
|
30
|
+
* - a token EQUAL to it is accepted, because equal has not gone backwards: the same holder
|
|
31
|
+
* is retrying, and the retry is idempotent
|
|
32
|
+
* - a token ABOVE it is accepted, and so is a token the log has never seen before, as long
|
|
33
|
+
* as it does not go backwards. The gate fences state that is behind, not state that
|
|
34
|
+
* merely differs
|
|
35
|
+
* - a token counted in a DIFFERENT SCOPE from the log's is refused,
|
|
36
|
+
* `fencing_scope_mismatch`, and an absent or malformed token is refused
|
|
37
|
+
* `fencing_token_unreadable`. Two markers from different scopes order nothing, and a
|
|
38
|
+
* write path that cannot order a token is not fenced. Neither refusal is in any source;
|
|
39
|
+
* both are this module's choice, and they are kept apart because an unreadable token is
|
|
40
|
+
* a different finding from a backwards one
|
|
41
|
+
*
|
|
42
|
+
* A refused write changes nothing: not the published payload, not the highest token. That
|
|
43
|
+
* is the property the whole class exists for, and a caller reads `accepted` to learn which
|
|
44
|
+
* of the two happened rather than comparing payloads.
|
|
45
|
+
*
|
|
46
|
+
* NOT DURABLE, NOT CONCURRENT, NOT A LOCK SERVICE. This holds one token and one payload in
|
|
47
|
+
* this process's heap. It acquires no lock, contacts no lock service, and its check-then-set
|
|
48
|
+
* is one synchronous statement sequence, which is safe on this runtime and is a property of
|
|
49
|
+
* the runtime rather than a pattern a durable store may copy. The same warning
|
|
50
|
+
* `InMemoryAuthorityRevocationStore` carries applies here for the same reason.
|
|
51
|
+
*/
|
|
52
|
+
export class FencedAuthorityStateLog {
|
|
53
|
+
highest = null;
|
|
54
|
+
payload = undefined;
|
|
55
|
+
written = false;
|
|
56
|
+
/** The highest token accepted so far, or `null` when nothing has been published. */
|
|
57
|
+
highestToken() {
|
|
58
|
+
return this.highest;
|
|
59
|
+
}
|
|
60
|
+
/** What is published now. `undefined` when no write has landed, which a caller must tell
|
|
61
|
+
* apart from a write that landed carrying `undefined`; `hasPublished()` is how. */
|
|
62
|
+
published() {
|
|
63
|
+
return this.payload;
|
|
64
|
+
}
|
|
65
|
+
hasPublished() {
|
|
66
|
+
return this.written;
|
|
67
|
+
}
|
|
68
|
+
/** Submit a write. See the class doc for the four outcomes. Never throws for a malformed
|
|
69
|
+
* token: an unreadable token is one that cannot be shown to have moved forward, so it is
|
|
70
|
+
* refused. */
|
|
71
|
+
write(write) {
|
|
72
|
+
const token = write?.token;
|
|
73
|
+
// Read the token on its own first. An unreadable token is a different finding from a
|
|
74
|
+
// stale one, and collapsing them would report a backwards move that never happened.
|
|
75
|
+
if (compareStateMarker(token, token) !== 'forward') {
|
|
76
|
+
return { accepted: false, code: 'fencing_token_unreadable', published: this.highest };
|
|
77
|
+
}
|
|
78
|
+
if (this.highest !== null) {
|
|
79
|
+
if (!sameScope(this.highest, token)) {
|
|
80
|
+
return { accepted: false, code: 'fencing_scope_mismatch', published: this.highest };
|
|
81
|
+
}
|
|
82
|
+
if (compareStateMarker(this.highest, token) !== 'forward') {
|
|
83
|
+
return { accepted: false, code: 'stale_fencing_token', published: this.highest };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.highest = token;
|
|
87
|
+
this.payload = write.payload;
|
|
88
|
+
this.written = true;
|
|
89
|
+
return { accepted: true, published: token, payload: write.payload };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=fencing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fencing.js","sourceRoot":"","sources":["../../../../src/v2/authority-state/fencing.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,sCAAsC;AACtC,EAAE;AACF,+DAA+D;AAC/D,2DAA2D;AAC3D,EAAE;AACF,2FAA2F;AAC3F,0FAA0F;AAC1F,0FAA0F;AAC1F,2FAA2F;AAC3F,sFAAsF;AACtF,0DAA0D;AAC1D,EAAE;AACF,2FAA2F;AAC3F,4FAA4F;AAC5F,yFAAyF;AACzF,mFAAmF;AACnF,uFAAuF;AAEvF,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAG3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,OAAO,uBAAuB;IAC1B,OAAO,GAAuB,IAAI,CAAA;IAClC,OAAO,GAAkB,SAAS,CAAA;IAClC,OAAO,GAAG,KAAK,CAAA;IAEvB,oFAAoF;IACpF,YAAY;QACV,OAAO,IAAI,CAAC,OAAO,CAAA;IACrB,CAAC;IAED;wFACoF;IACpF,SAAS;QACP,OAAO,IAAI,CAAC,OAAO,CAAA;IACrB,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,OAAO,CAAA;IACrB,CAAC;IAED;;mBAEe;IACf,KAAK,CAAC,KAAqB;QACzB,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,CAAA;QAC1B,qFAAqF;QACrF,oFAAoF;QACpF,IAAI,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,SAAS,EAAE,CAAC;YACnD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,0BAA0B,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,CAAA;QACvF,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC1B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;gBACpC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,wBAAwB,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,CAAA;YACrF,CAAC;YACD,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC1D,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,qBAAqB,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,CAAA;YAClF,CAAC;QACH,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QACpB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;QAC5B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;IACrE,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/v2/authority-state/index.ts"],"names":[],"mappings":"AAQA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright (c) 2026 Tymofii Pidlisnyi
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// PROPOSED, OPT-IN authority state markers, write fencing and revocation withdrawal.
|
|
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 candidates
|
|
7
|
+
// CAND-08 and CAND-02.
|
|
8
|
+
export * from './types.js';
|
|
9
|
+
export * from './marker.js';
|
|
10
|
+
export * from './retained.js';
|
|
11
|
+
export * from './fencing.js';
|
|
12
|
+
export * from './withdrawal.js';
|
|
13
|
+
export * from './report.js';
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/v2/authority-state/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,sCAAsC;AACtC,EAAE;AACF,qFAAqF;AACrF,2FAA2F;AAC3F,6FAA6F;AAC7F,uBAAuB;AAEvB,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA"}
|