@agentunion/fastaun 0.5.22 → 0.5.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/_packed_docs/CHANGELOG.md +33 -0
  3. package/_packed_docs/INDEX.md +32 -16
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +22 -18
  5. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +2 -2
  6. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -7
  7. package/_packed_docs/sdk/INDEX.md +21 -6
  8. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +49 -2
  9. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +123 -72
  10. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +173 -96
  11. package/dist/agent-md.d.ts +16 -2
  12. package/dist/agent-md.js +60 -29
  13. package/dist/agent-md.js.map +1 -1
  14. package/dist/aid-store.d.ts +2 -0
  15. package/dist/aid-store.js +76 -19
  16. package/dist/aid-store.js.map +1 -1
  17. package/dist/auth.d.ts +6 -0
  18. package/dist/auth.js +205 -69
  19. package/dist/auth.js.map +1 -1
  20. package/dist/client.d.ts +12 -10
  21. package/dist/client.js +193 -112
  22. package/dist/client.js.map +1 -1
  23. package/dist/index.d.ts +1 -1
  24. package/dist/keystore/aid-db.d.ts +1 -8
  25. package/dist/keystore/aid-db.js +46 -204
  26. package/dist/keystore/aid-db.js.map +1 -1
  27. package/dist/keystore/index.d.ts +0 -15
  28. package/dist/keystore/local-token-store.d.ts +1 -6
  29. package/dist/keystore/local-token-store.js +0 -15
  30. package/dist/keystore/local-token-store.js.map +1 -1
  31. package/dist/org-protocol.d.ts +10 -0
  32. package/dist/org-protocol.js +100 -12
  33. package/dist/org-protocol.js.map +1 -1
  34. package/dist/ownership-v21.d.ts +2 -2
  35. package/dist/ownership-v21.js +25 -12
  36. package/dist/ownership-v21.js.map +1 -1
  37. package/dist/ownership-workflow.d.ts +30 -59
  38. package/dist/ownership-workflow.js +260 -1667
  39. package/dist/ownership-workflow.js.map +1 -1
  40. package/dist/tools/cross-sdk-agent.d.ts +1 -2
  41. package/dist/tools/cross-sdk-agent.js +7 -14
  42. package/dist/tools/cross-sdk-agent.js.map +1 -1
  43. package/dist/version.d.ts +1 -1
  44. package/dist/version.js +1 -1
  45. package/package.json +1 -1
@@ -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(() => onSent ? ports.call(method, params, onSent) : ports.call(method, params)),
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 credentials = { [record.membership_binding.role === 'admin' ? 'approval_credential' : 'member_credential']: credential, membership_binding: record.membership_binding };
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
- const commitGuard = { ...guard, current: () => this.ports.currentAid() === actor && (!guard || guard.current()) && Date.now() / 1000 < Number(proof.expires_at) };
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
  });