refactor(org): keep archived-team grants as dead rows

Archiving a team no longer cascade-revokes its active TEAM->PROJECT grants
and memberships. The archived flag alone makes the team principal
unresolvable (permissions/principals.ts refuses archived teams), so the
dead grant/membership rows confer no access. listProjectTeamAccess now
filters archived teams out of the project view instead of relying on a
revokedAt cascade, and the org-admin teams page confirm copy is updated.
archiveTeam drops the revokedGrants count from its return shape.

ADR-0019 / Spec.System.Organization: principal resolution, not grant
mutation, is the access boundary for archived teams.
This commit is contained in:
2026-07-14 21:17:20 +08:00
parent cbe569d7e6
commit b574ef871c
4 changed files with 20 additions and 34 deletions
+10 -9
View File
@@ -131,9 +131,8 @@ export async function listProjectTeamAccess(
where: {
organizationId: project.organizationId,
id: { in: grants.map((grant) => grant.principalId) },
archivedAt: null,
},
select: { id: true, slug: true, name: true },
select: { id: true, slug: true, name: true, archivedAt: true },
});
const teamsById = new Map(teams.map((team) => [team.id, team]));
const missing = grants.filter((grant) => !teamsById.has(grant.principalId));
@@ -143,13 +142,15 @@ export async function listProjectTeamAccess(
);
}
return grants.map((grant) => entryFromGrant({
grantId: grant.id,
projectId: project.id,
organizationId: project.organizationId,
team: teamsById.get(grant.principalId)!,
role: grant.role,
}));
return grants
.filter((grant) => teamsById.get(grant.principalId)?.archivedAt === null)
.map((grant) => entryFromGrant({
grantId: grant.id,
projectId: project.id,
organizationId: project.organizationId,
team: teamsById.get(grant.principalId)!,
role: grant.role,
}));
}
type ProjectForAccess = {