Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions services/trust-graph/src/services/trust-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,16 +314,11 @@ export class TrustService {
// Reset circuit breaker on success
this.circuitBreaker.failureCount = 0
} else if (response.status >= 500) {
throw new TrustError(`Discovery service unavailable while resolving identity: ${did}`)
this.recordDiscoveryFailure(did)
}
} catch (error) {
if (error instanceof TrustError) throw error
// Discovery service unavailable — increment circuit breaker
this.circuitBreaker.failureCount++
if (this.circuitBreaker.failureCount >= CIRCUIT_BREAKER_THRESHOLD) {
this.circuitBreaker.openUntil = Date.now() + CIRCUIT_BREAKER_RESET_MS
}
throw new TrustError(`Discovery service unavailable while resolving identity: ${did}`)
this.recordDiscoveryFailure(did)
}

if (!identity || !identity.publicKey) {
Expand Down Expand Up @@ -517,6 +512,14 @@ export class TrustService {

return new Uint8Array(publicKey)
}

private recordDiscoveryFailure(did: string): never {
this.circuitBreaker.failureCount++
if (this.circuitBreaker.failureCount >= CIRCUIT_BREAKER_THRESHOLD) {
this.circuitBreaker.openUntil = Date.now() + CIRCUIT_BREAKER_RESET_MS
}
throw new TrustError(`Discovery service unavailable while resolving identity: ${did}`)
}
}

function isRevocationRecord(record: unknown): record is RevocationRecord {
Expand Down
25 changes: 24 additions & 1 deletion services/trust-graph/test/trust-service.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { TrustService } from '../src/services/trust-service.js'
import type { CreateTrustRequest } from '../src/types.js'
import {
Expand Down Expand Up @@ -39,6 +39,10 @@ describe('TrustService', () => {
}
})

afterEach(() => {
vi.unstubAllGlobals()
})

function mockIdentity(publicKey: Uint8Array) {
mockDb.select = vi.fn(() => ({
from: vi.fn(() => ({
Expand Down Expand Up @@ -212,6 +216,25 @@ describe('TrustService', () => {
})

describe('getScore', () => {
it('opens the discovery circuit breaker after repeated 5xx responses', async () => {
service = new TrustService('http://discovery.test')
const fetchMock = vi.fn(() => Promise.resolve(new Response('unavailable', { status: 503 })))
vi.stubGlobal('fetch', fetchMock)

for (let attempt = 0; attempt < 5; attempt++) {
await expect(service.getScore(mockDb, `did:fides:missing-${attempt}`)).rejects.toThrow(
'Discovery service unavailable'
)
}

expect(fetchMock).toHaveBeenCalledTimes(5)

await expect(service.getScore(mockDb, 'did:fides:circuit-open')).rejects.toThrow(
'Discovery service unavailable'
)
expect(fetchMock).toHaveBeenCalledTimes(5)
})

it('should return cached score if valid', async () => {
const now = new Date()
const recentCompute = new Date(now.getTime() - 1800000) // 30 min ago
Expand Down