Restart-when: git grep -n "valid_from|valid_until" -- packages/platform-objects/src/identity/sys-member.object.ts returns non-empty (an ADR-0091 window column is declared on sys_member)
Recording only, unassigned, ungraded. Surfaced while building the standing-key correspondence gate for #8734 (PR #8801) and deliberately not fixed there — it is a resolver question, not a guard one, and #8734's rulings forbid changing behaviour inside that tooling PR.
The observation
packages/core/src/security/resolve-authz-context.ts, in resolveUserAuthzGrants, reads sys_memberonce and uses it for two things. The ADR-0091 validity window is applied to only one of them:
constmembers=awaittryFind(ql,'sys_member',{user_id: userId},200);constaccessibleOrgIds=newSet();for(constmofmembers){if(!isGrantActive(m,nowMs))continue;// window appliedconstorg=m.organization_id??m.organizationId;if(typeoforg==='string'&&org)accessibleOrgIds.add(org);}grants.accessible_org_ids=Array.from(accessibleOrgIds);constactiveMembers=tenantId ? members.filter(/* active-org match */) : members;for(constmofactiveMembers){// no window applied// role split + mapMembershipRole -> grants.positions (org_owner / org_admin / ...)}So a sys_member row outside its window would be excluded from accessible_org_ids (the ADR-0105 D2 group-posture read reach) while still contributingorg_owner / org_admin to positions — and positions is what the auto-org-admin-grant path and downstream permission-set resolution read.
The comment above the first loop states the window intent for the whole read ("Rows outside their ADR-0091 validity window do not resolve"), which is true of accessible_org_ids and not of the role projection directly below it.
Why this is dormant, not broken
sys_member declares no valid_from / valid_until today — checked against packages/platform-objects/src/identity/sys-member.object.ts, zero hits — and isGrantActive treats an absent bound as unbounded, so both halves currently answer identically. Nothing is wrong at runtime.
What makes it worth recording is that the resolver's own comment already anticipates the columns arriving ("this is a no-op until they exist and correct the moment they do"). That sentence is true of the loop it sits above and not of the projection below it, so the day the columns land the two halves silently disagree: an expired membership would stop widening the group-posture read reach while continuing to confer organization-administration standing.
Adjacent, and why it is separate
plugin-auth's break-glass guard counts organization administrators by grade alone (isOrgAdminGrade, no window), which matches the role projection and not accessible_org_ids. #8801 records this in STANDING_KEY_EXCLUSIONS as the reason sys_member's window bounds are not treated as standing-bearing, with the note that the resolver leads and the guard's list follows. If these columns ever land on sys_member, the resolver is where the two halves have to be reconciled first; the guard's list is downstream of that decision.
Not asserting a fix
Both directions are defensible and the choice is a real decision, not a cleanup: window-filter the role projection too (consistent with sys_user_position, which is filtered), or state deliberately that membership grade is not window-bound and align the accessible_org_ids half instead. Recording it while the columns are still absent is the cheap moment to decide.
Generated by Claude Code
Restart-when: git grep -n "valid_from|valid_until" -- packages/platform-objects/src/identity/sys-member.object.ts returns non-empty (an ADR-0091 window column is declared on sys_member)
Recording only, unassigned, ungraded. Surfaced while building the standing-key correspondence gate for #8734 (PR #8801) and deliberately not fixed there — it is a resolver question, not a guard one, and #8734's rulings forbid changing behaviour inside that tooling PR.
The observation
packages/core/src/security/resolve-authz-context.ts, inresolveUserAuthzGrants, readssys_memberonce and uses it for two things. The ADR-0091 validity window is applied to only one of them:So a
sys_memberrow outside its window would be excluded fromaccessible_org_ids(the ADR-0105 D2 group-posture read reach) while still contributingorg_owner/org_admintopositions— andpositionsis what the auto-org-admin-grant path and downstream permission-set resolution read.The comment above the first loop states the window intent for the whole read ("Rows outside their ADR-0091 validity window do not resolve"), which is true of
accessible_org_idsand not of the role projection directly below it.Why this is dormant, not broken
sys_memberdeclares novalid_from/valid_untiltoday — checked againstpackages/platform-objects/src/identity/sys-member.object.ts, zero hits — andisGrantActivetreats an absent bound as unbounded, so both halves currently answer identically. Nothing is wrong at runtime.What makes it worth recording is that the resolver's own comment already anticipates the columns arriving ("this is a no-op until they exist and correct the moment they do"). That sentence is true of the loop it sits above and not of the projection below it, so the day the columns land the two halves silently disagree: an expired membership would stop widening the group-posture read reach while continuing to confer organization-administration standing.
Adjacent, and why it is separate
plugin-auth's break-glass guard counts organization administrators by grade alone (isOrgAdminGrade, no window), which matches the role projection and notaccessible_org_ids. #8801 records this inSTANDING_KEY_EXCLUSIONSas the reasonsys_member's window bounds are not treated as standing-bearing, with the note that the resolver leads and the guard's list follows. If these columns ever land onsys_member, the resolver is where the two halves have to be reconciled first; the guard's list is downstream of that decision.Not asserting a fix
Both directions are defensible and the choice is a real decision, not a cleanup: window-filter the role projection too (consistent with
sys_user_position, which is filtered), or state deliberately that membership grade is not window-bound and align theaccessible_org_idshalf instead. Recording it while the columns are still absent is the cheap moment to decide.Generated by Claude Code