# מחיקת תיק — runbook > **מתי להשתמש:** reset שלם של תיק (לבדיקות end-to-end), מחיקת תיק שנפתח בטעות, או ניקיון לפני העלאה חוזרת של מסמכים. > > **חשוב:** ה-API `DELETE /api/cases` בלבד **לא מספיק** — הוא מטפל רק בצד legal-ai (DB + on-disk dir). תיק חי במקביל ב-4 מערכות והכול חייב להתנקות יחד. --- ## איפה ה-state של תיק חי | מערכת | מה נשמר | איך מנקים | |---|---|---| | **legal-ai DB** (port 5433) | `cases` + `documents` + `document_chunks` + `claims` + `appraiser_facts` + `decisions` + `qa_results` + `case_precedents` | API DELETE (cascade על FK) | | **legal-ai disk** | `/data/cases/{N}/` בתוך ה-container — מכיל drafts/, documents/, .git/ | API עם `remove_files=true` (`shutil.rmtree` בתוך ה-container) | | **Paperclip DB** (port 54329) | `projects` + `issues` + `issue_comments` + `agent_wakeup_requests` + `heartbeat_runs` (audit) + עוד 6+ טבלאות | SQL ידני (אין API) | | **Gitea** | repo `cases/{N}` אם נוצר ב-case-create | Gitea API | ה-API לא מטפל ב-Paperclip ו-Gitea כי אלה מערכות חיצוניות שלגמרי מחוץ ל-DB של legal-ai. תועד מפורשות ב-docstring של [`services/db.py:delete_case`](../mcp-server/src/legal_mcp/services/db.py). --- ## תהליך מחיקה מלא — שלב אחרי שלב הצב את מספר התיק במשתנה לפני שמתחילים: ```bash CASE_NUMBER=8174-24 ``` ### שלב 1 — legal-ai (DB + disk) ```bash curl -s -X DELETE \ "https://legal-ai.nautilus.marcusgroup.org/api/cases?case_number=${CASE_NUMBER}&remove_files=true" \ -w "\nhttp=%{http_code}\n" ``` תוצאה צפויה: `200` עם `{"deleted": true, "removed_files": true, ...}`. מה זה עושה מאחורי הקלעים: 1. `DELETE FROM cases` — מפעיל **CASCADE** ל-7 טבלאות, **SET NULL** ל-`audit_log` ו-`chair_feedback`. 2. `shutil.rmtree(/data/cases/{N})` — מסיר את כל הספרייה כולל `.git`. > **הערה:** עד לפני [commit `903fb4d`](https://gitea.nautilus.marcusgroup.org/ezer-mishpati/legal-ai/commit/903fb4d) ה-endpoint הזה החזיר 500 כי `db.delete_case` לא היה מוגדר. אם נתקלת ב-500 בגרסה ישנה, השתמש ב-SQL הישיר (ראה Fallback בסוף). ### שלב 2 — Paperclip אין API. SQL ישיר: ```bash PGPASSWORD=paperclip psql -h localhost -p 54329 -U paperclip -d paperclip <>'issueId' IN (SELECT id::text FROM _issue_ids) ); DELETE FROM agent_wakeup_requests WHERE payload->>'issueId' IN (SELECT id::text FROM _issue_ids); -- 5. מחק blockers ברמת ה-project (NO ACTION FK ל-projects) DELETE FROM cost_events WHERE project_id IN (SELECT id FROM projects WHERE name LIKE '%${CASE_NUMBER}%'); DELETE FROM finance_events WHERE project_id IN (SELECT id FROM projects WHERE name LIKE '%${CASE_NUMBER}%'); -- 6. מחק את הפרויקט. CASCADE מטפל ב: -- execution_workspaces, project_goals, project_workspaces, routines DELETE FROM projects WHERE name LIKE '%${CASE_NUMBER}%' RETURNING id, name; COMMIT; SQL ``` > **למה Paperclip לא הוסיף API למחיקה?** כי זאת מערכת רב-משתמשית ומחיקה היא הרסנית מטבעה — Paperclip מעדיף `archive` (`projects.archived_at`). אנחנו אכן רוצים מחיקה אמיתית רק לסביבת בדיקות. ### שלב 3 — Gitea (אם repo נוצר) ```bash GITEA_TOKEN=$(infisical secrets get GITEA__API_TOKEN --silent || \ echo "$GITEA_TOKEN") # סגדור מ-Infisical או ENV curl -s -X DELETE \ -H "Authorization: token ${GITEA_TOKEN}" \ "https://gitea.nautilus.marcusgroup.org/api/v1/repos/cases/${CASE_NUMBER}" \ -w "http=%{http_code}\n" ``` תוצאה צפויה: `204` (deleted) או `404` (לא נוצר מעולם). ### שלב 4 — וידוא ניקיון ```bash echo "=== legal-ai ===" PGPASSWORD=$LEGAL_AI_PG psql -h localhost -p 5433 -U legal_ai -d legal_ai -t -c " SELECT count(*) FROM cases WHERE case_number = '${CASE_NUMBER}'; " # → 0 ls /home/chaim/legal-ai/data/cases/${CASE_NUMBER} 2>&1 | head -1 # → "No such file or directory" echo "=== Paperclip ===" PGPASSWORD=paperclip psql -h localhost -p 54329 -U paperclip -d paperclip -t -c " SELECT 'projects:'||count(*) FROM projects WHERE name LIKE '%${CASE_NUMBER}%' UNION ALL SELECT 'issues:'||count(*) FROM issues WHERE title LIKE '%${CASE_NUMBER}%' UNION ALL SELECT 'comments:'||count(*) FROM issue_comments WHERE body LIKE '%${CASE_NUMBER}%' UNION ALL SELECT 'wakeups:'||count(*) FROM agent_wakeup_requests WHERE payload::text LIKE '%${CASE_NUMBER}%'; " # → all 0 echo "=== Gitea ===" curl -s -H "Authorization: token ${GITEA_TOKEN}" \ "https://gitea.nautilus.marcusgroup.org/api/v1/repos/cases/${CASE_NUMBER}" \ | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('full_name','NOT FOUND'))" # → NOT FOUND ``` --- ## Fallback — אם ה-API נשבר אם משום מה ה-API DELETE לא עובד (ראינו את זה בעבר עם `delete_case` החסר), עשה DELETE ישיר ב-DB. ה-FK constraints יבצעו את העבודה: ```sql PGPASSWORD=$LEGAL_AI_PG psql -h localhost -p 5433 -U legal_ai -d legal_ai -c " DELETE FROM cases WHERE case_number = '${CASE_NUMBER}' RETURNING case_number, title; " ``` לאחר מכן הסר את הספרייה מהדיסק. הספרייה בבעלות `root` כי ה-container רץ כ-root, אז תצטרך `sudo`: ```bash sudo rm -rf /home/chaim/legal-ai/data/cases/${CASE_NUMBER} ``` --- ## הערות שנלמדו תוך כדי 1. **`heartbeat_runs.wakeup_request_id`** הוא ה-trap היחיד. הוא NO ACTION FK, ולכן חוסם מחיקה של `agent_wakeup_requests`. הפתרון: `UPDATE ... SET wakeup_request_id = NULL` לפני המחיקה. ה-runs עצמם נשמרים כ-audit log (לא הפסד). 2. **פרויקט "name" ב-Paperclip** — לפי הקונבנציה הוא מתחיל ב-"ערר {N}" — לכן `LIKE '%{N}%'` מספיק. אם יש מספר תיקים שמכילים את אותו מספר, להחמיר עם match מלא או לפי `id`. 3. **Container ↔ host file ownership** — קבצים שיוצר ה-container (כולל ספריית התיק) שייכים ל-`root`. מחיקה מהמארח דורשת `sudo`, או דרך docker exec, או דרך ה-API (שמבצעת `rmtree` בתוך ה-container). 4. **`audit_log` ו-`chair_feedback` נשארים** — FK שלהם הוא SET NULL כדי לשמור היסטוריה גם אחרי שהתיק נמחק. אם אתה צריך מחיקה היסטרית מוחלטת, מחק שורות אלה ידנית. --- ## TODO — אוטומציה ה-runbook הזה ניתן להמרה לסקריפט `scripts/delete-case.sh` שמקבל `CASE_NUMBER` ומבצע את 4 השלבים עם prompt confirmation. עדיין לא הוטמע — נכון להיום העבודה ידנית. מי שמטמיע: שמור את הסקריפט כ-`destructive` ב-SCRIPTS.md ודרוש `--confirm` או prompt אינטראקטיבי. אסור שיעבוד בלי אישור מפורש.