From 6ebe436cf3184cc8ae8b254e074a3171a5b0b096 Mon Sep 17 00:00:00 2001 From: martbost Date: Wed, 19 Aug 2026 14:37:48 -0500 Subject: [PATCH] Rotation queue: reconcile against live chain directs in normalizeStatuses - never activate a sponsor the chain says is already 2/2 qualified (also syncs stale direct counts upward) Co-Authored-By: Claude Fable 5 --- chain.js | 9 ++++++++- server.js | 11 +++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/chain.js b/chain.js index e973757..f98fdb8 100644 --- a/chain.js +++ b/chain.js @@ -667,6 +667,13 @@ function getOrgShare(rootId) { }; } +// Live direct count from the index (null when unknown) — used by the rotation +// queue to reconcile against chain reality before activating a sponsor. +function liveDirects(id) { + if (!state || !state.members[id]) return null; + return state.members[id].directCount || 0; +} + // Wallet address -> position id (for wallet-verified messaging sign-in). // One position per address by contract design. function memberIdByAccount(address) { @@ -744,4 +751,4 @@ async function getIncome(id) { }; } -module.exports = { startIndexer, getPayoutsPublic, verifyMember, memberLookup, memberPublic, getIncome, getOwnerUpgradeNeeds, getOrgRouting, getOrgShare, getCoachingScan, getMatrixTree, isInTeam, nextOpenPosition, memberIdByAccount, balanceOf, CONTRACT }; +module.exports = { startIndexer, getPayoutsPublic, verifyMember, memberLookup, memberPublic, getIncome, getOwnerUpgradeNeeds, getOrgRouting, getOrgShare, getCoachingScan, getMatrixTree, isInTeam, nextOpenPosition, memberIdByAccount, liveDirects, balanceOf, CONTRACT }; diff --git a/server.js b/server.js index cbc0f11..01bbc66 100644 --- a/server.js +++ b/server.js @@ -316,6 +316,17 @@ function recordEvent(event, source) { writeJson(ANALYTICS_FILE, a); } function normalizeStatuses(sponsors, preferredActiveId=null) { + // Chain reconciliation: queue members can qualify through leg activity long + // BEFORE their rotation turn, and the event-driven auto-counter never sees + // it (bit us twice on 2026-08-19: #41, then #46 activated while already + // 2/2 on-chain). Never activate someone the chain says is qualified. + sponsors=sponsors.map(s=>{ + if(s.status==='qualified')return s; + const dc=chain.liveDirects(Number(s.id)); + if(dc!=null&&dc>=2)return {...s,directs:2,status:'qualified'}; + if(dc!=null&&dc>(Number(s.directs)||0))return {...s,directs:dc}; + return s; + }); const eligible=sponsors.filter(s=>s.status!=='qualified'); let activeId=preferredActiveId; if(!activeId || !eligible.some(s=>s.id===activeId)){