fix(sync): סנכרון-הסטטוס יכתוב רק על issue-שורש פתוח, לא על sub-task שנסגר (#446)
הג'וב sync-case-status כתב על כל issue מקושר-לתיק שסטטוסו שונה מהיעד — השומר היחיד היה 'issue.status !== targetStatus'. לכן הוא החזיר sub-tasks שנסגרו (done) ל-in_progress, כפי שתועד ב-CMPA-140 / תיק 8125-09-24. בחירת-היעד חולצה ל-src/sync-target.ts: נבחר שורש יחיד (parentId === null) שאינו done/cancelled ואינו in_review/blocked; אפס או יותר-מאחד → לא נכתב דבר, ונרשם לוג עם הסיבה. שני הקבועים מובחנים בכוונה — CLOSED_ISSUE_STATUSES הוא ההגדרה היחידה של 'סגור' (מראה של legal-ai/web/paperclip_client.py:561), ו-NON_WRITABLE_STATUSES הוא 'מצב בבעלות Paperclip/בהמתנה-לאדם': כתיבת in_progress על issue ב-in_review מזמינה auto-block תוך דקה (legal-ai/docs/paperclip-quirks.md §3) וגונבת אותו מתור-הביקורת של היו"ר. הלולאה הכפולה הוחלפה במעבר אחד + קיבוץ למפת case_number→מועמדים; הקיבוץ נדרש כדי לבחור 'השורש היחיד', וכתוצר-לוואי O(NxM)→O(M) קריאות state.get. מבחן-רגרסיה (node --test, ללא תלויות חדשות) משחזר את 11 ה-issues שנמדדו לתיק 8125-09-24 ומוכיח את הדלתא: הכלל הישן היה כותב על CMPA-140, החדש לא. בבעלות ריפו זה בהחלטת פאנל — העברת הלוגיקה ל-legal-ai נפסלה כי היא מוסיפה UPDATE ישיר ל-DB של Paperclip, בהפרת INV-INT4 (GAP-24/25).
This commit is contained in:
79
src/sync-target.ts
Normal file
79
src/sync-target.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* בחירת יעד-הסנכרון של הג'וב `sync-case-status` (issue #446).
|
||||
*
|
||||
* המקור לבאג: הג'וב הישן כתב על **כל** issue מקושר-לתיק, כולל sub-issues
|
||||
* ו-issues בסטטוסים סגורים/בהמתנה-לאדם. זה החזיר CMPA-140 מ-`done` ל-`in_progress`
|
||||
* (8125-09-24) — כי sub-task שהושלם עדיין נמנה כ"מקושר".
|
||||
*
|
||||
* המודול הזה טהור בכוונה — בלי import מה-SDK ובלי side effects — כדי שאפשר
|
||||
* יהיה לייבא אותו ישירות בטסט בלי להריץ את `runWorker(plugin, import.meta.url)`
|
||||
* שקורה בזמן import של worker.ts.
|
||||
*/
|
||||
|
||||
/** "סגור" — ההגדרה היחידה. מראה של web/paperclip_client.py:561 ב-legal-ai. */
|
||||
export const CLOSED_ISSUE_STATUSES: ReadonlySet<string> = new Set([
|
||||
"done",
|
||||
"cancelled",
|
||||
]);
|
||||
|
||||
/**
|
||||
* מצב בבעלות Paperclip / בהמתנה-לאדם — **לא** הגדרה שנייה של "סגור".
|
||||
* `in_review` הוא מצב-ההמתנה-ליו"ר המכוון: CEO שמשאיר issue ב-`in_progress`
|
||||
* מקבל auto-block מ-Paperclip תוך דקה, ולכן הוא מעביר ל-`in_review`
|
||||
* (legal-ai/docs/paperclip-quirks.md §3). כתיבת-סטטוס אוטומטית עליו גונבת את
|
||||
* ה-issue מתור-הביקורת של היו"ר ומזמינה קרב-סטטוסים.
|
||||
* `blocked` — כתיבה עליו מסתירה חוסם קיים.
|
||||
*/
|
||||
export const NON_WRITABLE_STATUSES: ReadonlySet<string> = new Set([
|
||||
"in_review",
|
||||
"blocked",
|
||||
]);
|
||||
|
||||
export interface SyncCandidate {
|
||||
id: string;
|
||||
status: string;
|
||||
parentId: string | null;
|
||||
}
|
||||
|
||||
export type SyncTargetReason =
|
||||
| "ok"
|
||||
| "no_linked_issues"
|
||||
| "no_writable_root"
|
||||
| "ambiguous_writable_roots";
|
||||
|
||||
export interface SyncTargetResult {
|
||||
target: SyncCandidate | null;
|
||||
reason: SyncTargetReason;
|
||||
/** כמה שורשים ברי-כתיבה נמצאו — לצורך לוג. */
|
||||
writableRoots: number;
|
||||
}
|
||||
|
||||
export function isWritableStatus(status: string): boolean {
|
||||
return (
|
||||
!CLOSED_ISSUE_STATUSES.has(status) && !NON_WRITABLE_STATUSES.has(status)
|
||||
);
|
||||
}
|
||||
|
||||
export function pickSyncTargetIssue(
|
||||
candidates: readonly SyncCandidate[],
|
||||
): SyncTargetResult {
|
||||
if (candidates.length === 0) {
|
||||
return { target: null, reason: "no_linked_issues", writableRoots: 0 };
|
||||
}
|
||||
|
||||
const roots = candidates.filter(
|
||||
(c) => c.parentId === null && isWritableStatus(c.status),
|
||||
);
|
||||
|
||||
if (roots.length === 1) {
|
||||
return { target: roots[0], reason: "ok", writableRoots: 1 };
|
||||
}
|
||||
if (roots.length === 0) {
|
||||
return { target: null, reason: "no_writable_root", writableRoots: 0 };
|
||||
}
|
||||
return {
|
||||
target: null,
|
||||
reason: "ambiguous_writable_roots",
|
||||
writableRoots: roots.length,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user