Adds docs/spec/X14-storage-minio.md — the domain spec + phased plan for migrating binary document storage from the local data/ tree to the already-deployed MinIO service (Coolify svc `minio`). Captures: disk inventory, scattered file-I/O map (~8 services, no central layer), DB path columns, MinIO deploy state, Paperclip = API-consumer only. Defines 7 domain invariants (INV-STG1..7) and a 7-phase execution plan. Chair decisions (2026-06-08): git-per-case keeps text/metadata + MinIO holds binaries (INV-STG7); WORM Object-Lock on FINAL decisions only (INV-STG4); internal Docker network for legal-ai↔MinIO. Invariants: keeps G2 (single storage path replaces scattered I/O); INV-STG1..7 new. Spec-only PR — no code/behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 KiB
X14 — אחסון-אובייקטים (Object Storage: MinIO / S3)
קובץ-תחום זה כפוף ל-חוקת המערכת והוא ה-deep-dive על אחסון קבצים בינאריים —
מסמכי-מקור, נגזרים, וייצוא — והגירתם ממערכת-קבצים מקומית (data/) ל-MinIO (object store תואם-S3).
הוא מגדיר את חוזה-האחסון (שכבה יחידה), סכמת-הדליות-והמפתחות, מודל-האי-שינויוּת המשפטי, ותוכנית-ההגירה.
invariant הנדסי + תפעולי-משפטי. INV-STG1/2/5/6 נשענים על עקרונות מוכרים (S3 API, 12-Factor, presigned-URL, separation blob↔metadata) — ≥3 מקורות (docs.min.io, AWS S3 spec, minio-py). INV-STG3/4/7 הם תפעוליים/משפטיים של מערכת זו (גבול-ממשל, WORM להחלטות חתומות, git=טקסט) ונקשרים ל-G2 (מסלול-אחסון יחיד).
1. מצב קיים (מאומת מול הקוד וה-infra, 2026-06-08)
1.1 מלאי-הדיסק (data/, ללא backups/)
| קטגוריה | נפח | תוכן | סוג |
|---|---|---|---|
data/cases/{case}/ |
1.2GB | documents/{originals,extracted,proofread,research,backup}, drafts/, exports/, thumbnails/{doc_uuid}/pNNN.jpg, .git per-case |
מקור + נגזר |
data/digests/{reference,incoming}/ |
251MB | יומונים (X12) | מקור |
data/training/{cmp,cmpa}/{raw,proofread}/ |
157MB | קורפוס-קול + .git |
מקור |
data/precedent-library/{appeals_committee,court_ruling,other}/ |
105MB | פסיקה + thumbnails/ |
מקור |
data/internal-decisions/{region}/ |
45MB | החלטות-פנים לפי מחוז | מקור |
data/exports/ |
216KB | legacy (הוחלף ב-per-case) | נגזר |
data/{audit,eval,logs}/ |
~52MB | CSV/JSON תפעוליים — לא מסמכים, נשארים בדיסק | תפעולי |
ספירה (ללא backups): ~9,449 קבצים — 2,473 JPG (thumbnails נגזרים), 883 PDF, 250 TXT (extracted), 155 DOCX, 54 DOC.
1.2 הקונטיינר (Coolify)
legal-ai (gyjo0mtw2c42ej3xxvbz8zio) רץ עם bind-mounts: host data/→/data, host data/cases/→/cases.
האחסון היום = תיקייה על המארח, חשופה ישירות.
1.3 MinIO — כבר פרוס ובריא ✅ (שירות Coolify minio, bx2ykvw94xbutsex41hz4vv8, 2026-06-08)
- API:
https://s3.nautilus.marcusgroup.org(9000) · Console:https://minio.nautilus.marcusgroup.org(9001) - Credentials:
SERVICE_USER_MINIO/SERVICE_PASSWORD_MINIO(סודות מנוהלי-Coolify) - אחסון: named-volume
minio-data→/data— Single-Node Single-Drive; versioning/object-lock לא מופעלים עדיין - רשת: רשת-Docker משלו (
bx2ykvw..., external), לא משותפת ל-legal-ai → דרושה קישוריות (§4 שלב 0)
1.4 הקוד — אין שכבת-אחסון מרכזית (כשל-השורש שהתחום מייבש)
ה-I/O מפוזר על ~8 שירותים, נתיבים נבנים inline:
- העלאה:
tools/documents.py:54(originals),:152(training) - חילוץ + thumbnails:
services/processor.py:43,153 - staging פסיקה/יומונים/החלטות:
services/ingest.py:69 - ייצוא DOCX:
services/docx_exporter.py:462 - הגשה (FileResponse):
web/app.py— 6 endpoints - git per-case:
services/git_sync.py(git add .+ push ל-Gitea, sweep כל 30ש׳)
1.5 עמודות-DB המאחסנות נתיבים (schema inline ב-db.py, ללא migrations)
documents.file_path · cases.active_draft_path · case_law.source_document_path · digests.source_document_path
· document_image_pages.image_thumbnail_path · precedent_image_pages.image_thumbnail_path · draft_final_pairs.final_path
1.6 Paperclip — צרכן-API בלבד
הפלאגין ניגש דרך listDocuments/getDocumentText ל-API (plugin-legal-ai/src/legal-api.ts:89). אינו נוגע בדיסק →
הגירה שקופה אליו כל עוד ה-API יציב.
2. Invariants של התחום
INV-STG1: שכבת-אחסון יחידה — כל I/O דרך storage.py
כלל: קיים מודול-אחסון יחיד (services/storage.py) שכל קריאה/כתיבה של קובץ בינארי עוברת דרכו
(put/get/presign_get/presign_put/delete/list). אסור open()/shutil.copy()/Path.write_bytes() ישיר על
נתיב-אחסון מחוץ למודול. מקיים G2 — מבטל את ה-I/O המפוזר (§1.4) שהוא מסלול-מקביל-מתפצל.
INV-STG2: מפתח-אובייקט אטומי; שם עברי במטא בלבד
כלל: מפתח-האובייקט הוא ASCII/UUID (cases/{case}/originals/{uuid}.pdf). שם-הקובץ העברי המקורי נשמר ב-DB
(*_filename) וכ-x-amz-meta-filename + מוגש דרך Content-Disposition ב-presigned-GET. למה: תקציב-מפתח
1024 bytes (255/segment), עברית=2B/תו, ובעיות percent-encoding/XML — נמנעות.
INV-STG3: דליות לפי גבול-ממשל, prefix לפי קטגוריה/תיק
כלל: versioning/object-lock/replication הם per-bucket → מה שדורש ממשל שונה יושב בדלי נפרד. שלוש דליות קבועות (§3.1); תיקים/קטגוריות הם prefixes, לא דלי-לכל-תיק.
INV-STG4: "סופי" = WORM (Object-Lock COMPLIANCE)
כלל: החלטה חתומה/סופית נכתבת לדלי legal-immutable עם Object-Lock COMPLIANCE + versioning — בלתי-ניתנת
לשינוי/מחיקה ע"י איש (כולל root) עד תום-תקופת-השמירה. טיוטות חיות בדלי רגיל ו"מקודמות" (copy) לדלי-הסגור עם החתימה.
(הכרעת-יו"ר 2026-06-08: סופי בלבד; מסמכי-מקור — versioning ללא נעילה קשיחה.)
INV-STG5: pgvector נשאר מקור-האמת לטקסט/embeddings; MinIO = blob בלבד
כלל: טקסט-מחולץ + embeddings נשארים ב-Postgres/pgvector (מקור-אמת לאחזור). MinIO מאחסן את ה-blob המקורי
(+עותק-ארכיון אופציונלי של ה-extracted text). אסור ש-MinIO יהיה מקור-אמת לוקטורים. תואם
no-reocr-retrofit — לא מריצים OCR מחדש בהגירה.
INV-STG6: הגשה לדפדפן דרך presigned-URL — bytes לא דרך FastAPI
כלל: הורדה/תצוגה/העלאה מהדפדפן עוברות ב-presigned-URL (TTL דקות) מול s3.nautilus.marcusgroup.org.
ה-backend מנפיק את ה-URL בלבד; ה-bytes לא עוברים דרכו. endpoints קיימים שמחזירים FileResponse → 302→presigned.
INV-STG7: git-per-case שומר טקסט/מטא בלבד; בינאריים ב-MinIO
כלל: .git per-case ממשיך לגרסן case.json/notes.md/documents/extracted/*.txt/research/*.md. PDF/DOCX/JPG
מוחרגים מ-tracking (.gitignore per-case) ויושבים ב-MinIO. (הכרעת-יו"ר 2026-06-08.) git_sync.py ו-sweep
מסתמכים על אותו working-tree → ההחרגה חייבת לקדום לכל קומיט-הגירה כדי לא לשבור היסטוריה.
3. ארכיטקטורת-היעד
3.1 דליות ומפתחות
| דלי | Versioning | Object-Lock | prefixes |
|---|---|---|---|
legal-documents |
✅ | ❌ | cases/{case}/originals/{uuid}.pdf · cases/{case}/proofread/{uuid}.txt · precedent-library/{type}/{uuid}.pdf · internal-decisions/{region}/{uuid}.pdf · digests/{uuid}.pdf · training/{cmp|cmpa}/{raw|proofread}/{uuid}.pdf |
legal-immutable |
✅ | ✅ COMPLIANCE | decisions-final/{case}/{uuid}.docx (החלטות חתומות בלבד) |
legal-derived |
❌ | ❌ (+lifecycle) | thumbnails/{doc_uuid}/pNNN.jpg · extracted/{uuid}.txt (נגזר, ניתן-לשחזור) |
3.2 services/storage.py (לב ההגירה) — adapter כפול
put(category, key, data, content_type, meta) -> uri # category→bucket+prefix
get(uri) -> bytes
presign_get(key, ttl) / presign_put(key, ttl) -> url
delete(key) / list(prefix)
backend נבחר ב-env STORAGE_BACKEND ∈ {filesystem, dual, s3} (ברירת-מחדל filesystem) — מאפשר מעבר הדרגתי ללא
שינוי-התנהגות. SDK: aioboto3 (async-native מול endpoint_url=http://minio:9000); minio-py לסקריפטי-הגירה.
3.3 שינויי-DB
הוספת *_object_key (או נרמול ל-storage_uri עם סכמה s3:///file://) לצד העמודות הקיימות (§1.5); backfill;
דה-קומיישן הנתיב-קובץ. תוספת inline ב-db.py בסגנון הקיים (אין migrations).
4. תוכנית-ביצוע בשלבים (→ TaskMaster, tag legal-ai)
| שלב | תוכן | תלות |
|---|---|---|
| 0 — תשתית | חיבור רשת-Docker (minio↔legal-ai); הזרקת credentials ל-env legal-ai (Coolify); mc alias; יצירת 3 דליות + הפעלת versioning + Object-Lock (immutable); הוספת aioboto3 ל-deps |
— |
| 1 — שכבת-אחסון | services/storage.py + adapter כפול (default filesystem). אפס שינוי-התנהגות. PR מצהיר INV-STG1/2/3 |
0 |
| 2 — חיווט-כתיבה | הפניית כל נקודות-הכתיבה (§1.4) דרך storage.py; כתיבה-כפולה (STORAGE_BACKEND=dual) |
1 |
| 3 — הגירת-נתונים | mc mirror --dry-run→--overwrite של 5 הקטגוריות; backfill *_object_key ב-DB; אימות count+checksum |
0,2 |
| 4 — חיווט-קריאה + presigned | endpoints→302→presigned; thumbnails דרך presigned; dual-read (S3, fallback disk); החרגת בינאריים מ-git per-case (INV-STG7) | 2,3 |
| 5 — cutover | STORAGE_BACKEND=s3; mc mirror --watch עד החלפה; אימות מלא; כיבוי כתיבה-לדיסק |
4 |
| 6 — git + גיבוי + ניקוי | קידום-החלטות-סופיות ל-immutable (INV-STG4); mc mirror/bucket-replication מתוזמן off-site; דה-קומיישן bind-mount data/ (השארת audit/eval/logs) |
5 |
5. סיכונים
- I/O מפוזר → INV-STG1 (
storage.py) חובה לפני כל שאר השלבים, אחרת drift והפרת-G2. - שמות עבריים כמפתחות → INV-STG2 (UUID-keys + מטא).
- רשת נפרדת ל-MinIO → לאמת קישוריות בשלב 0 לפני הכל.
- git-per-case מצמיד בינאריים ל-Gitea → INV-STG7, ההחרגה חייבת לקדום לכל קומיט.
- SNSD ללא erasure-coding → גיבוי off-site (שלב 6) הוא חובה, לא nice-to-have.
- בידוד-worktree + ספ-first → כל PR מצהיר invariants (G2 + INV-STG*).
6. קישורים
- חוקה: 00-constitution.md · נתונים: 02-data-model.md · קליטה: 01-ingest.md
- deploy/env: X10-deploy-env-secrets.md · אינטגרציה: X3-integration-deploy.md
- מקורות-MinIO: docs.min.io (community), AWS S3 object-keys/bucket-naming/presigned-URL, github.com/minio/minio-py