Skip to content

feat(gateway): self-publish DID document at /.well-known/did.json#476

Merged
raahulrahl merged 1 commit intomainfrom
feat/gateway-did-wellknown
Apr 19, 2026
Merged

feat(gateway): self-publish DID document at /.well-known/did.json#476
raahulrahl merged 1 commit intomainfrom
feat/gateway-did-wellknown

Conversation

@raahulrahl
Copy link
Copy Markdown
Contributor

@raahulrahl raahulrahl commented Apr 19, 2026

Summary

Gateway now serves its own DID document at GET /.well-known/did.json, giving A2A peers a Hydra-independent path to resolve did:bindu:<gateway> to its Ed25519 public key.

Before this PR the only way to verify a signature from the gateway was to query Hydra admin for the client's metadata.public_key. That tightly couples every DID-signed peer to our Hydra deployment. With this endpoint, a peer can resolve via the standard well-known pattern — the same way it would resolve any agent's DID document.

What changed

  • gateway/src/api/did-route.ts — pure buildDidDocument(identity) + Hono buildDidHandler(identity). Serialization cached once at handler build — the document doesn't change for the life of the process, so repeat requests get byte-identical bodies (safe for clients that hash).
  • gateway/src/index.ts — wires the route after /plan, only when an identity is loaded. A gateway without DID signing has nothing to publish; a 404 accurately says so.
  • gateway/src/server/index.ts — docstring updated to list the new route.
  • gateway/tests/api/did-route.test.ts — 14 tests covering shape, Content-Type, cache headers, determinism, and the peer-resolution contract (pubkey round-trip).

Shape decisions worth calling out

  • @context: [w3c-did-v1, getbindu-ns-v1] — matches the Python agent's DID document so resolvers work across both.
  • authentication[0].type: Ed25519VerificationKey2020 — W3C standard value; Bindu's signing is Ed25519 throughout.
  • controller: self-referential (the DID controls its own key). Future multi-sig or delegation can extend this without breaking the current contract.
  • publicKeyBase58: byte-identical to identity.publicKeyBase58. If these drift, peer verification fails with crypto_mismatch. Test asserts exact equality.
  • created deliberately omitted. The gateway's identity is env-driven and stateless — there's no persisted "first published" moment to report. Emitting Date.now() or process start time would mislead clients into thinking the key was rotated at that moment. W3C DID Core v1 has created as optional; absence is valid. A test guards this so the field isn't accidentally added later.

HTTP response properties

  • Status: 200 when identity loaded; route doesn't exist → 404 otherwise
  • Content-Type: application/did+json per W3C DID Core (stricter resolvers reject application/json)
  • Cache-Control: public, max-age=300 — short TTL as defense against misbehaving caches. Restarts with a new seed are uncommon enough that 5 min is fine; critical peers can still bypass cache.
  • Unauthenticated: well-known endpoints are public by spec

Test plan

  • npx vitest run tests/api/did-route.test.ts14/14 pass
  • npx tsc --noEmit → no errors in modified files
  • Cross-language parity: pubkey round-trip (doc → extract → identity.publicKeyBase58) asserted exact
  • Manual smoke with a live gateway: curl http://localhost:<port>/.well-known/did.json | jq (operator test, out of CI scope)

Relation to other Phase 1 work

  • #472 (gateway DID primitive) ✅ merged — this PR builds on LocalIdentity
  • #473 (Hydra auto-reg) ✅ merged — this PR doesn't replace Hydra, it adds an A2A-native alternative
  • #474 (frontend POC) — open; orthogonal
  • #475 (docs rewrite) — open; references the W3C DID-doc pattern that this PR now actually ships for the gateway
  • This PR — adds peer-resolvable identity surface

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added new endpoint at /.well-known/did.json to publish gateway identity documents with HTTP caching enabled (5-minute max-age) for secure peer identity verification and cryptographic key discovery.
  • Tests

    • Added comprehensive test coverage for DID document generation, JSON response validation, HTTP caching behavior, and end-to-end peer resolution testing.

A2A peers now have a Hydra-independent path to resolve the gateway's
DID to its Ed25519 public key. Matches the W3C DID Core + Bindu
extension shape already published by Python agents, so any peer that
can verify an agent's DID document can also verify the gateway's.

The route is registered only when an identity is loaded. A gateway
without DID signing has nothing to publish here, and a 404 accurately
says so.

Shape omits ``created``. The gateway's identity is env-driven and
stateless — there's no persisted "first published" moment. Emitting
process-start or current-now would mislead clients into thinking the
key was rotated at that moment. W3C DID Core v1 has ``created`` as
optional; absence is valid. Test asserts this explicitly so the
decision isn't re-introduced accidentally.

Content-Type is ``application/did+json`` per W3C spec — plain
``application/json`` is tolerated by most resolvers but stricter
ones reject it.

Caches serialized JSON once at handler build time. The document
doesn't change for the life of the process, so repeated requests
return byte-identical bodies (safe for clients that hash the
response). 5-minute public Cache-Control TTL — defensive against
misbehaving caches holding forever without making restarts require
a cache purge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

📝 Walkthrough

Walkthrough

