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>
A live calibration run crashed with no error handling and lost ~2h of work:
the per-cell loop in _run() reached block-yod (discussion), block_writer.write_block
raised ValueError("לא ניתן לכתוב בלוק דיון ללא כיוון מאושר…") because the discussion
block needs an approved direction calibration cases don't have, and since the report
was only written at the very end of main(), every completed generation was lost.
Three robustness fixes (no metric/scoring change — G2):
1. Drop block-yod from default CALIBRATABLE_BLOCKS — out of WS5 interim-draft scope
AND structurally non-calibratable standalone (needs brainstorm→approve_direction).
Kept in new VALID_BLOCKS so a user can still force it via --blocks block-yod.
2. Per-cell try/except in _run(): any single (case, block, effort) cell failure is
logged as a warning (case/block/effort/error) and skipped, never fatal —
defense-in-depth for a forced block-yod and for transient rate-limits mid-run.
3. Incremental persistence: _write_report flushes after each completed block (ts
threaded into _run), so a crash later in the grid never loses finished blocks.
JSON/MD shape unchanged — partial results omit not-yet-done blocks from by_block.
--self-test: ALL PASS (14/14). block-yod gone from --dry-run defaults.
Invariants — נוגע / מקיים:
- G8 (eval-harness robustness) — partial results persist; a single-cell failure is
non-fatal; the harness survives block-yod's missing-direction raise and rate-limits.
- G2 (מקור-מדידה יחיד) — no metric-path change; _score_cell / block_distance_to_final
/ recommend_effort / aggregate_cell untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>