feat(#88): implement QUERY message type for federation

Implement complete QUERY message protocol for federated queries between
Mosaic Stack instances, building on existing connection infrastructure.

Database Changes:
- Add FederationMessageType enum (QUERY, COMMAND, EVENT)
- Add FederationMessageStatus enum (PENDING, DELIVERED, FAILED, TIMEOUT)
- Add FederationMessage model for tracking all federation messages
- Add workspace and connection relations

Types & DTOs:
- QueryMessage: Signed query request payload
- QueryResponse: Signed query response payload
- QueryMessageDetails: API response type
- SendQueryDto: Client request DTO
- IncomingQueryDto: Validated incoming query DTO

QueryService:
- sendQuery: Send signed query to remote instance via ACTIVE connection
- handleIncomingQuery: Process and validate incoming queries
- processQueryResponse: Handle and verify query responses
- getQueryMessages: List workspace queries with optional status filter
- getQueryMessage: Get single query message details
- Message deduplication via unique messageId
- Signature verification using SignatureService
- Timestamp validation (5-minute window)

QueryController:
- POST /api/v1/federation/query: Send query (authenticated)
- POST /api/v1/federation/incoming/query: Receive query (public, signature-verified)
- GET /api/v1/federation/queries: List queries (authenticated)
- GET /api/v1/federation/queries/🆔 Get query details (authenticated)

Security:
- All messages signed with instance private key
- All responses verified with remote public key
- Timestamp validation prevents replay attacks
- Connection status validation (must be ACTIVE)
- Workspace isolation enforced via RLS

Testing:
- 15 QueryService tests (100% coverage)
- 9 QueryController tests (100% coverage)
- All tests passing with proper mocking
- TypeScript strict mode compliance

Refs #88

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-03 13:12:12 -06:00
parent 70a6bc82e0
commit 1159ca42a7
10 changed files with 1672 additions and 2 deletions

View File

@@ -173,6 +173,19 @@ enum FederationConnectionStatus {
DISCONNECTED
}
enum FederationMessageType {
QUERY
COMMAND
EVENT
}
enum FederationMessageStatus {
PENDING
DELIVERED
FAILED
TIMEOUT
}
// ============================================
// MODELS
// ============================================
@@ -257,6 +270,7 @@ model Workspace {
qualityGates QualityGate[]
runnerJobs RunnerJob[]
federationConnections FederationConnection[]
federationMessages FederationMessage[]
@@index([ownerId])
@@map("workspaces")
@@ -1273,7 +1287,8 @@ model FederationConnection {
disconnectedAt DateTime? @map("disconnected_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
messages FederationMessage[]
@@unique([workspaceId, remoteInstanceId])
@@index([workspaceId])
@@ -1301,3 +1316,40 @@ model FederatedIdentity {
@@index([oidcSubject])
@@map("federated_identities")
}
model FederationMessage {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
connectionId String @map("connection_id") @db.Uuid
// Message metadata
messageType FederationMessageType @map("message_type")
messageId String @unique @map("message_id") // UUID for deduplication
correlationId String? @map("correlation_id") // For request/response tracking
// Message content
query String? @db.Text
response Json? @default("{}")
// Status tracking
status FederationMessageStatus @default(PENDING)
error String? @db.Text
// Security
signature String @db.Text
// Timestamps
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
deliveredAt DateTime? @map("delivered_at") @db.Timestamptz
// Relations
connection FederationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@index([workspaceId])
@@index([connectionId])
@@index([messageId])
@@index([correlationId])
@@map("federation_messages")
}