fix(sync): onWebhook statusLabels נגזרת מ-/api/status-model (legal-ai #616) #13

Merged
chaim merged 1 commits from fix/616-onwebhook-status-labels into main 2026-09-01 20:35:00 +00:00
Owner

מטפל ב-legal-ai #616
(‏issue-tracker בריפו האחר; אסגור אותו ידנית אחרי המיזוג — Gitea לא סוגר cross-repo).
המשך ישיר ל-#604 / plugin PR#9.

מה תוקן ולמה

בפלאגין היו שתי מפות-תוויות עבריות לאותו enum של סטטוסי-תיק. ‏#604 גזר את הראשונה
(CASE_STATUS_TO_ISSUE_STATUS, הג'וב sync-case-status) מ-GET /api/status-model.
השנייה — statusLabels בתוך onWebhook — נשארה קשיחה, וכבר הספיקה לסטות.

מדידת-הדריפט מול 12 הסטטוסים החיים (case_status_model.py:51-64):

מצב מפתחות
מכוסים נכון new · processing · documents_ready · outcome_set · direction_approved · qa_review · drafted · exported · reviewed · final (10)
חסרו — נפלו לערך-גולמי analyst_verified · research_complete (2) — נוספו למודל ב-30.6
☠️ מתים — לא קיימים במודל uploading · brainstorming · drafting (הוסרו מה-enum) · in_progress (זה issue.status של Paperclip, לא case.status — ערבוב-מושגים) · qa_failed (הוסר מפורשות; web/app.py:7396: "pass→drafted, fail→qa_review; הסטטוס qa_failed הוסר")

כלומר: תיק שנכנס ל-analyst_verified הפיק תגובה בעברית שבתוכה מפתח-אנגלית גולמי, בלי שום
עקבה בלוג שמשהו חסר.

label כבר קיים ב-SSOT (StatusDef.labelto_dict()/api/status-model), ו-labelFor()
מ-#604 כבר קוראת אותו. ⇒ legal-ai לא נגע כלל בשינוי הזה — אין endpoint חדש, אין שדה חדש,
אפס סמל ספציפי-Paperclip נכנס לשכבת-האינטליגנציה (G12).

טבלת AC ↔ מימוש

# קריטריון (legal-ai #616) מימוש איפה
1 statusLabels נמחקת, מוחלפת בגזירה מ-/api/status-model 17 שורות המפה הוסרו; labelFor() הקיימת היא הלוקאפ היחיד worker.ts (היה 1707-1724)
2 אין רשימת-סטטוסים קשיחה — grep מחזיר רק שימושים שלוש רשימות הוסרו, לא אחת: המפה, ה-enum המיושן ב-legal_case_update, והמנייה בתיאור legal_case_list. grep על 9 שמות-סטטוס → 0 תוצאות worker.ts:114 · 257-261 · 1707-1723
3 סטטוס לא-מוכר → warning + ערך-גולמי, בלי בליעה logger.warn עם caseNumber+newStatus; ובנפרד שני logger.error לשתי הסיבות ל"מודל לא-זמין" (כשל-רשת · LegalApi חסר) worker.ts
4 טסט: סטטוס שאינו במודל → warning, לא קורס 4 טסטים חדשים ל-resolveStatusLabel (מוכר / לא-מוכר + doesNotThrow / מודל-null / רגרסיה על 10 המפתחות ששרדו) sync-target.test.ts

החלטות-תכנון שראוי לשים לב אליהן

  • resolveStatusLabel ב-sync-target.ts, לא ב-worker.ts. היא מרכיבה את labelFor
    הקיימת (לא מחפשת שוב — אין כפילות-לוגיקה), ומחזירה גם את סיבת הנפילה-לגולמי
    ("unknown_status" / "model_unavailable" / null). ההפרדה בין ההחלטה לדיווח
    היא מה שמאפשר כיסוי-טסט: worker.ts אינו ניתן ל-import בטסט (מריץ runWorker() ברמת-המודול)
    — בדיוק הסיבה ש-sync-target.ts קיים כמודול-טהור מאז #446.
  • שתי סיבות model_unavailable נבדלות, ושתיהן משאירות עקבה. תווית חסרה אינה מפילה
    את ה-webhook: התגובה עדיין מתפרסמת והערת-ה-CEO עדיין נורית, עם מפתח גולמי + error בלוג.
    לזרוק כאן היה מחליף באג-שקט בהשבתת-תפקוד.
  • הסרת ה-enum מ-legal_case_update היא הסרת-דריפט, לא הרפיית-ולידציה. המניפסט —
    מה שהסוכנים באמת רואים — כבר מגדיר status: { type: "string" } בלי enum (manifest.ts:103);
    ה-enum ב-worker.ts חסם 8 סטטוסים חוקיים והתיר אחד שאינו קיים. גזירתו מ-/api/status-model
    בלתי-אפשרית מבנית: הרישום קורה ב-setup(), שאין לו הקשר-חברה ולכן אינו יכול לפתור את
    legalApiBaseUrl (מתועד ב-worker.ts:41-70 — זה מה ששבר את הפלאגין ב-722.0). הוולידציה
    האמיתית היא server-side.
  • ויתור מודע על האימוג'י. תוויות ה-SSOT הן טקסט נקי ("בעיבוד"), הקשיחות נשאו אימוג'י
    ("⚙️ עיבוד מסמכים"). לא הוספתי שדה-אימוג'י ל-case_status_model.py — זה היה מכניס עיטור
    ספציפי-לצרכן ל-SSOT. מסלול-חזרה: אם ירצו אימוג'י, הוא חוזר בשורה אחת בפלאגין
    (Record<phase, emoji> לפי phase שכבר מגיע ב-/api/status-model), בלי לגעת ב-legal-ai.
  • oldStatus מתורגם אף הוא (היה מודפס כמפתח-אנגלית בתוך משפט עברי), אך בלי לוג —
    הוא ריק לגיטימית בתיק-חדש ועשוי להיות מפתח היסטורי מת; warn עליו היה רעש כרוני.
  • legalApi עשה hoist ליד pluginCtx ומועבר מפורשות כפרמטר ל-handleCaseStatusWebhook.
    onWebhook הוא מתודה-אחות של setup() ואין לו closure על המופע. אותו מופע, לא שני.
  • newStatus === "qa_failed" לא נגעתי בו — הוא שימוש ולא הגדרה. עם התיקון הוא נעשה
    גלוי: מסירה כזו תפיק warn מפורש במקום ליפול בשקט על תווית קשיחה.

שער (אין CI בריפו הזה — האימות הוא ההרצה, והורצה פעמיים ע"י wf-lead)

npx tsc --noEmit        → exit 0, פלט ריק
npx biome check src/    → Checked 13 files in 37ms. No fixes applied.
node --test src/*.test.ts → tests 39 / pass 39 / fail 0     (בסיס לפני: 35/35)
grep -nE '(uploading|brainstorming|drafting|documents_ready|outcome_set|
           direction_approved|qa_review|analyst_verified|research_complete)' src/worker.ts
                        → 0 תוצאות   ← AC2

‏35 הטסטים הקיימים עברו ללא שינוי באסרשנים שלהם; הפיקסצ'ר STATUS_MODEL כבר הכיל את כל 12
הסטטוסים ולא נזקק להרחבה (טסט-הרגרסיה מסנן לפיו — ואומת שהוא מכסה את כל 10 המפתחות, אינו ריק).

Invariants

G2 — מקור-אמת אחד לתוויות-הסטטוס, ושני צרכני-הפלאגין (sync-case-status מ-#604
ו-onWebhook כאן) קוראים אותו דרך אותה פונקציה ואותו endpoint. לא נוצר מודול/endpoint/מטמון
מקביל. · G1 — נרמול-במקור: הפלאגין מפסיק להחזיק העתק-סטטי ומושך מה-SSOT; לא תיקון-תסמין
בצד-התצוגה. · G12legal-ai לא נגע כלל. · X7 INV-INT9onWebhook עדיין אינו
כותב issue.status (רק createComment + agents.invoke); "המשבצת השמורה" נשארת שמורה. ·
כלל-הנדסה §6 — אין בליעה שקטה בשום נתיב.

🚧 פריסה — לא בוצעה, ממתינה להחלטת חיים

הפלאגין אינו נטען מ-git; מיזוג לבדו אינו מפעיל כלום:

cd ~/plugin-legal-ai && git pull && npm run build
npx paperclipai plugin uninstall marcusgroup.legal-ai --api-base http://localhost:3100 --api-key <key>
npx paperclipai plugin install ~/plugin-legal-ai      --api-base http://localhost:3100 --api-key <key>
pm2 restart paperclip

(‏ב-#604 שלב-ההתקנה הוחמץ פעם אחת וה-restart הפעיל את הקוד הישן.)
זו פעולה על שירות-ייצור חי — לא בוצעה אוטומטית.

פער שנפתח כ-issue המשך (לא נבלע)

worker.ts — הענף newStatus === "qa_failed" (עֵרָת-CEO בכשל-QA) הוא ככל הנראה קוד-מת:
הסטטוס הוסר מ-case_status_model.py, והנתיב החי הוא qa_review. שינוי-ההתנהגות מחוץ ל-AC
של #616 ודורש הכרעה (להעביר את הטריגר ל-qa_review? או שכשל-QA כבר לא אמור להעיר CEO?).
התיקון הזה הופך אותו לגלוי במקום להסתירו.

מטפל ב-**[legal-ai #616](https://gitea.nautilus.marcusgroup.org/ezer-mishpati/legal-ai/issues/616)** (‏issue-tracker בריפו האחר; אסגור אותו ידנית אחרי המיזוג — Gitea לא סוגר cross-repo). המשך ישיר ל-#604 / plugin PR#9. ## מה תוקן ולמה בפלאגין היו **שתי** מפות-תוויות עבריות לאותו enum של סטטוסי-תיק. ‏#604 גזר את הראשונה (`CASE_STATUS_TO_ISSUE_STATUS`, הג'וב `sync-case-status`) מ-`GET /api/status-model`. השנייה — `statusLabels` בתוך `onWebhook` — נשארה קשיחה, וכבר הספיקה לסטות. **מדידת-הדריפט** מול 12 הסטטוסים החיים (`case_status_model.py:51-64`): | מצב | מפתחות | |---|---| | ✅ מכוסים נכון | `new` · `processing` · `documents_ready` · `outcome_set` · `direction_approved` · `qa_review` · `drafted` · `exported` · `reviewed` · `final` (10) | | ❌ **חסרו** — נפלו לערך-גולמי | `analyst_verified` · `research_complete` (2) — נוספו למודל ב-30.6 | | ☠️ **מתים** — לא קיימים במודל | `uploading` · `brainstorming` · `drafting` (הוסרו מה-enum) · `in_progress` (זה `issue.status` של Paperclip, לא `case.status` — ערבוב-מושגים) · `qa_failed` (הוסר מפורשות; `web/app.py:7396`: *"pass→drafted, fail→qa_review; הסטטוס qa_failed הוסר"*) | כלומר: תיק שנכנס ל-`analyst_verified` הפיק תגובה בעברית שבתוכה מפתח-אנגלית גולמי, בלי שום עקבה בלוג שמשהו חסר. **‏`label` כבר קיים ב-SSOT** (`StatusDef.label` → `to_dict()` → `/api/status-model`), ו-`labelFor()` מ-#604 כבר קוראת אותו. ⇒ **‏`legal-ai` לא נגע כלל בשינוי הזה** — אין endpoint חדש, אין שדה חדש, אפס סמל ספציפי-Paperclip נכנס לשכבת-האינטליגנציה (G12). ## טבלת AC ↔ מימוש | # | קריטריון (legal-ai #616) | מימוש | איפה | |---|---|---|---| | 1 | `statusLabels` נמחקת, מוחלפת בגזירה מ-`/api/status-model` | ✅ 17 שורות המפה הוסרו; `labelFor()` הקיימת היא הלוקאפ היחיד | `worker.ts` (היה 1707-1724) | | 2 | אין רשימת-סטטוסים קשיחה — `grep` מחזיר רק שימושים | ✅ **שלוש** רשימות הוסרו, לא אחת: המפה, ה-`enum` המיושן ב-`legal_case_update`, והמנייה בתיאור `legal_case_list`. `grep` על 9 שמות-סטטוס → **0 תוצאות** | `worker.ts:114` · `257-261` · `1707-1723` | | 3 | סטטוס לא-מוכר → warning + ערך-גולמי, בלי בליעה | ✅ `logger.warn` עם `caseNumber`+`newStatus`; ובנפרד **שני** `logger.error` לשתי הסיבות ל"מודל לא-זמין" (כשל-רשת · `LegalApi` חסר) | `worker.ts` | | 4 | טסט: סטטוס שאינו במודל → warning, לא קורס | ✅ 4 טסטים חדשים ל-`resolveStatusLabel` (מוכר / לא-מוכר + `doesNotThrow` / מודל-`null` / רגרסיה על 10 המפתחות ששרדו) | `sync-target.test.ts` | ## החלטות-תכנון שראוי לשים לב אליהן - **‏`resolveStatusLabel` ב-`sync-target.ts`, לא ב-`worker.ts`.** היא **מרכיבה** את `labelFor` הקיימת (לא מחפשת שוב — אין כפילות-לוגיקה), ומחזירה גם את **סיבת** הנפילה-לגולמי (`"unknown_status"` / `"model_unavailable"` / `null`). ההפרדה בין ה**החלטה** ל**דיווח** היא מה שמאפשר כיסוי-טסט: `worker.ts` אינו ניתן ל-import בטסט (מריץ `runWorker()` ברמת-המודול) — בדיוק הסיבה ש-`sync-target.ts` קיים כמודול-טהור מאז #446. - **שתי סיבות `model_unavailable` נבדלות, ושתיהן משאירות עקבה.** תווית חסרה **אינה** מפילה את ה-webhook: התגובה עדיין מתפרסמת והערת-ה-CEO עדיין נורית, עם מפתח גולמי + `error` בלוג. לזרוק כאן היה מחליף באג-שקט בהשבתת-תפקוד. - **הסרת ה-`enum` מ-`legal_case_update` היא הסרת-דריפט, לא הרפיית-ולידציה.** המניפסט — מה שהסוכנים באמת רואים — כבר מגדיר `status: { type: "string" }` בלי `enum` (`manifest.ts:103`); ה-`enum` ב-`worker.ts` חסם 8 סטטוסים **חוקיים** והתיר אחד שאינו קיים. גזירתו מ-`/api/status-model` **בלתי-אפשרית מבנית**: הרישום קורה ב-`setup()`, שאין לו הקשר-חברה ולכן אינו יכול לפתור את `legalApiBaseUrl` (מתועד ב-`worker.ts:41-70` — זה מה ששבר את הפלאגין ב-722.0). הוולידציה האמיתית היא server-side. - **ויתור מודע על האימוג'י.** תוויות ה-SSOT הן טקסט נקי ("בעיבוד"), הקשיחות נשאו אימוג'י ("⚙️ עיבוד מסמכים"). לא הוספתי שדה-אימוג'י ל-`case_status_model.py` — זה היה מכניס עיטור ספציפי-לצרכן ל-SSOT. **מסלול-חזרה:** אם ירצו אימוג'י, הוא חוזר בשורה אחת **בפלאגין** (`Record<phase, emoji>` לפי `phase` שכבר מגיע ב-`/api/status-model`), בלי לגעת ב-legal-ai. - **‏`oldStatus` מתורגם אף הוא** (היה מודפס כמפתח-אנגלית בתוך משפט עברי), אך **בלי** לוג — הוא ריק לגיטימית בתיק-חדש ועשוי להיות מפתח היסטורי מת; `warn` עליו היה רעש כרוני. - **‏`legalApi` עשה hoist ליד `pluginCtx`** ומועבר מפורשות כפרמטר ל-`handleCaseStatusWebhook`. ‏`onWebhook` הוא מתודה-אחות של `setup()` ואין לו closure על המופע. **אותו מופע, לא שני.** - **‏`newStatus === "qa_failed"` לא נגעתי בו** — הוא *שימוש* ולא הגדרה. עם התיקון הוא נעשה **גלוי**: מסירה כזו תפיק `warn` מפורש במקום ליפול בשקט על תווית קשיחה. ## שער (אין CI בריפו הזה — האימות הוא ההרצה, והורצה פעמיים ע"י wf-lead) ``` npx tsc --noEmit → exit 0, פלט ריק npx biome check src/ → Checked 13 files in 37ms. No fixes applied. node --test src/*.test.ts → tests 39 / pass 39 / fail 0 (בסיס לפני: 35/35) grep -nE '(uploading|brainstorming|drafting|documents_ready|outcome_set| direction_approved|qa_review|analyst_verified|research_complete)' src/worker.ts → 0 תוצאות ← AC2 ``` ‏35 הטסטים הקיימים עברו ללא שינוי באסרשנים שלהם; הפיקסצ'ר `STATUS_MODEL` כבר הכיל את כל 12 הסטטוסים ולא נזקק להרחבה (טסט-הרגרסיה מסנן לפיו — ואומת שהוא מכסה את כל 10 המפתחות, אינו ריק). ## Invariants **G2** — מקור-אמת אחד לתוויות-הסטטוס, ושני צרכני-הפלאגין (`sync-case-status` מ-#604 ו-`onWebhook` כאן) קוראים אותו דרך **אותה** פונקציה ואותו endpoint. לא נוצר מודול/endpoint/מטמון מקביל. · **G1** — נרמול-במקור: הפלאגין מפסיק להחזיק העתק-סטטי ומושך מה-SSOT; לא תיקון-תסמין בצד-התצוגה. · **G12** — `legal-ai` לא נגע כלל. · **X7 INV-INT9** — `onWebhook` עדיין **אינו** כותב `issue.status` (רק `createComment` + `agents.invoke`); "המשבצת השמורה" נשארת שמורה. · **כלל-הנדסה §6** — אין בליעה שקטה בשום נתיב. ## 🚧 פריסה — לא בוצעה, ממתינה להחלטת חיים הפלאגין **אינו נטען מ-git**; מיזוג לבדו אינו מפעיל כלום: ```bash cd ~/plugin-legal-ai && git pull && npm run build npx paperclipai plugin uninstall marcusgroup.legal-ai --api-base http://localhost:3100 --api-key <key> npx paperclipai plugin install ~/plugin-legal-ai --api-base http://localhost:3100 --api-key <key> pm2 restart paperclip ``` (‏ב-#604 שלב-ההתקנה הוחמץ פעם אחת וה-restart הפעיל את הקוד הישן.) זו פעולה על שירות-ייצור חי — **לא בוצעה אוטומטית.** ## פער שנפתח כ-issue המשך (לא נבלע) `worker.ts` — הענף `newStatus === "qa_failed"` (עֵרָת-CEO בכשל-QA) הוא ככל הנראה **קוד-מת**: הסטטוס הוסר מ-`case_status_model.py`, והנתיב החי הוא `qa_review`. שינוי-ההתנהגות מחוץ ל-AC של #616 ודורש הכרעה (להעביר את הטריגר ל-`qa_review`? או שכשל-QA כבר לא אמור להעיר CEO?). התיקון הזה **הופך אותו לגלוי** במקום להסתירו.
chaim added 1 commit 2026-09-01 20:34:53 +00:00
מפת-התוויות העברית הקשיחה ב-onWebhook הייתה מקור-האמת השני לאותו enum
של סטטוסי-תיק, והספיקה לסטות: חסרו בה analyst_verified ו-research_complete
(שנוספו ל-case_status_model.py ב-30.6), והיו בה חמישה מפתחות מתים —
uploading, brainstorming, drafting, in_progress ו-qa_failed. #604 תיקן את
המפה הראשונה (CASE_STATUS_TO_ISSUE_STATUS); זו השנייה.

- statusLabels נמחקת; התווית נגזרת מ-GET /api/status-model דרך labelFor()
  הקיימת — אותו SSOT שהג'וב sync-case-status כבר צורך (G2).
- resolveStatusLabel() חדשה ב-sync-target.ts (המודול הטהור): מרכיבה את
  labelFor ומחזירה גם את **סיבת** הנפילה-לגולמי, כדי שההיגיון יהיה
  בר-בדיקה — worker.ts אינו ניתן ל-import בטסט (runWorker ברמת-המודול).
- אין בליעה שקטה (כלל-הנדסה §6): סטטוס שנטען ואינו במודל → logger.warn עם
  מספר-התיק והסטטוס; מודל שלא נטען כלל (כשל-רשת או LegalApi חסר) → שני
  logger.error נבדלים. בכל המקרים התגובה עדיין מתפרסמת והערת-ה-CEO עדיין
  נורית — תווית חסרה אינה מפילה webhook.
- oldStatus מתורגם אף הוא לתווית (היה מודפס כמפתח-אנגלית בתוך משפט עברי).
- legalApi עשה hoist ליד pluginCtx ומועבר מפורשות ל-handleCaseStatusWebhook
  — אותו מופע, לא שני.
- AC2: הוסרו שתי רשימות-הסטטוסים הקשיחות הנוספות ב-worker.ts — ה-enum
  המיושן ב-legal_case_update (חסם 8 סטטוסים חוקיים והתיר את in_progress
  שאינו case.status; המניפסט כבר מגדיר את השדה כמחרוזת חופשית, והוולידציה
  האמיתית היא server-side) והמנייה בתיאור legal_case_list.

Invariants: G2 (מקור-אמת אחד לתוויות, בשני הצרכנים) · G1 (נרמול-במקור —
הפלאגין מושך מה-SSOT במקום להחזיק העתק סטטי) · G12 (legal-ai לא נגע כלל;
אפס סמל ספציפי-Paperclip נכנס אליו) · X7 INV-INT9 (onWebhook עדיין אינו
כותב issue.status — המשבצת השמורה נשארת שמורה) · כלל-הנדסה §6.

tsc --noEmit נקי · biome check src/ נקי · node --test 39/39.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
chaim merged commit 314745d7a7 into main 2026-09-01 20:35:00 +00:00
chaim deleted branch fix/616-onwebhook-status-labels 2026-09-01 20:35:00 +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/plugin-legal-ai#13