# X7 — לקוח-Paperclip ופרמטרי-חיבור (Paperclip Client & Connection Parameters)

קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) ומשלים את [X3](X3-integration-deploy.md):
בעוד X3 מתאר את **זרימות**-האינטגרציה (wakeup, ניתוב comments, webhook), קובץ זה הוא ה-deep-dive
על **שכבת-הלקוח והפרמטרים** — *איך* legal-ai מדבר עם Paperclip בקוד (אילו לקוחות, אילו מסלולים),
ועל **כל הפרמטרים המחברים** (מזהי-חברה/סוכן, env, מפתחות, `plugin_state`, גזירת `company_id`).

> **invariant פרויקטלי-תפעולי.** ה-invariants כאן הם עובדות על איך *מערכת זו* בנויה — אין להן
> סמכות חיצונית; מקור-הסמכות = ה-runbooks והקוד ([root CLAUDE.md](../../../CLAUDE.md),
> [legal-ai/CLAUDE.md](../../CLAUDE.md), [web/paperclip_api.py](../../web/paperclip_api.py),
> [web/paperclip_client.py](../../web/paperclip_client.py)). כל invariant **נקשר** ל-G גלובלי שהוא משרת —
> כאן בעיקר [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) (מסלול קנוני יחיד)
> ו-[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai) (עקיבוּת/audit), וכלל-ההנדסה "סימטריה" ([חוקה §6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)).

---

## 1. מצב קיים — שני לקוחות מקבילים

ל-legal-ai יש **שני לקוחות Paperclip שונים** שחיים בו-זמנית, וזהו מקור-השורש לרוב הפערים כאן:

| לקוח | קובץ | אופי | מה מנהל |
|------|------|------|---------|
| "current" (API) | [web/paperclip_api.py](../../web/paperclip_api.py) | HTTP דרך `pc_request` + board API key | webhooks יוצאים, wakeup חלקי |
| "legacy" (DB-ישיר) | [web/paperclip_client.py](../../web/paperclip_client.py) | **חיבור psql ישיר** ל-DB של Paperclip + API | projects, issues, comments, wakeup, queries |

[legal-ai/CLAUDE.md](../../CLAUDE.md) מתעד ש-`paperclip_client.py` הוא "legacy — השתמש ב-paperclip_api.py",
אך בפועל ה-legacy עדיין מבצע את **רוב העבודה הכבדה** (יצירת תיקים/issues, comments, wakeup-ים),
וחלקו דרך **`INSERT`/`SELECT` ישיר** ל-DB של Paperclip — מסלול-מקביל לעוקף את ה-API.

זוהי בדיוק התבנית ש-[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) אוסר:
שני מסלולי-קוד מקבילים ליכולת אחת (גישה ל-Paperclip), שמתפצלים ועלולים לסטות.

---

## 2. הפרמטרים המחברים (Connection Parameters)

### 2א. משתני-סביבה
| Var | קורא | ברירת-מחדל | סוד? |
|-----|------|-----------|------|
| `PAPERCLIP_API_URL` | [paperclip_api.py](../../web/paperclip_api.py) | `http://localhost:3100` | לא |
| `PAPERCLIP_BOARD_API_KEY` | paperclip_api.py / paperclip_client.py | `""` | **כן** (board key long-lived, לא JWT) |
| `PAPERCLIP_DB_URL` | [paperclip_client.py:21](../../web/paperclip_client.py), [app.py:3789](../../web/app.py) | `postgresql://paperclip:paperclip@127.0.0.1:54329/paperclip` | **כן — creds בתוך ברירת-המחדל** |
| `PAPERCLIP_COMPANY_ID` | [app.py:3976](../../web/app.py) | `42a7acd0-...` (CMP, hardcoded) | לא |
| `legalApiBaseUrl` | plugin (instance config) | `http://localhost:8085` | לא |

> ראה גם [X10-deploy-env-secrets.md](X10-deploy-env-secrets.md) — חוזה-ה-env המלא וטיפול-הסודות.

