Interview Lifecycle - Technical Manual
Version: 1.0
Last Updated: 2026-07-16
This guide is intended for Developers, Architects, DevOps, and System Integrators.
Purpose
The InterviewExecution entity serves as the aggregate root for all survey interview operations. It represents a single survey session with a respondent and manages the complete lifecycle from creation to final submission.
Unlike traditional form-filling approaches that treat each submission as independent, MQUAD's interview model provides:
- State Persistence: Interview progress survives app restarts and connectivity loss
- Lifecycle Tracking: Explicit status transitions enable workflow automation
- Multi-Device Support: Interviews can be resumed across devices
- Revision Management: Rejected interviews maintain context for corrections
Responsibilities
InterviewExecution
The InterviewExecution aggregate owns:
- Overall interview status and state machine
- Session token for authentication
- Progress tracking (
current_page,answered_count) - Response linkage (
current_response_id) - Interview metadata and timestamps
FormSession
The FormSession entity manages:
- Runtime form state during filling
- Draft responses while offline
- Position and navigation state
- Session-specific validation cache
SurveyResponse
The SurveyResponse entity owns:
- Final response data after completion
- Status transitions (draft → submitted → approved/rejected)
- Answer records (
ResponseAnswer) - Edit history (
ResponseEdit) - Review workflow state
Sync Service
The sync-service handles:
- Offline queue management
- Conflict detection and resolution
- Device synchronization
- Interview state persistence across devices
Form Service
The form-service manages:
- Interview lifecycle transitions
- State validation and business rules
- Event publishing for state changes
- Response submission coordination
Data Service
The data-service manages:
- Response storage and retrieval
- Answer validation against schemas
- Webhook event generation
- Audit trail persistence
State Machine
Diagram source
stateDiagram-v2
[*] --> Created: Initial state
Created --> InProgress: First question answered
InProgress --> Paused: Explicit pause or timeout
InProgress --> Interrupted: System error
InProgress --> Completed: All questions answered
InProgress --> Terminated: Premature end
Paused --> InProgress: Resume session
Interrupted --> InProgress: Recovery action
Completed --> NeedsRevision: Supervisor rejection
NeedsRevision --> InProgress: Revision started
NeedsRevision --> Completed: Revision completeState Transitions
Created → In Progress
| Property | Value |
|---|---|
| Trigger | First answer saved or explicit start |
| Validation | Survey must be published |
| Database Updates | status = 'in_progress', started_at = now() |
| Events Published | None (implicit in first answer) |
| Services Involved | Form Service |
| Failure Behaviour | Interview remains in "created" state |
| Idempotency | Starting an already-in-progress interview is idempotent |
In Progress → Paused
| Property | Value |
|---|---|
| Trigger | Explicit pause or session timeout |
| Validation | Interview must exist and be active |
| Database Updates | status = 'paused', paused_at = now() |
| Events Published | None |
| Services Involved | Form Service |
| Failure Behaviour | Interview remains in "in_progress" |
In Progress → Interrupted
| Property | Value |
|---|---|
| Trigger | System error or unexpected closure |
| Validation | None required |
| Database Updates | status = 'interrupted' |
| Events Published | None |
| Services Involved | Form Service |
| Failure Behaviour | Requires manual recovery |
In Progress → Completed
| Property | Value |
|---|---|
| Trigger | All questions answered and submit called |
| Validation | All required questions must have answers |
| Database Updates | status = 'completed', completed_at = now(), response_rid, response_uid populated |
| Events Published | response.submitted |
| Services Involved | Form Service, Data Service |
| Failure Behaviour | Interview remains in "in_progress" |
In Progress → Terminated
| Property | Value |
|---|---|
| Trigger | Explicit termination or system failure |
| Validation | Termination reason required |
| Database Updates | status = 'terminated', terminated_at = now(), termination_reason set |
| Events Published | None |
| Services Involved | Form Service |
| Failure Behaviour | Interview remains in "in_progress" |
Completed → Needs Revision
| Property | Value |
|---|---|
| Trigger | Supervisor rejection |
| Validation | Response must exist and be rejected |
| Database Updates | status = 'needs_revision', current_response_id set |
| Events Published | response.rejected (triggers interview transition) |
| Services Involved | Data Service (via event), Form Service |
| Failure Behaviour | Interview remains in "completed" |
Needs Revision → In Progress
| Property | Value |
|---|---|
| Trigger | Enumerator begins revision |
| Validation | Interview must be in "needs_revision" state |
| Database Updates | status = 'in_progress' |
| Events Published | None |
| Services Involved | Form Service |
Needs Revision → Completed
| Property | Value |
|---|---|
| Trigger | Revision finished and resubmitted |
| Validation | All corrections must be applied |
| Database Updates | status = 'completed', current_response_id cleared |
| Events Published | Response update events |
| Services Involved | Form Service, Data Service |
Needs Revision Workflow
Diagram source
sequenceDiagram
participant Sup as Supervisor
participant DS as Data Service
participant Event as Event Bus
participant FS as Form Service
participant Enum as Enumerator
Sup->>DS: PATCH /responses/{id}/reject
Note over DS: Mark response rejected
Note over DS: Publish response.rejected event
Event-->>FS: Receive response.rejected
Note over FS: Lookup InterviewExecution<br/>by current_response_id
FS->>FS: Update status to needs_revision
Note over FS: Set current_response_id
Enum->>FS: GET /interviews/{id}
Note over FS: Return interview with needs_revision status
Enum->>FS: PATCH /interviews/{id}/transition
Note over FS: Set status = in_progress
Enum->>FS: PATCH /responses/{id}/edit
Note over FS: Apply corrections
Enum->>FS: POST /interviews/{id}/complete
Note over FS: Clear current_response_id<br/>Set completed_atAggregate Design
InterviewExecution as Aggregate Root
InterviewExecution is the aggregate root because:
- Single source of truth for interview lifecycle
- Clear ownership boundary for state transitions
- Facilitates optimistic locking via version columns
- Enables CQRS pattern for query separation
SurveyResponse Separation
SurveyResponse is separate because:
- Responses may exist without interviews (direct submission)
- Audit trail requires immutable response history
- Multiple interview revisions map to one response chain
- Review workflow operates at response level
FormSession Runtime State
FormSession owns runtime state because:
- Sessions are ephemeral compared to interviews
- Multiple sessions may reference one interview
- Session data is cache-like and can be rebuilt
- Separation enables efficient cleanup
Event Driven Architecture
Events
| Event | Source | Trigger |
|---|---|---|
response.submitted | data-service | Response created |
response.approved | data-service | Supervisor approves |
response.rejected | data-service | Supervisor rejects |
response.edited | data-service | Response modified |
interview.created | form-service | Interview initialized |
interview.started | form-service | Status → in_progress |
interview.completed | form-service | Status → completed |
interview.paused | form-service | Status → paused |
interview.terminated | form-service | Status → terminated |
interview.needs_revision | form-service | Response rejection handler |
Session Management
Heartbeat
Heartbeats are sent every 30 seconds while interviews are in progress:
- Updates
last_heartbeat_attimestamp - Enables crash detection and recovery
- Prevents stale session conflicts
- Triggers timeout if missed for > 2 minutes
Pause
Pausing preserves interview state:
- No heartbeats sent while paused
- Interview unlocked but locked to session
- Draft data saved locally
- Can be resumed across devices
Resume
Resuming validates session integrity:
- Checks interview is in pausable state
- Verifies no other active session exists
- Loads latest draft data
- Begins heartbeats immediately
Timeouts
Timeout rules:
- 2 minutes without heartbeat → session considered stale
- 24 hours in "created" → auto-pause
- 7 days without activity → session cleanup
Multi-Tab Synchronization
Tab ownership is enforced via:
- Single active tab per interview
- Tab switch triggers heartbeat
- Lost tab → automatic recovery
Offline Behaviour
Offline Edits
While offline:
- Answers saved to local SQLite database
- Interview state updated locally
- Draft responses created/updated
- Sync queue populated for later submission
Draft Saving
Draft saving strategy:
- Every answered question triggers save
- Position state stored with each answer
- Conflict-free merge on sync
- Retry logic for failed submissions
Sync
Sync process:
- Queue processed on connectivity restoration
- Conflicts detected and recorded
- Responses submitted with idempotency
- Interview status updated on success
Conflict Handling
Conflict detection:
- HTTP 409 on push triggers conflict creation
- Both versions preserved in
SyncConflict - Interview status set to indicate conflict
- Supervisor notification (future feature)
Sequence Diagrams
Normal Interview
Diagram source
sequenceDiagram
participant Enum as Enumerator
participant FS as Form Service
participant DS as Data Service
participant SS as Sync Service
Enum->>FS: POST /interviews (survey_id, respondent)
FS-->>Enum: {id, session_token}
loop Question Loop
Enum->>FS: PATCH /interviews/{id} (answers)
Note over FS: Update progress<br/>Save heartbeat
end
Enum->>FS: POST /interviews/{id}/complete
FS->>DS: POST /responses (answers)
DS-->>FS: {response_id}
FS-->>Enum: Success
Enum->>SS: POST /sync/push
SS->>DS: Forward response
DS-->>SS: 201 Created
SS-->>Enum: Status completedRejected Interview
Diagram source
sequenceDiagram
participant Sup as Supervisor
participant DS as Data Service
participant Event as Event Bus
participant FS as Form Service
participant Enum as Enumerator
Sup->>DS: PATCH /responses/{id}/reject
Note over DS: Set status = rejected
DS->>Event: Publish response.rejected
Event-->>FS: Subscribe
Note over FS: Lookup interview<br/>Set needs_revision<br/>Store response_idRevision Resume
Diagram source
sequenceDiagram
participant Enum as Enumerator
participant FS as Form Service
Enum->>FS: GET /interviews/{id}
Note over FS: Check needs_revision<br/>Return editable questions
Enum->>FS: PATCH /interviews/{id}/transition
Note over FS: Status → in_progress
Enum->>FS: PATCH /responses/{id}/edit
Note over FS: Apply correctionsState Diagram
Diagram source
stateDiagram-v2
[*] --> Created
Created --> InProgress: Start Interview
InProgress --> Paused: Pause
InProgress --> Interrupted: System Error
InProgress --> Completed: All Answered
InProgress --> Terminated: Terminate
Paused --> InProgress: Resume
Interrupted --> InProgress: Recovery
Completed --> NeedsRevision: Rejected
NeedsRevision --> InProgress: Start Revision
NeedsRevision --> Completed: Revision DoneError Handling
Illegal Transitions
Invalid transitions return HTTP 400 with descriptive messages. Common invalid transitions:
- Created → Completed (must go through In Progress)
- Paused → Completed (must resume first)
- Needs Revision → Terminated (revision required)
Stale Sessions
Stale session detection:
- Heartbeat timeout triggers stale flag
- New session requests denied if active exists
- Supervisor can force session reset
Revision Failures
Revision failure handling:
- Validation errors prevent transition
- Original rejection reason preserved
- Revision attempt logged
Sync Failures
Sync failure behavior:
- Failed pushes remain in queue
- Exponential backoff retry
- Permanent failure after max attempts
Concurrent Edits
Concurrent edit handling:
- Last-write-wins with conflict detection
- Conflicts recorded for supervision
- Interview state remains consistent
Performance Considerations
Heartbeat Frequency
- 30-second intervals balance load and safety
- Batch updates reduce database writes
- Connection pooling handles concurrent heartbeats
Database Writes
Optimizations:
- Single UPDATE per answer save
- Progress tracked incrementally
- Batch inserts for multiple answers
Polling
Client polling strategy:
- Long polling for interview status
- WebSocket consideration for future
- Backoff on connection loss
Event Processing
Event processing:
- Async handlers for non-critical updates
- Dead letter queue for failures
- Idempotent event handlers
Redis
Redis usage:
- Session state cache
- Heartbeat tracking
- Queue management for sync
