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>
This commit is contained in:
SentryAgent.ai Developer
2026-04-02 03:50:47 +00:00
parent ceec22f714
commit f1fbe0e29a
53 changed files with 3019 additions and 0 deletions

View File

@@ -0,0 +1,353 @@
# 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.
```yaml
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.
```yaml
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).
```yaml
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.
```yaml
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.
```sql
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
```sql
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