RapidPM integrates with OnlyOffice Document Server for real-time collaborative document editing. This page describes the architecture, data flow, and safety mechanisms that ensure documents are never corrupted.
Key Principles:
┌─────────────────────────────────────────────────────────────────────────┐
│ CLIENTS │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Browser 1│ │ Browser 2│ │ Browser 3│ │ Browser N│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼─────────────┼─────────────┼─────────────┼───────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ ONLYOFFICE DOCUMENT SERVER │
│ (onlyoffice.rapidpm.uk) │
│ - In-memory document state │
│ - Real-time collaboration │
│ - Conflict resolution │
│ - Auto-save triggers │
└─────────────────────────────────────────────────────────────────────────┘
│ Callback (Status 2/6) ▲ Fetch Document
▼ │
┌─────────────────────────────────────────────────────────────────────────┐
│ RAPIDPM APPLICATION │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ Callback │ │ Stream │ │ Document │ │
│ │ Handler │ │ Endpoint │ │ Controllers │ │
│ │ /callback │ │ /stream │ │ /view /edit │ │
│ └───────┬────────┘ └───────┬────────┘ └───────┬────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ SFTP CONNECTION POOL │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ SFTP STORAGE SERVER │
│ (SOURCE OF TRUTH) │
│ /{company_id}/{project_id}/{artefact_id}/{filename}.{ext} │
│ /{company_id}/{project_id}/{artefact_id}/{filename}.{ts}.bak │
└─────────────────────────────────────────────────────────────────────────┘
| Operation | Single User | Multi-User | Shared Link |
|---|---|---|---|
| VIEW | Read-only | Read-only (parallel) | Token-based |
| EDIT | Full edit | Real-time collab | Token-based |
| COMMENT | Add comments only | Multi-user comments | Token-based |
| DOWNLOAD | From SFTP | From SFTP | From SFTP |
| DELETE | With locks | Blocked if editing | Not allowed |
OnlyOffice caches documents by key. If the same key is reused after external modifications, old cached state merges with new document causing corruption.
Solution: Session Versioning
Document Key Format: {environment}_{artefact_id}_{mode}_{version}
Example: prod_12345_edit_42
| Component | Description | When Changed |
|---|---|---|
environment |
Multi-environment isolation | Never (config) |
artefact_id |
Database primary key | Never |
mode |
view or edit |
Per request |
version |
Monotonic integer (Redis INCR) | On session end |
Version uses Redis INCR, not timestamps - prevents key collisions when two saves occur in the same second.
When OnlyOffice sends a callback (Status 2 = session end, Status 6 = force save):
Step 0: CHECK IDEMPOTENCY STATE
├── If DONE → Return {error: 0} (already saved)
├── If PENDING → Return {error: 0} (in progress)
└── If empty → Continue
Step 1: ACQUIRE TRANSITION LOCK
├── SET lock:transition:{id} with NX, EX=120s
├── If acquired → Set PENDING, continue
└── If locked → Handle contention
Step 2: DOWNLOAD FROM ONLYOFFICE
├── GET from callback URL
├── Timeout: 120 seconds
└── Retry: 3 attempts
Step 3-6: VALIDATION PIPELINE
├── File size check (min 1KB for Office files)
├── Magic bytes (ZIP signature for docx/xlsx/pptx)
├── ZIP integrity (testzip + required entries)
├── XML well-formedness
└── Zombie detection (empty documents)
Step 7-8: ATOMIC SFTP WRITE
├── Write to {filename}.tmp.{uuid}
├── Rename current to {filename}.{timestamp}.bak
└── Rename temp to current (atomic)
Step 9: UPDATE VERSION + MARKERS
├── Set save_done:{id} = timestamp
├── Status 2: Bump version (Redis INCR)
└── Status 6: Keep version stable
Step 10-12: CLEANUP
├── Audit trail
├── Cache invalidation
└── Release lock
OnlyOffice retries callbacks on network issues. We use a two-phase state machine:
| Current State | Action | Result |
|---|---|---|
| (none) | Set PENDING, process | Normal flow |
| PENDING | Return {error: 0} |
Wait for other process |
| DONE | Return {error: 0} |
Skip (already saved) |
| PENDING + timeout | Retry sets new PENDING | Recovers from crash |
Locks prevent concurrent saves to the same document:
Every save is validated before writing:
| Check | What It Catches |
|---|---|
| File size | Empty/truncated files |
| Magic bytes | Wrong file type |
| ZIP integrity | Corrupted archives |
| XML well-formedness | Malformed Office XML |
| Zombie detection | Valid XML but no content |
Write-temp-then-rename pattern ensures no partial/corrupt files:
{filename}.tmp.{uuid}{filename}.{timestamp}.bakIf step 3 fails, we still have temp + backup for recovery.
User sessions are tracked in Redis (survives app restarts):
session:{artefact_id}:users → SET of user IDs
session:{artefact_id}:last_activity → ISO timestamp
Stale Session Cleanup runs every 15 minutes:
When downloading a document with active editors:
save_done:{id} marker newer than requestDocuments being edited cannot be deleted. Deletion checks:
session:{id}:users set| File | Purpose |
|---|---|
app/services/onlyoffice_safety.py |
Idempotency, locks, validation, atomic write |
app/services/onlyoffice_service.py |
JWT generation, editor HTML, configuration |
app/services/onlyoffice_metrics.py |
Prometheus metrics |
app/services/onlyoffice_trace.py |
Trace logging |
app/controllers/onlyoffice_controller.py |
Callback handler, health check |
app/controllers/download.py |
Stream and download endpoints |
| Status | Name | Action |
|---|---|---|
| 1 | EDITING | Log only, track users |
| 2 | READY_TO_SAVE | Save + bump version |
| 3 | SAVE_ERROR | Log error |
| 4 | CLOSED_NO_CHANGES | Bump version (with safety checks) |
| 6 | FORCE_SAVE | Save, keep version |
| 7 | CORRUPTED | Alert + don't save |
| Code | Name | Description |
|---|---|---|
DOC_001 |
EDIT_LOCK_ACTIVE | Cannot perform operation, document being edited |
DOC_002 |
SAVE_VALIDATION_FAILED | Content failed validation checks |
DOC_003 |
SFTP_WRITE_FAILED | Could not write to SFTP storage |
DOC_004 |
CALLBACK_INVALID | Invalid or expired callback token |
DOC_005 |
KEY_PARSE_FAILED | Could not parse document key |
DOC_006 |
STREAM_UNAUTHORIZED | Invalid stream token |
DOC_007 |
PATH_TRAVERSAL | Attempted directory traversal |
DOC_008 |
SESSION_CONFLICT | Document key version mismatch |