- מפרט: ננעל ומאושר (Version 1.0 · Locked & Approved)
- מימוש: מימוש הושלם, בדיקות קבלה עברו (21/21) ואימות שוק חי עבר (PASS)
- CH3-002 כולו: ייחשב Completed רק לאחר מימוש, בדיקות הקבלה והגשת דוח זה עם ראיות.
- כל שינוי עתידי במפרט — גרסה חדשה + ADR. אין צורך בגרסה 0.32 או בסבב סקירה נוסף.
קבצים שייקראו במלואם לפני השינוי — יתועדו ויישמרו לחזרה:
- ›base44/entities/Trade.jsonc (סכמה נוכחית — לפני הוספת השדות)
- ›base44/entities/SystemSettings.jsonc (סכמה נוכחית — לפני proposal_ttl_hours)
- ›base44/shared/tradeExecution.ts
- ›base44/functions/refreshPortfolioPrices/entry.ts
- ›base44/functions/runAgentScan/entry.ts
- ›base44/functions/executeTrade/entry.ts
- ›src/pages/Trades.jsx + src/components/invest/Badges.jsx
| מישור | היקף | תחום |
|---|---|---|
| מצב מחזור ה-Orchestrator | פנימי ל-backend בלבד — לא נשמר ב-DB PRICE_REFRESHED → VALIDITY_CHECKED → CIO_SCANNED → RISK_GATED → APPROVAL_SET → EXECUTION_DONE | — |
| הסטטוס העסקי של ה-Trade | Trade.status — גלוי למשתמש ולכל הממשקים null → pending → approved → wait → approved → executed (או rejected/expired/escalated) | CH3-002 |
| מצב פקודת הביצוע מול הברוקר | Trade.execution_state — שדה נפרד NOT_SENT → SENT → CONFIRMED / FAILED / RECONCILIATION_REQUIRED | CH3-003 |
ההרץ מבצע יצירת Trade זמני, הפעלת מעברים דרך המכונה, וטענת ראייה לכל קריטריון — ואז מחיקת ה-Trade הזמני. התוצאות להלן נלקחו מריצה אמיתית.
אימות חוזה CH3-003 (approved→executed gated on CONFIRMED) על מחיר שוק אמיתי — המקרה היחיד שלא ניתן היה לבדוק דטרמיניסטית בשוק סגור. כעת מאומת גם בלייב.
סמכות: base44/shared/stateMachine.ts (transitionTradeStatus / createTradeWithAudit — הנתיב היחיד)
| קובץ | תפקיד | כתיבות status |
|---|---|---|
| base44/shared/stateMachine.ts | סמכות | transitionTradeStatus / createTradeWithAudit |
| base44/shared/tradeExecution.ts | קורא | transitionTradeStatus (to: executed) — אין כתיבת status ישירה |
| base44/shared/protectionReview.ts | קורא | createTradeWithAudit + transitionTradeStatus — אין כתיבת status ישירה |
| base44/functions/executeTrade/entry.ts | קורא | transitionTradeStatus לכל מעבר; כתיבות ישירות נוגעות רק ל-price/quantity/order_type/management_mode/proposal_expires_at |
| base44/functions/rejectTrade/entry.ts | קורא | transitionTradeStatus (to: rejected) — אין כתיבת status ישירה |
| base44/functions/refreshPortfolioPrices/entry.ts | קורא | transitionTradeStatus / createTradeWithAudit — אין כתיבת status ישירה |
| base44/functions/runAgentScan/entry.ts | קורא | createTradeWithAudit (null→pending) — אין כתיבת status ישירה |
| src/pages/Trades.jsx | UI | מפנה דחייה ל-rejectTrade (פונקציית backend) — אין כתיבת status ישירה לישות |
0 כתיבות ישירות של Trade.status מחוץ ל-stateMachine.ts. המכונה היא נתיב האכיפה היחיד.
| # | קריטריון | תוצאה |
|---|---|---|
| 1 | מעבר לא-חוקי (pending→wait, pending→executed, wait→executed) נדחה בשגיאה ומתועד. ראיה: assertTransitionAllowed זורק IllegalTransition על המסלולים החסומים. | ✓ בוצע |
| 2 | wait נכנס רק מ-approved; wait→approved אינו אישור חדש אלא החזרת אישור קיים לאחר TTL+Validity+Risk Gate מחדש. ראיה: executeTrade (שוק סגור) ו-refreshPortfolioPrices (unwait) מקדמים wait→approved דרך EXECUTION_GATE. | ✓ בוצע |
| 3 | approved→executed מתרחש רק לאחר אישור סופי מ-CH3-003; שליחה/Timeout/Partial Fill אינם executed; RECONCILIATION_REQUIRED אינו משנה status. ראיה: חוזה CH3-003: approved→executed מחייב execution_state=CONFIRMED בלבד. tradeExecution קובע CONFIRMED לפני המעבר. בדיקה: NOT_SENT נחסם, SENT נחסם (שליחה לבד ≠ executed), רק CONFIRMED הצליח. | ✓ בוצע |
| 4 | Risk Gate אינו מבצע מעבר ל-approved (רק APPROVAL / ידני). ראיה: runAgentScan מאשר רק דרך actor=APPROVAL; Risk Gate רק דוחה/מסלים. | ✓ בוצע |
| 5 | createTradeWithAudit יוצר Trade+status_version+Audit+transition_id+idempotency_key יחד (null→pending מתועד). ראיה: מימוש ב-stateMachine.ts; נקרא מ-runAgentScan/refreshPortfolioPrices/protectionReview. | ✓ בוצע |
| 6 | Retry עם אותו idempotency_key מחזיר אותה תוצאה ללא מעבר/Audit נוסף; פעולה חדשה → מפתח חדש. ראיה: findOutbox מחזיר תוצאה שמורה; idemKey מייצר מפתח חדש לכל פעולה עסקית. | ✓ בוצע |
| 7 | transition_id ו-idempotency_key שמורים בשני שדות נפרדים. ראיה: last_transition_id ו-last_idempotency_key ב-Trade; ערכים נפרדים ב-Outbox. | ✓ בוצע |
| 8 | expectedFrom/expectedVersion אינם נשמרים ב-Trade (פרמטרי קריאה בלבד). ראיה: לא נוספו שדות expectedFrom/expectedVersion לסכמת Trade. | ✓ בוצע |
| 9 | מרוץ מקבילי נבלם: שתי בקשות על pending → רק אחת מצליחה (CAS / status_version). ראיה: עדכון מותנה version=expected עם $inc; השנייה נכשלת ב-CAS. | ✓ בוצע |
| 10 | שחזור: Transition Journal/Outbox נבחר מאפשר התאוששות מלאה. transition_id ו-idempotency_key נשמרים בנפרד ב-Outbox; ב-Trade רק last_transition_id ו-last_idempotency_key כ-Cache. ראיה: writeOutbox כותב Message מסוג ייעודי עם transition_id ו-idempotency_key נפרדים. | ✓ בוצע |
| 11 | Actor נגזר מהקשר מאומת; טבלת הרשאויות Actor לכל מעבר נאכפת; reason_code קנוני. ראיה: ACTOR_PERMISSIONS + assertActorAllowed + REASON_CODES ב-stateMachine. | ✓ בוצע |
| 12 | גבול TTL ב-UTC: now < approval_expires_at → תקף; now >= → פג; pending משתמש ב-Proposal TTL נפרד. ראיה: proposal_expires_at ל-pending; approval_expires_at ל-approved/wait; השוואה ב-UTC. | ✓ בוצע |
| 13 | אישור מחדש של expired → Trade חדש + Message קישור (expired_trade_id, new_trade_id, סיבה, מזמים). ראיה: executeTrade reapprove: יצירת Trade חדש דרך createTradeWithAudit + Message קישור (Reapprove|expired|new). בדיקה: new≠expired, קישור נוצר. | ✓ בוצע |
| 14 | Migration מומש רק עם ראיית Audit מפורשת; ללא ראיה — rejected; שדות תפוגה ריקים כשאינו expired. ראיה: 12 רשומות executed — לא היו rejected להמרה; status_version ו-execution_state מולאו. | ✓ בוצע |
| 15 | grep בקוד מאפס Trade.update({ status }) מחוץ ל-stateMachine. ראיה: Trades.jsx מפנה דחייה ידנית ל-rejectTrade (מכונת המצבים); שינוי status עם Actor לא-מורשה נדחה. בדיקה: ניסיון עם CIO_SCAN נחסם. | ✓ בוצע |
| 16 | כל מעבר מתועד ב-Message עם from→to, actor, reason_code, trade_id, transition_id, idempotency_key. ראיה: writeOutbox כותב שדות אלו ב-Message בכל מעבר. | ✓ בוצע |
| 17 | מעבר לאותו סטטוס נוכחי = no-op שקט. כל הסטטוסים lowercase. ראיה: בדיקת same-status מחזירה ללא כתיבה; כל הערכים lowercase. | ✓ בוצע |
| 18 | כאשר execution_state != NOT_SENT — Trade Validity/Risk Gate/Global Freeze אינן משנות Trade.status; רק חוזה הסיום מ-CH3-003. ראיה: assertNotPreSend מופעל במעברי טרום-שליחה; חסימה כאשר execution_state != NOT_SENT. | ✓ בוצע |
| 19 | מקור האמת ל-idempotency הוא ה-Outbox/Journal (תוצאה מקורית שאינה נדרסת); ב-Trade רק Cache. ראיה: findOutbox קורא מ-Message; Trade שומר Cache בלבד. | ✓ בוצע |
| 20 | proposal_expires_at קיים ב-Trade; proposal_ttl_hours ב-SystemSettings; createTradeWithAudit מאתחל; Migration ל-pending קיימות; קבלה now >= proposal_expires_at. ראיה: שדה בסכמה; 24 ב-SystemSettings; אתחול ביצירה; Migration רץ. | ✓ בוצע |
| 21 | Actor קנוני: pending→approved = Approval/משתמש; approved↔wait = EXECUTION_GATE; approved→executed = EXECUTION(CH3-003). טבלת הרשאויות ו-reason_code מצורפות. ראיה: ACTOR_PERMISSIONS מגדיר את המפה הקנונית; reason_code קנוני בכל קריאה. | ✓ בוצע |
CH3-002 — Acceptance Tests Passed (21/21) + Live Market Validated (PASS) — Ready for CH3-003. מימוש הליבה הושלם (stateMachine.ts + רפקטור צינור + Migration), בדיקות הקבלה היזומות עברו במלואן (21/21) ב-7.8.2026 09:57 UTC, ואימות שוק חי עבר (PASS) ב-7.8.2026 16:30 IL (13:30 UTC) על מחיר אמיתי ($13.83). חוזה CH3-003 תוקם ואומת גם בלייב: approved→executed מחייב execution_state=CONFIRMED בלבד — NOT_SENT ו-SENT שניהם נחסמים (שליחה לבד ≠ executed). כל הפערים נסגרו: אישור-מחדש כ-Trade חדש + Message מקשר, הסרת כתיבת status ישירה מ-Trades.jsx (rejectTrade), grep מאשר 0 כתיבות ישירות מחוץ למכונה. CH3-002 מוכן לסגירה ולמעבר ל-CH3-003 בלי לפתוח מחדש את המפרט.