### 2ב. מזהים קשיחים בקוד (hardcoded) — סתירה ל-X3
[paperclip_client.py:36-62](../../web/paperclip_client.py) מכיל **מזהי-חברה וסוכן קשיחים**:
- `COMPANIES["licensing"] = "42a7acd0-..."` (CMP), `COMPANIES["betterment"] = "8639e837-..."` (CMPA)
- CEO/curator/analyst UUIDs לכל חברה (CMP CEO `752cebdd-...`, וכו').
- ה-plugin ([worker.ts](../../../plugin-legal-ai/src/worker.ts)) מכיל CEO IDs קשיחים משלו.

זו **סתירה ישירה** ל-[X3 §1א](X3-integration-deploy.md) הקובע "מזהה-ה-CEO נגזר מ-`$PAPERCLIP_COMPANY_ID`,
**לעולם לא UUID hardcoded**". הסתירה מתועדת כממצא ([gap-audit GAP-26](gap-audit.md), וכן GAP-56 ב-X10).

### 2ג. `plugin_state` keys (חוזה הקישור Paperclip↔legal-ai)
| `scope_kind` | `state_key` | ערך | משמעות |
|--------------|-------------|-----|--------|
| `issue` | `legal-case-number` | מספר-תיק | קישור issue→תיק |
| `issue` | `precedent-case-law-id` | case_law_id | קישור issue→פסיקה לחילוץ |
| `instance` | `webhook-idem-{requestId}` | timestamp | guard idempotency 5 דק' (inbound) |

### 2ד. גזירת `company_id` — שתי דרכים שונות
- **app.py**: נגזר מ-prefix מספר-התיק (`1`→licensing, `8/9`→betterment) ([X3 §1ג](X3-integration-deploy.md)).
- **paperclip_client.py**: מ-`_FALLBACK_APPEAL_TYPE_TO_COMPANY` (מיפוי tag→company) + lookup ב-DB.

שתי דרכי-גזירה לאותו ערך = drift פוטנציאלי ([gap-audit GAP-27](gap-audit.md)).

---

## 3. צד נכנס (Inbound) — הפלאגין

[plugin-legal-ai/src/worker.ts](../../../plugin-legal-ai/src/worker.ts) (לא בריפו זה) קורא ל-legal-ai דרך
`legalApiBaseUrl`. שלושה סוגי-משטח, שכולם חוזה-API שאינו מתועד היום ב-[X6](X6-ui-api-contract.md):
- **16 כלי `legal_*`** — עוטפים endpoints של `/api/cases/...`, `/api/search`, וכו'.
- **`onWebhook`** — מקבל את ה-webhook היוצא (ראה [X3 §1ג](X3-integration-deploy.md) ו-INV-INT8 להלן).
- **3 cron jobs** — `sync-case-status` (כל 15 דק'), `stale-case-reminder` (יומי), `weekly-feedback-analysis` (שבועי).

---

## 4. Invariants של התחום

### INV-INT4: לקוח-Paperclip קנוני יחיד — אין לקוח-מקביל ואין גישת-DB ישירה
**כלל:** כל גישה ל-Paperclip עוברת דרך **לקוח-API קנוני יחיד** (`pc_request`/`pc.sh`). **אסור** מסלול-מקביל —
לא לקוח שני, ולא `INSERT`/`SELECT`/`UPDATE` ישיר ל-DB של Paperclip. נתונים נקראים/נכתבים דרך ה-API
הרשמי בלבד; ה-DB של Paperclip הוא מקור-האמת של Paperclip, ו-legal-ai אינו מסלול-כתיבה מקביל אליו.
מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) וכלל "סימטריה" ([חוקה §6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)).
**מקור-סמכות:** [legal-ai/CLAUDE.md](../../CLAUDE.md) ("paperclip_client.py legacy — השתמש ב-paperclip_api.py";
"קריאות API — תמיד דרך helper"); [X3 INV-INT3](X3-integration-deploy.md). (פרויקטלי-תפעולי — משרת G2.)
**אכיפה:** איחוד שני הלקוחות ללקוח-API אחד; הסרת `PAPERCLIP_DB_URL` כמסלול-כתיבה. **כיום אין אכיפה** —
שני הלקוחות דו-קיימים (יעד FU-9).
**הפרה ידועה:** [paperclip_client.py](../../web/paperclip_client.py) — `create_project`/`post_comment`-fallback
עושים `INSERT` ישיר ל-`projects`/`issues`/`comments`/`plugin_state` ([gap-audit GAP-24, GAP-25](gap-audit.md)).

### INV-INT5: מזהי-חברה/סוכן מ-config — לא hardcoded בקוד
**כלל:** מזהי-החברה (CMP/CMPA) ומזהי-הסוכנים (CEO/curator/analyst) **נגזרים מ-config** (env/טבלת-מיפוי),
**לא** קבועים בקוד. הוספת חברה/החלפת instance אינה דורשת שינוי-קוד. מופע של
[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) (SSoT למיפוי) — מקור-אמת יחיד למיפוי.
**מקור-סמכות:** [X3 §1א](X3-integration-deploy.md) ("לעולם לא UUID hardcoded"); [X2-multi-company.md](X2-multi-company.md).
(פרויקטלי-תפעולי — משרת G2.)
**אכיפה:** טבלת-מיפוי/env יחידה; code-review. **כיום אין אכיפה** — UUIDs קשיחים.
**הפרה ידועה:** [paperclip_client.py:36-62](../../web/paperclip_client.py) + [app.py:3976](../../web/app.py) +
[plugin worker.ts](../../../plugin-legal-ai/src/worker.ts) — IDs קשיחים. **סותר את X3 §1א** ([gap-audit GAP-26](gap-audit.md)).

### INV-INT6: גזירת `company_id` קנונית יחידה
**כלל:** ל-`company_id` יש **מסלול-גזירה אחד** מתוך מספר-התיק/סוג-הערר, במקום יחיד. אסור שתי לוגיקות-גזירה
מקבילות (prefix מול fallback-map) שעלולות לסטות. מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים).
**מקור-סמכות:** [X3 §1ג](X3-integration-deploy.md); [X2-multi-company.md](X2-multi-company.md). (פרויקטלי-תפעולי.)
**אכיפה:** פונקציית-גזירה יחידה משותפת ל-app.py ול-client.py (יעד FU-9). **כיום אין.**
**הפרה ידועה:** prefix ב-[app.py](../../web/app.py) מול `_FALLBACK_APPEAL_TYPE_TO_COMPANY` ב-[paperclip_client.py](../../web/paperclip_client.py) ([gap-audit GAP-27](gap-audit.md)).

