Skip to content

[ME-05] Tenant-scoped dead-letter self-service #110

Description

@esune

Allow tenant administrators to view and replay their own failed async jobs without
requiring platform-admin intervention. Server enforces tenant isolation — callers
can only see and replay jobs where job.data.tenant_id matches their JWT tenant_id.

Queues exposed to tenants (jobs that directly impact their operations):

  • credential.state-update — failed to correlate a webhook from the agent to an Operation
  • webhook.dispatch — delivery to the tenant's registered webhook URL failed after all retries
  • audit.write — failed to persist an audit log entry
  • credential.bulk-item — a single item in a bulk issuance/revocation batch failed

Endpoints:

  • GET /api/v1/tenants/:tenantId/events/dead-letter
    Query params: queue (filter), since, until, cursor, limit
    Returns: TenantDeadLetterJob[] — redacted of sensitive fields; correlation IDs only
  • POST /api/v1/tenants/:tenantId/events/dead-letter/:jobId/replay
    Validates job.data.tenant_id === jwt.tenant_id before calling boss.resume(id)
    Returns 404 if job not found or belongs to another tenant (indistinguishable by design)
    Returns 202 on success

Security:

  • tenant_id filter applied server-side from JWT (never from request params)
  • Job data is redacted before returning — only correlation IDs (operation_id, external_id,
    webhook_id) are exposed, not raw credential attributes or connector secrets
  • Requires tenants:admin scope

Implementation note:

  • Queries pgboss.job WHERE state = 'failed' AND data->>'tenant_id' = $1
  • Index on (data->>'tenant_id', state) required for acceptable query performance
  • Add GIN index on job.data for JSONB path filtering

Dependencies

Definition of Done

  • Implementation complete and builds without errors
  • Unit/integration tests pass
  • PR reviewed and approved
  • No regressions in existing functionality
  • API endpoints documented (if applicable)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    Status
    No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions