Implements FED-010: Agent Spawn via Federation feature that enables spawning and managing Claude agents on remote federated Mosaic Stack instances via COMMAND message type. Features: - Federation agent command types (spawn, status, kill) - FederationAgentService for handling agent operations - Integration with orchestrator's agent spawner/lifecycle services - API endpoints for spawning, querying status, and killing agents - Full command routing through federation COMMAND infrastructure - Comprehensive test coverage (12/12 tests passing) Architecture: - Hub → Spoke: Spawn agents on remote instances - Command flow: FederationController → FederationAgentService → CommandService → Remote Orchestrator - Response handling: Remote orchestrator returns agent status/results - Security: Connection validation, signature verification Files created: - apps/api/src/federation/types/federation-agent.types.ts - apps/api/src/federation/federation-agent.service.ts - apps/api/src/federation/federation-agent.service.spec.ts Files modified: - apps/api/src/federation/command.service.ts (agent command routing) - apps/api/src/federation/federation.controller.ts (agent endpoints) - apps/api/src/federation/federation.module.ts (service registration) - apps/orchestrator/src/api/agents/agents.controller.ts (status endpoint) - apps/orchestrator/src/api/agents/agents.module.ts (lifecycle integration) Testing: - 12/12 tests passing for FederationAgentService - All command service tests passing - TypeScript compilation successful - Linting passed Refs #93 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
474 lines
13 KiB
Markdown
474 lines
13 KiB
Markdown
# Activity Logging API
|
|
|
|
The Activity Logging API provides comprehensive audit trail and activity tracking functionality for the Mosaic Stack platform. It logs user actions, workspace changes, task/event modifications, and authentication events.
|
|
|
|
## Overview
|
|
|
|
Activity logs are automatically created for:
|
|
|
|
- **CRUD Operations**: Task, event, project, and workspace modifications
|
|
- **Authentication Events**: Login, logout, password resets
|
|
- **User Actions**: Task assignments, workspace member changes
|
|
- **System Events**: Configuration updates, permission changes
|
|
|
|
All activity logs are workspace-scoped and support multi-tenant isolation through Row-Level Security (RLS).
|
|
|
|
## Endpoints
|
|
|
|
### List Activity Logs
|
|
|
|
```
|
|
GET /api/activity
|
|
```
|
|
|
|
Get a paginated list of activity logs with optional filters.
|
|
|
|
**Query Parameters:**
|
|
|
|
| Parameter | Type | Required | Description |
|
|
| ------------- | -------------- | -------- | ---------------------------------------------- |
|
|
| `workspaceId` | UUID | Yes | Workspace to filter by |
|
|
| `userId` | UUID | No | Filter by user who performed the action |
|
|
| `action` | ActivityAction | No | Filter by action type (CREATED, UPDATED, etc.) |
|
|
| `entityType` | EntityType | No | Filter by entity type (TASK, EVENT, etc.) |
|
|
| `entityId` | UUID | No | Filter by specific entity |
|
|
| `startDate` | ISO 8601 | No | Filter activities after this date |
|
|
| `endDate` | ISO 8601 | No | Filter activities before this date |
|
|
| `page` | Number | No | Page number (default: 1) |
|
|
| `limit` | Number | No | Items per page (default: 50, max: 100) |
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"workspaceId": "660e8400-e29b-41d4-a716-446655440001",
|
|
"userId": "770e8400-e29b-41d4-a716-446655440002",
|
|
"action": "CREATED",
|
|
"entityType": "TASK",
|
|
"entityId": "880e8400-e29b-41d4-a716-446655440003",
|
|
"details": {
|
|
"title": "New Task",
|
|
"status": "NOT_STARTED"
|
|
},
|
|
"ipAddress": "192.168.1.1",
|
|
"userAgent": "Mozilla/5.0...",
|
|
"createdAt": "2024-01-28T12:00:00Z",
|
|
"user": {
|
|
"id": "770e8400-e29b-41d4-a716-446655440002",
|
|
"name": "John Doe",
|
|
"email": "john@example.com"
|
|
}
|
|
}
|
|
],
|
|
"meta": {
|
|
"total": 150,
|
|
"page": 1,
|
|
"limit": 50,
|
|
"totalPages": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
**Example Requests:**
|
|
|
|
```bash
|
|
# Get all activities in a workspace
|
|
GET /api/activity?workspaceId=660e8400-e29b-41d4-a716-446655440001
|
|
|
|
# Get activities for a specific user
|
|
GET /api/activity?workspaceId=660e8400-e29b-41d4-a716-446655440001&userId=770e8400-e29b-41d4-a716-446655440002
|
|
|
|
# Get task creation events
|
|
GET /api/activity?workspaceId=660e8400-e29b-41d4-a716-446655440001&action=CREATED&entityType=TASK
|
|
|
|
# Get activities in date range
|
|
GET /api/activity?workspaceId=660e8400-e29b-41d4-a716-446655440001&startDate=2024-01-01&endDate=2024-01-31
|
|
|
|
# Paginate results
|
|
GET /api/activity?workspaceId=660e8400-e29b-41d4-a716-446655440001&page=2&limit=25
|
|
```
|
|
|
|
---
|
|
|
|
### Get Single Activity Log
|
|
|
|
```
|
|
GET /api/activity/:id
|
|
```
|
|
|
|
Retrieve a single activity log entry by ID.
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Required | Description |
|
|
| --------- | ---- | -------- | --------------- |
|
|
| `id` | UUID | Yes | Activity log ID |
|
|
|
|
**Query Parameters:**
|
|
|
|
| Parameter | Type | Required | Description |
|
|
| ------------- | ---- | -------- | ----------------------------------------- |
|
|
| `workspaceId` | UUID | Yes | Workspace ID (for multi-tenant isolation) |
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"workspaceId": "660e8400-e29b-41d4-a716-446655440001",
|
|
"userId": "770e8400-e29b-41d4-a716-446655440002",
|
|
"action": "UPDATED",
|
|
"entityType": "TASK",
|
|
"entityId": "880e8400-e29b-41d4-a716-446655440003",
|
|
"details": {
|
|
"changes": {
|
|
"status": "IN_PROGRESS"
|
|
}
|
|
},
|
|
"ipAddress": "192.168.1.1",
|
|
"userAgent": "Mozilla/5.0...",
|
|
"createdAt": "2024-01-28T12:00:00Z",
|
|
"user": {
|
|
"id": "770e8400-e29b-41d4-a716-446655440002",
|
|
"name": "John Doe",
|
|
"email": "john@example.com"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Example Request:**
|
|
|
|
```bash
|
|
GET /api/activity/550e8400-e29b-41d4-a716-446655440000?workspaceId=660e8400-e29b-41d4-a716-446655440001
|
|
```
|
|
|
|
---
|
|
|
|
### Get Entity Audit Trail
|
|
|
|
```
|
|
GET /api/activity/audit/:entityType/:entityId
|
|
```
|
|
|
|
Retrieve complete audit trail for a specific entity (task, event, project, etc.).
|
|
|
|
**Path Parameters:**
|
|
|
|
| Parameter | Type | Required | Description |
|
|
| ------------ | ---------- | -------- | ------------------------------------------------------ |
|
|
| `entityType` | EntityType | Yes | Type of entity (TASK, EVENT, PROJECT, WORKSPACE, USER) |
|
|
| `entityId` | UUID | Yes | Entity ID |
|
|
|
|
**Query Parameters:**
|
|
|
|
| Parameter | Type | Required | Description |
|
|
| ------------- | ---- | -------- | ----------------------------------------- |
|
|
| `workspaceId` | UUID | Yes | Workspace ID (for multi-tenant isolation) |
|
|
|
|
**Response:**
|
|
|
|
Returns array of activity logs in chronological order (oldest first).
|
|
|
|
```json
|
|
[
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"workspaceId": "660e8400-e29b-41d4-a716-446655440001",
|
|
"userId": "770e8400-e29b-41d4-a716-446655440002",
|
|
"action": "CREATED",
|
|
"entityType": "TASK",
|
|
"entityId": "880e8400-e29b-41d4-a716-446655440003",
|
|
"details": {
|
|
"title": "New Task"
|
|
},
|
|
"createdAt": "2024-01-28T10:00:00Z",
|
|
"user": {
|
|
"id": "770e8400-e29b-41d4-a716-446655440002",
|
|
"name": "John Doe",
|
|
"email": "john@example.com"
|
|
}
|
|
},
|
|
{
|
|
"id": "660e8400-e29b-41d4-a716-446655440004",
|
|
"workspaceId": "660e8400-e29b-41d4-a716-446655440001",
|
|
"userId": "770e8400-e29b-41d4-a716-446655440005",
|
|
"action": "UPDATED",
|
|
"entityType": "TASK",
|
|
"entityId": "880e8400-e29b-41d4-a716-446655440003",
|
|
"details": {
|
|
"changes": {
|
|
"status": "IN_PROGRESS"
|
|
}
|
|
},
|
|
"createdAt": "2024-01-28T12:00:00Z",
|
|
"user": {
|
|
"id": "770e8400-e29b-41d4-a716-446655440005",
|
|
"name": "Jane Smith",
|
|
"email": "jane@example.com"
|
|
}
|
|
}
|
|
]
|
|
```
|
|
|
|
**Example Requests:**
|
|
|
|
```bash
|
|
# Get audit trail for a task
|
|
GET /api/activity/audit/TASK/880e8400-e29b-41d4-a716-446655440003?workspaceId=660e8400-e29b-41d4-a716-446655440001
|
|
|
|
# Get audit trail for a project
|
|
GET /api/activity/audit/PROJECT/990e8400-e29b-41d4-a716-446655440006?workspaceId=660e8400-e29b-41d4-a716-446655440001
|
|
|
|
# Get audit trail for a workspace
|
|
GET /api/activity/audit/WORKSPACE/660e8400-e29b-41d4-a716-446655440001?workspaceId=660e8400-e29b-41d4-a716-446655440001
|
|
```
|
|
|
|
---
|
|
|
|
## Enums
|
|
|
|
### ActivityAction
|
|
|
|
Actions that can be logged:
|
|
|
|
- `CREATED` - Entity was created
|
|
- `UPDATED` - Entity was updated
|
|
- `DELETED` - Entity was deleted
|
|
- `COMPLETED` - Task or project was completed
|
|
- `ASSIGNED` - Task was assigned to a user
|
|
- `COMMENTED` - Comment was added (future use)
|
|
- `LOGIN` - User logged in
|
|
- `LOGOUT` - User logged out
|
|
- `PASSWORD_RESET` - Password was reset
|
|
- `EMAIL_VERIFIED` - Email was verified
|
|
|
|
### EntityType
|
|
|
|
Types of entities that can be tracked:
|
|
|
|
- `TASK` - Task entity
|
|
- `EVENT` - Calendar event
|
|
- `PROJECT` - Project
|
|
- `WORKSPACE` - Workspace
|
|
- `USER` - User profile
|
|
|
|
---
|
|
|
|
## Automatic Logging
|
|
|
|
The Activity Logging system includes an interceptor that automatically logs:
|
|
|
|
- **POST requests** → `CREATED` action
|
|
- **PATCH/PUT requests** → `UPDATED` action
|
|
- **DELETE requests** → `DELETED` action
|
|
|
|
The interceptor extracts:
|
|
|
|
- User information from the authenticated session
|
|
- Workspace context from request
|
|
- IP address and user agent from HTTP headers
|
|
- Entity ID from route parameters or response
|
|
|
|
---
|
|
|
|
## Manual Logging
|
|
|
|
For custom logging scenarios, use the `ActivityService` helper methods:
|
|
|
|
```typescript
|
|
import { ActivityService } from "@/activity/activity.service";
|
|
|
|
@Injectable()
|
|
export class TaskService {
|
|
constructor(private activityService: ActivityService) {}
|
|
|
|
async createTask(data, userId, workspaceId) {
|
|
const task = await this.prisma.task.create({ data });
|
|
|
|
// Log task creation
|
|
await this.activityService.logTaskCreated(workspaceId, userId, task.id, { title: task.title });
|
|
|
|
return task;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Available Helper Methods
|
|
|
|
#### Task Activities
|
|
|
|
- `logTaskCreated(workspaceId, userId, taskId, details?)`
|
|
- `logTaskUpdated(workspaceId, userId, taskId, details?)`
|
|
- `logTaskDeleted(workspaceId, userId, taskId, details?)`
|
|
- `logTaskCompleted(workspaceId, userId, taskId, details?)`
|
|
- `logTaskAssigned(workspaceId, userId, taskId, assigneeId)`
|
|
|
|
#### Event Activities
|
|
|
|
- `logEventCreated(workspaceId, userId, eventId, details?)`
|
|
- `logEventUpdated(workspaceId, userId, eventId, details?)`
|
|
- `logEventDeleted(workspaceId, userId, eventId, details?)`
|
|
|
|
#### Project Activities
|
|
|
|
- `logProjectCreated(workspaceId, userId, projectId, details?)`
|
|
- `logProjectUpdated(workspaceId, userId, projectId, details?)`
|
|
- `logProjectDeleted(workspaceId, userId, projectId, details?)`
|
|
|
|
#### Workspace Activities
|
|
|
|
- `logWorkspaceCreated(workspaceId, userId, details?)`
|
|
- `logWorkspaceUpdated(workspaceId, userId, details?)`
|
|
- `logWorkspaceMemberAdded(workspaceId, userId, memberId, role)`
|
|
- `logWorkspaceMemberRemoved(workspaceId, userId, memberId)`
|
|
|
|
#### User Activities
|
|
|
|
- `logUserUpdated(workspaceId, userId, details?)`
|
|
|
|
---
|
|
|
|
## Security & Privacy
|
|
|
|
### Multi-Tenant Isolation
|
|
|
|
All activity logs are scoped to workspaces using Row-Level Security (RLS). Users can only access activity logs for workspaces they belong to.
|
|
|
|
### Data Retention
|
|
|
|
Activity logs are retained indefinitely by default. Consider implementing a retention policy based on:
|
|
|
|
- Compliance requirements
|
|
- Storage constraints
|
|
- Business needs
|
|
|
|
### Sensitive Data
|
|
|
|
Activity logs should NOT contain:
|
|
|
|
- Passwords or authentication tokens
|
|
- Credit card information
|
|
- Personal health information
|
|
- Other sensitive PII
|
|
|
|
Store only metadata needed for audit purposes. Use the `details` field for non-sensitive context.
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use Descriptive Details
|
|
|
|
Include enough context to understand what changed:
|
|
|
|
```typescript
|
|
// Good
|
|
await activityService.logTaskUpdated(workspaceId, userId, taskId, {
|
|
changes: {
|
|
status: { from: "NOT_STARTED", to: "IN_PROGRESS" },
|
|
assignee: { from: null, to: "user-456" },
|
|
},
|
|
});
|
|
|
|
// Less useful
|
|
await activityService.logTaskUpdated(workspaceId, userId, taskId);
|
|
```
|
|
|
|
### 2. Log Business-Critical Actions
|
|
|
|
Prioritize logging actions that:
|
|
|
|
- Change permissions or access control
|
|
- Delete data
|
|
- Modify billing or subscription
|
|
- Export data
|
|
- Change security settings
|
|
|
|
### 3. Query Efficiently
|
|
|
|
Use appropriate filters to reduce data transfer:
|
|
|
|
```typescript
|
|
// Efficient - filters at database level
|
|
const activities = await fetch("/api/activity?workspaceId=xxx&entityType=TASK&page=1&limit=50");
|
|
|
|
// Inefficient - transfers all data then filters
|
|
const activities = await fetch("/api/activity?workspaceId=xxx");
|
|
const taskActivities = activities.filter((a) => a.entityType === "TASK");
|
|
```
|
|
|
|
### 4. Display User-Friendly Activity Feeds
|
|
|
|
Transform activity logs into human-readable messages:
|
|
|
|
```typescript
|
|
function formatActivityMessage(activity: ActivityLog) {
|
|
const { user, action, entityType, details } = activity;
|
|
|
|
switch (action) {
|
|
case "CREATED":
|
|
return `${user.name} created ${entityType.toLowerCase()} "${details.title}"`;
|
|
case "UPDATED":
|
|
return `${user.name} updated ${entityType.toLowerCase()}`;
|
|
case "DELETED":
|
|
return `${user.name} deleted ${entityType.toLowerCase()}`;
|
|
default:
|
|
return `${user.name} performed ${action}`;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
Activity logging failures should NOT block the primary operation. The interceptor and service methods handle errors gracefully:
|
|
|
|
```typescript
|
|
try {
|
|
await activityService.logActivity(data);
|
|
} catch (error) {
|
|
// Log error but don't throw
|
|
logger.error("Failed to log activity", error);
|
|
}
|
|
```
|
|
|
|
If activity logging is critical for compliance, implement synchronous validation before the operation completes.
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
### Indexing
|
|
|
|
The following indexes optimize common queries:
|
|
|
|
- `workspaceId` - Filter by workspace
|
|
- `workspaceId + createdAt` - Recent activities per workspace
|
|
- `entityType + entityId` - Audit trail queries
|
|
- `userId` - User activity history
|
|
- `action` - Filter by action type
|
|
|
|
### Pagination
|
|
|
|
Always use pagination for activity queries. Default limit is 50 items, maximum is 100.
|
|
|
|
### Background Processing
|
|
|
|
For high-volume systems, consider:
|
|
|
|
- Async activity logging with message queues
|
|
- Batch inserts for multiple activities
|
|
- Separate read replicas for reporting
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- [Authentication API](../2-authentication/README.md)
|
|
- [API Conventions](../1-conventions/README.md)
|
|
- [Database Schema](../../3-architecture/2-database/README.md)
|