Skip to content

Fix did:webvh implicit services and path resolution - #142

Merged
moisesja merged 3 commits into
mainfrom
fix/issue-136-webvh-implicit-services
Aug 23, 2026
Merged

Fix did:webvh implicit services and path resolution#142
moisesja merged 3 commits into
mainfrom
fix/issue-136-webvh-implicit-services

Conversation

@moisesja

Copy link
Copy Markdown
Owner

Fixes #136

Summary

  • project the implicit #files and Linked-VP #whois services into successful current and historical did:webvh resolution without mutating signed log state
  • add the missing services to parallel did:web artifacts, preserve explicit overrides, and rewrite subject-bound absolute service IDs to did:web
  • resolve did:webvh paths through the conventional services with /whois precedence and constrained HTTP(S) redirect construction
  • update the PRD, README, changelog, and pinned vector divergence report

Security and compatibility

  • conventional bare-path dispatch is gated to did:webvh, so other DID methods retain their previous behavior
  • path redirects ignore injected relativeRef, cannot replace the selected authority or escape the deployment resource base, reject URI-list control injection, and preserve endpoint-fragment precedence
  • implicit services are projected only after log, identity, and witness validation; they are never added to signed did.jsonl state

Verification

  • Release build: 0 errors and 0 source/compiler warnings
  • full Release matrix: 1,730 passed, 0 failed, 7 environment-skipped
  • W3C conformance: 233/233 passed
  • focused regressions: Core 15/15; WebVh 6/6
  • all five samples completed successfully
  • pinned DIF replay at f792ce4568c8c3efb3b6a055a1c2ba963dc00c35: all 21 negative cases rejected; all targeted missing service/type/endpoint/context divergences removed
  • independent correctness and security adversarial reviews: clean after confirmed findings were fixed

Known pre-existing warning

A normal audited restore reports NU1903 for Testcontainers 4.13.0's transitive SSH.NET 2025.1.0 dependency. It predates and is unrelated to issue #136, so the dependency upgrade is intentionally left for a focused security PR; an audit-disabled restore/build confirms this change has zero source/compiler warnings.

@moisesja moisesja self-assigned this Aug 23, 2026
@moisesja moisesja added this to the 3.3.0 milestone Aug 23, 2026
@moisesja moisesja added this to net-did Aug 23, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in net-did Aug 23, 2026

@moisesja moisesja left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Reviewed the full diff and independently verified the claims: on the PR head I ran all 15 new Issue136 dereferencer tests, the 6 new WebVh tests, and the full Core (433/433) and WebVh (443/443) suites — all green. The security tests are real, not aspirational: the ///host, scheme-in-path, %2e%2e traversal, and %0d%0a uri-list injection cases actually exercise .NET 10 Uri normalization behavior and pass. The core design is right — projecting implicit services at resolution time without touching signed did.jsonl state (locked in by Issue136_Resolve_MaterializesImplicitServicesWithoutChangingSignedState), authority pinning including userinfo, and post-normalization base-path containment.

Four specific issues, none blocking, but 1 and 2 deserve action before merge:

1. The ConstructServiceUrl change is a cross-method behavior change hiding in a did:webvh CHANGELOG bullet. DefaultDidUrlDereferencer.cs:405-410 now preserves an endpoint fragment for every DID method's ?service= / ?serviceType= dereference, not just webvh paths. Previously, endpoint https://ex.com/base#frag plus a relativeRef silently dropped the fragment via RFC 3986 resolution — contradicting the method's own doc comment, so the fix is correct — but did:key/did:peer/did:ethr consumers will see different output. The CHANGELOG entry scopes everything under "did:webvh implicit services"; this needs its own unscoped line so a consumer diffing 3.2→3.3 behavior can find it.

2. invalidDid is the wrong error code at DefaultDidUrlDereferencer.cs:127. When an explicit #files service has an ftp:// or non-URI endpoint, the DID is not invalid — the selected service is unusable. Per the DID Resolution error registry, invalidDid means the input DID string failed syntax. notFound or internalError is more accurate, and the new test currently locks the wrong code in. Related asymmetry: a set-valued serviceEndpoint is legal per DID Core and the ?service= flow handles sets (lines 218-228), but the path flow hard-fails it. If deliberately out of scope, say so in the CHANGELOG line ("Unsupported explicit endpoint shapes fail") with the corrected error code.

3. ContainsService and FindServiceById disagree on bare ids. ImplicitWebVhServices.cs:50-61 matches only #files, <docId>#files, <requestedDid>#files, but NormalizeServiceId (DefaultDidUrlDereferencer.cs:279-286) also treats a bare files (no #, no :) as <did>#files. A controller-authored service with id: "files" escapes the dedup check, so materialization adds #files beside it and the resolved document carries two services that normalize to the same absolute id — a spec-invalid document. Dereferencing still picks the explicit one by list order, so no wrong redirect, but either normalize consistently or document why bare ids are out of scope.

4. parsed.Did.Method == "webvh" hardcodes method knowledge into NetDid.Core (DefaultDidUrlDereferencer.cs:94). The gating is deliberate and correctly prevents other methods from gaining path semantics, but the moment a second method wants conventional path dispatch this should become a capability flag on the method/resolution metadata, not a string comparison in Core. Fine for now; flagging so it doesn't ossify.

One confirmation question: /whois/ and /whois?x fall through to the #files path handler because the whois check is exact-match on /whois (line 118). That reads as the correct reading of the spec's "special path", but confirm it's intentional rather than incidental.

Everything else — resource-base derivation reusing the hardened ValidateDomain/ValidatePathSegments, .well-known exclusion for root DIDs, did:web artifact projection with subject-bound id rewriting, historical-version materialization — checks out against issue #136 and the spec text quoted there.


