Files
sentryagent-idp/openspec/changes/archive/2026-04-02-phase-3-enterprise/specs/w3c-dids/spec.md
SentryAgent.ai Developer f1fbe0e29a chore(openspec): archive all completed changes, sync 14 new specs to library
Archived 4 completed OpenSpec changes (2026-04-02):
- phase-3-enterprise (100/100 tasks) — 6 Phase 3 capabilities synced
- devops-documentation (48/48 tasks) — 3 new + 1 merged capability
- bedroom-developer-docs (33/33 tasks) — 4 new capabilities synced
- engineering-docs (superseded by 2026-03-29 archive) — no tasks

Main spec library grows from 21 → 35 capabilities (+14 new):
federation, multi-tenancy, oidc, soc2, w3c-dids, webhooks,
database, operations, system-overview, api-reference, core-concepts,
developer-guides, quick-start + deployment (merged additive requirements)

Active changes: 0 — project board is clear for Phase 4 planning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 03:50:47 +00:00

11 KiB

W3C Decentralized Identifiers (DIDs) — Specification

Workstream: 2 of 6 Phase: 3 — Enterprise Author: Virtual Architect Date: 2026-03-29


Overview

Issue a W3C did:web identifier for every registered agent and serve DID Documents over HTTPS. The AgentIdP instance itself has a root DID Document at /.well-known/did.json. Each agent has an individual DID Document at /agents/:id/did. A DID resolution endpoint wraps the standard resolution workflow. Agent cards in AGNTCY format are derivable from DID Documents.

The did:web method resolves to https://<host>/.well-known/did.json (instance) and https://<host>/agents/<agentId>/did (per-agent). All DID Documents are W3C DID Core 1.0 compliant.


API Endpoints

GET /.well-known/did.json

Root DID Document for the AgentIdP instance. No authentication required — this is a public discovery endpoint.

GET /.well-known/did.json
No authentication required

Responses:
  200 OK:
    Content-Type: application/json
    schema:
      type: object
      description: W3C DID Core 1.0 compliant DID Document
      required: [id, "@context", verificationMethod, authentication]
      properties:
        "@context":
          type: array
          items:
            type: string
          example:
            - "https://www.w3.org/ns/did/v1"
            - "https://w3id.org/security/suites/jws-2020/v1"
        id:
          type: string
          description: DID for this AgentIdP instance
          example: "did:web:idp.sentryagent.ai"
        controller:
          type: string
          example: "did:web:idp.sentryagent.ai"
        verificationMethod:
          type: array
          items:
            $ref: '#/components/schemas/VerificationMethod'
        authentication:
          type: array
          items:
            type: string
          description: References to verification methods for authentication
        assertionMethod:
          type: array
          items:
            type: string
        service:
          type: array
          items:
            $ref: '#/components/schemas/DIDService'
    example:
      "@context":
        - "https://www.w3.org/ns/did/v1"
      id: "did:web:idp.sentryagent.ai"
      controller: "did:web:idp.sentryagent.ai"
      verificationMethod:
        - id: "did:web:idp.sentryagent.ai#key-1"
          type: "JsonWebKey2020"
          controller: "did:web:idp.sentryagent.ai"
          publicKeyJwk:
            kty: "EC"
            crv: "P-256"
            x: "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU"
            y: "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
      authentication:
        - "did:web:idp.sentryagent.ai#key-1"
      service:
        - id: "did:web:idp.sentryagent.ai#agent-registry"
          type: "AgentIdentityProvider"
          serviceEndpoint: "https://idp.sentryagent.ai"
  500 Internal Server Error:
    schema:
      $ref: '#/components/schemas/ErrorResponse'

GET /agents/:id/did

Per-agent DID Document. No authentication required — DID Documents are public.

GET /agents/{agentId}/did
No authentication required

Path Parameters:
  agentId:
    type: string
    description: Agent ID

Responses:
  200 OK:
    Content-Type: application/json
    schema:
      type: object
      description: W3C DID Core 1.0 compliant per-agent DID Document
    example:
      "@context":
        - "https://www.w3.org/ns/did/v1"
        - "https://w3id.org/agntcy/v1"
      id: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890"
      controller: "did:web:idp.sentryagent.ai"
      verificationMethod:
        - id: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890#key-1"
          type: "JsonWebKey2020"
          controller: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890"
          publicKeyJwk:
            kty: "EC"
            crv: "P-256"
            x: "abc123"
            y: "def456"
      authentication:
        - "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890#key-1"
      service:
        - id: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890#agent-card"
          type: "AgentCard"
          serviceEndpoint: "https://idp.sentryagent.ai/agents/agt_01HXK7Z9P3FKWABCDEF67890/did/card"
      agntcy:
        agentId: "agt_01HXK7Z9P3FKWABCDEF67890"
        agentType: "orchestrator"
        capabilities:
          - "task-planning"
          - "tool-use"
        deploymentEnv: "production"
        owner: "acme-ai"
        version: "1.2.0"
  404 Not Found:
    schema:
      $ref: '#/components/schemas/ErrorResponse'
    example:
      code: "AGENT_NOT_FOUND"
      message: "Agent not found"
  410 Gone:
    schema:
      $ref: '#/components/schemas/ErrorResponse'
    example:
      code: "AGENT_DECOMMISSIONED"
      message: "Agent has been decommissioned — DID Document is no longer active"

GET /agents/:id/did/resolve

DID resolution endpoint: resolves any did:web DID and returns the DID resolution result in W3C DID Resolution format. This enables external systems to use AgentIdP as a resolver for agent DIDs. Authentication required (agents:read scope).

GET /agents/{agentId}/did/resolve
Authorization: Bearer <token with agents:read scope>

