@agentunion/fastaun 0.5.22 → 0.5.23
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/_packed_docs/INDEX.md +5 -1
- package/_packed_docs/KITE_DOCS_GUIDE.md +1 -0
- package/dist/agent-md.d.ts +15 -1
- package/dist/agent-md.js +49 -20
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +1 -0
- package/dist/aid-store.js +43 -8
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.d.ts +6 -0
- package/dist/auth.js +205 -69
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +3 -0
- package/dist/client.js +79 -19
- package/dist/client.js.map +1 -1
- package/dist/org-protocol.d.ts +10 -0
- package/dist/org-protocol.js +100 -12
- package/dist/org-protocol.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/org-protocol.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ type MembershipImportGuard = {
|
|
|
15
15
|
};
|
|
16
16
|
export interface CredentialRenewalPorts {
|
|
17
17
|
atomicCheckpointSave?(): boolean;
|
|
18
|
+
queryContext?(): string;
|
|
18
19
|
currentAid(): string;
|
|
19
20
|
loadRevocations?(aid: string): RenewalObject;
|
|
20
21
|
saveRevocations?(aid: string, state: RenewalObject, guard?: MembershipImportGuard): void | Promise<void>;
|
|
@@ -27,10 +28,18 @@ export interface CredentialRenewalPorts {
|
|
|
27
28
|
}
|
|
28
29
|
export declare class CredentialRenewal {
|
|
29
30
|
private tail;
|
|
31
|
+
private queryEpoch;
|
|
32
|
+
private readonly verifiedQueries;
|
|
33
|
+
private readonly queryFlights;
|
|
30
34
|
private readonly ports;
|
|
31
35
|
private scanGuard?;
|
|
32
36
|
constructor(ports: CredentialRenewalPorts);
|
|
33
37
|
private checkScan;
|
|
38
|
+
private queryScope;
|
|
39
|
+
invalidateQueries(): void;
|
|
40
|
+
private invalidateMutation;
|
|
41
|
+
private verifiedQuery;
|
|
42
|
+
private currentMember;
|
|
34
43
|
private scanOperation;
|
|
35
44
|
private serial;
|
|
36
45
|
remember(credential: JsonObject, material?: RenewalObject): Promise<void>;
|
|
@@ -42,6 +51,7 @@ export declare class CredentialRenewal {
|
|
|
42
51
|
receive(from: string, payload: unknown): Promise<boolean>;
|
|
43
52
|
private verifyEnvelope;
|
|
44
53
|
private status;
|
|
54
|
+
private statusFresh;
|
|
45
55
|
private receiveNow;
|
|
46
56
|
private recoverRegistration;
|
|
47
57
|
private receipt;
|
package/dist/org-protocol.js
CHANGED
|
@@ -46,6 +46,9 @@ export class ProtocolError extends Error {
|
|
|
46
46
|
}
|
|
47
47
|
export class CredentialRenewal {
|
|
48
48
|
tail = Promise.resolve();
|
|
49
|
+
queryEpoch = 0;
|
|
50
|
+
verifiedQueries = new Map();
|
|
51
|
+
queryFlights = new Map();
|
|
49
52
|
ports;
|
|
50
53
|
scanGuard;
|
|
51
54
|
constructor(ports) {
|
|
@@ -59,7 +62,11 @@ export class CredentialRenewal {
|
|
|
59
62
|
},
|
|
60
63
|
signer: (aid) => this.scanOperation(() => ports.signer(aid, this.scanGuard)),
|
|
61
64
|
certificate: (aid, fingerprint, signedAt) => this.scanOperation(() => ports.certificate(aid, fingerprint, signedAt)),
|
|
62
|
-
call: (method, params, onSent) => this.scanOperation(() =>
|
|
65
|
+
call: (method, params, onSent) => this.scanOperation(async () => {
|
|
66
|
+
const result = await (onSent ? ports.call(method, params, onSent) : ports.call(method, params));
|
|
67
|
+
this.invalidateMutation(method);
|
|
68
|
+
return result;
|
|
69
|
+
}),
|
|
63
70
|
send: (to, payload) => this.scanOperation(() => ports.send(to, payload)),
|
|
64
71
|
};
|
|
65
72
|
}
|
|
@@ -67,6 +74,75 @@ export class CredentialRenewal {
|
|
|
67
74
|
if (this.scanGuard && !this.scanGuard.current())
|
|
68
75
|
throw invalid('credential renewal was superseded');
|
|
69
76
|
}
|
|
77
|
+
queryScope() {
|
|
78
|
+
return JSON.stringify([this.ports.currentAid(), this.ports.queryContext?.() ?? '']);
|
|
79
|
+
}
|
|
80
|
+
invalidateQueries() {
|
|
81
|
+
this.queryEpoch++;
|
|
82
|
+
this.verifiedQueries.clear();
|
|
83
|
+
this.queryFlights.clear();
|
|
84
|
+
}
|
|
85
|
+
invalidateMutation(method) {
|
|
86
|
+
if (method === 'credential.register' || method === 'credential.revoke'
|
|
87
|
+
|| method.startsWith('org.') && !['org.lifecycle.get', 'org.lifecycle.list', 'org.member.get', 'org.member.list', 'org.credential.getStatus', 'org.credential.revocationList', 'org.audit.query', 'org.authz.check', 'org.health', 'org.status'].includes(method))
|
|
88
|
+
this.invalidateQueries();
|
|
89
|
+
}
|
|
90
|
+
async verifiedQuery(key, load) {
|
|
91
|
+
const scope = this.queryScope(), cacheKey = scope + ':' + key, now = Date.now() / 1000;
|
|
92
|
+
for (const [entryKey, entry] of this.verifiedQueries)
|
|
93
|
+
if (now >= entry.expiresAt)
|
|
94
|
+
this.verifiedQueries.delete(entryKey);
|
|
95
|
+
const cached = this.verifiedQueries.get(cacheKey);
|
|
96
|
+
if (cached)
|
|
97
|
+
return structuredClone(cached.value);
|
|
98
|
+
const pending = this.queryFlights.get(cacheKey);
|
|
99
|
+
if (pending)
|
|
100
|
+
return structuredClone(await pending);
|
|
101
|
+
const epoch = this.queryEpoch;
|
|
102
|
+
const request = (async () => {
|
|
103
|
+
const result = await load();
|
|
104
|
+
if (epoch !== this.queryEpoch || scope !== this.queryScope())
|
|
105
|
+
throw invalid('verified query was superseded');
|
|
106
|
+
const expiresAt = Math.min(Date.now() / 1000 + 60, result.expiresAt);
|
|
107
|
+
if (Date.now() / 1000 < expiresAt)
|
|
108
|
+
this.verifiedQueries.set(cacheKey, { value: structuredClone(result.value), expiresAt });
|
|
109
|
+
return result.value;
|
|
110
|
+
})();
|
|
111
|
+
this.queryFlights.set(cacheKey, request);
|
|
112
|
+
try {
|
|
113
|
+
return structuredClone(await request);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
if (this.queryFlights.get(cacheKey) === request)
|
|
117
|
+
this.queryFlights.delete(cacheKey);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async currentMember(credential, binding, service, actor, signer) {
|
|
121
|
+
const credentials = { [binding.role === 'admin' ? 'approval_credential' : 'member_credential']: credential, membership_binding: binding };
|
|
122
|
+
const params = { org_id: credential.iss, member_aid: actor, credentials };
|
|
123
|
+
return this.verifiedQuery('org.member.get:' + service + ':' + digest(params), async () => {
|
|
124
|
+
const query = prepareOrganizationRequest('org.member.get', params, service, signer);
|
|
125
|
+
const result = await this.ports.call('org.member.get', query);
|
|
126
|
+
const member = result.member ?? result;
|
|
127
|
+
if (!isInt(member.version) || member.credential_cid !== undefined && member.credential_cid !== credential.cid)
|
|
128
|
+
throw invalid('current membership differs from managed credential');
|
|
129
|
+
const proof = result.membership_status;
|
|
130
|
+
if (!isRecord(proof))
|
|
131
|
+
return { value: member, expiresAt: 0 };
|
|
132
|
+
const now = Math.trunc(Date.now() / 1000);
|
|
133
|
+
if (proof.ver !== 'aun-membership-status/1' || proof.org_id !== credential.iss || proof.member_aid !== actor || proof.credential_cid !== credential.cid
|
|
134
|
+
|| proof.membership_id !== binding.membership_id || proof.role !== binding.role || proof.version !== member.version
|
|
135
|
+
|| proof.status !== 'active' || proof.credential_status !== 'active' || proof.responder !== service || proof.target !== actor || proof.request_id !== query.request_id || proof.nonce !== query.nonce
|
|
136
|
+
|| !isInt(proof.as_of) || proof.as_of > now + 300 || !isInt(proof.expires_at) || proof.expires_at <= now || proof.expires_at <= proof.as_of || proof.expires_at > proof.as_of + 300
|
|
137
|
+
|| !isRecord(proof.signature) || proof.signature.timestamp !== proof.as_of)
|
|
138
|
+
throw invalid('current membership proof does not match the query');
|
|
139
|
+
const cert = await this.ports.certificate(service, String(proof.signature.cert_fingerprint));
|
|
140
|
+
const unsigned = { ...proof };
|
|
141
|
+
delete unsigned.signature;
|
|
142
|
+
verifyAgentSignature(proof.signature, unsigned, service, () => cert, Math.trunc(Date.now() / 1000), false);
|
|
143
|
+
return { value: { ...member, version: proof.version }, expiresAt: Math.min(proof.expires_at, Number(credential.exp), Date.parse(new X509Certificate(cert).validTo) / 1000) };
|
|
144
|
+
});
|
|
145
|
+
}
|
|
70
146
|
async scanOperation(operation) {
|
|
71
147
|
this.checkScan();
|
|
72
148
|
const result = await operation();
|
|
@@ -173,6 +249,7 @@ export class CredentialRenewal {
|
|
|
173
249
|
});
|
|
174
250
|
}
|
|
175
251
|
observe(method, params, result) {
|
|
252
|
+
this.invalidateMutation(method);
|
|
176
253
|
if (method !== 'credential.register' && !method.startsWith('org.'))
|
|
177
254
|
return Promise.resolve();
|
|
178
255
|
return this.serial(async () => {
|
|
@@ -244,12 +321,7 @@ export class CredentialRenewal {
|
|
|
244
321
|
delete request.signature;
|
|
245
322
|
if (record.membership_binding && !request.membership_binding) {
|
|
246
323
|
if (now < credential.exp) {
|
|
247
|
-
const
|
|
248
|
-
const query = prepareOrganizationRequest('org.member.get', { org_id: credential.iss, member_aid: actor, credentials }, record.service_aid, signer);
|
|
249
|
-
const current = await this.ports.call('org.member.get', query);
|
|
250
|
-
const member = current.member ?? current;
|
|
251
|
-
if (!isInt(member.version) || member.credential_cid !== undefined && member.credential_cid !== cid)
|
|
252
|
-
throw invalid('current membership differs from managed credential');
|
|
324
|
+
const member = await this.currentMember(credential, record.membership_binding, record.service_aid, actor, signer);
|
|
253
325
|
record.version = member.version;
|
|
254
326
|
}
|
|
255
327
|
if (!isInt(record.version))
|
|
@@ -306,6 +378,14 @@ export class CredentialRenewal {
|
|
|
306
378
|
verifyAgentSignature(payload.signature, unsigned, expected, () => cert, now);
|
|
307
379
|
}
|
|
308
380
|
async status(credential, actor, allowExpired = false, registrationConfirmedAt, confirmation) {
|
|
381
|
+
if (registrationConfirmedAt !== undefined || confirmation)
|
|
382
|
+
return this.statusFresh(credential, actor, allowExpired, registrationConfirmedAt, confirmation);
|
|
383
|
+
return this.verifiedQuery('credential.status:' + digest({ credential, actor, allowExpired }), async () => {
|
|
384
|
+
const expiresAt = await this.statusFresh(credential, actor, allowExpired);
|
|
385
|
+
return { value: expiresAt, expiresAt: Math.min(expiresAt, allowExpired ? Infinity : Number(credential.exp)) };
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
async statusFresh(credential, actor, allowExpired = false, registrationConfirmedAt, confirmation) {
|
|
309
389
|
const target = 'credential.' + String(credential.iss).split('.').slice(1).join('.');
|
|
310
390
|
const query = { credential_cid: credential.cid, request_id: organizationIdentifier(), nonce: organizationNonce(), include_chain: true };
|
|
311
391
|
const requestedAt = Math.trunc(Date.now() / 1000);
|
|
@@ -328,7 +408,7 @@ export class CredentialRenewal {
|
|
|
328
408
|
throw new ProtocolError('CREDENTIAL_STATUS_UNKNOWN', 'membership registration confirmation is invalid');
|
|
329
409
|
if (confirmation)
|
|
330
410
|
Object.assign(confirmation, structuredClone(proof));
|
|
331
|
-
return proof.proofExpiresAt;
|
|
411
|
+
return Math.min(proof.proofExpiresAt, Date.parse(new X509Certificate(cert).validTo) / 1000);
|
|
332
412
|
}
|
|
333
413
|
async receiveNow(from, value, localIssuer, replay = false) {
|
|
334
414
|
if (!isRecord(value) || !['credential.renewal.request', 'credential.renewal.result'].includes(String(value.type)))
|
|
@@ -736,6 +816,7 @@ export class CredentialRenewal {
|
|
|
736
816
|
await this.ports.saveRevocations(actor, state, { ...guard, current });
|
|
737
817
|
if (!current())
|
|
738
818
|
throw new ProtocolError('CREDENTIAL_STATUS_UNKNOWN', 'credential revocation event was superseded');
|
|
819
|
+
this.invalidateQueries();
|
|
739
820
|
});
|
|
740
821
|
}
|
|
741
822
|
_syncRevocations(guard) {
|
|
@@ -751,8 +832,6 @@ export class CredentialRenewal {
|
|
|
751
832
|
const records = await this.ports.load(actor);
|
|
752
833
|
check();
|
|
753
834
|
const authorities = new Set();
|
|
754
|
-
if (AID_RE.test(actor))
|
|
755
|
-
authorities.add('credential.' + actor.split('.').slice(1).join('.'));
|
|
756
835
|
for (const record of Object.values(records)) {
|
|
757
836
|
if (!isRecord(record) || !isRecord(record.credential))
|
|
758
837
|
continue;
|
|
@@ -772,6 +851,12 @@ export class CredentialRenewal {
|
|
|
772
851
|
}
|
|
773
852
|
}
|
|
774
853
|
for (const authority of authorities) {
|
|
854
|
+
const scope = this.queryScope(), epoch = this.queryEpoch;
|
|
855
|
+
const cachePrefix = scope + ':credential.revocationList:' + authority + ':';
|
|
856
|
+
const cached = this.verifiedQueries.get(cachePrefix + digest(state.authorities[authority] ?? {}));
|
|
857
|
+
if (cached && Date.now() / 1000 < cached.expiresAt)
|
|
858
|
+
continue;
|
|
859
|
+
let expiresAt = Infinity;
|
|
775
860
|
for (let page = 0; page < 32; page++) {
|
|
776
861
|
check();
|
|
777
862
|
const previous = state.authorities[authority] ?? { revoked: {}, since_event_seq: 0, cursor: '' };
|
|
@@ -807,15 +892,18 @@ export class CredentialRenewal {
|
|
|
807
892
|
for (const event of proof.events)
|
|
808
893
|
Object.defineProperty(revoked, String(event.cid), { value: Math.max(Object.hasOwn(revoked, String(event.cid)) ? Number(revoked[String(event.cid)]) : 0, Number(event.event_seq)), enumerable: true, configurable: true, writable: true });
|
|
809
894
|
next.authorities[authority] = { revoked, since_event_seq: proof.next_cursor === '' ? last : Number(proof.next_cursor), cursor: proof.next_cursor, as_of: proof.as_of, expires_at: proof.expires_at };
|
|
810
|
-
|
|
895
|
+
expiresAt = Math.min(expiresAt, Number(proof.expires_at), Date.parse(new X509Certificate(certificate).validTo) / 1000);
|
|
896
|
+
const commitGuard = { ...guard, current: () => epoch === this.queryEpoch && scope === this.queryScope() && this.ports.currentAid() === actor && (!guard || guard.current()) && Date.now() / 1000 < expiresAt };
|
|
811
897
|
if (!commitGuard.current())
|
|
812
898
|
throw new ProtocolError('CREDENTIAL_STATUS_UNKNOWN', 'revocation list expired before persistence');
|
|
813
899
|
await this.ports.saveRevocations(actor, next, commitGuard);
|
|
814
900
|
if (!commitGuard.current())
|
|
815
901
|
throw new ProtocolError('CREDENTIAL_STATUS_UNKNOWN', 'revocation synchronization was superseded');
|
|
816
902
|
state = next;
|
|
817
|
-
if (proof.next_cursor === '')
|
|
903
|
+
if (proof.next_cursor === '') {
|
|
904
|
+
this.verifiedQueries.set(cachePrefix + digest(state.authorities[authority]), { value: true, expiresAt: Math.min(Date.now() / 1000 + 60, expiresAt) });
|
|
818
905
|
break;
|
|
906
|
+
}
|
|
819
907
|
}
|
|
820
908
|
}
|
|
821
909
|
});
|