Generated by Claude Code

@moisesja

Copy link
Copy Markdown
Owner Author

Addressed in 7912d22. Thanks for the detailed review.

  • Cross-method fragment behavior now has its own CHANGELOG entry and regression coverage. Fragment precedence is applied after RFC 3986 resolution, so an encoded # in relativeRef cannot override the service endpoint fragment.
  • I retained invalidDid for unsupported endpoint schemes. The did:webvh v1.0 DID URL Path Resolution and WHOIS Resolution algorithms explicitly require invalidDid when the selected service endpoint scheme is unsupported. I did close the legal-shape gap: URI-set endpoints are now supported and returned as a CRLF-delimited text/uri-list; empty, structured, or unsupported endpoint selections fail closed.
  • Bare files and whois service IDs now suppress implicit duplicates and are canonicalized to #files / #whois in resolved did:webvh projection and parallel did:web output. Signed log state remains unchanged.
  • The webvh method gate remains explicit for this scope. The source now records the intended design boundary: if another method adopts conventional bare-path routing, replace the string gate with a resolver capability instead of adding another method comparison.
  • WHOIS matching is intentional: /whois?x still selects #whois because the query does not alter the parsed path; /whois/ is an ordinary path routed through #files. Both behaviors now have tests and public documentation.

The adversarial follow-up also found and fixed two related edges: encoded-fragment precedence and a caller-owned URI set being mutated to empty after construction.

Verification: Release build 0 warnings / 0 errors; 1,738 passed, 0 failed, 7 environment-skipped; focused Core 21/21 and WebVh 8/8; all five Release samples passed; final adversarial verdict clean.

@moisesja moisesja left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Re-reviewed at 7912d22verdict: approved (GitHub won't accept a formal Approve from the PR author's own credentials, so recording it here). All five round-1 items are resolved — four fixed, one correctly rebutted — and I verified each independently rather than taking the response at its word.

On invalidDid (item 2): you're right and my round-1 suggestion was wrong. I fetched did:webvh v1.0 and both algorithms state it verbatim — DID URL Path Resolution: "If the scheme of the serviceEndpoint is not supported by the resolver (e.g., non-HTTP(S) protocol), the resolver MUST return an invalidDid error"; WHOIS Resolution has the same MUST. The method-specific algorithm overrides the generic registry's narrower reading, and the tasks/lessons.md entry capturing "check the method-specific algorithm before the generic registry" is the right takeaway. The legal-shape gap I flagged alongside it is genuinely closed: URI-set endpoints now produce per-entry validated CRLF uri-lists, and the fail-closed choice (one bad set entry poisons the whole result rather than returning the good entries) is defensible and now documented.

Verified fixed, with the right mechanism:

  • Cross-method fragment change now has its own unscoped CHANGELOG bullet plus two non-webvh regressions. The rework is better than what I asked for: applying the fragment as a URI component via UriBuilder after RFC 3986 resolution (DefaultDidUrlDereferencer.cs, ConstructServiceUrl) closes the encoded-%23-in-relativeRef override that the round-1 string-concatenation version would have allowed — and Issue136_ServiceTypeQuery_EncodedFragmentCannotOverrideEndpointFragment locks it in.
  • Bare-id dedup is fixed on both sides: ContainsService now accepts the bare spelling, and CanonicalizeConventionalId / RewriteServiceId canonicalize files/whois to fragment form in resolved projection and the parallel did:web artifact, so the duplicate-normalized-id document can no longer be produced. Fail-first tests cover both surfaces.
  • Method gate comment now records the capability-flag exit criterion. Good enough.
  • /whois matching confirmed intentional with tests for both /whois?x=1 (still #whois) and /whois/ (ordinary #files path), plus README/PRD language.

The extra adversarial catch — a caller-owned endpoint set mutated to empty after construction now snapshots and fails closed — is a real TOCTOU-shaped edge and the test proves it.

Independent verification at 7912d22: focused Core Issue136 21/21 and WebVh 8/8; full Core 439/439, WebVh 445/445, W3C conformance 233/233 — all matching your claimed counts exactly. No further changes requested.


Generated by Claude Code

@moisesja
moisesja merged commit 512c8a0 into main Aug 23, 2026
1 check passed
@moisesja
moisesja deleted the fix/issue-136-webvh-implicit-services branch August 23, 2026 04:03
@github-project-automation github-project-automation Bot moved this from Backlog to Done in net-did Aug 23, 2026
moisesja added a commit that referenced this pull request Aug 23, 2026
Rebase onto main (PR #142). Conflict resolutions:

- NetDidPRD.md: kept this branch's witness wire-format text in
  resolution step 8 and main's rewritten steps 9-10 (implicit service
  projection + IncludeLog artifacts).
- CHANGELOG.md: merged the two [Unreleased] "Fixed" sections into one,
  main's #136 entries first, then the #135 entries.
- tasks/webvh-vector-divergence.md: regenerated from the combined
  state rather than resolved by hand - it is generated output, and
  both changes affect it. Witnessed happy-path replays stay 10/10
  accepted alongside #136's service projection; 21/21 negatives still
  rejected.

Re-verified after the rebase: 0-warning Release build; 1,760 tests
green (Core 439, WebVh 467, Ethr 490, W3C 233/233, Key 52, Peer 48,
DI 20, Ethr.Integration 11 +7 opt-in skips); all 5 samples exit 0;
git diff --check clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[High] did:webvh implicit #files / #whois services are neither materialized in the resolved document nor resolvable as DID URLs

1 participant