InvestIQ

InvestIQ

ניהול השקעות אוטומטי 1.0

דאשבורדהתראותתיק השקעותחיפוש וניתוח ניירות ערךעסקאותהודעותשווקיםדוח כספים יומישאלות ותשובותשאל את המערכתדוחות מסהגדרותמדריךדף הנחייה למערכת
מצב: PAPER TRADING
info@investiq.co.il054-4860090
מפרטים ומסמכים/כרך 2/V2-CH2-001

V2-CH2-001 — חבילת Candidate רשמית

כרך 2 · פרק 2 · משימה 001 · ישות Candidate ושלב קדם-ניתוח בצינור ההחלטה

v1.0 · Shadow Active · 11.8.2026

מטרת המסמך

הגדרת ישות Candidate כשלב רשמי בצינור ההחלטה — הזדמנות מזוהה ולא-כפולה שממתינה לניתוח מרובה-סוכנים לפני יצירת Decision. כיום (As-Is, V2-CH1-001) כל סריקה יוצרת Trade/Decision ישירות מפלט של סוכן בודד, ללא דה-דופליקציה וללא המתנה להשלמת כל הסוכנים. פער זה (G6) מונע את מנוע ההחלטה המרובה-סוכנים שכרך 2 בונה. מסמך זה מגדיר את הסכמה, מחזור החיים, לוגיקת הדה-דופליקציה, נקודות השילוב וקריטריוני הקבלה.

הבעיה (G6 — מתוך V2-CH1-001)

אין דה-דופליקציה: שני סוכנים שונים (למשל US Scanner ו-Technical Analyst) עשויים לזהות את אותו symbol באותו cycle. כיום כל אחד יוצר Decision משלו — נוצרות שתי הצעות כפולות לאותו נכס.

אין המתנה להשלמת ניתוח: Decision נוצר מיד כשסוכן בודד מסיים, לפני ששאר הסוכנים סיימו. אין איחוד דעות.

אין תיעוד הזדמנות: אם סוכן זיהה הזדמנות אך היא לא הפכה ל-Decision — אין עקבות. Candidate משמר את היסטוריית הגילוי לצורך audit ושיפור.

גבול כרך 1: ישות Candidate חדשה — אינה פוגעת ברכיבי כרך 1 הנעולים. השינויים הם: (1) ישות חדשה, (2) שדה candidate_id אופציונלי ב-DecisionData (shadow בלבד בשלב זה), (3) שדה candidate_id אופציונלי ב-AnalysisOutput (shadow). כרך 1 נשאר תקף ונעול.

מיפוי End-to-End — עם שלב Candidate ★

תרשים זרימה End-to-End — עם שלב Candidate ★

