File size: 8,517 Bytes
4cef597
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82f53e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4cef597
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# Staging Deployment Runbook

**Purpose:** Validate the LDV stack on a staging server before promoting to production.  
**Audience:** The engineer running the deploy.  
**Gate:** Every checkbox in this document must pass before flipping `LDV_PRODUCTION=1` on the real server.

---

## 1. Prerequisites

| Requirement | Check |
|-------------|-------|
| Python 3.10+ | `python3 --version` |
| libmagic (`libmagic1`) | `apt install libmagic1` |
| rsync (for backups) | `apt install rsync` |
| NVIDIA driver + CUDA (optional, speeds up L2/L4) | `nvidia-smi` |
| Port 5000 open on the staging host | firewall / security group |
| A second machine or directory for backup rsync target | — |

---

## 2. Clone and Install

```bash
git clone https://github.com/vadhh/cra.git /opt/ldv
cd /opt/ldv/ldv-backend
pip install -r requirements.txt
```

Verify pinned deps installed cleanly — no version conflicts in pip output.

---

## 2a. Alternative: Docker Compose (skips steps 3–5)

`docker-compose.yml` runs the full stack (app + redis + nginx/TLS) in one command. Faster for local/staging smoke-testing than the bare-metal path above.

```bash
git clone https://github.com/vadhh/cra.git /opt/ldv && cd /opt/ldv

# nginx needs a cert — deploy/certs/ is gitignored (holds a private key),
# so a fresh clone has none. Generate a self-signed one first:
bash deploy/gen-cert.sh          # writes deploy/certs/server.{crt,key}

export LDV_SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
export LDV_ENCRYPTION_KEY=$(cd ldv-backend && python3 manage.py gen-key)

docker compose up --build
```

Skipping `gen-cert.sh` is why nginx fails with `cannot load certificate "/etc/nginx/certs/server.crt"` — the mount exists, the file doesn't.

Seed the admin account (once containers are up):
```bash
docker compose exec app python3 manage.py seed-admin
```

Serves on `https://localhost` (self-signed — browser will warn). Continue at section 6 for the health check.

---

## 3. Environment Variables

Create `/opt/ldv/ldv-backend/.env` (never commit this file):

```bash
# --- REQUIRED ---
LDV_SECRET_KEY=<generate: python3 -c "import secrets; print(secrets.token_hex(32))">
LDV_ENCRYPTION_KEY=<generate: python3 manage.py gen-key>
LDV_PRODUCTION=1

# --- SECURITY ---
LDV_COOKIE_SECURE=1          # set to 0 only if staging has no HTTPS
LDV_CORS_ORIGINS=https://staging.example.com

# --- STORAGE ---
LDV_DB_PATH=/opt/ldv/data/sydeco.db
LDV_RETENTION_DAYS=30

# --- BACKUP ---
LDV_BACKUP_DIR=/var/backups/ldv
LDV_BACKUP_REMOTE=user@backup-host:/backups/ldv   # optional

# --- OPTIONAL ---
LDV_MAX_UPLOAD_MB=10
LDV_DOWNLOAD_LINK_TTL=900
LDV_REMOTE_TRANSLATION=0     # 1=Google, local=Helsinki-NLP offline
LDV_DEBUG=0                  # never 1 in staging/prod
```

Load before any command:
```bash
set -a && source /opt/ldv/ldv-backend/.env && set +a
```

Harden the file:
```bash
chmod 600 /opt/ldv/ldv-backend/.env
```

---

## 4. Database Initialisation

```bash
cd /opt/ldv/ldv-backend
mkdir -p /opt/ldv/data

# Init schema + run all migrations
python3 -c "import database; database.init_db()"

# Seed the first admin account
LDV_ADMIN_EMAIL=admin@example.com \
LDV_ADMIN_PASSWORD=<strong-password> \
python3 manage.py seed-admin

# Create a test org and users
python3 manage.py create-org "Test Org"
python3 manage.py create-user analyst@example.com "Test Org" --role analyst
python3 manage.py create-user reviewer@example.com "Test Org" --role reviewer
```

---

## 5. Start the Server

```bash
cd /opt/ldv/ldv-backend
gunicorn -w 2 -b 0.0.0.0:5000 \
  --timeout 120 \
  --access-logfile /var/log/ldv-access.log \
  --error-logfile /var/log/ldv-error.log \
  app:app
```

> Use `-w 1` if GPU memory is tight — each worker loads its own model copy.

---

## 6. Health Check

```bash
curl -s http://localhost:5000/health | python3 -m json.tool
```

Expected — all fields must be non-degraded:

```json
{
  "status": "ok",
  "encryption": "enabled",
  "layer2_available": true,
  "layer4_available": false,
  "db": "ok",
  "datasets": "ok"
}
```

**Block on:** `encryption: degraded` (means `LDV_ENCRYPTION_KEY` not set), `db: error`.

---

## 7. Automated Test Suite

