fix(tz): timezone-aware datetimes — unblock TZ=Asia/Jerusalem (#210) #368

Merged
chaim merged 1 commits from worktree-agent-a29624b2a56f70804 into main 2026-06-30 17:25:56 +00:00
Owner

מה ולמה

ההמשך-בצד-השרת ל-INV-UI9 (#210). הצד-UI (PR #367) כבר נעץ תצוגה אנושית ב-Asia/Jerusalem. ה-PR הזה הוא הדרישה-המקדימה שמאפשרת באופן בטוח להגדיר TZ=Asia/Jerusalem בקונטיינר — מימוש ה"אודיט-נלווה (לא מיושם)" המוצהר ב-docs/spec/X6-ui-api-contract.md §INV-UI9: "הגדרת TZ=Asia/Jerusalem בקונטיינר בטוחה רק אם אף לוגיקה עסקית אינה תלויה בשעון-ה-process כ-UTC."

האחסון נשאר UTC (TIMESTAMPTZ, ISO-UTC ב-API). התצוגה האנושית — ישראל, דטרמיניסטית.

תיקונים

  1. קריטי — mcp-server/src/legal_mcp/services/audit.py:41datetime.utcnow() נאיבי נכתב ל-עמודת TIMESTAMPTZ (audit_log.created_at). תחת TZ=Asia/Jerusalem הערך-הנאיבי (שעון-קיר UTC) היה מתפרש כשעון-ישראל מקומי ונשמר ~3 שעות סטייה. → datetime.now(timezone.utc) (tz-aware). הוספת timezone ל-import.
  2. scripts/calibrate_effort.py:_ts() — חותמת-הדוח עכשיו שעון-ישראל דרך ZoneInfo("Asia/Jerusalem") עם סיומת בטוחה-לשם-קובץ -IL (הסקריפט רץ על ה-host שהוא UTC). נוספה שורת-כותרת מתויגת "שעון ישראל · Asia/Jerusalem" כדי שהחותמת מול היו"ר תהיה חד-משמעית. --self-test עדיין ALL PASS.
  3. שדות-תצוגה (mcp-server/src/legal_mcp/services/research_md.py) — נעשו דטרמיניסטיים: update_chair_position timestamp (שורה 363, datetime.now(IL_TZ)) ו-parse() mtime (שורה 200, datetime.fromtimestamp(..., tz=IL_TZ)) — נכונים ללא תלות ב-TZ של ה-process.

הושארו ללא נגיעה במכוון (Israel-local-day — נכונים ברגע ש-TZ מתהפך, לא-מזיקים עד אז): date.today() ב-tools/drafting.py, services/party_claims_summary.py, services/block_writer.py (תאריך-החלטה מודפס), ו-year-cutoff ב-services/court_fetch_supreme.py — ועדה ירושלמית רוצה את היום-הקלנדרי הישראלי.

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

  • נוגע / מקיים: INV-UI9 (זמן-מאוחסן UTC · תצוגה אנושית דטרמיניסטית ב-Asia/Jerusalem) — בצד-השרת; §6 כלל-הנדסה (datetimes tz-aware, אין סטייה-שקטה ~3 שעות, אין בליעת-שגיאות). אין מסלול-מקביל חדש (G2).

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

  • קראתי את docs/spec/00-constitution.md (§6) + docs/spec/X6-ui-api-contract.md (INV-UI9) לפני הכתיבה
  • השינוי לא יוצר מסלול מקביל ליכולת קיימת (G2) ולא מתקן תסמין בקריאה (G1) — נורמול במקור-הכתיבה
  • לא הוספתי מגע-Paperclip מחוץ ל-Platform Port (G12) — mcp-server/src נקי
  • אין בליעה שקטה של שגיאות — שינוי tz בלבד, התנהגות-שגיאה קיימת נשמרת (כלל-הנדסה §6)
  • בדקתי מול docs/spec/gap-audit.md — לא נגעתי ב-GAP/FU ממופה
  • בדיקות עוברות — --self-test ALL PASS
  • אין data-migration — אין שינוי-סכמה, אין --apply

אימות

  • mcp-server/.venv/bin/python scripts/calibrate_effort.py --self-testALL PASS (14/14, EXIT=0).
  • python -c "import ast; ..." על שלושת הקבצים → clean.
  • PYTHONPATH=mcp-server/src python -c "import legal_mcp.server"OK (אין שבירת-import); import legal_mcp.services.audit + research_md → OK.
  • _ts() מפיק 20260630T202319-IL — בטוח-לשם-קובץ, חד-משמעי-ישראל; datetime.now(IL_TZ).isoformat()+03:00.

🤖 Generated with Claude Code

## מה ולמה ההמשך-בצד-השרת ל-INV-UI9 (#210). הצד-UI (PR #367) כבר נעץ תצוגה אנושית ב-`Asia/Jerusalem`. ה-PR הזה הוא **הדרישה-המקדימה** שמאפשרת באופן בטוח להגדיר `TZ=Asia/Jerusalem` בקונטיינר — מימוש ה"אודיט-נלווה (לא מיושם)" המוצהר ב-[`docs/spec/X6-ui-api-contract.md`](docs/spec/X6-ui-api-contract.md) §INV-UI9: "הגדרת `TZ=Asia/Jerusalem` בקונטיינר בטוחה רק אם אף לוגיקה עסקית אינה תלויה בשעון-ה-process כ-UTC." **האחסון נשאר UTC** (`TIMESTAMPTZ`, ISO-UTC ב-API). התצוגה האנושית — ישראל, דטרמיניסטית. ### תיקונים 1. **קריטי — `mcp-server/src/legal_mcp/services/audit.py:41`** — `datetime.utcnow()` נאיבי נכתב ל-עמודת `TIMESTAMPTZ` (`audit_log.created_at`). תחת `TZ=Asia/Jerusalem` הערך-הנאיבי (שעון-קיר UTC) היה מתפרש כשעון-ישראל מקומי ונשמר ~3 שעות סטייה. → `datetime.now(timezone.utc)` (tz-aware). הוספת `timezone` ל-import. 2. **`scripts/calibrate_effort.py:_ts()`** — חותמת-הדוח עכשיו שעון-ישראל דרך `ZoneInfo("Asia/Jerusalem")` עם סיומת בטוחה-לשם-קובץ `-IL` (הסקריפט רץ על ה-host שהוא UTC). נוספה שורת-כותרת מתויגת "שעון ישראל · Asia/Jerusalem" כדי שהחותמת מול היו"ר תהיה חד-משמעית. `--self-test` עדיין **ALL PASS**. 3. **שדות-תצוגה (`mcp-server/src/legal_mcp/services/research_md.py`)** — נעשו דטרמיניסטיים: `update_chair_position` timestamp (שורה 363, `datetime.now(IL_TZ)`) ו-`parse()` mtime (שורה 200, `datetime.fromtimestamp(..., tz=IL_TZ)`) — נכונים ללא תלות ב-TZ של ה-process. **הושארו ללא נגיעה במכוון** (Israel-local-day — נכונים ברגע ש-TZ מתהפך, לא-מזיקים עד אז): `date.today()` ב-`tools/drafting.py`, `services/party_claims_summary.py`, `services/block_writer.py` (תאריך-החלטה מודפס), ו-year-cutoff ב-`services/court_fetch_supreme.py` — ועדה ירושלמית רוצה את היום-הקלנדרי הישראלי. ## Invariants — הצהרה (חובה) - **נוגע / מקיים:** **INV-UI9** (זמן-מאוחסן UTC · תצוגה אנושית דטרמיניסטית ב-Asia/Jerusalem) — בצד-השרת; **§6 כלל-הנדסה** (datetimes tz-aware, אין סטייה-שקטה ~3 שעות, אין בליעת-שגיאות). אין מסלול-מקביל חדש (G2). ## צ'קליסט — פרוטוקול כתיבת-קוד - [x] קראתי את `docs/spec/00-constitution.md` (§6) + `docs/spec/X6-ui-api-contract.md` (INV-UI9) לפני הכתיבה - [x] השינוי **לא** יוצר מסלול מקביל ליכולת קיימת (G2) ולא מתקן תסמין בקריאה (G1) — נורמול במקור-הכתיבה - [x] **לא** הוספתי מגע-Paperclip מחוץ ל-Platform Port (G12) — `mcp-server/src` נקי - [x] אין בליעה שקטה של שגיאות — שינוי tz בלבד, התנהגות-שגיאה קיימת נשמרת (כלל-הנדסה §6) - [x] בדקתי מול `docs/spec/gap-audit.md` — לא נגעתי ב-GAP/FU ממופה - [x] בדיקות עוברות — `--self-test` ALL PASS - [x] אין data-migration — אין שינוי-סכמה, אין `--apply` ## אימות - `mcp-server/.venv/bin/python scripts/calibrate_effort.py --self-test` → **ALL PASS** (14/14, EXIT=0). - `python -c "import ast; ..."` על שלושת הקבצים → **clean**. - `PYTHONPATH=mcp-server/src python -c "import legal_mcp.server"` → **OK** (אין שבירת-import); `import legal_mcp.services.audit` + `research_md` → OK. - `_ts()` מפיק `20260630T202319-IL` — בטוח-לשם-קובץ, חד-משמעי-ישראל; `datetime.now(IL_TZ).isoformat()` → `+03:00`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
chaim added 1 commit 2026-06-30 17:25:08 +00:00
fix(tz): timezone-aware datetimes — unblock TZ=Asia/Jerusalem (#210)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
9402c7302f
Backend follow-up to INV-UI9 (X6 §"אודיט-נלווה"): make server-side
datetimes explicit so flipping the container to TZ=Asia/Jerusalem cannot
silently skew stored or displayed timestamps. Storage stays UTC; human
display is Israel time.

- audit.py: naive datetime.utcnow() → datetime.now(timezone.utc) for the
  TIMESTAMPTZ audit_log.created_at. Under TZ=Asia/Jerusalem the naive UTC
  wall-clock would be read as Israel-local and stored ~3h off. (§6: no
  silent skew.)
- calibrate_effort.py: _ts() report stamp now Israel-time via
  ZoneInfo("Asia/Jerusalem") with a filename-safe "-IL" suffix (script runs
  on the UTC host); added a labeled "שעון ישראל" line to the report header
  so the chair-facing timestamp is unambiguous. --self-test still ALL PASS.
- research_md.py: display fields made deterministic Israel-tz —
  update_chair_position timestamp (datetime.now(IL_TZ)) and parse() mtime
  (datetime.fromtimestamp(..., tz=IL_TZ)) — correct regardless of process TZ.

Left intentionally Israel-local-day date.today()/year-cutoff call sites
untouched (drafting/party_claims_summary/block_writer/court_fetch_supreme)
— a Jerusalem committee wants the Israel calendar day; they resolve
correctly once TZ flips.

Invariants: INV-UI9 (store-UTC / display-Israel, server side); §6
(timezone-aware datetimes, no silent ~3h skew).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chaim merged commit b2d673fdb0 into main 2026-06-30 17:25:56 +00:00
chaim deleted branch worktree-agent-a29624b2a56f70804 2026-06-30 17:25:56 +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#368