Trigger
runAgentScan
←
Agent (LLM)
analyzeAgent → InvokeLLM
←
Candidate ★
createCandidate (dedup, queue)
←
Multi-Agent Analysis
N × adaptAgentOutput
←
AnalysisOutput[]
conclusion, confidence, evidence
↓
Evidence Fusion
V2-CH5 (future)
←
Decision
createDecision + publishDecision
←
Risk Gate
assertDecisionActive + checkRiskGatePassed
←
Approval
checkApprovalValid
←
Execution
executeTrade
★ שלבים חדשים ב-V2-CH2 · Evidence Fusion (מקווקו) יטופל ב-V2-CH5 · שאר השלבים קיימים ונשארים ללא שינוי
שלבפאזהפונקציה · קובץקלט → פלטישות נשמרת
1
CIO_SCAN
Cron 10min / Manual
analyzeAgent()
runAgentScan/entry.ts:452
קלט: portfolioSymbols, agent config, LLM prompt
פלט: LLM result: { symbols[], sentiment, summary }
AgentLog, Message
1c ★
CANDIDATE_CREATE
After LLM result
createCandidate()
candidateService.ts (V2-CH2 new)
קלט: symbol → asset_id (AssetMaster), market, discovered_by, initial_score, expected_agent_ids (active+relevant)
פלט: Candidate (discovered) — dedup by asset_id+window. צילום expected_agent_ids נשמר.
Candidate ★
1d ★
CANDIDATE_DEDUP
Within createCandidate (atomic)
resolveAssetId() + idempotency check
candidateService.ts (V2-CH2 new)
קלט: symbol → asset_id, idempotency_key, active candidates
פלט: new Candidate OR No-Op (returns existing). AuditEvent נרשם על ניסיון כפול. אין רשומות SUPERSEDED
Candidate (no SUPERSEDED records)
2 ★
CANDIDATE_QUEUE
After dedup
queueForAnalysis()
candidateService.ts (V2-CH2 new)
קלט: Candidate (discovered, non-duplicate)
פלט: Candidate (status: analyzing) — ממתין ל-quorum מתוך expected_agent_ids (צילום קבוע)
Candidate
3 ★
MULTI_AGENT_ANALYSIS
Candidate queue populated
adaptAgentOutput() × N agents
agentAdapters.ts:11 (existing, reused)
קלט: Candidate + each agent LLM result
פלט: N × AnalysisOutput with candidate_id (unique on candidate_id+agent_id; Idempotent writes — no override after Quorum)
AnalysisOutput (candidate_id = source of truth)
4 ★
CANDIDATE_PROMOTE
Quorum (≥60% expected_agent_ids) הושג עד analysis_deadline
promoteCandidate()
candidateService.ts (V2-CH2 new)
קלט: Candidate (analyzing) + linked AnalysisOutputs
פלט: Candidate (status: promoting) — מעבר אטומי: יצירת Decision + עדכון decision_id + מעבר ל-PROMOTED בטרנזקציה אחת
Candidate (promoting→promoted)
4e ★
CANDIDATE_EXPIRE
analysis_deadline הגיע, quorum לא הושג
sweepExpired()
candidateService.ts (V2-CH2 new)
קלט: Candidate (analyzing) עם analysis_deadline שעבר
פלט: Candidate (status: expired) — אין קידום אוטומטי מחמת תום זמן
Candidate (status: expired)
4r ★
PROMOTING_RECONCILE
Candidate תקוע ב-PROMOTING > 60 שניות
reconcileStuckPromoting()
candidateService.ts (V2-CH2 new)
קלט: Candidate (status: promoting, promotion_locked_at stale)
פלט: השלמת קידום (אם Decision נוצר) או סימון כ-rejected. נעילה משוחררת. AuditEvent נרשם
Candidate (promoting→promoted/rejected)
5
DECISION
Candidate promoted (אטומי)
createDecision() + publishDecision()
decisionObject.ts:243,306 (existing, reused)
קלט: DecisionData from promoted Candidate + AnalysisOutputs
פלט: Decision: DRAFT → ACTIVE (frozen snapshot)
Decision
5a
TRADE_CREATION
After Decision published
createTradeWithAudit()
stateMachine.ts (existing)
קלט: Trade data + decision_id + candidate_id
פלט: Trade (pending)
Trade
5b
APPROVAL
After Trade creation
checkApprovalValid()
decisionObject.ts:617 (existing)
קלט: Decision (ACTIVE) + Trade (pending)
פלט: ApprovalRecord (approved / rejected)
ApprovalRecord
6
RISK_GATE
Before each execute
assertDecisionActive + checkRiskGatePassed
decisionObject.ts:569,607 (existing)
קלט: Decision (ACTIVE)
פלט: Pass / Fail (veto)
RiskVerdict
7
EXECUTION
After gates pass
executeTrade()
tradeExecution.ts (existing, reused)
קלט: Approved trades
פלט: Executed trades
Trade (executed)

סכמת ישות Candidate

