fix(sync): onWebhook גוזר תוויות-סטטוס מ-/api/status-model (legal-ai #616)
מפת-התוויות העברית הקשיחה ב-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>
This commit is contained in:
@@ -115,3 +115,36 @@ export function labelFor(
|
||||
): string | null {
|
||||
return statuses.find((s) => s.key === caseStatus)?.label ?? null;
|
||||
}
|
||||
|
||||
/** למה נפלנו לערך-הגולמי, או `null` כשהסטטוס מוכר. הקורא **חייב** לדווח על ערך לא-null. */
|
||||
export type StatusLabelFallback = "unknown_status" | "model_unavailable" | null;
|
||||
|
||||
export interface StatusLabelResolution {
|
||||
label: string;
|
||||
fallback: StatusLabelFallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* התווית להצגה עבור `caseStatus`, יחד עם **סיבת** נפילה-לגולמי אם הייתה.
|
||||
*
|
||||
* ההחלטה מופרדת כאן מהדיווח (`ctx.logger`) בכוונה: `worker.ts` אינו ניתן
|
||||
* ל-import בטסט (הוא מריץ `runWorker(...)` ברמת-המודול), ולכן ההיגיון שחייב
|
||||
* כיסוי-טסט חי במודול הטהור הזה — בדיוק כמו `pickSyncTargetIssue` (#446)
|
||||
* ו-`resolveIssueStatus` (#604). legal-ai issue #616.
|
||||
*
|
||||
* `statuses === null` = מודל-הסטטוסים לא נטען כלל (כשל-רשת) — נבדל מסטטוס
|
||||
* שנטען ולא נמצא, כי הן שתי תקלות שונות עם אבחון שונה.
|
||||
*/
|
||||
export function resolveStatusLabel(
|
||||
statuses: readonly StatusModelEntry[] | null,
|
||||
caseStatus: string,
|
||||
): StatusLabelResolution {
|
||||
if (statuses === null) {
|
||||
return { label: caseStatus, fallback: "model_unavailable" };
|
||||
}
|
||||
const label = labelFor(statuses, caseStatus);
|
||||
if (label === null) {
|
||||
return { label: caseStatus, fallback: "unknown_status" };
|
||||
}
|
||||
return { label, fallback: null };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user