Skip to content

feat!: did-resolver interface + spec-conformant query parameters (#89, #130) - #4

Closed
brianorwhatever wants to merge 18 commits into
mainfrom
design/did-resolver-query-params
Closed

feat!: did-resolver interface + spec-conformant query parameters (#89, #130)#4
brianorwhatever wants to merge 18 commits into
mainfrom
design/did-resolver-query-params

Conversation

@brianorwhatever

@brianorwhatever brianorwhatever commented Jun 25, 2026

Copy link
Copy Markdown

Summary

Adopts the standard W3C did-resolver interface and settles the open query-parameter questions. Breaking change — major version bump to 3.0.0.

Resolves decentralized-identity#89 (support did-resolver interface) and decentralized-identity#130 (query parameter support).

What changed

  1. resolveDID / resolveDIDFromLog return the standard resolution shape { didResolutionMetadata, didDocument, didDocumentMetadata } — a clean break from { did, doc, meta, controlled }. They no longer throw on failure; errors come back as a result with didDocument: null and a standard error code.
  2. New getResolver(config?) returns { webvh: DIDResolver }, registrable in a did-resolver Resolverdid:webvh is now a drop-in alongside did:web, did:ethr, etc. Works zero-config via a built-in defaultVerifier (Ed25519), overridable with getResolver({ verifier }).
  3. verificationMethod resolution removed entirely — implementation-specific, not in any spec, and a footgun (returned a historical document version).
  4. Resolution selectors are mutually exclusive — at most one of versionId / versionTime / versionNumber; any combination returns invalidDidUrl.
  5. Both v1.0 and v0.5 map to the standard shape via a single mapping layer; internal updateDID/deactivateDID keep using the rich per-version core resolver unchanged.

New modules

  • src/verifier.tsdefaultVerifier (Ed25519 via @stablelib/ed25519).
  • src/resolver-result.ts — pure mapping (toResolutionResult, toErrorResult, assertSingleVersionSelector, mapErrorToCode, InvalidDidUrlError) + result types.
  • src/resolver.tsgetResolver(); parses DID-URL query params, enforces selector rules, returns DIDResolutionResult.

Implementation note

did-resolver exposes ?-query parameters as the raw, undecoded parsed.query string (only ;-matrix params populate parsed.params). Since the did:webvh spec uses ? query parameters, getResolver parses parsed.query itself. It decodes per RFC 3986 (decodeURIComponent, + is literal) rather than URLSearchParams (form-encoding, + → space) so a versionTime with a +HH:MM timezone offset isn't corrupted.

Testing

  • New unit tests: defaultVerifier, the mapping layer, and the mutual-exclusivity guard.
  • New integration test: getResolver() in a real did-resolver Resolver — fresh DID, ?versionId/?versionNumber/?versionTime, selector-combination → invalidDidUrl, not-found, deactivated, zero-config.
  • Migrated the full existing suite to the standard shape; deleted the obsolete verificationMethod resolution tests.

Gates (all green locally)

  • bun test — 273 pass / 0 fail
  • bunx biome ci . — clean
  • bun run check — clean
  • bun run build — succeeds

🤖 Generated with Claude Code

Note

Implement W3C did-resolver interface and spec-conformant query parameters for did:webvh

  • resolveDID and resolveDIDFromLog now return DIDResolutionResult (with didDocument, didDocumentMetadata, didResolutionMetadata) instead of a custom { did, doc, meta } shape; errors are reported via didResolutionMetadata.error rather than thrown exceptions.
  • Adds src/resolver.ts with a getResolver factory for registering did:webvh with the did-resolver library, including robust RFC 3986 decoding of version selector query parameters (versionId, versionTime, versionNumber).
  • Adds src/resolver-result.ts with helpers toResolutionResult, toErrorResult, mapErrorToCode, and assertSingleVersionSelector; standardized error codes include invalidDid, invalidDidUrl, notFound, and internalError.
  • Adds src/verifier.ts with defaultVerifier, a built-in Ed25519 verifier used automatically when callers omit a verifier option.
  • Removes verificationMethod as a resolution selector from both v0.5 and v1.0 method implementations and from ResolutionOptions.
  • Risk: breaking change (v3.0.0) — all callers must switch from { did, doc, meta } destructuring to DIDResolutionResult fields and from catching thrown errors to checking didResolutionMetadata.error.

Macroscope summarized 5e08585.

…hape

- delete removed verificationMethod resolution tests
- convert resolution-failure assertions from throws to error results
- add getResolver integration + resolver-result/verifier unit tests
- apply biome formatting
URLSearchParams applies application/x-www-form-urlencoded rules where '+'
decodes to a space. A DID URL query is a URI component (RFC 3986) where '+'
is literal, so a versionTime with a +HH:MM timezone offset was corrupted into
an Invalid Date. Parse the query manually with decodeURIComponent instead.
@brianorwhatever
brianorwhatever force-pushed the design/did-resolver-query-params branch from 71d3b8c to c340fc0 Compare June 25, 2026 07:11
@brianorwhatever

Copy link
Copy Markdown
Author

@macroscope review

@macroscopeapp

macroscopeapp Bot commented Jun 25, 2026

Copy link
Copy Markdown

Manual reviews triggered for commit 5e08585:

All prior checks · these links stay valid even if you push more commits.

@macroscopeapp

macroscopeapp Bot commented Jun 25, 2026

Copy link
Copy Markdown

Just FYI for future @mentions, I'm Macroscope-App, not Macroscope.

Review in progress. Results will be posted as check runs when complete.

Comment thread src/cli.ts
Comment thread src/resolver.ts
Comment thread examples/express-resolver.ts
Comment thread src/resolver-result.ts
@macroscopeapp

macroscopeapp Bot commented Jun 25, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

1 blocking correctness issue found. Major version bump (v3.0.0) introducing W3C did-resolver interface integration with breaking API changes. New feature capabilities and an unresolved review comment about incorrect handling of DID URL paths/fragments warrant human review.

You can customize Macroscope's approvability policy. Learn more.

- resolver.ts: reject NaN versionNumber / Invalid Date versionTime as invalidDidUrl
- resolver-result.ts: tighten mapErrorToCode so validation errors embedding
  '404'/'not found' (attacker-controlled log data) aren't misclassified as notFound;
  only genuine log-fetch absence maps to notFound
- cli.ts: surface resolution errors in update/deactivate instead of TypeError on meta.updateKeys
- examples: return the resolution error before file-serving fallback (avoid https:/// fetch)
- tests: cover the new validation paths; reclassify invalid-update-key read as invalidDid
@brianorwhatever

Copy link
Copy Markdown
Author

@macroscope-app review

@macroscopeapp

macroscopeapp Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review in progress. Results will be posted as check runs when complete.

Comment thread src/resolver-result.ts Outdated
Comment thread src/resolver-result.ts
Comment thread src/cli.ts
- resolver-result.ts: preserve a valid earlier document when returned alongside a
  warning-level error (e.g. explicit version selector resolves while a later entry
  fails witness verification) instead of dropping it
- resolver-result.ts: toErrorResult now synthesizes RFC9457 problemDetails (type/title/detail)
  so fetch/validation/invalidDidUrl errors keep structured error info
- cli.ts: resolve a deactivated DID's identifier from the log instead of printing an empty id
- tests: cover preserved-doc and problemDetails behavior
@brianorwhatever

Copy link
Copy Markdown
Author

@macroscope-app review

@macroscopeapp

macroscopeapp Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review in progress. Results will be posted as check runs when complete.

Comment thread src/resolver-result.ts
Comment thread src/resolver.ts
}

// parsed.did is the bare DID without query/fragment.
return resolveDID(parsed.did, selector);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium src/resolver.ts:79

getResolver() ignores parsed.path and parsed.fragment when building the selector, so DID URLs with paths or fragments (e.g., did:webvh:...#key-1) return the full DID document instead of the dereferenced resource. did-resolver passes these components to method resolvers specifically for dereferencing, so discarding them produces incorrect results for valid inputs.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/resolver.ts around line 79:

`getResolver()` ignores `parsed.path` and `parsed.fragment` when building the selector, so DID URLs with paths or fragments (e.g., `did:webvh:...#key-1`) return the full DID document instead of the dereferenced resource. `did-resolver` passes these components to method resolvers specifically for dereferencing, so discarding them produces incorrect results for valid inputs.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentionally not changing this. Per the W3C DID spec, resolve() returns the DID document; dereferencing a path/fragment to a specific resource is a separate operation (dereferenceURL). did-resolver method resolvers (did:web, did:ethr, …) all return the full DID document and leave fragment/path handling to the caller — so discarding parsed.path/parsed.fragment here is the correct, standard behavior. did:webvh file/path dereferencing is handled separately by the resolver servers (see the /resolve/:id/* routes in the examples).

- resolver-result.ts: add 'internalError' code; classify 5xx/network/transport
  failures as internalError instead of invalidDid (document-validation errors stay
  invalidDid). Synthesize problemDetails for it too.
- examples/express-resolver.ts: map resolution error codes to HTTP status
  (notFound->404, invalid*->400, internalError->500) instead of a blanket 400.
- tests: cover internalError classification; update network-failure expectation.
@brianorwhatever

Copy link
Copy Markdown
Author

@macroscope-app review

@macroscopeapp

macroscopeapp Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review in progress. Results will be posted as check runs when complete.

Comment thread examples/elysia-resolver.ts Outdated
…rning error

Only bail on resolution error when didDocument is null; a valid earlier version
can be returned alongside warning-level error metadata (consistent with
toResolutionResult preserving the doc).
@brianorwhatever

Copy link
Copy Markdown
Author

@macroscope-app review

@macroscopeapp

macroscopeapp Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review in progress. Results will be posted as check runs when complete.

Comment thread src/resolver-result.ts
A valid DID served from an unauthorized (401/403), gone (410), rate-limited (429),
or failing (5xx) endpoint is a resolver-side internal error, not a document-validation
failure. 404/empty-log stay notFound; document-validation errors stay invalidDid.
@brianorwhatever

Copy link
Copy Markdown
Author

@macroscope-app review

@macroscopeapp

macroscopeapp Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review in progress. Results will be posted as check runs when complete.

@brianorwhatever

Copy link
Copy Markdown
Author

Superseded by the upstream PR at decentralized-identity#138 (same branch, rebased onto current DIF main). Closing in favor of that one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support did-resolver interface

1 participant