The PR adds support for publishing a gateway's Decentralized Identifier (DID) document at /.well-known/did.json. A new module creates W3C-compliant DID documents with Ed25519 verification keys. The route is registered conditionally when the gateway loads an identity, with caching headers and JSON serialization.

Changes

Cohort / File(s) Summary
DID Route Implementation
gateway/src/api/did-route.ts
New module exports GatewayVerificationMethod and GatewayDidDocument interfaces defining W3C DID structure. Implements buildDidDocument() to construct a DID document with a single Ed25519 verification method and buildDidHandler() to create a Hono handler that serves the precomputed document with application/did+json content-type and 5-minute cache control.
Gateway Server Integration
gateway/src/index.ts, gateway/src/server/index.ts
Imports buildDidHandler and registers the GET /.well-known/did.json route when gateway identity is present. Updates route documentation to reflect the new endpoint.
Test Coverage
gateway/tests/api/did-route.test.ts
Comprehensive test suite validating DID document structure, verification method properties, HTTP response headers, caching behavior, and response determinism. Includes contract test for peer-resolution scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A gateway now proclaims its key,
Published in the wild and free!
At well-known paths, a rabbit hops—
With Ed25519, crypto stops!
DID documents serve with care,
Cached and cached beyond compare. 🗝️

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main feature: adding a self-published DID document endpoint at the well-known path.
Description check ✅ Passed The description is comprehensive and addresses all key template sections: summary with problem/solution, change type, scope, security impact, test verification, and detailed shape/HTTP decisions with rationale.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gateway-did-wellknown

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@gateway/tests/api/did-route.test.ts`:
- Around line 22-35: The tests currently call afterEach(() =>
setSeedEnv(undefined)) which always deletes TEST_ENV and can clobber a
pre-existing environment value; change the setup to capture the original value
(e.g., const originalSeed = process.env[TEST_ENV]) in a beforeEach and restore
it in afterEach by calling setSeedEnv(originalSeed) (or passing undefined if
originalSeed is undefined), and update all similar occurrences (uses of
setSeedEnv/afterEach) so tests restore the prior global TEST_ENV instead of
unconditionally deleting it; references: TEST_ENV, setSeedEnv, beforeEach,
afterEach in the did-route.test.ts file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 550a8ead-c0dd-4d5f-bb20-b3bc9f3a6569

📥 Commits

Reviewing files that changed from the base of the PR and between 303630a and e57ba20.

📒 Files selected for processing (4)
  • gateway/src/api/did-route.ts
  • gateway/src/index.ts
  • gateway/src/server/index.ts
  • gateway/tests/api/did-route.test.ts

Comment on lines +22 to +35
import { describe, it, expect, beforeEach, afterEach } from "vitest"
import { Hono } from "hono"
import {
buildDidDocument,
buildDidHandler,
} from "../../src/api/did-route"
import { loadLocalIdentity } from "../../src/bindu/identity/local"

const TEST_ENV = "TEST_DID_ROUTE_SEED"

function setSeedEnv(b64: string | undefined) {
if (b64 === undefined) delete process.env[TEST_ENV]
else process.env[TEST_ENV] = b64
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Restore the prior test env value instead of always deleting it.

afterEach(() => setSeedEnv(undefined)) can clobber a pre-existing TEST_DID_ROUTE_SEED for later tests in the same worker. Capture the original value and restore it globally.

🧪 Proposed test isolation fix
-import { describe, it, expect, beforeEach, afterEach } from "vitest"
+import { describe, it, expect, afterEach } from "vitest"
@@
 const TEST_ENV = "TEST_DID_ROUTE_SEED"
+const ORIGINAL_TEST_ENV = process.env[TEST_ENV]
@@
 function setSeedEnv(b64: string | undefined) {
   if (b64 === undefined) delete process.env[TEST_ENV]
   else process.env[TEST_ENV] = b64
 }
+
+afterEach(() => setSeedEnv(ORIGINAL_TEST_ENV))
@@
 describe("buildDidDocument", () => {
-  afterEach(() => setSeedEnv(undefined))
-
@@
 describe("buildDidHandler — HTTP response shape", () => {
-  afterEach(() => setSeedEnv(undefined))
-
@@
 describe("peer-resolution contract", () => {
-  afterEach(() => setSeedEnv(undefined))
-

Also applies to: 53-54, 110-111, 159-160

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway/tests/api/did-route.test.ts` around lines 22 - 35, The tests
currently call afterEach(() => setSeedEnv(undefined)) which always deletes
TEST_ENV and can clobber a pre-existing environment value; change the setup to
capture the original value (e.g., const originalSeed = process.env[TEST_ENV]) in
a beforeEach and restore it in afterEach by calling setSeedEnv(originalSeed) (or
passing undefined if originalSeed is undefined), and update all similar
occurrences (uses of setSeedEnv/afterEach) so tests restore the prior global
TEST_ENV instead of unconditionally deleting it; references: TEST_ENV,
setSeedEnv, beforeEach, afterEach in the did-route.test.ts file.

@raahulrahl raahulrahl merged commit a9ed7cd into main Apr 19, 2026
3 of 5 checks passed
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.

1 participant