@metalabel/dfos-client 0.52.1 → 0.53.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.
@@ -1,5 +1,5 @@
1
1
  export { ApiRequestVerifyError, DEFAULT_PROOF_SKEW_SECONDS, DEFAULT_PROOF_WINDOW_SECONDS, DFOS_AUTH_SCHEME, EMPTY_BODY_SHA256, IDENTITY_PROOF_JWS_TYP, IdentityProofPayload, MAX_BODY_BYTES, MAX_PROOF_FRESHNESS_SPAN_SECONDS, MAX_REQUEST_PROOF_SIZE, ProofExtraMembers, REQUEST_PROOF_JWS_TYP, RequestProofFailurePhase, RequestProofFailureReason, RequestProofPayload, SignApiIdentityRequestInput, SignApiRequestInput, apiIdentitySigningInput, apiRequestSigningInput, assertProofVerifierConfig, buildApiAuthHeaders, buildApiIdentityHeaders, parseDfosAuthorization, sha256BodyHash, signApiIdentityRequest, signApiRequest } from '@metalabel/dfos-protocol/credentials';
2
- import { a as Client } from './types-BbIEz7BN.js';
2
+ import { a as Client } from './types-DClhQw33.js';
3
3
  import '@metalabel/dfos-protocol/chain';
4
4
  import '@metalabel/dfos-web-relay/peer-client';
5
5
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { C as ClientConfig, a as Client, b as Callbacks, R as RevChecker } from './types-BbIEz7BN.js';
2
- export { c as CallOptions, D as DocumentBlob, E as EffectiveIdentity, d as EverProvedIdentity, G as GlobalLogOptions, e as GlobalLogPage, f as GlobalLogResult, I as IndexCapabilities, g as IndexContentPage, h as IndexContentRow, i as IndexCountersignatureRow, j as IndexCountersignaturesPage, k as IndexCredentialRow, l as IndexCredentialsPage, m as IndexIdentitiesPage, n as IndexIdentityProfile, o as IndexIdentityRow, p as IndexOrder, q as IndexRecencyOrder, L as LogOp, P as Provenance, r as RelayHealth, s as RelayResponse, t as Resolution, u as Resolved, v as ResolvedContent, w as ResolvedCredential, S as Store, T as Trust, U as UnverifiableAxis, V as VerifyResult } from './types-BbIEz7BN.js';
3
- export { m as memoryStore } from './memory-B5WMnEUQ.js';
1
+ import { C as ClientConfig, a as Client, b as Callbacks, R as RevChecker } from './types-DClhQw33.js';
2
+ export { c as CallOptions, D as DocumentBlob, E as EffectiveIdentity, d as EverProvedIdentity, G as GlobalLogOptions, e as GlobalLogPage, f as GlobalLogResult, I as IndexCapabilities, g as IndexContentPage, h as IndexContentRow, i as IndexCountersignatureRow, j as IndexCountersignaturesPage, k as IndexCredentialRow, l as IndexCredentialsPage, m as IndexIdentitiesPage, n as IndexIdentityProfile, o as IndexIdentityRow, p as IndexOrder, q as IndexRecencyOrder, L as LogOp, P as Provenance, r as RelayHealth, s as RelayResponse, t as Resolution, u as Resolved, v as ResolvedContent, w as ResolvedCredential, S as Store, T as Trust, U as UnverifiableAxis, V as VerifyResult } from './types-DClhQw33.js';
3
+ export { m as memoryStore } from './memory-aYqSas8m.js';
4
4
  import '@metalabel/dfos-protocol/chain';
5
5
  import '@metalabel/dfos-protocol/credentials';
6
6
  import '@metalabel/dfos-web-relay/peer-client';
@@ -65,7 +65,9 @@ declare const divergenceErrorFrom: (err: unknown) => DivergenceError | undefined
65
65
  * payload binds exactly the queried (issuerDID, credentialCID). Anything less —
66
66
  * unreachable relay, negative answer, forged or mismatched proof — moves on to
67
67
  * the next relay; false only after the full set has been consulted and at least
68
- * one relay answered with a parseable status body. Zero answers throws.
68
+ * one relay answered with a parseable status body. Zero answers throws, and so
69
+ * does a positive answer whose signing key this client could not resolve: a
70
+ * dependency failure is never a negative status.
69
71
  *