Path Parameters:
  agentId:
    type: string

Responses:
  200 OK:
    Content-Type: application/ld+json;profile="https://w3id.org/did-resolution"
    schema:
      type: object
      required: [didDocument, didDocumentMetadata, didResolutionMetadata]
      properties:
        didDocument:
          type: object
          description: The resolved DID Document
        didDocumentMetadata:
          type: object
          properties:
            created:
              type: string
              format: date-time
            updated:
              type: string
              format: date-time
            deactivated:
              type: boolean
        didResolutionMetadata:
          type: object
          properties:
            contentType:
              type: string
              example: "application/did+ld+json"
            retrieved:
              type: string
              format: date-time
    example:
      didDocument:
        "@context": ["https://www.w3.org/ns/did/v1"]
        id: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890"
      didDocumentMetadata:
        created: "2026-03-29T12:00:00Z"
        updated: "2026-03-29T12:00:00Z"
        deactivated: false
      didResolutionMetadata:
        contentType: "application/did+ld+json"
        retrieved: "2026-03-29T14:00:00Z"
  401 Unauthorized:
    schema:
      $ref: '#/components/schemas/ErrorResponse'
  404 Not Found:
    schema:
      $ref: '#/components/schemas/ErrorResponse'

GET /agents/:id/did/card

AGNTCY-format agent card derived from DID Document. Returns a JSON object representing the agent's identity and capabilities in the AGNTCY agent card format. No authentication required.

GET /agents/{agentId}/did/card
No authentication required

Responses:
  200 OK:
    Content-Type: application/json
    schema:
      type: object
      description: AGNTCY-format agent card
      properties:
        did:
          type: string
        name:
          type: string
        agentType:
          type: string
        capabilities:
          type: array
          items:
            type: string
        owner:
          type: string
        version:
          type: string
        deploymentEnv:
          type: string
        identityProvider:
          type: string
          description: DID of the issuing AgentIdP instance
        issuedAt:
          type: string
          format: date-time
    example:
      did: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890"
      name: "acme-orchestrator"
      agentType: "orchestrator"
      capabilities: ["task-planning", "tool-use"]
      owner: "acme-ai"
      version: "1.2.0"
      deploymentEnv: "production"
      identityProvider: "did:web:idp.sentryagent.ai"
      issuedAt: "2026-03-29T12:00:00Z"
  404 Not Found:
    schema:
      $ref: '#/components/schemas/ErrorResponse'

Database Schema Changes

New Table: agent_did_keys

Stores the public/private key pair used to sign each agent's DID Document. The private key is stored in Vault; only the public key JWK is stored in PostgreSQL.

CREATE TABLE agent_did_keys (
  key_id            VARCHAR(40)  PRIMARY KEY,
  agent_id          VARCHAR(40)  NOT NULL UNIQUE REFERENCES agents(agent_id),
  organization_id   VARCHAR(40)  NOT NULL REFERENCES organizations(organization_id),
  public_key_jwk    JSONB        NOT NULL,
  vault_key_path    VARCHAR(255) NOT NULL,  -- Vault path where private key is stored
  key_type          VARCHAR(20)  NOT NULL DEFAULT 'EC',
  curve             VARCHAR(10)  NOT NULL DEFAULT 'P-256',
  created_at        TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
  rotated_at        TIMESTAMPTZ,
  CONSTRAINT agent_did_keys_key_type_check CHECK (key_type IN ('EC', 'RSA'))
);

CREATE INDEX idx_agent_did_keys_agent_id ON agent_did_keys(agent_id);
CREATE INDEX idx_agent_did_keys_org_id ON agent_did_keys(organization_id);

New Column: agents.did

ALTER TABLE agents
  ADD COLUMN did VARCHAR(255),
  ADD COLUMN did_created_at TIMESTAMPTZ;

-- Populated automatically on agent creation
-- Example value: "did:web:idp.sentryagent.ai:agents:agt_01HXK7Z9P3FKWABCDEF67890"

Configuration

Environment Variable Description Default
DID_WEB_DOMAIN Domain name for did:web construction Required — derived from HOST if not set
DID_KEY_TYPE Cryptographic key type for DID keys EC
DID_KEY_CURVE Elliptic curve for EC keys P-256
DID_DOCUMENT_CACHE_TTL_SECONDS How long to cache DID Documents in Redis 300

Dependencies

Package Version Purpose
did-resolver ^4.1.0 W3C DID resolution interface
web-did-resolver ^2.0.27 DID:WEB method resolver

Security Considerations

  • DID Documents are public endpoints — no authentication, no rate-limit-sensitive data exposed
  • Private keys for DID signing are stored in Vault; never written to PostgreSQL
  • DID Document cache in Redis has a TTL — stale documents are evicted automatically
  • Decommissioned agents return HTTP 410 Gone with deactivated: true in DID Document metadata
  • DID rotation: when a credential is rotated, the DID Document key can optionally be rotated; the old key is retained in history
  • GET /agents/:id/did/card exposes only data already present in the agent registration — no new sensitive fields

Acceptance Criteria

  • Every new agent registration automatically generates a did:web DID and key pair
  • Root DID Document at /.well-known/did.json is W3C DID Core 1.0 compliant (validated by did-resolver)
  • Per-agent DID Document returns correct did:web identifier and public key JWK
  • DID resolution endpoint returns W3C DID Resolution format
  • Decommissioned agent DID Document returns 410 Gone with deactivated: true
  • Agent card at /agents/:id/did/card matches AGNTCY agent card format
  • Private keys never appear in any API response or log
  • TypeScript strict, zero any, >80% test coverage on DIDService