שדהסוגחובהתיאור
symbolstringחובהסמל נכס (למשל NVDA, AAPL)
namestringאופציונלישם חברה
marketenumחובהTASE / NYSE / NASDAQ / LSE / TSE / HKEX
asset_typeenumאופציונליstock / etf / fund / bond / index / reit / commodity / crypto
discovered_bystringחובהשם הסוכן שזיהה את ההזדמנות
discovery_cycle_idstringחובהמזהה מחזור הסריקה (לקיבוץ Candidates מאותו cycle)
expected_agent_idsarray<string>חובהצילום קבוע של מזהי הסוכנים הצפויים בעת יצירת ה-Candidate. כולל רק סוכנים פעילים (status=active) ורלוונטיים לנכס ולשוק (לפי asset_types ו-market ב-AgentConfig). מקפיא את קבוצת הסוכנים למחזור זה. quorum = ceil(60% × |expected_agent_ids|). פלט תקף אחד בלבד לכל סוכן (ייחודיות על candidate_id+agent_id; כתיבה חוזרת Idempotent — אין דריסה לאחר השגת Quorum).
analysis_deadlinedate-timeחובהמועד אחרון להשלמת quorum. חייב להיות ≤ expires_at. אם לא הושג עד מועד זה — ה-Candidate עובר ל-EXPIRED (אין קידום אוטומטי מחמת תום זמן).
initial_scorenumber (0-10)אופציונליציון ביטחון ראשוני מהסוכן המגלה
initial_directionenumאופציונליbuy / sell / hold / investigate (ברירת מחדל: investigate)
initial_reasoningstringאופציונלינימוק ראשוני מהסוכן המגלה
current_pricenumberאופציונלימחיר בעת גילוי
statusenumחובהdiscovered / analyzing / promoting / promoted / rejected / expired
asset_idstring (ref)חובהמזהה קנוני מ-AssetMaster (CH5). משמש לדה-דופליקציה — מונע כפילויות הנובעות מכינויים וסימולים שונים לאותו נכס.
dedup_keystringחובהמפתח דה-דופליקציה: hash(asset_id + window_bucket). מבוסס על asset_id הקנוני, לא על symbol.
idempotency_keystringחובהמפתח Idempotency: hash(asset_id + discovery_cycle_id). מאפשר אטימיות רק באמצעות Unique/Atomic Upsert או Claim ייחודי. ללא מנגנון כזה — Best Effort ב-Shadow בלבד.
analysis_outputsarray<string> (derived)אופציונלינגזר מ-AnalysisOutput.candidate_id. אינו מקור אמת — לקריאה בלבד.
decision_idstring (ref)אופציונלימזהה Decision שנוצר (מתמלא בקידום)
promotion_locked_atdate-timeאופציונלימועד תחילת נעילת PROMOTING. אם ה-Candidate תקוע ב-PROMOTING מעל 60 שניות — Reconciliation פעיל.
priority_classenumאופציונליP0_PROTECTION / P1_ANALYSIS / P2_BACKGROUND (ברירת מחדל: P1_ANALYSIS)
expires_atdate-timeחובהתפוגה (TTL) — ברירת מחדל: created_date + 30 דקות
promoted_atdate-timeאופציונלימועד קידום (מתמלא ב-promoteCandidate)
rejected_reasonstringאופציונליסיבת דחייה (אם נדחה)
נתונים מובנים (id, created_date, updated_date, created_by_id) נוספים אוטומטית על-ידי הפלטפורמה ואינם מופיעים כאן.

מחזור חיים ומעברי מצבים

DISCOVERED

סוכן זיהה הזדמנות. נוצר Candidate עם dedup_key (על בסיס asset_id קנוני), idempotency_key, וצילום expected_agent_ids (סוכנים פעילים+רלוונטיים). אם כפיל — No-Op מחזיר את המקורי + AuditEvent.

ANALYZING

ה-Candidate ממתין להשלמת quorum (ceil(60% × |expected_agent_ids|)). כל AnalysisOutput מקושר אליו דרך candidate_id (מקור האמת). פלט תקף אחד לכל סוכן (ייחודיות על candidate_id+agent_id; כתיבה חוזרת Idempotent). אין קידום אוטומטי מחמת תום זמן.

PROMOTING

מצב ביניים אטומי: יצירת Decision + עדכון decision_id + מעבר ל-PROMOTED בטרנזקציה אחת. promotion_locked_at נשמר. אם תקוע > 60 שניות — Reconciliation פעיל.

PROMOTED

המעבר האטומי הושלם. ה-Candidate מכיל decision_id ואינו בר-שינוי. קישור דו-כיווני ל-Decision.

REJECTED

נדחה על-ידי המערכת (רוב NO_OPINION, סכסוך בלתי-פתיר, ספים לא עברו, Reconciliation של PROMOTING תקוע).

EXPIRED

analysis_deadline הגיע ו-quorum לא הושג, או expires_at עבר. אין קידום אוטומטי מחמת תום זמן.

