diff --git a/docs/spec/X3-integration-deploy.md b/docs/spec/X3-integration-deploy.md new file mode 100644 index 0000000..3c8cdea --- /dev/null +++ b/docs/spec/X3-integration-deploy.md @@ -0,0 +1,212 @@ +# X3 — אינטגרציה ו-Deploy (Integration & Deploy) + +קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) והוא ה-deep-dive על **שני ממדי-התפעול** +של עוזר משפטי: (א) **האינטגרציה עם Paperclip** — איך המערכת מעירה סוכנים, איך תגובות-משתמש +מנותבות, ואיך שינוי-סטטוס תיק מתפרסם חזרה; (ב) **מודל ה-Deploy** — שני מודלי-הרצה הדו-קיימים +על שרת Nautilus (Coolify-Docker מול pm2-מקומי) ומחזור-השינוי של legal-ai. + +> **invariant פרויקטלי-תפעולי.** ה-invariants כאן הם **עובדות על איך המערכת *הזו* משתלבת +> ונפרסת** — לא תאוריה הנדסית כללית ולא תוכן משפטי. אין סמכות חיצונית ל"איך מעירים סוכן +> Paperclip" או "איך פורסים את legal-ai"; לכן הם נושאים שדה `מקור-סמכות` = הראנבוקים והקוד +> של הפרויקט עצמו ([root CLAUDE.md](../../../CLAUDE.md), [legal-ai/CLAUDE.md](../../CLAUDE.md), +> [HEARTBEAT.md](../../.claude/agents/HEARTBEAT.md), [MEMORY reference_paperclip_wakeup](../../../.claude/projects/-home-chaim-legal-ai/memory/reference_paperclip_wakeup.md), +> ו-[web/paperclip_api.py](../../web/paperclip_api.py)) — **לא** ≥3 מקורות חיצוניים ו**ללא** +> סטטוס verified/UNVERIFIED. אבל כל invariant **נקשר לעיקרון הגלובלי שהוא משרת**: כלל +> ה-wakeup-דרך-API-בלבד הוא מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) +> (מסלול קנוני יחיד; ה-DB-insert המקביל אסור כי הוא מתפצל מהמסלול שיוצר `heartbeat_run`). + +--- + +## 1. אינטגרציית Paperclip + +עוזר משפטי משתלב עם Paperclip בשלושה כיוונים: **wakeup** (legal-ai/אוטומציה → סוכן), +**ניתוב comments** (משתמש → CEO → סוכן), ו-**webhook יוצא** (legal-ai → פלאגין). + +### 1א. Wakeup — תמיד דרך API, לעולם לא דרך DB + +הנתיב הקנוני היחיד להערת סוכן הוא `POST /api/agents/{agent-id}/wakeup` עם `payload` המכיל +`issueId` ([root CLAUDE.md](../../../CLAUDE.md) "Wakeup API"; [legal-ai/CLAUDE.md](../../CLAUDE.md) +"Wakeup API"; [HEARTBEAT.md §4ד, שורות 152–158](../../.claude/agents/HEARTBEAT.md)): + +```bash +~/legal-ai/scripts/pc.sh POST "/api/agents/$CEO_ID/wakeup" \ + '{"source":"automation","triggerDetail":"system","reason":"...", + "payload":{"issueId":"...","mutation":"comment","commentId":"..."}}' +``` + +- **`POST .../wakeup`, לא `/wake`** — שם-הנתיב מדויק ([legal-ai/CLAUDE.md](../../CLAUDE.md)). +- **חובה `payload.issueId`** — בלעדיו הסוכן מתעורר בלי הקשר (בלי תיק, בלי issue, בלי `cwd` + נכון) ([HEARTBEAT.md שורה 156](../../.claude/agents/HEARTBEAT.md)). +- **אסור `INSERT INTO agent_wakeup_requests` ישיר** — insert ל-DB יוצר רשומת-בקשה בלבד **בלי + `heartbeat_run`**, והסוכן **לא יתעורר לעולם** ([HEARTBEAT.md שורה 158](../../.claude/agents/HEARTBEAT.md); + [reference_paperclip_wakeup](../../../.claude/projects/-home-chaim-legal-ai/memory/reference_paperclip_wakeup.md)). + זהו בדיוק "מסלול מקביל מתפצל" שאסור לפי [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים). +- **CEO לכל חברה** — מזהה-ה-CEO ל-wakeup נגזר מ-`$PAPERCLIP_COMPANY_ID`, לעולם לא UUID + hardcoded; wakeup לחברה אחרת נדחה (`Agent key cannot access another company`) + ([HEARTBEAT.md §4ג](../../.claude/agents/HEARTBEAT.md); ראה [X2-multi-company.md §2](X2-multi-company.md)). + +### 1ב. ניתוב comments — דרך ה-CEO + +תגובת-משתמש על issue ב-Paperclip **אינה** מנותבת ישירות לסוכן-המטרה. הזרימה +([root CLAUDE.md](../../../CLAUDE.md) "Comment routing"; [legal-ai/CLAUDE.md](../../CLAUDE.md)): + +``` +user comment → plugin-legal-ai → ctx.agents.invoke() מעיר CEO + → CEO קורא comment, מחליט ניתוב, יוצר issue לסוכן המתאים +``` + +- ה-CEO הוא נקודת-הניתוב היחידה — סוכן-משנה לא מקבל עבודה ישירות מ-comment. +- כל סוכן **חייב** לקרוא comments אחרונים לפני שהוא מתחיל עבודה ([HEARTBEAT שלבים 2b–2c](../../.claude/agents/HEARTBEAT.md)). + +### 1ג. Webhook יוצא — עדכון סטטוס תיק לפלאגין + +כשסטטוס תיק משתנה דרך `PUT /api/cases/{case_number}`, הבקאנד שולח webhook אסינכרוני +לפלאגין כ-BackgroundTask, fire-and-forget: + +``` +PUT /api/cases/{n} → [BackgroundTask] emit_case_status_webhook() + → POST /api/plugins/marcusgroup.legal-ai/webhooks/case-status + → plugin-legal-ai/onWebhook() → comment בעברית + CEO wakeup (כש-qa_failed) +``` + +מאומת מול הקוד: + +- ה-call-site: [web/app.py:2045-2061](../../web/app.py) — ה-webhook מתוזמן רק כש-`old_status + != new_status`, ו-`company_id` נגזר מ-prefix מספר-התיק (`1`→licensing, `8/9`→betterment). +- המימוש: [web/paperclip_api.py:87-117](../../web/paperclip_api.py) — `emit_case_status_webhook` + קורא ל-`pc_request("POST", "/api/plugins/.../webhooks/case-status", ...)` עם `timeout=5.0`, + בלוק `try/except` שמתעד `logger.warning` ולעולם לא raise (לא חוסם את הקורא). +- אותו דפוס משרת אירועים נוספים: `emit_missing_precedent_webhook` + ([paperclip_api.py:120-165](../../web/paperclip_api.py)) ו-`emit_export_complete_webhook` + ([paperclip_api.py:168+](../../web/paperclip_api.py)). + +### 1ד. כל קריאת-API דרך helper — לא curl/httpx ישיר + +קריאות ל-Paperclip עוברות תמיד דרך helper, לא דרך לקוח גולמי: + +- **bash (סוכנים):** `~/legal-ai/scripts/pc.sh [BODY]` — מוסיף אוטומטית + `Authorization: Bearer`, `X-Paperclip-Run-Id`, `Content-Type`, ו-base URL + ([HEARTBEAT.md §0, שורות 15–32](../../.claude/agents/HEARTBEAT.md); [scripts/pc.sh:8-9,39-40](../../scripts/pc.sh)). +- **Python (FastAPI):** `from web.paperclip_api import pc_request` — בונה headers דרך + `_build_headers` ([paperclip_api.py:47-84](../../web/paperclip_api.py)), משתמש ב-board API key. +- **למה:** ה-skill הרשמי דורש `X-Paperclip-Run-Id` בכל קריאה משנה issue (audit trail); + ה-helper מבטיח עקביות + תאימות ל-board API keys long-lived שלא נושאות JWT claims + ([legal-ai/CLAUDE.md](../../CLAUDE.md) "קריאות API — תמיד דרך helper"). + +--- + +## 2. מודל ה-Deploy — שני מודלים דו-קיימים + +על שרת Nautilus דרים **שני מודלי-הרצה**. ערבוב ביניהם הוא הטעות הנפוצה ביותר +([root CLAUDE.md](../../../CLAUDE.md) "Deploy architecture"; [legal-ai/CLAUDE.md](../../CLAUDE.md) +"ארכיטקטורת Deploy"). + +| ממד | legal-ai (web + web-ui) | Paperclip + legal-chat-service | +|------|--------------------------|--------------------------------| +| מודל | **Coolify-managed (Docker)** | **PM2-managed (Node/Python מקומי)** | +| מחזור-שינוי | commit → push → Gitea Actions build → Coolify redeploy (~2–4 דק') | עריכה → `pm2 restart` | +| Coolify UUID | `gyjo0mtw2c42ej3xxvbz8zio` | — | +| build_pack | **`dockerimage`** (לא `dockerfile`) | — | +| פורטים | Next.js `:3000` (חשוף) + FastAPI `:8000` (פנימי) | Paperclip `localhost:3100`; legal-chat-service `127.0.0.1:8770` (loopback) | +| הרצה מקומית | **אין** — אין venv של Python על ה-host; אסור `uvicorn`/`next dev` לפרוד | יש; מתחזק דרך pm2 | + +### 2א. מחזור-השינוי של legal-ai (Coolify dockerimage) + +שינוי קוד ב-`web/` או `web-ui/` **לא נכנס לתוקף** עד שמריצים את כל הצעדים, בסדר: + +1. `git commit` + `git push origin main` ל-Gitea. +2. Gitea Actions בונה image ודוחף ל-registry (`gitea.nautilus.marcusgroup.org/...`). +3. ה-workflow מפעיל Coolify redeploy דרך API (UUID `gyjo0mtw2c42ej3xxvbz8zio`). +4. ~2–4 דקות end-to-end. בדיקה: `curl -s https://legal-ai.nautilus.marcusgroup.org/api/health`. + +- **אסור** לנסות `uvicorn`/`next dev` לפרוד — הקונטיינר מספק את שני התהליכים; אין סביבת + Python על ה-host ([root CLAUDE.md](../../../CLAUDE.md); [legal-ai/CLAUDE.md](../../CLAUDE.md)). +- **endpoint חדש ≠ זמין ל-UI.** הוספת endpoint ב-`web/app.py` היא תנאי הכרחי אך לא מספיק + לצריכה מה-frontend — חובה `npm run api:types` בתוך `web-ui/` כדי לחדש את ה-OpenAPI types + ([root CLAUDE.md](../../../CLAUDE.md), שורה 89; [legal-ai/CLAUDE.md](../../CLAUDE.md)). + +### 2ב. legal-chat-service ו-host.docker.internal + +legal-chat-service (`127.0.0.1:8770`, pm2) הוא גשר host-side שעוטף את `claude` CLI ב-streaming +לטאב הצ'אט ב-`/training`. הקונטיינר מגיע אליו דרך `host.docker.internal:8770` — ולכן ה-Service +Definition של legal-ai ב-Coolify **חייב** לכלול `extra_hosts: host.docker.internal:host-gateway`, +אחרת ה-proxy יקבל `ConnectError` ([root CLAUDE.md](../../../CLAUDE.md); [legal-ai/CLAUDE.md](../../CLAUDE.md) +"legal-chat-service"). הנחת-היסוד של "קריאות LLM רק ממקומי" נשמרת — ראה +[reference: claude_session local only](../../../.claude/projects/-home-chaim-legal-ai/memory/feedback_claude_session_local_only.md). + +--- + +## 3. Invariants של התחום (פרויקטלי-תפעולי) + +### INV-INT1: wakeup דרך API בלבד — DB-insert אסור +**כלל:** הערת סוכן Paperclip **חייבת** לעבור דרך `POST /api/agents/{agent-id}/wakeup` עם +`payload.issueId`. **אסור** `INSERT INTO agent_wakeup_requests` ישיר — insert ל-DB אינו יוצר +`heartbeat_run`, ולכן הסוכן **לא יתעורר לעולם**. זהו המסלול הקנוני היחיד; ה-DB-insert הוא +מסלול-מקביל-מתפצל אסור (מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) +— מקור-אמת/מסלול קנוני יחיד; וכלל-ההנדסה "סימטריה", [חוקה §6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)). +**מקור-סמכות:** "Wakeup API" ב-[root CLAUDE.md](../../../CLAUDE.md) + ב-[legal-ai/CLAUDE.md](../../CLAUDE.md) + +[reference_paperclip_wakeup](../../../.claude/projects/-home-chaim-legal-ai/memory/reference_paperclip_wakeup.md) + +[HEARTBEAT.md §4ד, שורות 152–158](../../.claude/agents/HEARTBEAT.md). (invariant פרויקטלי-תפעולי — +ללא פרוטוקול ≥3-המקורות; משרת את העיקרון הגלובלי G2.) +**אכיפה:** קריאות-wakeup דרך `pc.sh`/`pc_request` בלבד; `payload.issueId` חובה; בדיקה +ש-`heartbeat_run` נוצר. **אין אכיפה סכמתית** שתחסום insert ישיר ל-`agent_wakeup_requests` — +המניעה היא נוהל (ראה §4). +**הפרה ידועה:** insert ישיר ל-`agent_wakeup_requests` (fallback ישן) → רשומה בלי `heartbeat_run`, +הסוכן נשאר רדום ([reference_paperclip_wakeup](../../../.claude/projects/-home-chaim-legal-ai/memory/reference_paperclip_wakeup.md)). + +### INV-INT2: שינוי-קוד legal-ai נכנס לתוקף רק דרך commit→push→Coolify deploy +**כלל:** שינוי קוד ב-`web/` או `web-ui/` **לא נכנס לתוקף** עד `git commit` + `git push origin main` ++ build ב-Gitea Actions + Coolify redeploy (build_pack `dockerimage`, UUID `gyjo0mtw2c42ej3xxvbz8zio`). +**אין** הרצת `uvicorn`/`next dev` מקומית לפרוד. endpoint חדש ב-`web/app.py` דורש גם +`npm run api:types` ב-`web-ui/` כדי להיחשף ל-UI. +**מקור-סמכות:** "Deploy architecture" ב-[root CLAUDE.md](../../../CLAUDE.md) (UUID, dockerimage, +no local uvicorn, api:types) + "ארכיטקטורת Deploy" ב-[legal-ai/CLAUDE.md](../../CLAUDE.md) + +[reference: deployment](../../../.claude/projects/-home-chaim-legal-ai/memory/reference_deployment.md). +(invariant פרויקטלי-תפעולי — ללא פרוטוקול ≥3-המקורות.) +**אכיפה:** pipeline Gitea Actions → Coolify (אוטומטי בדחיפה ל-main); בדיקה ידנית +`curl .../api/health` אחרי deploy. **אין** מסלול-פריסה חלופי. +**הפרה ידועה:** בדיקת שינוי מול הרצה מקומית שלא קיימת — הקוד בפרוד נשאר ישן עד deploy; וכן +drift אפשרי Infisical↔Coolify env (env לא מתעדכן אוטומטית מ-Infisical, ראה +[reference: infisical/coolify drift](../../../.claude/projects/-home-chaim-legal-ai/memory/feedback_infisical_coolify_drift.md)). + +### INV-INT3: כל קריאת-Paperclip דרך helper — לא curl/httpx ישיר +**כלל:** קריאות ל-Paperclip API עוברות **תמיד** דרך helper — `pc.sh` (bash/סוכנים) או +`pc_request` (Python/FastAPI) — ולעולם לא `curl`/`httpx` גולמי. ה-helper מזריק `Authorization`, +`X-Paperclip-Run-Id` (audit), ו-`Content-Type` באופן עקבי, ותומך ב-board API keys long-lived +(מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) — מסלול-גישה +קנוני יחיד ל-Paperclip; ושל [G9](00-constitution.md#inv-g9-עקיבות-מקור--audit-trail-ל-ai) — +audit-trail עקבי). +**מקור-סמכות:** "קריאות API — תמיד דרך helper" ב-[legal-ai/CLAUDE.md](../../CLAUDE.md) + +[HEARTBEAT.md §0, שורות 15–32](../../.claude/agents/HEARTBEAT.md) + +[scripts/pc.sh:8-9,39-40](../../scripts/pc.sh) + [web/paperclip_api.py:47-84](../../web/paperclip_api.py). +(invariant פרויקטלי-תפעולי — ללא פרוטוקול ≥3-המקורות.) +**אכיפה:** נוהל + code-review; `pc.sh` ו-`pc_request` הם נקודות-הכניסה היחידות. **אין אכיפה +אוטומטית** שתחסום `httpx.AsyncClient` ישיר ל-Paperclip בקוד חדש. +**הפרה ידועה:** — + +--- + +## 4. מצב קיים מול יעד — פער אכיפה + +האינטגרציה נשענת על **נוהל**, לא על מחסום-קוד: + +- **wakeup (INV-INT1):** אין constraint סכמתי שחוסם insert ישיר ל-`agent_wakeup_requests`; + המניעה היא ידע-נוהל ([HEARTBEAT](../../.claude/agents/HEARTBEAT.md)). **יעד:** wrapper/בדיקת-בריאות + שמסמן בקשות-wakeup ללא `heartbeat_run` תואם. +- **helper (INV-INT3):** אין linter/בדיקה שתתפוס `httpx`/`curl` ישיר ל-Paperclip בקוד חדש. + **יעד:** כלל-lint שמכריח שימוש ב-`pc_request`/`pc.sh`. + +--- + +## 5. הפניות-אחיות + +- [00-constitution.md](00-constitution.md) — [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) + (מסלול קנוני יחיד) + [G9](00-constitution.md#inv-g9-עקיבות-מקור--audit-trail-ל-ai) (audit-trail) + + כלל-ההנדסה "סימטריה" ([§6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)). +- [X2-multi-company.md](X2-multi-company.md) — wakeup-per-company + ניתוב לפי `company_id` משלים את §1 כאן. +- [X4-agents.md](X4-agents.md) — מפת הסוכנים שה-CEO מנתב אליהם comments. +- [root CLAUDE.md](../../../CLAUDE.md) + [legal-ai/CLAUDE.md](../../CLAUDE.md) — "Wakeup API", + "Comment routing", "Deploy architecture", "קריאות API — תמיד דרך helper". +- [.claude/agents/HEARTBEAT.md](../../.claude/agents/HEARTBEAT.md) — §0 (pc.sh), §4ג–§4ד (wake CEO + payload). +- [web/paperclip_api.py](../../web/paperclip_api.py) — `pc_request`, `emit_case_status_webhook`. +- [scripts/pc.sh](../../scripts/pc.sh) — helper ה-bash.