V2-CH2-001 — דוח ביצוע Shadow
כרך 2 · פרק 2 · משימה 001 · ישות Candidate — יישום Shadow ואימות
סיכום ביצוע
Shadow הופעל בהצלחה: ישות Candidate נוצרת בכל סריקת סוכן, מבוצעת דה-דופליקציה (Best Effort), מקושרת ל-AnalysisOutput, ומתוחזקת אוטומטית (תפוגה + Reconciliation). הנתיב הקיים (סוכן → Decision) לא השתנה. מעבר ל-Enforced חסום עד סגירת C2 (אטימיות) ו-V2-CH5 (Evidence Fusion).
רכיבים שמומשו
| # | רכיב | קובץ | ראיה | סטטוס |
|---|---|---|---|---|
| 1 | ישות Candidate | base44/entities/Candidate.jsonc | סכמה מלאה: symbol, market, asset_id, dedup_key, idempotency_key, expected_agent_ids, analysis_deadline, status (6 מצבים כולל promoting), promotion_locked_at, decision_id, expires_at. כל השדות הנדרשים ב-C1 מוגדרים. | PASS |
| 2 | candidateService.ts | base44/shared/candidateService.ts | 7 פונקציות: resolveAssetId(), createCandidate() (דה-דופליקציה No-Op + AuditEvent), queueForAnalysis(), evaluateCandidate(), promoteCandidate() (PROMOTING אטומי), sweepExpired(), reconcileStuckPromoting() (60s timeout). | PASS |
| 3 | AnalysisOutput.candidate_id | base44/entities/AnalysisOutput.jsonc | שדה candidate_id נוסף לישות AnalysisOutput. מקור האמת לקישור — AnalysisOutput.candidate_id (לא מערך נגזר ב-Candidate). | PASS |
| 4 | שילוב ב-runAgentScan | base44/functions/runAgentScan/entry.ts | createCandidate() נקרא לאחר analyzeAgent() בנוסף לנתיב הקיים (תוספתי, לא-חוסם). createDecision הקיים לא השתנה. Candidate נוצר ב-shadow ומקושר ל-AnalysisOutput. | PASS |
| 5 | sweepCandidates (פונקציה) | base44/functions/sweepCandidates/entry.ts | פונקציית תחזוקה: קוראת ל-sweepExpired() + reconcileStuckPromoting(). נבדקה — החזירה { expired: 3, reconciled: 0 }. | PASS |
| 6 | Candidate Lifecycle Sweep (workflow) | base44/workflows/Candidate Lifecycle Sweep.jsonc | Workflow מתוזמן כל 5 דקות (interval) הקורא ל-sweepCandidates. עובד אוטומטית — Candidates שפג תוקפם מנוקים ללא התערבות ידנית. | PASS |
תוצאות בדיקה
runAgentScan (סריקת סוכן): Candidate נוצר בהצלחה עם asset_id קנוני, dedup_key, idempotency_key, expected_agent_ids (צילום קבוע). AnalysisOutput מקושר דרך candidate_id.
sweepCandidates (תחזוקה): נבדק — החזיר { expired: 3, reconciled: 0 }. 3 Candidates שפג תוקפם עברו ל-expired. 0 תקועים ב-PROMOTING.
Workflow (Candidate Lifecycle Sweep): פעיל — רץ כל 5 דקות אוטומטית.
דה-דופליקציה (Best Effort): filter + return existing פעיל אך אינו אטומי — עלול לאפשר כפילויות במקביל. מסומן EXCEPTION ב-C2.
סיכום קריטריוני קבלה
| מזהה | סוג | תוצאה | הערה |
|---|---|---|---|
| C1 | Schema | PASS | ישות Candidate עם כל השדות הנדרשים |
| C2 | Dedup | EXCEPTION | Best Effort ב-Shadow — filter+return אינו אטומי. חסום מ-Enforced עד Unique/Atomic Upsert או Claim ייחודי |
| C3 | Linkage | PASS | AnalysisOutput.candidate_id כמקור אמת |
| C4 | Promotion | PASS | quorum = ceil(60% × |expected_agent_ids|). אין קידום אוטומטי מחמת תום זמן |
| C5 | Linkage | PASS | Shadow: מקשר ל-Decision הקיים, לא יוצר מקביל |
| C6 | Invariant | PASS | PROMOTING אטומי + reconcileStuckPromoting (60s) |
| C7 | TTL | PASS | sweepExpired פעיל + workflow מתוזמן |
| C8 | Mode | PASS | Shadow אינו יוצר Decision מקביל |
| C9 | Invariant | PASS | Candidate אינו Risk Gate — אין וטו |
| C10 | Audit | PARTIAL | AuditEvent נרשם בדה-דופליקציה. נדרש אימות נוסף לכל מעברי המצב |
חסמים למעבר ל-Enforced
הפלטפורמה (Base44) אינה מספקת Unique/Atomic Upsert על dedup_key/idempotency_key. המנגנון הנוכחי (filter + return existing) אינו אטומי ועלול לאפשר יצירת כפילויות במקביל. פתרון אפשרי: ישות Claim נפרדת עם מפתח ייחודי, או מנעול אופטימי (CAS) על סטטוס. עד אז — Best Effort ב-Shadow בלבד.
קידום Candidate (promoteCandidate) מצפה לאיחוד ראיות מרובה-סוכנים. כיום קידום מתבצע על בסיס רוב פשוט (majority vote). מעבר מלא ל-Fusion ב-V2-CH5.
AuditEvent נרשם בדה-דופליקציה (DUPLICATE_DISCOVERY_ATTEMPT). נדרש אימות שכל מעבר מצב (discovered→analyzing→promoting→promoted/rejected/expired) רושם AuditEvent עם פרטי המעבר.