מאלטריגרתנאי
discovered
analyzing
queueForAnalysis()non-duplicate (No-Op returned existing, or new created)
analyzing
promoting
promoteCandidate()quorum הושג: ceil(60% × |expected_agent_ids|) עד analysis_deadline — לא קידום אוטומטי מחמת תום זמן
promoting
promoted
atomic transactionיצירת Decision + עדכון decision_id + מעבר ל-PROMOTED בטרנזקציה אחת
promoting
rejected
reconcileStuckPromoting()תקוע ב-PROMOTING > 60 שניות — Reconciliation משלים (אם Decision נוצר) או מסמן כ-rejected
analyzing
rejected
evaluateCandidate()quorum הושג אך רוב NO_OPINION, או סכסוך בלתי-פתיר, או ספים לא עברו
discovered
expired
sweepExpired()expires_at passed without promotion
analyzing
expired
sweepExpired()analysis_deadline הגיע ו-quorum לא הושג — אין קידום אוטומטי
promoted
(terminal)
Decision createddecision_id set — Candidate lifecycle complete

אילוצי עיצוב (Invariants)

חד-פעמיות אטומית (No-Op): לכל asset_id+window קיים Candidate פעיל אחד לכל היותר. דה-דופליקציה במודל No-Op — createCandidate עם dedup_key/idempotency_key קיים מחזיר את ה-Candidate המקורי, לא יוצר רשומת SUPERSEDED. AuditEvent (DUPLICATE_DISCOVERY_ATTEMPT) נרשם.

דה-דופליקציה קנונית: dedup_key מבוסס על asset_id מ-AssetMaster (CH5), לא על symbol — מונע כפילויות הנובעות מכינויים וסימולים שונים לאותו נכס.

קידום אטומי (PROMOTING): מעבר מ-analyzing ל-promoted עובר דרך מצב ביניים PROMOTING. יצירת Decision + עדכון decision_id + מעבר ל-PROMOTED מתבצעים בטרנזקציה אחת — מונע מצב שבו Candidate מקודם ללא decision_id.

Reconciliation: אם Candidate תקוע ב-PROMOTING מעל 60 שניות — reconcileStuckPromoting() משלים את הקידום (אם Decision נוצר) או מסמן כ-rejected. promotion_locked_at משוחרר.

מצב קפוא: לאחר קידום ל-promoted, ה-Candidate אינו בר-שינוי. ניתן לקרוא אותו לצורך Decision אך לא לעדכן שדות ניתוח.

קישור ל-Decision: promoted Candidate חייב להכיל decision_id. Decision חייב להכיל candidate_id. קישור דו-כיווני מאומת.

Shadow אינו יוצר Decision: ב-shadow, promoteCandidate() אינו יוצר Decision מקביל. הוא מתעד איזה Decision היה נוצר ומקשר ל-Decision הקיים מהנתיב הסטנדרטי.

אין קידום אוטומטי מחמת תום זמן: אם analysis_deadline הגיע ו-quorum לא הושג — ה-Candidate עובר ל-EXPIRED. קידום מתבצע רק כאשר quorum הושג.

אין וטו: Candidate אינו Risk Gate. הוא אוסף ומסנן הזדמנויות אך אינו חוסם ביצוע. וטו שייך רק ל-Risk Gate הדטרמיניסטי.

עסקאות ידניות: ב-enforced, candidate_id חובה רק לעסקאות origin=agent. עסקה ידנית (origin=manual, מהגרף) מותרת ללא candidate_id עם Audit מלא.

Quorum חד-משמעי: expected_agent_ids כולל רק סוכנים פעילים (status=active) ורלוונטיים לנכס ולשוק. quorum = ceil(60% × |expected_agent_ids|). פלט תקף אחד בלבד לכל סוכן (ייחודיות על candidate_id+agent_id; כתיבה חוזרת Idempotent — אין דריסה לאחר השגת Quorum). analysis_deadline ≤ expires_at.

כללי דה-דופליקציה

