docs: case-deletion runbook (legal-ai + Paperclip + Gitea)
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 7s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 7s
Captures the full deletion procedure we worked out empirically while
wiping case 8174-24 for a clean rerun. Covers all four systems where
case state lives, in dependency order:
1. legal-ai DB + on-disk dir — DELETE /api/cases?remove_files=true
(now actually works after 903fb4d added the missing db.delete_case)
2. Paperclip DB — no API; raw SQL with explicit FK-blocker ordering
(issue_comments, cost_events, finance_events, feedback_votes,
issue_inbox_archives, issue_read_states must go before issues;
heartbeat_runs.wakeup_request_id must be NULLed before
agent_wakeup_requests can be deleted)
3. Gitea — DELETE /api/v1/repos/cases/{N}
4. Verification queries for each system
Two gotchas worth highlighting in the doc:
• The case directory inside /data/cases is owned by root because the
container runs as root — host-side rm needs sudo, or use the API
(rmtree happens inside the container).
• Paperclip projects are referenced via name LIKE '%{N}%' since
there's no slug column. Stricter matching is recommended if N
appears in multiple project names.
Linked from legal-ai/CLAUDE.md docs index. A future scripts/delete-case.sh
that automates the runbook with a confirmation prompt is noted as TODO
inside the runbook itself.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,7 @@
|
|||||||
| [`docs/new-company-setup-guide.md`](docs/new-company-setup-guide.md) | מדריך הקמת חברה חדשה (CMPA) — skills, corpus, style analysis | לפני הוספת חברה/סוג ערר חדש |
|
| [`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/audit-report.md`](docs/audit-report.md) | דוח audit של המערכת | רקע כללי |
|
||||||
| [`docs/case-migration-tracker.md`](docs/case-migration-tracker.md) | מעקב מיגרציה של תיקים קיימים | לצורך מעקב |
|
| [`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/decision-block-mapping.md`](docs/decision-block-mapping.md) | מיפוי בלוקים להחלטות — איך 12 הבלוקים משתקפים ב-DOCX | להתמצאות במבנה |
|
||||||
| [`docs/memory.md`](docs/memory.md) | הקשר כללי — skills, פרויקטים שהושלמו, מבנה vault | להתמצאות כללית |
|
| [`docs/memory.md`](docs/memory.md) | הקשר כללי — skills, פרויקטים שהושלמו, מבנה vault | להתמצאות כללית |
|
||||||
| [`skills/decision/SKILL.md`](skills/decision/SKILL.md) | מדריך סגנון מלא של דפנה — טון, מבנה, ביטויים, מתודולוגיה | **לפני כל כתיבת החלטה** |
|
| [`skills/decision/SKILL.md`](skills/decision/SKILL.md) | מדריך סגנון מלא של דפנה — טון, מבנה, ביטויים, מתודולוגיה | **לפני כל כתיבת החלטה** |
|
||||||
|
|||||||
179
docs/case-deletion-runbook.md
Normal file
179
docs/case-deletion-runbook.md
Normal file
@@ -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 <<SQL
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- 1. מצא את כל ה-issues של הפרויקט (לפי שם)
|
||||||
|
CREATE TEMP TABLE _issue_ids AS
|
||||||
|
SELECT i.id, i.identifier
|
||||||
|
FROM issues i
|
||||||
|
JOIN projects p ON i.project_id = p.id
|
||||||
|
WHERE p.name LIKE '%${CASE_NUMBER}%';
|
||||||
|
|
||||||
|
SELECT identifier FROM _issue_ids ORDER BY identifier; -- וידוא לפני המחיקה
|
||||||
|
|
||||||
|
-- 2. מחק blockers ל-FK עם NO ACTION (אסור למחוק issue אם יש להם reference)
|
||||||
|
DELETE FROM issue_comments WHERE issue_id IN (SELECT id FROM _issue_ids);
|
||||||
|
DELETE FROM cost_events WHERE issue_id IN (SELECT id FROM _issue_ids);
|
||||||
|
DELETE FROM finance_events WHERE issue_id IN (SELECT id FROM _issue_ids);
|
||||||
|
DELETE FROM feedback_votes WHERE issue_id IN (SELECT id FROM _issue_ids);
|
||||||
|
DELETE FROM issue_inbox_archives WHERE issue_id IN (SELECT id FROM _issue_ids);
|
||||||
|
DELETE FROM issue_read_states WHERE issue_id IN (SELECT id FROM _issue_ids);
|
||||||
|
|
||||||
|
-- 3. מחק את ה-issues. CASCADE מטפל ב-7 טבלאות נוספות:
|
||||||
|
-- issue_approvals, issue_attachments, issue_documents,
|
||||||
|
-- issue_execution_decisions, issue_labels, issue_relations,
|
||||||
|
-- issue_work_products
|
||||||
|
DELETE FROM issues WHERE id IN (SELECT id FROM _issue_ids);
|
||||||
|
|
||||||
|
-- 4. שבור FK מ-heartbeat_runs כדי שאפשר יהיה למחוק wakeup_requests.
|
||||||
|
-- heartbeat_runs נשמרים כ-audit log לא משויך.
|
||||||
|
UPDATE heartbeat_runs
|
||||||
|
SET wakeup_request_id = NULL
|
||||||
|
WHERE wakeup_request_id IN (
|
||||||
|
SELECT id FROM agent_wakeup_requests
|
||||||
|
WHERE payload->>'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 אינטראקטיבי. אסור שיעבוד בלי אישור מפורש.
|
||||||
Reference in New Issue
Block a user