```bash
cd /opt/ldv/ldv-backend

# Generate fixtures (once)
python3 tests/create_fixtures.py

# Quick regression (~2 min)
python3 tests/run_validation.py

# Full checklist (~10 min, skips L4 PENDING sections)
python3 tests/run_full_validation.py
```

Expected: **≥60 PASS · 0 FAIL**. WARN on `legal_mlp.pkl` is acceptable. Any FAIL = stop.

---

## 8. Auth & MFA Smoke Test

```bash
BASE=http://localhost:5000

# 1. Login as analyst
curl -sc cookies.txt -X POST $BASE/login \
  -H "Content-Type: application/json" \
  -d '{"email":"analyst@example.com","password":"<password>"}'
# Expect: {"mfa_enroll_required": true}  OR  session cookie

# 2. Skip MFA as analyst (must succeed — analyst role not mandatory)
curl -sb cookies.txt -X POST $BASE/api/v1/mfa/skip
# Expect: {"ok": true}

# 3. Set org mfa_required and retry (must be blocked)
# sqlite3 /opt/ldv/data/sydeco.db \
#   "UPDATE organizations SET mfa_required=1 WHERE name='Test Org';"
curl -sb cookies.txt -X POST $BASE/api/v1/mfa/skip
# Expect: 403 {"error": "MFA is mandatory for this account"}

# 4. Reviewer role — skip must always be 403 (role is mandatory)
curl -sc cookies2.txt -X POST $BASE/login \
  -H "Content-Type: application/json" \
  -d '{"email":"reviewer@example.com","password":"<password>"}'
curl -sb cookies2.txt -X POST $BASE/api/v1/mfa/skip
# Expect: 403
```

---

## 9. Upload & Analysis Smoke Test

```bash
# Upload a contract (requires auth cookie from step 8)
curl -sb cookies.txt -X POST $BASE/upload \
  -F "file=@/path/to/test-contract.pdf"
# Expect: {"id": "<uuid>", "status": "queued"}

UUID=<uuid from above>

# Poll until completed
curl -sb cookies.txt $BASE/api/v1/result/$UUID
# Expect: {"status": "completed", "progress_pct": 100, "_meta": {"encryption_enabled": true}, ...}
```

---

## 10. Cross-Org Access Control

```bash
# Create a second org + user
python3 manage.py create-org "Other Org"
python3 manage.py create-user other@example.com "Other Org" --role analyst

# Login as other@example.com, try to fetch the UUID from step 9
# Expect: 403 Forbidden
```

---

## 11. Backup Smoke Test

```bash
mkdir -p /var/backups/ldv

# Dry run first
python3 manage.py backup --dry-run

# Real run
python3 manage.py backup

# Confirm timestamped dir exists
ls /var/backups/ldv/
# Expect: 20260701T020000Z/sydeco.db  20260701T020000Z/uploads/
```

Install the nightly cron:
```bash
sudo cp /opt/ldv/deploy/ldv-backup.cron /etc/cron.d/ldv-backup
sudo chmod 644 /etc/cron.d/ldv-backup
# Edit the file: update /opt/ldv path and www-data user if different
```

---

## 12. Manual QA — Lawyer Spot-Check

Upload 5–10 real contracts (mix of FR/ID/NL, invoice + service agreement) and verify:

- [ ] `governing_law` detected correctly
- [ ] `jurisdiction` detected correctly
- [ ] `document_type` matches the actual contract type
- [ ] `red_flags` fire on genuinely risky clauses, not on clean contracts
- [ ] Citations shown are `verified` only (no `draft` in output)
- [ ] Risk score feels calibrated
- [ ] Non-contract documents (invoices) score low / get non-contract routing

This is the hardest gate. Code cannot substitute for a lawyer reading the output.

---

## 13. Production Promotion Checklist

Only proceed when all sections above pass:

- [ ] Section 6: `encryption: enabled`, `db: ok`
- [ ] Section 7: 0 FAIL
- [ ] Section 8: MFA skip returns 403 when mandatory
- [ ] Section 9: `encryption_enabled: true` in result, `progress_pct: 100`
- [ ] Section 10: 403 on cross-org UUID
- [ ] Section 11: backup dir created, nightly cron installed
- [ ] Section 12: lawyer spot-check signed off
- [ ] `LDV_COOKIE_SECURE=1` confirmed (HTTPS in place)
- [ ] `LDV_DEBUG=0` confirmed
- [ ] `.env` permissions: `chmod 600`
- [ ] Gunicorn running as non-root user (`www-data` or dedicated `ldv` user)
- [ ] Log rotation configured (`/etc/logrotate.d/ldv`)

---

## Known Acceptable Gaps at Staging

These are tracked but do not block the staging gate:

| Gap | Status |
|-----|--------|
| `legal_mlp.pkl` missing — `clause_tags` returns `[]` | Acceptable, sydeco_engine decoupled |
| L4 (Qwen) not loaded — `layer4_available: false` | Acceptable unless `?explain=1` is tested |
| FR/BE citation rows `draft` | Output suppresses drafts — acceptable |
| Bootstrap MLP risk scorer | Deterministic fallback active — acceptable |