feat(documents): מסמך-עיקרי — is_primary נגזר + רשימה קנונית (#200) #354

Merged
chaim merged 1 commits from worktree-agent-a59ef17c17ecd1d31 into main 2026-06-30 12:03:01 +00:00
Owner

מה ולמה

WS2 / TaskMaster #200 (יוזמת עיצוב-מחדש זרימת-העבודה). מוסיף את מושג "מסמך עיקרי" (מסמך-מהות שהיו"ר עוקבת אחריו לניתוח) מול מסמך-משני, עם רשימה קנונית מאושרת-יו"ר: appeal / response / objection / protocol / appraisal / decision. כל doc_type אחר (plan/permit/court_decision/exhibit/reference) = משני.

הסיווג נחשף דרך document_list / case_get / API-המסמכים (שדות is_primary + doc_category). בונה את התשתית ל-#201 (דגל "לא-נותח" + כלי ניתוח-מחדש).

עיצוב מרכזי — נגזר, לא נכתב (G1/G2/INV-DM7): מקור-האמת היחיד הוא doc_type + הרשימה הקנונית db.PRIMARY_DOC_TYPES. העמודה documents.is_primary היא BOOLEAN GENERATED ALWAYS AS (doc_type = ANY(PRIMARY_DOC_TYPES)) STORED (SCHEMA_V47) — Postgres מחשב אותה, אין מסלול-כתיבה מקביל ולא יכולה לסטות מ-doc_type, בדיוק כמו ה-tsvectors ב-INV-DM3. נמנעת עמודה כפולה הניתנת-לדריפט (היפוך של INV-DM7 "מחושבת בקריאה, אין עמודה כפולה").

שינוי-סכמה מדויק

-- SCHEMA_V47 (idempotent, re-runnable)
ALTER TABLE documents ADD COLUMN IF NOT EXISTS is_primary BOOLEAN
    GENERATED ALWAYS AS (doc_type = ANY(
        ARRAY['appeal','response','objection','protocol','appraisal','decision']::text[]
    )) STORED;
CREATE INDEX IF NOT EXISTS idx_documents_primary ON documents(case_id) WHERE is_primary;

ביטוי-ה-ARRAY נבנה מ-PRIMARY_DOC_TYPES (tuple יחיד) דרך _primary_doc_types_sql_array() — הגדרה אחת לרשימה.

קבצים

  • mcp-server/src/legal_mcp/services/db.pyPRIMARY_DOC_TYPES + is_primary_doc_type() + SCHEMA_V47_SQL (רשום ב-_apply_schema_ddl) + גזירת is_primary/doc_category ב-_row_to_doc.
  • mcp-server/src/legal_mcp/tools/documents.py — docstring של document_list מתעד את השדות הנגזרים.
  • docs/spec/02-data-model.md — §2ג חדש "מסמך-עיקרי" + שורת-הישות documents.

Invariants — הצהרה (חובה)

  • נוגע / מקיים:
    • G1 (נרמול-במקור, לא תיקון-בקריאה) — is_primary נגזר מ-doc_type, לא נכתב במקביל.
    • G2 / INV-DM7 (מקור-אמת יחיד, אין עמודה כפולה / מסלול מקביל) — רשימה קנונית אחת (PRIMARY_DOC_TYPES); אין דגל-bool עצמאי שיכול להתפצל מ-doc_type.
    • INV-DM3 (דפוס GENERATED ALWAYS … STORED drift-free) — אותה ערובה כמו ה-tsvectors.
    • INV-DM6 (ללא enum-TEXT חופשי חדש) — סיווג נגזר, לא עמודת-טקסט נוספת.
    • G4 / INV-DM1 — לא נפגע; אין שינוי בחוזה-ה-searchable.
    • §6 (אין בליעה שקטה) — אין except חדש; הגזירה דטרמיניסטית.

צ'קליסט — פרוטוקול כתיבת-קוד

  • קראתי את docs/spec/00-constitution.md + 02-data-model.md לפני הכתיבה
  • השינוי לא יוצר מסלול מקביל ליכולת קיימת (G2) ולא מת��ן תסמין בקריאה (G1) — is_primary GENERATED, מקור-אמת יחיד
  • לא הוספתי מגע-Paperclip מחוץ ל-Platform Port (G12) — נגיעה רק ב-db.py / tools / spec
  • אין בליעה שקטה של שגיאות (§6)
  • בדקתי מול gap-audit.md — אזור זה אינו ממופה ל-GAP/FU קיים
  • בדיקות עוברות (ראה אימות) / לא נדרשות
  • אם data-migration — לא רלוונטי: שינוי-סכמה אדיטיבי אידמפוטנטי בלבד, ללא backfill (העמודה GENERATED — Postgres ממלא רטרואקטיבית)

אימות

  • ast.parse עובר על שני קבצי-ה-Python שנערכו.
  • אומתה לוגיקת-הסיווג ב-stand-alone: 6 העיקריים → primary; משניים/None/ריק → secondary; רשומות טרום-מיגרציה (ללא is_primary) נגזרות נכון מ-doc_type; רשומות פוסט-מיגרציה (עם is_primary מה-DB) מכובדות.
  • אומת ביטוי-ה-SQL הנגזר: doc_type = ANY(ARRAY['appeal','response','objection','protocol','appraisal','decision']::text[]).
  • לא הורצה מיגרציה מול Postgres המשותף (כלל-קשיח worktree); המיגרציה אידמפוטנטית (ADD COLUMN IF NOT EXISTS, ביטוי קבוע) ותוחל ב-deploy.

חוסם ל-#201: אין. #201 (דגל "לא-נותח" + ניתוח-מחדש) יכול לשאול WHERE is_primary (אינדקס idx_documents_primary מוכן) ולהשתמש ב-db.PRIMARY_DOC_TYPES כמקור-אמת.

🤖 Generated with Claude Code

## מה ולמה WS2 / TaskMaster #200 (יוזמת עיצוב-מחדש זרימת-העבודה). מוסיף את מושג **"מסמך עיקרי" (מסמך-מהות שהיו"ר עוקבת אחריו לניתוח)** מול מסמך-משני, עם **רשימה קנונית מאושרת-יו"ר**: `appeal` / `response` / `objection` / `protocol` / `appraisal` / `decision`. כל doc_type אחר (`plan`/`permit`/`court_decision`/`exhibit`/`reference`) = משני. הסיווג נחשף דרך `document_list` / `case_get` / API-המסמכים (שדות `is_primary` + `doc_category`). בונה את התשתית ל-#201 (דגל "לא-נותח" + כלי ניתוח-מחדש). **עיצוב מרכזי — נגזר, לא נכתב (G1/G2/INV-DM7):** מקור-האמת היחיד הוא `doc_type` + הרשימה הקנונית `db.PRIMARY_DOC_TYPES`. העמודה `documents.is_primary` היא `BOOLEAN GENERATED ALWAYS AS (doc_type = ANY(PRIMARY_DOC_TYPES)) STORED` (SCHEMA_V47) — **Postgres מחשב אותה, אין מסלול-כתיבה מקביל ולא יכולה לסטות** מ-`doc_type`, בדיוק כמו ה-tsvectors ב-INV-DM3. נמנעת עמודה כפולה הניתנת-לדריפט (היפוך של INV-DM7 "מחושבת בקריאה, אין עמודה כפולה"). ### שינוי-סכמה מדויק ```sql -- SCHEMA_V47 (idempotent, re-runnable) ALTER TABLE documents ADD COLUMN IF NOT EXISTS is_primary BOOLEAN GENERATED ALWAYS AS (doc_type = ANY( ARRAY['appeal','response','objection','protocol','appraisal','decision']::text[] )) STORED; CREATE INDEX IF NOT EXISTS idx_documents_primary ON documents(case_id) WHERE is_primary; ``` ביטוי-ה-`ARRAY` נבנה מ-`PRIMARY_DOC_TYPES` (tuple יחיד) דרך `_primary_doc_types_sql_array()` — הגדרה אחת לרשימה. ### קבצים - `mcp-server/src/legal_mcp/services/db.py` — `PRIMARY_DOC_TYPES` + `is_primary_doc_type()` + `SCHEMA_V47_SQL` (רשום ב-`_apply_schema_ddl`) + גזירת `is_primary`/`doc_category` ב-`_row_to_doc`. - `mcp-server/src/legal_mcp/tools/documents.py` — docstring של `document_list` מתעד את השדות הנגזרים. - `docs/spec/02-data-model.md` — §2ג חדש "מסמך-עיקרי" + שורת-הישות `documents`. ## Invariants — הצהרה (חובה) - **נוגע / מקיים:** - **G1** (נרמול-במקור, לא תיקון-בקריאה) — `is_primary` נגזר מ-`doc_type`, לא נכתב במקביל. - **G2 / INV-DM7** (מקור-אמת יחיד, אין עמודה כפולה / מסלול מקביל) — רשימה קנונית אחת (`PRIMARY_DOC_TYPES`); אין דגל-bool עצמאי שיכול להתפצל מ-`doc_type`. - **INV-DM3** (דפוס `GENERATED ALWAYS … STORED` drift-free) — אותה ערובה כמו ה-tsvectors. - **INV-DM6** (ללא enum-TEXT חופשי חדש) — סיווג נגזר, לא עמודת-טקסט נוספת. - **G4 / INV-DM1** — לא נפגע; אין שינוי בחוזה-ה-searchable. - **§6** (אין בליעה שקטה) — אין except חדש; הגזירה דטרמיניסטית. ## צ'קליסט — פרוטוקול כתיבת-קוד - [x] קראתי את `docs/spec/00-constitution.md` + `02-data-model.md` לפני הכתיבה - [x] השינוי **לא** יוצר מסלול מקביל ליכולת קיימת (G2) ולא מת��ן תסמין בקריאה (G1) — `is_primary` GENERATED, מקור-אמת יחיד - [x] **לא** הוספתי מגע-Paperclip מחוץ ל-Platform Port (G12) — נגיעה רק ב-db.py / tools / spec - [x] אין בליעה שקטה של שגיאות (§6) - [x] בדקתי מול `gap-audit.md` — אזור זה אינו ממופה ל-GAP/FU קיים - [x] בדיקות עוברות (ראה אימות) / לא נדרשות - [ ] **אם data-migration** — לא רלוונטי: שינוי-סכמה אדיטיבי אידמפוטנטי בלבד, ללא backfill (העמודה GENERATED — Postgres ממלא רטרואקטיבית) ## אימות - `ast.parse` עובר על שני קבצי-ה-Python שנערכו. - אומתה לוגיקת-הסיווג ב-stand-alone: 6 העיקריים → `primary`; משניים/None/ריק → `secondary`; רשומות טרום-מיגרציה (ללא `is_primary`) נגזרות נכון מ-`doc_type`; רשומות פוסט-מיגרציה (עם `is_primary` מה-DB) מכובדות. - אומת ביטוי-ה-SQL הנגזר: `doc_type = ANY(ARRAY['appeal','response','objection','protocol','appraisal','decision']::text[])`. - **לא הורצה מיגרציה מול Postgres המשותף** (כלל-קשיח worktree); המיגרציה אידמפוטנטית (`ADD COLUMN IF NOT EXISTS`, ביטוי קבוע) ותוחל ב-deploy. > חוסם ל-#201: אין. #201 (דגל "לא-נותח" + ניתוח-מחדש) יכול לשאול `WHERE is_primary` (אינדקס `idx_documents_primary` מוכן) ולהשתמש ב-`db.PRIMARY_DOC_TYPES` כמקור-אמת. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
chaim added 1 commit 2026-06-30 11:17:31 +00:00
feat(documents): מסמך-עיקרי — is_primary נגזר + רשימה קנונית (#200)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
7a7249af38
WS2 (#200): מבחין בין מסמך-עיקרי (מסמך-מהות שהיו"ר עוקבת אחריו לניתוח)
למשני. רשימה קנונית מאושרת-יו"ר: appeal/response/objection/protocol/
appraisal/decision; כל doc_type אחר = משני.

- db.PRIMARY_DOC_TYPES = מקור-אמת יחיד (tuple) + is_primary_doc_type().
- SCHEMA_V47: documents.is_primary BOOLEAN GENERATED ALWAYS AS
  (doc_type = ANY(PRIMARY_DOC_TYPES)) STORED — Postgres גוזר, אין מסלול-
  כתיבה מקביל, לא יכול לסטות מ-doc_type (G1/G2/INV-DM7, כמו ה-tsvectors).
  אידמפוטנטי (ADD COLUMN IF NOT EXISTS, ביטוי קבוע) + אינדקס חלקי
  idx_documents_primary לשאילתת "עיקריים שטרם-נותחו" (#201).
- _row_to_doc גוזר גם בקריאה (רשומות טרום-מיגרציה) + מוסיף doc_category
  ∈ {primary, secondary}. נחשף דרך document_list / case_get / API.
- 02-data-model.md §2ג מתעד את השדה הנגזר.

Invariants: G1 (נרמול-במקור — נגזר, לא נכתב), G2/INV-DM7 (מקור-אמת
יחיד, אין עמודה כפולה/מסלול מקביל), INV-DM3 (דפוס GENERATED drift-free),
INV-DM6 (ללא enum-TEXT חדש — סיווג נגזר). §6 (אין בליעה שקטה).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chaim merged commit 8ece726b8d into main 2026-06-30 12:03:01 +00:00
chaim deleted branch worktree-agent-a59ef17c17ecd1d31 2026-06-30 12:03:01 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ezer-mishpati/legal-ai#354