מזההחומרהכלל
D1P0חלון דה-דופליקציה: אותו asset_id (קנוני מ-AssetMaster, CH5) בתוך אותו discovery_cycle_id. אם קיים Candidate פעיל (discovered/analyzing/promoting) — הקריאה היא No-Op: מחזירה את ה-Candidate הקיים. אין יצירת רשומת SUPERSEDED.
D2P1חלון חוצה-cycle: אם קיים Candidate פעיל מ-cycle קודם שטרם פג תוקפו (expires_at) — הקריאה היא No-Op: מחזירה את ה-Candidate הקיים. AuditEvent נרשם על ניסיון הגילוי הכפול.
D3P0dedup_key = hash(asset_id + window_bucket). מבוסס על asset_id הקנוני מ-AssetMaster (CH5), לא על symbol — מונע כפילויות הנובעות מכינויים וסימולים שונים לאותו נייר (למשל BRK.B ו-BRK-B — סימונים שונים לאותו נייר; להבדיל מ-GOOG/GOOGL שהם סוגי מניות שונים ואסור למפותם לאותו asset_id).
D4P1אין מחיקת Candidates. סגירה אך ורק דרך מעבר מצב (expired/rejected).
D5P0מודל No-Op אטומי: createCandidate עם dedup_key/idempotency_key קיים מחזיר את ה-Candidate המקורי מבלי ליצור רשומה חדשה. AuditEvent נרשם על ניסיון הגילוי הכפול (actor = הסוכן שניסה, entity_ref = ה-Candidate המקורי, event_type = DUPLICATE_DISCOVERY_ATTEMPT).
D6P0אטימיות דה-דופליקציה: filter + return existing אינו אטומי — אינו מונע משני סוכנים ליצור Candidate במקביל. נדרש Unique/Atomic Upsert אמיתי על dedup_key/idempotency_key, או ישות נעילה/Claim בעלת מפתח ייחודי. אם הפלטפורמה אינה מאפשרת זאת — המנגנון מסומן Best Effort ב-Shadow בלבד, ואסור לעבור ל-Enforced או לאשרר את C2 עד להוכחת אטימיות. במעבר ל-PROMOTING, נשמר promotion_locked_at. אם ה-Candidate תקוע ב-PROMOTING מעל 60 שניות — reconcileStuckPromoting() משלים (אם Decision נוצר) או מסמן כ-rejected.

נקודות שילוב בקוד הקיים

runAgentScan/entry.ts (לאחר analyzeAgent): ב-Shadow — קוראים ל-createCandidate() בנוסף לנתיב הקיים (createDecision), באופן לא-חוסם. אין לשנות או לעצור את יצירת ה-Decision הקיימת. ה-Candidate נכנס לתור וממתין לשאר הסוכנים — לצורך תיעוד והכנה ל-Evidence Fusion בלבד.

agentAdapters.ts:adaptAgentOutput(): נוסף פרמטר candidate_id ב-AnalysisOutput. מקור האמת הוא AnalysisOutput.candidate_id עם ייחודיות על (candidate_id, agent_id) — פלט אחד תקף לכל סוכן. המערך candidate.analysis_outputs[] הוא נגזר בלבד (לקריאה). כתיבה חוזרת Idempotent — אין "האחרון דורס" לאחר השגת Quorum.

decisionObject.ts:createDecision(): נוסף שדה אופציונלי candidate_id ב-DecisionData. במצב enforced — חובה רק לעסקאות מקור סוכן (origin=agent). עסקה ידנית (origin=manual, מהגרף) אינה דורשת candidate_id — נשמרת עם Audit מלא. ב-shadow — אופציונלי לכל. ה-Decision מקושר בחזרה דרך candidate.decision_id.

candidateService.ts (חדש): מודול חדש ב-base44/shared/ המכיל: resolveAssetId(), createCandidate(), queueForAnalysis(), promoteCandidate(), evaluateCandidate(), sweepExpired(), reconcileStuckPromoting().

טרנזקציה והתאוששות: (1) אטימיות דה-דופליקציה — נדרש Unique/Atomic Upsert אמיתי על idempotency_key, או ישות נעילה/Claim בעלת מפתח ייחודי. filter + return existing אינו אטומי ואינו מספיק. אם הפלטפורמה אינה מאפשרת — המנגנון מסומן Best Effort ב-Shadow בלבד, ואסור לעבור ל-Enforced או לאשרר את C2; (2) נעילה — promotion_locked_at נשמר בכניסה ל-PROMOTING; (3) Reconciliation — reconcileStuckPromoting() (רקע) מזהה Candidates תקועים ב-PROMOTING מעל 60 שניות, משלים את הקידום (אם Decision נוצר) או מסמן כ-rejected.

