feat: Deploy HA auto-failover with database promotion

- Enhanced DNS failover monitor on secondary (72.62.39.24)
- Auto-promotes database: pg_ctl promote on failover
- Creates DEMOTED flag on primary via SSH (split-brain protection)
- Telegram notifications with database promotion status
- Startup safety script ready (integration pending)
- 90-second automatic recovery vs 10-30 min manual
- Zero-cost 95% enterprise HA benefit

Status: DEPLOYED and MONITORING (14:52 CET)
Next: Controlled failover test during maintenance
This commit is contained in:
mindesbunister
2025-12-12 15:54:03 +01:00
parent 7ff5c5b3a4
commit d637aac2d7
25 changed files with 1071 additions and 170 deletions

View File

@@ -408,8 +408,19 @@ export async function POST(request: NextRequest): Promise<NextResponse<RiskCheck
// Get current price for the blocked signal record
const currentPrice = await getCurrentPrice(body.symbol, fallbackPrice)
// CRITICAL FIX (Dec 12, 2025): Smart validation integration
// Check if signal quality is in validation range (50-89)
const isInValidationRange = qualityScore.score >= 50 && qualityScore.score < 90
// Save blocked signal to database for future analysis
if (currentPrice > 0) {
// SMART VALIDATION QUEUE (Nov 30, 2025 - FIXED Dec 12, 2025)
// Queue marginal quality signals (50-89) for validation instead of hard-blocking
const blockReason = isInValidationRange ? 'SMART_VALIDATION_QUEUED' : 'QUALITY_SCORE_TOO_LOW'
const blockDetails = isInValidationRange
? `Score: ${qualityScore.score}/${minQualityScore} - Queued for validation (will enter if +0.3%, abandon if -1.0%)`
: `Score: ${qualityScore.score}/${minQualityScore} - ${qualityScore.reasons.join(', ')}`
await createBlockedSignal({
symbol: body.symbol,
direction: body.direction,
@@ -421,41 +432,41 @@ export async function POST(request: NextRequest): Promise<NextResponse<RiskCheck
volumeRatio: body.volumeRatio,
pricePosition: body.pricePosition,
signalQualityScore: qualityScore.score,
signalQualityVersion: 'v4', // Update this when scoring logic changes
signalQualityVersion: 'v4',
scoreBreakdown: { reasons: qualityScore.reasons },
minScoreRequired: minQualityScore, // Use direction-specific threshold
blockReason: 'QUALITY_SCORE_TOO_LOW',
blockDetails: `Score: ${qualityScore.score}/${minQualityScore} - ${qualityScore.reasons.join(', ')}`,
minScoreRequired: minQualityScore,
blockReason: blockReason,
blockDetails: blockDetails,
indicatorVersion: body.indicatorVersion || 'v5',
})
// SMART VALIDATION QUEUE (Nov 30, 2025)
// Queue marginal quality signals (50-89) for validation instead of hard-blocking
const validationQueue = getSmartValidationQueue()
// CRITICAL FIX (Dec 1, 2025): Normalize TradingView symbol format to Drift format
// Bug: Market data cache uses "SOL-PERP" but TradingView sends "SOLUSDT"
// Without normalization, validation queue can't find matching price data
// Result: Wrong/stale price shown in Telegram abandonment notifications
const normalizedSymbol = normalizeTradingViewSymbol(body.symbol)
const queued = await validationQueue.addSignal({
blockReason: 'QUALITY_SCORE_TOO_LOW',
symbol: normalizedSymbol, // Use normalized format for cache lookup
direction: body.direction,
originalPrice: currentPrice,
qualityScore: qualityScore.score,
atr: body.atr,
adx: body.adx,
rsi: body.rsi,
volumeRatio: body.volumeRatio,
pricePosition: body.pricePosition,
indicatorVersion: body.indicatorVersion || 'v5',
timeframe: body.timeframe || '5',
})
if (queued) {
console.log(`🧠 Signal queued for smart validation: ${normalizedSymbol} ${body.direction} (quality ${qualityScore.score})`)
// Add to validation queue if in range
if (isInValidationRange) {
const validationQueue = getSmartValidationQueue()
// CRITICAL FIX (Dec 1, 2025): Normalize TradingView symbol format to Drift format
const normalizedSymbol = normalizeTradingViewSymbol(body.symbol)
const queued = await validationQueue.addSignal({
blockReason: 'SMART_VALIDATION_QUEUED',
symbol: normalizedSymbol,
direction: body.direction,
originalPrice: currentPrice,
qualityScore: qualityScore.score,
atr: body.atr,
adx: body.adx,
rsi: body.rsi,
volumeRatio: body.volumeRatio,
pricePosition: body.pricePosition,
indicatorVersion: body.indicatorVersion || 'v5',
timeframe: body.timeframe || '5',
})
if (queued) {
console.log(`🧠 Signal queued for smart validation: ${normalizedSymbol} ${body.direction} (quality ${qualityScore.score})`)
}
} else {
console.log(` Signal quality too low for validation: ${qualityScore.score} (need 50-89 range)`)
}
} else {
console.warn('⚠️ Skipping blocked signal save: price unavailable (quality block)')