From 16470f6279c0eb09a0873c388960b4f7193fddd1 Mon Sep 17 00:00:00 2001 From: Chaim Date: Mon, 8 Jun 2026 06:30:06 +0000 Subject: [PATCH] =?UTF-8?q?docs(spec):=20X14=20=E2=80=94=20object-storage?= =?UTF-8?q?=20(MinIO/S3)=20migration=20plan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- docs/spec/README.md | 4 +- docs/spec/X14-storage-minio.md | 146 +++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 docs/spec/X14-storage-minio.md diff --git a/docs/spec/README.md b/docs/spec/README.md index 35128ea..149cc20 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -3,10 +3,10 @@ זהו מקור-האמת הקנוני ל"מהו תקין" במערכת. שער-הכניסה: [00-constitution.md](00-constitution.md). כל invariant מגובה ב-≥3 מקורות סמכותיים; פריט לא-מאומת מסומן ⚠ UNVERIFIED ומועלה ליו"ר. -מבנה: 00 חוקה · 01–07 מחזור-חיים · X1–X13 חוצי-שלבים. ראה אינדקס מלא בחוקה. +מבנה: 00 חוקה · 01–07 מחזור-חיים · X1–X14 חוצי-שלבים. ראה אינדקס מלא בחוקה. - X1–X5: מזהים · רב-חברתי · אינטגרציה+deploy · סוכנים · audit. - X6–X10 (מחזור-2, 8 משטחי-האפליקציה): חוזה UI↔API · לקוח-Paperclip · מילוי-שדות · חוזה כלי-MCP · deploy/env/secrets. -- X11–X13 (הרחבות-תחום): citator פנימי (תיקוף-הלכות) · יומונים כשכבת-גילוי (radar) · אחזור-פסיקה אוטומטי מנט המשפט (שירות). +- X11–X14 (הרחבות-תחום): citator פנימי (תיקוף-הלכות) · יומונים כשכבת-גילוי (radar) · אחזור-פסיקה אוטומטי מנט המשפט (שירות) · אחסון-אובייקטים (MinIO/S3, הגירת `data/`). מפות-ממצאים: [gap-audit.md](gap-audit.md) (GAP-01..62 → FU-1..15; מחזור-1 ✅ הושלם, מחזור-2 פתוח) · [ui-audit.md](ui-audit.md) (ביקורת 13 דפי-UI). בסיס-עיצוב: docs/superpowers/specs/2026-05-30-system-spec-design.md diff --git a/docs/spec/X14-storage-minio.md b/docs/spec/X14-storage-minio.md new file mode 100644 index 0000000..33b1a7c --- /dev/null +++ b/docs/spec/X14-storage-minio.md @@ -0,0 +1,146 @@ +# X14 — אחסון-אובייקטים (Object Storage: MinIO / S3) + +קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) והוא ה-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](00-constitution.md) (מסלול-אחסון יחיד). + +--- + +## 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](00-constitution.md)** — מבטל את ה-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](00-constitution.md) · נתונים: [02-data-model.md](02-data-model.md) · קליטה: [01-ingest.md](01-ingest.md) +- deploy/env: [X10-deploy-env-secrets.md](X10-deploy-env-secrets.md) · אינטגרציה: [X3-integration-deploy.md](X3-integration-deploy.md) +- מקורות-MinIO: docs.min.io (community), AWS S3 object-keys/bucket-naming/presigned-URL, github.com/minio/minio-py