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 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-31 14:51:16 -05:00
parent 7e390aa48e
commit c039c04ceb
+25 -1
View File
@@ -686,6 +686,23 @@ async function handleApi(req,res,pathname){
if(r.error)return json(res,401,{error:r.error}); 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)}); 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) ──── // ── Circle Suite entitlement helper (shared by suite-me and the tools) ────
async function suiteEntitlement(req){ async function suiteEntitlement(req){
const s=messages.authFromCookie(req); const s=messages.authFromCookie(req);
@@ -701,7 +718,14 @@ async function handleApi(req,res,pathname){
const cfg=getConfig(); const cfg=getConfig();
const roots=String(cfg.teamRootId||cfg.orgRootId||'21').split(',').map(x=>Number(x.trim())).filter(Boolean); 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 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 allow=String(cfg.suiteAllowlist||'').split(',').map(x=>Number(x.trim())).filter(Boolean);
const beta=allow.length>0; const beta=allow.length>0;
const allowed=!beta||allow.includes(Number(d.id)); const allowed=!beta||allow.includes(Number(d.id));