shadow mode: בשלב ראשון, השילוב פעיל ב-shadow בלבד — Candidate נוצר ומתועד, ומקושר ל-Decision שנוצר על-ידי הנתיב הקיים (סוכן → Decision). promoteCandidate() אינו יוצר Decision מקביל ב-shadow — הוא רק מתעד איזה Decision היה נוצר ומקשר את ה-Candidate ל-Decision הקיים מהנתיב הסטנדרטי. מעבר ל-enforced רק לאחר אימות קריטריוני הקבלה וסגירת V2-CH5 (Evidence Fusion).

קריטריוני קבלה

מזההסוגקריטריון קבלהתוצאה
C1Schemaקיימת ישות Candidate עם שדות: asset_id (קנוני מ-AssetMaster), symbol, market, discovered_by, discovery_cycle_id, expected_agent_ids (סוכנים פעילים+רלוונטיים), analysis_deadline (≤ expires_at), idempotency_key, initial_score, initial_direction, status (כולל promoting), dedup_key (hash(asset_id+window)), promotion_locked_at, decision_id, expires_at (created_date + 30 דקות)PASS
C2DedupcreateCandidate() מבצע דה-דופליקציה אטומית במודל No-Op: אם קיים Candidate פעיל עם אותו dedup_key/idempotency_key — הקריאה מחזירה את המקורי מבלי ליצור רשומה חדשה. AuditEvent (DUPLICATE_DISCOVERY_ATTEMPT) נרשם. אין רשומות SUPERSEDED. dedup_key מבוסס על asset_id קנוני (לא symbol). דורש Unique/Atomic Upsert אמיתי או ישות נעילה/Claim — filter+return אינו מספיק. אם הפלטפורמה אינה מאפשרת — Best Effort ב-Shadow בלבד, ואסור לעבור ל-Enforced או לאשרר C2EXCEPTION
C3Linkageמקור האמת: AnalysisOutput.candidate_id עם ייחודיות על (candidate_id, agent_id) — פלט אחד תקף לכל סוכן. המערך candidate.analysis_outputs[] הוא נגזר בלבד (לקריאה). כתיבה חוזרת Idempotent — אין "האחרון דורס" לאחר השגת QuorumPASS
C4PromotionpromoteCandidate() מופעל רק לאחר ש-quorum הושג: quorum = ceil(60% × |expected_agent_ids|). expected_agent_ids כולל רק סוכנים פעילים (status=active) ורלוונטיים לנכס ולשוק (לפי asset_types ו-market ב-AgentConfig). פלט יחיד לכל (candidate_id, agent_id); כתיבה חוזרת Idempotent ואין דריסה לאחר השגת Quorum. analysis_deadline ≤ expires_at. אם quorum לא הושג עד analysis_deadline — EXPIRED. אין קידום אוטומטי מחמת תום זמןPASS
C5LinkageDecision שנוצר מ-Candidate מכיל candidate_id בחוזה. Candidate מכיל decision_id. קישור דו-כיווני מאומת. ב-shadow: promoteCandidate() אינו יוצר Decision מקביל — מקשר ל-Decision הקיים מהנתיב הסטנדרטיPASS
C6Invariantמעבר analyzing→promoting→promoted אטומי: יצירת Decision + עדכון decision_id + מעבר ל-PROMOTED בטרנזקציה אחת. promotion_locked_at נשמר בכניסה ל-PROMOTING. אם ה-Candidate תקוע ב-PROMOTING > 60 שניות — reconcileStuckPromoting() משלים (אם Decision נוצר) או מסמן כ-rejected. Candidate עם status=promoted אינו בר-שינויPASS
C7TTLsweepExpired() מסמן Candidates שעברו את expires_at או ש-quorum לא הושג עד analysis_deadline כ-expired. אין קידום של Candidate שפג תוקפוPASS
C8Modeנתיב legacy פעיל רק ב-shadow. ב-enforced — candidate_id חובה רק לעסקאות מקור סוכן (origin=agent). עסקה ידנית (origin=manual, מהגרף) מותרת ללא candidate_id עם Audit מלא. ב-shadow: Candidate אינו יוצר Decision מקבילPASS
C9InvariantCandidate אינו Risk Gate — אינו חוסם ביצוע. וטו שמור ל-Risk Gate בלבד (G4)PASS
C10AuditAuditEvent נרשם לכל מעבר מצב של Candidate, וכן על ניסיון גילוי כפול (DUPLICATE_DISCOVERY_ATTEMPT) — ה-Candidate המקורי מוחזר (No-Op) ואין רשומת SUPERSEDEDPARTIAL
סה"כ 10 קריטריונים · 8 PASS · 1 EXCEPTION · 1 PARTIAL · Shadow Verified 11.8.2026 — מעבר ל-Enforced חסום עד סגירת C2 + V2-CH5

