fix(ingest): read staged file via storage.ensure_local — s3-only upload 500 (INV-STG1)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
תיקון: העלאת פסיקה/החלטת-ועדה (precedent-library + internal-decisions) נכשלה תחת backend s3-only עם "Package not found at '/data/...docx'" / "Converted file not found". השורש: `ingest._stage_file` כותב את הקובץ דרך `storage.put_file` ומחזיר נתיב-DATA_DIR, אבל תחת s3-only ה-blob נכתב רק ל-MinIO ואין עותק בדיסק — ואז הצינור קרא את הנתיב ישירות מהדיסק (extract_text) → קובץ לא קיים. מסלול תיקי-המקרה לא נפגע כי הוא שומר עותק-דיסק + mirror_file; רק מסלול _stage_file המשותף קרא את ה-key כאילו הוא על הדיסק. התיקון (נרמול-במקור, G1; קריאה דרך שכבת-האחסון, INV-STG1): - `_stage_file` מחזיר עכשיו את ה-KEY (נתיב יחסי-DATA_DIR), לא Path. - `ingest_document` ו-`digest_library` מאתרים נתיב-קריאה מקומי דרך `storage.ensure_local` (עותק-דיסק תחת filesystem/dual; הורדה ל-temp תחת s3-only) ומנקים את ה-temp ב-finally — בלי דליפה ל-/tmp. - מולטימודל (PDF) קורא את אותו נתיב מקומי מאומת. בדיקות: test_unified_ingest::test_ingest_reads_via_ensure_local_when_no_disk_copy מדמה backend ללא עותק-דיסק ומוודא שהצינור משלים (נכשל מול הקוד הישן). 55 עוברות. Invariants: מקיים INV-STG1 (קריאה/כתיבה רק דרך שכבת-האחסון), G1 (נרמול-במקור, לא תיקון-בקריאה), G2 (אין מסלול מקביל — תיקון הצינור הקנוני). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@ from typing import Awaitable, Callable
|
||||
from uuid import UUID
|
||||
|
||||
from legal_mcp import config
|
||||
from legal_mcp.services import db, embeddings, extractor, ingest
|
||||
from legal_mcp.services import db, embeddings, extractor, ingest, storage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -144,12 +144,23 @@ async def create_pending_digest(
|
||||
raise ValueError(f"file not found: {file_path}")
|
||||
|
||||
await progress("staging", 10, "מעתיק קובץ")
|
||||
staged = await ingest._stage_file(src, DIGEST_LIBRARY_DIR, "incoming")
|
||||
rel_path = str(staged.relative_to(config.DATA_DIR)) \
|
||||
if str(staged).startswith(str(config.DATA_DIR)) else str(staged)
|
||||
# ``_stage_file`` returns the storage KEY (DATA_DIR-relative path). Resolve a
|
||||
# real local path to read from — on s3-only this downloads to a temp file we
|
||||
# own and remove after extraction (INV-STG1; the key is not guaranteed to be
|
||||
# on local disk).
|
||||
rel_path = await ingest._stage_file(src, DIGEST_LIBRARY_DIR, "incoming")
|
||||
local = await storage.ensure_local(rel_path, bucket=storage.Bucket.DOCUMENTS)
|
||||
local_is_tmp = storage.local_path(rel_path, bucket=storage.Bucket.DOCUMENTS) is None
|
||||
|
||||
await progress("extracting_text", 50, "מחלץ טקסט")
|
||||
raw_text, _pc, _off = await extractor.extract_text(str(staged))
|
||||
try:
|
||||
raw_text, _pc, _off = await extractor.extract_text(str(local))
|
||||
finally:
|
||||
if local_is_tmp:
|
||||
try:
|
||||
local.unlink(missing_ok=True)
|
||||
except OSError as e: # noqa: BLE001 — temp cleanup, never fatal
|
||||
logger.debug("could not remove temp digest file %s: %s", local, e)
|
||||
raw_text = (raw_text or "").strip()
|
||||
if not raw_text:
|
||||
raise ValueError("no text extracted from digest")
|
||||
|
||||
Reference in New Issue
Block a user