70
72
  * When the caller supplies `asOfUnix` (the protocol does, on every cold fold, with
71
73
  * each operation's own `createdAt`), a verified revocation only counts if its own
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import { dagCborCanonicalEncode, decodeJwsUnsafe as decodeJwsUnsafe2 } from "@me
12
12
  import { createHttpPeerClient } from "@metalabel/dfos-web-relay/peer-client";
13
13
 
14
14
  // src/transport.ts
15
+ import { isDependencyMissing, markDependencyMissing } from "@metalabel/dfos-protocol";
15
16
  var PAGE_LIMIT = 1e3;
16
17
  var MAX_PAGES = 1e4;
17
18
  var normalizeRelays = (relays) => {
@@ -62,6 +63,14 @@ var fanOutLog = async (fetchPage, relays, quorum, verifyCandidate) => {
62
63
  const byDigest = /* @__PURE__ */ new Map();
63
64
  const badDigests = /* @__PURE__ */ new Set();
64
65
  let lastVerifyError;
66
+ let lastDependencyError;
67
+ const recordFailure = (err, digest) => {
68
+ if (!(err instanceof StaleAnswerError)) {
69
+ if (isDependencyMissing(err)) lastDependencyError = err;
70
+ else lastVerifyError = err;
71
+ }
72
+ badDigests.add(digest);
73
+ };
65
74
  for (const url of relays) {
66
75
  let entries = null;
67
76
  try {
@@ -89,8 +98,7 @@ var fanOutLog = async (fetchPage, relays, quorum, verifyCandidate) => {
89
98
  provenance: { answeredBy: group.url, responses, agreed: true, fromCache: false }
90
99
  };
91
100
  } catch (err) {
92
- if (!(err instanceof StaleAnswerError)) lastVerifyError = err;
93
- badDigests.add(digest);
101
+ recordFailure(err, digest);
94
102
  }
95
103
  }
96
104
  }
@@ -105,8 +113,7 @@ var fanOutLog = async (fetchPage, relays, quorum, verifyCandidate) => {
105
113
  provenance: { answeredBy: group.url, responses, agreed: false, fromCache: false }
106
114
  };
107
115
  } catch (err) {
108
- if (!(err instanceof StaleAnswerError)) lastVerifyError = err;
109
- badDigests.add(digest);
116
+ recordFailure(err, digest);
110
117
  }
111
118
  }
112
119
  if (lastVerifyError !== void 0) {
@@ -115,6 +122,14 @@ var fanOutLog = async (fetchPage, relays, quorum, verifyCandidate) => {
115
122
  cause: lastVerifyError
116
123
  });
117
124
  }