ראיות יישום Shadow (11.8.2026)

ישות Candidate: base44/entities/Candidate.jsonc — סכמה מלאה עם כל השדות הנדרשים (asset_id, dedup_key, idempotency_key, expected_agent_ids, analysis_deadline, 6 מצבים כולל promoting, promotion_locked_at, decision_id, expires_at).

candidateService.ts: base44/shared/candidateService.ts — 7 פונקציות: resolveAssetId, createCandidate (דה-דופליקציה No-Op + AuditEvent), queueForAnalysis, evaluateCandidate, promoteCandidate (PROMOTING אטומי), sweepExpired, reconcileStuckPromoting (60s timeout).

AnalysisOutput.candidate_id: שדה נוסף לישות — מקור האמת לקישור (ייחודיות על candidate_id+agent_id).

שילוב ב-runAgentScan: base44/functions/runAgentScan/entry.ts — createCandidate נקרא לאחר analyzeAgent בנוסף לנתיב הקיים (תוספתי, לא-חוסם). הנתיב הקיים (סוכן → Decision) לא השתנה.

תחזוקה אוטומטית: base44/functions/sweepCandidates/entry.ts + base44/workflows/Candidate Lifecycle Sweep.jsonc — workflow מתוזמן כל 5 דקות קורא ל-sweepExpired + reconcileStuckPromoting. נבדק: { expired: 3, reconciled: 0 }.

חריג C2 (אטימיות): דה-דופליקציה פועלת ב-Best Effort (filter + return existing) — אינה אטומית. חסום מ-Enforced עד Unique/Atomic Upsert או Claim ייחודי. פירוט מלא בדוח הביצוע.

סיכום והמלצה

סגירת G6: ישות Candidate מביאה דה-דופליקציה, תיעוד הזדמנויות, והמתנה להשלמת כל הסוכנים — הבסיס לאיחוד ראיות (V2-CH5) ול-CIO (V2-CH7).

אי-פגיעה בכרך 1: כל השינויים הם תוספתיים — ישות חדשה + שדות אופציונליים. כרך 1 נעול ונשאר תקף.

תלות הדדית: קידום Candidate (promoteCandidate) מצפה ל-Evidence Fusion (V2-CH5). בינתיים, קידום יתבצע על בסיס רוב פשוט (majority vote) או הסוכן בעל הציון הגבוה ביותר. מעבר מלא ל-Fusion ב-V2-CH5.

אין וטו: Candidate אינו Risk Gate. הוא אוסף, מסנן ומתעד הזדמנויות אך אינו חוסם ביצוע. וטו שמור ל-Risk Gate הדטרמיניסטי בלבד (G4).

המלצה: המפרט אושר (v1.0) ליישום ב-shadow: יצירת ישות Candidate, מודול candidateService.ts, וחיבור ל-runAgentScan במקביל לנתיב הקיים (תוספתי, לא-חוסם). אין מעבר ל-Enforced לפני: (1) השלמת 10 קריטריוני הקבלה, (2) הוכחת אטימיות C2 (Unique/Atomic Upsert או Claim ייחודי), (3) סגירת Evidence Fusion ב-V2-CH5.

מפרט · v1.0 · 11.8.2026 · Shadow Active
חזרה למפרטים ומסמכים