diff --git a/CLAUDE.md b/CLAUDE.md index ce2e18e..0b029f9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,6 +50,7 @@ | [`docs/new-company-setup-guide.md`](docs/new-company-setup-guide.md) | מדריך הקמת חברה חדשה (CMPA) — skills, corpus, style analysis | לפני הוספת חברה/סוג ערר חדש | | [`docs/audit-report.md`](docs/audit-report.md) | דוח audit של המערכת | רקע כללי | | [`docs/case-migration-tracker.md`](docs/case-migration-tracker.md) | מעקב מיגרציה של תיקים קיימים | לצורך מעקב | +| [`docs/case-deletion-runbook.md`](docs/case-deletion-runbook.md) | runbook מלא למחיקת תיק — legal-ai DB + disk + Paperclip + Gitea, FK ordering, fallback ל-SQL ישיר | לפני reset שלם של תיק (מבחן, מחיקה בטעות) | | [`docs/decision-block-mapping.md`](docs/decision-block-mapping.md) | מיפוי בלוקים להחלטות — איך 12 הבלוקים משתקפים ב-DOCX | להתמצאות במבנה | | [`docs/memory.md`](docs/memory.md) | הקשר כללי — skills, פרויקטים שהושלמו, מבנה vault | להתמצאות כללית | | [`skills/decision/SKILL.md`](skills/decision/SKILL.md) | מדריך סגנון מלא של דפנה — טון, מבנה, ביטויים, מתודולוגיה | **לפני כל כתיבת החלטה** | diff --git a/docs/case-deletion-runbook.md b/docs/case-deletion-runbook.md new file mode 100644 index 0000000..7a91467 --- /dev/null +++ b/docs/case-deletion-runbook.md @@ -0,0 +1,179 @@ +# מחיקת תיק — 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 אינטראקטיבי. אסור שיעבוד בלי אישור מפורש.