125
+ if (lastDependencyError !== void 0) {
126
+ const message = lastDependencyError instanceof Error ? lastDependencyError.message : "unknown error";
127
+ throw markDependencyMissing(
128
+ new Error(`candidate log verification could not complete: ${message}`, {
129
+ cause: lastDependencyError
130
+ })
131
+ );
132
+ }
118
133
  return {
119
134
  outcome: "unreachable",
120
135
  entries: [],
@@ -234,11 +249,13 @@ var createIndexQueries = (relays, fetchImpl) => {
234
249
  // src/resolvers.ts
235
250
  import {
236
251
  decodeMultikey,
252
+ IdentityStateNoSeenKeysError,
237
253
  verifyContentChain,
238
254
  verifyContentExtensionFromTrustedState,
239
255
  verifyIdentityChain,
240
256
  verifyIdentityExtensionFromTrustedState
241
257
  } from "@metalabel/dfos-protocol/chain";
258
+ import { CredentialVerificationError } from "@metalabel/dfos-protocol/credentials";
242
259
  import { decodeJwsUnsafe } from "@metalabel/dfos-protocol/crypto";
243
260
  var DID_PREFIX = "did:dfos";
244
261
  var cacheKey = (kind, id) => `${kind}:${id}`;
@@ -292,6 +309,8 @@ var keyBytesFor = (state, keyId) => {
292
309
  );
293
310
  return key ? decodeMultikey(key.publicKeyMultibase).keyBytes : null;
294
311
  };
312
+ var NoStateAsOfError = class extends CredentialVerificationError {
313
+ };
295
314
  var createResolvers = (deps) => {
296
315
  const relaysFor = (o) => normalizeRelays(o?.relays ?? deps.relays);
297
316
  const getIdentityChain = async (did, options) => {
@@ -323,17 +342,28 @@ var createResolvers = (deps) => {
323
342
  let headCID = cached.headCID;
324
343
  let lastCreatedAt = cached.lastCreatedAt;
325
344
  const log = [...cached.log];
326
- for (const entry of entries.slice(cached.log.length)) {
327
- const r = await verifyIdentityExtensionFromTrustedState({
328
- currentState: state,
329
- headCID,
330
- lastCreatedAt,
331
- newOp: entry.jwsToken
332
- });
333
- state = r.state;
334
- headCID = r.operationCID;
335
- lastCreatedAt = r.createdAt;
336
- log.push(entry.jwsToken);
345
+ try {
346
+ for (const entry of entries.slice(cached.log.length)) {
347
+ const r = await verifyIdentityExtensionFromTrustedState({
348
+ currentState: state,
349
+ headCID,
350
+ lastCreatedAt,
351
+ newOp: entry.jwsToken
352
+ });
353
+ state = r.state;
354
+ headCID = r.operationCID;
355
+ lastCreatedAt = r.createdAt;
356
+ log.push(entry.jwsToken);
357
+ }
358
+ } catch (e) {
359
+ if (!(e instanceof IdentityStateNoSeenKeysError)) throw e;
360
+ const full = entries.map((entry) => entry.jwsToken);
361
+ const replayed = await verifyIdentityChain({ didPrefix: DID_PREFIX, log: full });
362
+ if (replayed.did !== did) {
363
+ throw new Error(`relay served a mismatched identity: asked ${did}, got ${replayed.did}`);
364
+ }
365
+ const last = opMeta(full[full.length - 1]);
366
+ return { state: replayed, log: full, headCID: last.cid, lastCreatedAt: last.createdAt };
337
367
  }
338
368
  return { state, log, headCID, lastCreatedAt };
339
369
  };
@@ -375,13 +405,18 @@ var createResolvers = (deps) => {
375
405
  const { state, log } = resolution;
376
406
  const last = log[log.length - 1];
377
407
  if (basis === void 0 || last === void 0 || opMeta(last).createdAt <= basis) return state;
408
+ const first = log[0];
409
+ if (first !== void 0 && basis < opMeta(first).createdAt) {
410
+ throw new NoStateAsOfError(`identity has no state as of ${basis}`);
411
+ }
378
412
  const asOf = await verifyIdentityChain({ didPrefix: DID_PREFIX, log, asOf: basis });
379
- return { ...asOf, isDeleted: state.isDeleted };
413
+ return { ...asOf, isDeleted: state.isDeleted, basisDeterminate: true };
380
414
  };
381
415
  const resolveIdentity = async (did, basis) => {
382
416
  try {
383
417
  return await stateAsOf(await getIdentityChain(did), basis);
384
- } catch {
418
+ } catch (err) {
419
+ if (err instanceof NoStateAsOfError) throw err;
385
420
  return void 0;
386
421
  }
387
422
  };
@@ -506,12 +541,29 @@ var createResolvers = (deps) => {
506
541
  };
507
542
 
508
543
  // src/revocation.ts
544
+ import { isDependencyMissing as isDependencyMissing2 } from "@metalabel/dfos-protocol";
509
545
  import { parseProtocolTimestampUnix, verifyRevocation } from "@metalabel/dfos-protocol/chain";
546
+ import { CredentialVerificationError as CredentialVerificationError2 } from "@metalabel/dfos-protocol/credentials";
510
547
  import { REVOCATIONS_BASE_PATH } from "@metalabel/dfos-web-relay/peer-client";
548
+ var unavailable = (why) => new Error(`revocation status unavailable: ${why}`);
549
+ var KeyUnresolvableError = class extends Error {
550
+ };
551
+ var isResolverVerdict = (err) => err instanceof CredentialVerificationError2 && !isDependencyMissing2(err);
511
552
  var createRevocationChecker = (relays, fetchImpl, resolveKey) => {
512
553
  const relaySet = normalizeRelays(relays);
554
+ const guardedResolveKey = async (kid, basis) => {
555
+ try {
556
+ return await resolveKey(kid, basis);
557
+ } catch (err) {
558
+ if (isResolverVerdict(err)) throw err;
559
+ throw new KeyUnresolvableError(`could not resolve the revocation signing key ${kid}`, {
560
+ cause: err
561
+ });
562
+ }
563
+ };
513
564
  return async (issuerDID, credentialCID, asOfUnix) => {
514
565
  let answered = false;
566
+ let unresolvable;
515
567
  for (const url of relaySet) {
516
568
  let body = null;
517
569
  try {
@@ -532,17 +584,22 @@ var createRevocationChecker = (relays, fetchImpl, resolveKey) => {
532
584
  }
533
585
  if (!body?.revoked || !body.revocation) continue;
534
586
  try {
535
- const verified = await verifyRevocation({ jwsToken: body.revocation, resolveKey });
587
+ const verified = await verifyRevocation({
588
+ jwsToken: body.revocation,
589
+ resolveKey: guardedResolveKey
590
+ });
536
591
  if (verified.did === issuerDID && verified.credentialCID === credentialCID) {
537
592
  if (asOfUnix === void 0 || asOfUnix <= 0) return true;
538
593
  const revokedAtUnix = parseProtocolTimestampUnix(verified.createdAt);
539
594
  if (revokedAtUnix === null || revokedAtUnix <= asOfUnix) return true;
540
595
  continue;
541
596
  }
542
- } catch {
597
+ } catch (err) {
598
+ if (err instanceof KeyUnresolvableError) unresolvable = err;
543
599
  }
544
600
  }
545
- if (!answered) throw new Error("revocation status unavailable: no relay answered");
601
+ if (!answered) throw unavailable("no relay answered");
602
+ if (unresolvable) throw unavailable(unresolvable.message);
546
603
  return false;
547
604
  };
548
605
  };
@@ -1,4 +1,4 @@
1
- import { S as Store } from './types-BbIEz7BN.js';
1
+ import { S as Store } from './types-DClhQw33.js';
2
2
 
3
3
  declare const memoryStore: () => Store;
4
4
 
package/dist/siwd.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Signer, VerifiedIdentity, VerifiedSignRequest } from '@metalabel/dfos-protocol/chain';
2
- import { a as Client, V as VerifyResult } from './types-BbIEz7BN.js';
2
+ import { a as Client, V as VerifyResult } from './types-DClhQw33.js';
3
3
  import '@metalabel/dfos-protocol/credentials';
4
4
  import '@metalabel/dfos-web-relay/peer-client';
5
5
 
@@ -1,5 +1,5 @@
1
- export { m as memoryStore } from '../memory-B5WMnEUQ.js';
2
- import { S as Store } from '../types-BbIEz7BN.js';
1
+ export { m as memoryStore } from '../memory-aYqSas8m.js';
2
+ import { S as Store } from '../types-DClhQw33.js';
3
3
  import '@metalabel/dfos-protocol/chain';
4
4
  import '@metalabel/dfos-protocol/credentials';
5
5
  import '@metalabel/dfos-web-relay/peer-client';
@@ -5,6 +5,12 @@ import { PeerClient } from '@metalabel/dfos-web-relay/peer-client';
5
5
  /** Head/as-of state; excludes the credit-claim projection. */
6
6
  type EffectiveIdentity = VerifiedIdentity & {
7
7
  readonly resolution?: 'effective';
8
+ /**
9
+ * True when no operation dated at or before the basis can still arrive and
10
+ * change this key state — the protocol's `ResolvedIdentity.basisDeterminate`,
11
+ * which decides whether a missing key is a verdict or a retryable miss.
12
+ */
13
+ basisDeterminate?: boolean;
8
14
  };
9
15
  /** Every key ever proved, including memberships absent from effective state. */
10
16
  type EverProvedIdentity = VerifiedIdentity & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metalabel/dfos-client",
3
- "version": "0.52.1",
3
+ "version": "0.53.0",
4
4
  "type": "module",
5
5
  "description": "DFOS Client — the client-side kit for participating in the protocol: resolve, verify, prove. Fetch, resolve, verify-orchestration and cache over untrusted relays, plus the SIWD and API-AUTH proof surfaces. Holds no keys; all crypto truth comes from @metalabel/dfos-protocol",
6
6
  "license": "MIT",
@@ -47,16 +47,16 @@
47
47
  "README.md"
48
48
  ],
49
49
  "peerDependencies": {
50
- "@metalabel/dfos-protocol": "^0.52.1",
51
- "@metalabel/dfos-web-relay": "^0.52.1"
50
+ "@metalabel/dfos-protocol": "^0.53.0",
51
+ "@metalabel/dfos-web-relay": "^0.53.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^24.10.4",
55
55
  "tsup": "^8.5.1",
56
56
  "typescript": "^5.9.3",
57
57
  "vitest": "^4.1.8",
58
- "@metalabel/dfos-protocol": "0.52.1",
59
- "@metalabel/dfos-web-relay": "0.52.1"
58
+ "@metalabel/dfos-protocol": "0.53.0",
59
+ "@metalabel/dfos-web-relay": "0.53.0"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "tsup",