### INV-INT7: webhook יוצא — at-least-once + idempotency + ללא בליעה שקטה
**כלל:** ה-webhook היוצא (legal-ai→plugin) מספק **at-least-once** עם **מפתח-idempotency יציב** (event id),
כך שמסירה-כפולה בטוחה בצד-המקבל; וכישלון-מסירה **נרשם ומדווח** (telemetry/health), לא נבלע בשקט.
זהו invariant **הנדסי** (סמנטיקת-מסירה כללית), הקשור ל-[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai)
(עקיבוּת) ולכלל "אין בליעה שקטה" ([חוקה §6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)).
**מקורות:** Stripe — *Webhooks / at-least-once delivery & idempotency*
(https://docs.stripe.com/webhooks) · Hookdeck — *At-Least-Once vs Exactly-Once Webhook Delivery*
(https://hookdeck.com/webhooks/guides/webhook-delivery-guarantees) · Martin Kleppmann, *DDIA*
(O'Reilly 2017, idempotence & exactly-once semantics) | סטטוס: verified
**אכיפה:** event-id יציב + UNIQUE-dedup בצד-המקבל; ה-emitter רושם כישלון ל-telemetry (יעד). **כיום:**
inbound יש guard 5 דק' ([X3 §1ג](X3-integration-deploy.md)); **outbound אין idempotency**, וה-emitter בולע
שגיאות ב-`logger.warning` בלבד.
**הפרה ידועה:** `emit_*_webhook` ב-[paperclip_api.py](../../web/paperclip_api.py) — fire-and-forget, `try/except`
שמתעד warning ולעולם לא raise, ללא event-id/dedup ([gap-audit GAP-28](gap-audit.md)).

### INV-INT8: חוזה-אירועי-webhook מתוקען ומגורס
**כלל:** ל-webhook חוזה-אירוע **מפורש ומגורס** — `eventType` מתוך קבוצה סגורה, סכמת-payload מתועדת לכל
סוג, וגרסה. אין `eventType` חופשי ואין "ברירת-מחדל שקטה". מופע של
[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)/[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai).
**מקור-סמכות:** [X3 §1ג](X3-integration-deploy.md) (3 סוגי-האירוע: `status_change`, `missing_precedent_created`,
`export_complete`); קוד ה-emitter ([paperclip_api.py:87+](../../web/paperclip_api.py)). (פרויקטלי-תפעולי — משרת G2/G9.)
**אכיפה:** enum + סכמה משותפים emitter↔handler. **כיום:** `eventType` נופל ל-`status_change` כברירת-מחדל
אם חסר/לא-מוכר ([gap-audit GAP-29](gap-audit.md)).

---

## 5. מצב קיים מול יעד — פער אכיפה
האינטגרציה נשענת על **נוהל + שני לקוחות**, לא על מסלול-קוד קנוני אחד:
- **לקוח (INV-INT4):** יעד — לקוח-API יחיד; הסרת מסלול-ה-DB הישיר.
- **מזהים (INV-INT5/INT6):** יעד — טבלת-מיפוי/env יחידה; פונקציית-גזירה אחת.
- **webhook (INV-INT7/INT8):** יעד — event-id + dedup + enum-אירוע מגורס + רישום-כישלון.

כל אלה מקובצים ל-**FU-9** ([gap-audit.md](gap-audit.md)).

---

## 6. הפניות-אחיות
- [X3-integration-deploy.md](X3-integration-deploy.md) — זרימות (wakeup, comments, webhook) + INV-INT1/2/3.
- [X10-deploy-env-secrets.md](X10-deploy-env-secrets.md) — חוזה-env מלא, סודות, hardcoded IDs/creds.
- [X2-multi-company.md](X2-multi-company.md) — CMP/CMPA, sync, company filtering.
- [X6-ui-api-contract.md](X6-ui-api-contract.md) — חוזה ה-API שהפלאגין (inbound) צורך.
- [00-constitution.md](00-constitution.md) — [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים), [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai), כלל "סימטריה" ([§6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)).
- [web/paperclip_api.py](../../web/paperclip_api.py), [web/paperclip_client.py](../../web/paperclip_client.py), [scripts/pc.sh](../../scripts/pc.sh).
