From 105d9626cace5b98aa8b55fc7fe569ea53f5363a Mon Sep 17 00:00:00 2001 From: Chaim Date: Sun, 31 May 2026 06:12:43 +0000 Subject: [PATCH] docs(spec): FU-2b internal identifier reconciliation design (GAP-07/08) + split external to #68 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deterministic migration of ~52 internal_committee rows whose case_number holds a full citation → normalized bare number (citation_formatted already correct). DB analysis (2026-05-31): clean 1-token extraction, 0 key-collisions, 0 citation↔case_number mismatches, no month-padding dups. Chair-gated reversible migration (backup→dry-run→approve→apply). One edge for chair: 8047/23 ערר vs בל"מ. External (#68/FU-2c) split out — its citation_formatted is inconsistent. Verified all 11 case_law FKs use id(UUID), not case_number → rename is FK-safe. Co-Authored-By: Claude Opus 4.8 (1M context) --- .taskmaster/tasks/tasks.json | 13 +++ ...1-fu2b-identifier-reconciliation-design.md | 101 ++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-31-fu2b-identifier-reconciliation-design.md diff --git a/.taskmaster/tasks/tasks.json b/.taskmaster/tasks/tasks.json index 2cfefd4..cf166bc 100644 --- a/.taskmaster/tasks/tasks.json +++ b/.taskmaster/tasks/tasks.json @@ -2372,6 +2372,19 @@ "parentId": "67" } ] + }, + { + "id": "68", + "title": "[FU-2c] תיאום מזהי external_upload (case_number↔citation_formatted)", + "description": "פסיקה חיצונית: case_number מחזיק ציטוט מלא; citation_formatted לא תמיד תואם (נמצאה סתירה 25226-04-25 מול 1975/24). דורש קודם תיקון סתירות citation_formatted↔case_number, ואז הכרעה אם docket מחולץ הופך ל-case_number או שהציטוט נשאר המזהה.", + "details": "מקור: בדיקת DB 2026-05-31 (FU-2b scoping). 22/24 external עם ציטוט ב-case_number; citation_formatted נוצר בנפרד (LLM) ולא אמין כ-ground truth. שונה מ-internal (שם 0 סתירות). דורש סקירת-יו\"ר פר-רשומה. severity: Medium. סוג: data-migration + chair. תלוי בהחלטה: האם זהות external = ציטוט (FU-1) או docket מנורמל (INV-ID2). מופרד מ-FU-2b לפי החלטת chaim 2026-05-31.", + "testStrategy": "", + "status": "pending", + "dependencies": [ + "67" + ], + "priority": "medium", + "subtasks": [] } ], "metadata": { diff --git a/docs/superpowers/specs/2026-05-31-fu2b-identifier-reconciliation-design.md b/docs/superpowers/specs/2026-05-31-fu2b-identifier-reconciliation-design.md new file mode 100644 index 0000000..3cfac78 --- /dev/null +++ b/docs/superpowers/specs/2026-05-31-fu2b-identifier-reconciliation-design.md @@ -0,0 +1,101 @@ +# FU-2b — תיאום מזהי `case_number` (Identifier Reconciliation) — עיצוב + +**סטטוס:** מאושר-לעיצוב · **תאריך:** 2026-05-31 · **ענף:** TBD +**מכסה:** GAP-07, GAP-08 (scope: `internal_committee` בלבד) · **מספק:** INV-ID1, INV-ID2, INV-DM2 +**משימה:** TaskMaster #67 · **תלוי ב:** FU-2a (#60, פונקציית הנרמול) · **סוג:** **data-migration + chair-gate** +**מחוץ-להיקף:** external_upload → **#68 / FU-2c** (נתונים סותרים, ראה §1). + +--- + +## 1. הבעיה והיקף (מאומת מול DB, 2026-05-31) + +`internal_committee` הוא הקורפוס שבו `case_number` חייב להיות **מספר-ועדה מנורמל** (X1 §1), אך +~52/56 רשומות מחזיקות **ציטוט-מלא** בשדה-המזהה (GAP-08 — "החלטות סופר"), בניגוד ל-INV-ID2 +(ציטוט = שדה-תצוגה נגזר, לעולם לא מזהה). + +**ממצאי-נתונים שמעצבים את המיגרציה:** +- **חילוץ דטרמיניסטי ונקי:** כל 56 הרשומות → בדיוק token-מספר אחד (regex `[0-9]{2,6}(?:[-/][0-9]{1,2}){1,2}`). 0 רב-משמעיים, 0 בלתי-פתירים. +- **עקביות מושלמת:** ב-55/56 המספר המחולץ **מופיע** ב-`citation_formatted`; **0 סתירות**. (1 רשומה בלי citation_formatted — כבר bare.) +- **0 התנגשויות-מפתח** על (bare, proceeding_type) → **אין dedup**. +- **אין בעיית with/without-month:** ה"צורות הכפולות" (1024-24 מול 1024-25 וכו') הן **שנים שונות** = תיקים שונים, לא padding. +- **edge יחיד ליו"ר:** `8047/23` קיים פעמיים — אחת `proceeding_type=ערר`, אחת `בל"מ` (48 chunks כל אחת). לפי X1 אלו **שתי רשומות מובחנות** (ערר מול בל"מ), אך זהות chunk-count מצדיקה אימות-יו"ר שאינן כפילות מתויגת-שגוי. + +**external מופרד (#68):** ב-external נמצאה **סתירה** (`case_number=25226-04-25` מול +`citation_formatted=1975/24`) — ה-citation_formatted נוצר בנפרד ואינו ground-truth אמין; דורש +טיפול נפרד. בנוסף, זהות פסיקה-חיצונית היא טבעית הציטוט (אין מספר-ועדה). מחוץ ל-FU-2b. + +## 2. ההכרעה (מבוססת X1 + ממצאי-נתונים) + +הצורה הקנונית של `case_number` ל-internal = **trim · prefix-strip · `/`→`-`** על המספר הרשמי, +**בלי להמציא/להסיר חודש** (X1 §1; מקורות: Codd 1NF · Kleppmann DDIA · SSOT — verified ב-X1). +המיגרציה **דטרמיניסטית** (לא LLM): מחלצת את ה-token המספרי היחיד ומנרמלת. הציטוט כבר חי +ב-`citation_formatted` — אין מה לנגוע בו. + +**דפוס-בטיחות (chair-gated reversible migration):** גיבוי-לפני-שינוי → dry-run שמפיק טבלת-תיאום +→ **שער-אישור-יו"ר** → apply מפורש → אימות. זהו דפוס סטנדרטי למיגרציה בלתי-הפיכה על נתוני-ייצור. + +## 3. הרכיבים + +- **סקריפט** `scripts/fu2b_reconcile_internal_case_numbers.py` (לא MCP tool — מיגרציה חד-פעמית מבוקרת): + - `--dry-run` (ברירת-מחדל): מפיק טבלת-תיאום `data/audit/fu2b-reconciliation-.csv` + + `.md` קריא ליו"ר. עמודות: `id, current_case_number, proposed_bare, proceeding_type, + citation_formatted, consistency_ok, flag`. + - `--apply`: דורש קובץ-אישור (ראה §4); מגבה ואז מבצע. + - מעבד **רק** `source_kind='internal_committee'` ו**רק** רשומות שבהן `proposed_bare != case_number` + (idempotent — already-bare לא נוגעים). +- **חילוץ:** `_extract_bare(case_number) -> str|None` — regex token יחיד + `_canonical_case_number` + (מ-FU-2a, db.py) לנרמול הסופי. אם 0 או >1 tokens → `None` + flag `NEEDS_CHAIR`. +- **consistency guard:** אם `proposed_bare` **לא** מופיע ב-`citation_formatted` → flag `MISMATCH` (לא + יוחל אוטומטית; ליו"ר). (כיום 0 כאלה, אך הסקריפט בודק בזמן-ריצה.) +- **גיבוי:** לפני apply, כתיבת `data/audit/fu2b-backup-.csv` = `(id, old_case_number)` לכל רשומה + שתשונה → revert-script טריוויאלי. +- **edge 8047/23:** הסקריפט **לא** ממזג; מסמן את הזוג ב-flag `DUP_CHECK` בטבלה. ההכרעה (מובחנות מול + כפילות) היא של היו"ר; אם כפילות — מחיקה ידנית נפרדת (לא חלק מה-apply הדטרמיניסטי). + +## 4. שער-אישור-היו"ר (chair gate) + +1. הרצת `--dry-run` → טבלת-תיאום (`.md`) + סיכום (כמה ישתנו, אילו flags). +2. **הצגה לדפנה**: הטבלה (52 שורות: ציטוט-נוכחי → bare מוצע) + ה-edge של 8047/23. היא מסמנת + שורות שגויות (אם יש) ומכריעה על 8047/23. +3. תיקון flags לפי הערותיה (אם יש), ואז `--apply --approved data/audit/fu2b-approved-.csv` + (קובץ-האישור = הטבלה לאחר סקירתה; הסקריפט מחיל רק שורות שאושרו). +4. אימות אחרי apply: כל internal `case_number` תואם regex bare; 0 ציטוטים בשדה-המזהה; + `search`/`get_case_by_number` עדיין פותרים (FU-2a tolerant-read + הנרמול). + +## 5. אינטראקציה עם FU-2a (forward-consistency) + +FU-2a `_canonical_case_number` מנרמל prefix+separator אך **אינו מחלץ מספר מתוך ציטוט-מלא**. לכן +אם קליטה עתידית תעביר ציטוט-מלא כ-`case_number`, ייווצר שוב מזהה מלוכלך. **הערכת-סיכון:** נמוכה — +טופס-ההעלאה וה-MCP tool מעבירים שדה-`case_number` נפרד (בד"כ נקי). **החלטה:** FU-2b הוא ניקוי-נתונים +בלבד; הקשחת-כתיבה (חילוץ-token גם ב-create) **לא בהיקף** — תיפתח רק אם יתגלה caller שמעביר ציטוט. +(מתועד; לא לשנות התנהגות-כתיבה בלי ראיה.) + +## 6. שינויי-התנהגות וסיכון + +| שינוי | השפעה | סיכון | +|--------|--------|--------| +| `case_number` של ~52 internal → bare | חיפוש exact-match על המספר עובד; (case_number,proceeding_type) נקי | נמוך — דטרמיניסטי, גיבוי, שער-יו"ר, 0 collisions | +| 8047/23 edge | אולי מחיקת רשומה כפולה | בינוני — **רק** בהחלטת-יו"ר, מחיקה ידנית נפרדת, לא ב-apply האוטומטי | +| citation_formatted | **לא משתנה** (כבר תקין) | אין | +| FK/relations | `case_law_relations`/`precedent_internal_citations` מפנים ל-`id` (UUID), לא ל-case_number | אין — שינוי case_number לא שובר קשרים | +| chunks/embeddings | מפתח-זר `case_law_id` (UUID) — לא תלוי ב-case_number | אין — re-index לא נדרש | + +## 7. אסטרטגיית בדיקה + +- **בדיקות-יחידה offline** (`tests/test_fu2b_reconcile.py`): `_extract_bare` — token יחיד→bare מנורמל; + ציטוט מלא→המספר הנכון (דוגמאות אמיתיות: `"ערר (...) 403/17 אהרון ברק..."`→`403-17`, + `"...8136-10-24 שחר..."`→`8136-10-24` חודש נשמר); 0/רב-token→None+flag; consistency guard. +- **dry-run מול DB מקומי**: הטבלה מופקת, מספר-השורות-לשינוי = ~52, 0 MISMATCH, 1 DUP_CHECK (8047). +- **apply בסביבת-בדיקה**: על עותק/תיק-בדיקה — אימות idempotency (הרצה שנייה = 0 שינויים) + revert מהגיבוי. +- ה-apply בייצור רץ **רק אחרי אישור-יו"ר** (לא חלק מה-CI/PR; ידני ומבוקר). + +## 8. סדר-ביצוע + +1. בדיקות אדומות ל-`_extract_bare` + consistency guard. +2. `_extract_bare` + הסקריפט (`--dry-run` בלבד תחילה) + הפקת טבלת-תיאום + גיבוי. +3. בדיקות ירוקות + dry-run מול DB → הפקת הטבלה. +4. **עצירה: הצגת הטבלה + 8047/23 ליו"ר (דפנה)** — שער-אישור. +5. (אחרי אישור) מימוש `--apply --approved` + אימות + revert-script. +6. הרצת apply בייצור (מבוקר) + אימות-אחרי + TaskMaster #67. + +> צעדים 1–3 לא דורשים את דפנה (אני מכין הכל). צעד 4 הוא שער-האישור. צעדים 5–6 אחרי אישורה.