From c039c04ceb34a76c44d3d7fa673d15d9f3d5f08d Mon Sep 17 00:00:00 2001 From: martbost Date: Mon, 31 Aug 2026 14:51:16 -0500 Subject: [PATCH] Suite: entitle the positions above our team root, not just below it The org test only ever looked downward: you are in if a root appears in your upline chain. The founders and contract owners sit ABOVE #21, so #2 - a Corona position that has been here since the beginning - was told "this position isn't inside our team's organization" and shown a locked wall. That is backwards. Their positions are the reason the contract exists. Each root's own upline is now entitled as well, which covers the chain up through #2 and the root account. A position's upline is fixed at registration, so it is resolved once and memoised rather than walked per request. Nothing else changes: level still comes from the contract, the allowlist and level-override behave as before, and members below the root are unaffected. Co-Authored-By: Claude Fable 5 --- server.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 57578a8..6eb7234 100644 --- a/server.js +++ b/server.js @@ -686,6 +686,23 @@ async function handleApi(req,res,pathname){ if(r.error)return json(res,401,{error:r.error}); return json(res,200,{ok:true,id:r.id},{'Set-Cookie':messages.sessionCookie(r.token)}); } + // Positions on the team root's own upline - i.e. everyone above us, up to and + // including the contract root. Fixed at registration, so cache indefinitely. + let _suiteUpCache=null, _suiteUpKey=''; + async function suiteRootUpline(roots){ + const key=roots.join(','); + if(_suiteUpCache&&_suiteUpKey===key)return _suiteUpCache; + const out=[]; + for(const r of roots){ + try{ + const rd=await chain.memberPublic(r); + if(rd&&Array.isArray(rd.uplineChain))rd.uplineChain.forEach(x=>{const n=Number(x);if(n&&!out.includes(n))out.push(n);}); + }catch(e){} + } + if(out.length){_suiteUpCache=out;_suiteUpKey=key;} + return out; + } + // ── Circle Suite entitlement helper (shared by suite-me and the tools) ──── async function suiteEntitlement(req){ const s=messages.authFromCookie(req); @@ -701,7 +718,14 @@ async function handleApi(req,res,pathname){ const cfg=getConfig(); const roots=String(cfg.teamRootId||cfg.orgRootId||'21').split(',').map(x=>Number(x.trim())).filter(Boolean); const chainIds=Array.isArray(d.uplineChain)?d.uplineChain.map(Number):[]; - const inOrg=roots.some(r=>Number(d.id)===r||chainIds.includes(r)); + // The org test only looks DOWNWARD: you are in if a root sits in your upline. + // The people ABOVE our root - the founders and contract owners, up through + // position #2 and the root account - can never pass that, yet the Suite is + // as much theirs as ours. Walk each root's own upline and entitle those + // positions too. Registration fixes a position's upline permanently, so this + // is computed once and memoised rather than hit on every request. + const upIds=await suiteRootUpline(roots); + const inOrg=roots.some(r=>Number(d.id)===r||chainIds.includes(r))||upIds.includes(Number(d.id)); const allow=String(cfg.suiteAllowlist||'').split(',').map(x=>Number(x.trim())).filter(Boolean); const beta=allow.length>0; const allowed=!beta||allow.includes(Number(d.id));