Found while verifying the notification principal set for a docs card (objectstack-ai/objectos#109). Filed, not fixed — the two readings below have very different blast radii and I cannot separate them from source alone.
packages/services/service-messaging/src/objects/notification-subscription.object.ts:75-78 carries this note justifying the (topic, principal) per-organization unique index:
`principal` names are per-organization: `role:x` and `team:x`
resolve against `sys_permission_set` / `sys_position` rows that
#8461 and #8556 already scoped per organization, so
`role:sales_manager` denoted a DIFFERENT principal in each
organization while colliding on one installation-wide key.
The resolver disagrees on both halves. packages/services/service-messaging/src/recipient-resolver.ts documents and implements:
'role:name' → sys_member where role = name (tenant-scoped)
'team:id' → sys_team_member where team_id = id
via MEMBER_OBJECT = 'sys_member' / resolveRole and TEAM_MEMBER_OBJECT = 'sys_team_member' / resolveTeam. The test pins it: recipient-resolver.test.ts:105 is named "expands role: via sys_member (tenant-scoped) and de-dups" and asserts the query where equals { role: 'admin', organization_id: 'org_1' }.
The comment's own worked example is a further signal that it is not describing this code path: role:sales_manager is not a reachable sys_member.role value — mapMembershipRole in packages/spec/src/identity/eval-user.zod.ts normalizes owner / admin / member and nothing else — whereas sales_manageris the canonical example position name in packages/spec/src/identity/position.zod.ts.
The two readings
- The comment is simply wrong prose on a correct index, and should be corrected to name
sys_member / sys_team_member. Cheap fix, no behaviour change. - The comment records intended behaviour —
role: resolving against positions — that RecipientResolver never implemented. Then the tenancy argument the index rests on is about a graph the code does not read, and the real question is which of the two should move.
The per-organization unique index is not in question under either reading: sys_member and sys_team_member rows are tenant-scoped too, so the index is right regardless of which object backs the selector.
Worth resolving because this comment is load-bearing for exactly the reader who comes looking: it is the most detailed prose in the repo about what a notification principal denotes, and it is what a docs or spec agent verifying the principal set will find first.
Verified on origin/main (ed4ca59).
Found while verifying the notification principal set for a docs card (objectstack-ai/objectos#109). Filed, not fixed — the two readings below have very different blast radii and I cannot separate them from source alone.
packages/services/service-messaging/src/objects/notification-subscription.object.ts:75-78carries this note justifying the(topic, principal)per-organization unique index:The resolver disagrees on both halves.
packages/services/service-messaging/src/recipient-resolver.tsdocuments and implements:via
MEMBER_OBJECT = 'sys_member'/resolveRoleandTEAM_MEMBER_OBJECT = 'sys_team_member'/resolveTeam. The test pins it:recipient-resolver.test.ts:105is named "expands role: via sys_member (tenant-scoped) and de-dups" and asserts the querywhereequals{ role: 'admin', organization_id: 'org_1' }.The comment's own worked example is a further signal that it is not describing this code path:
role:sales_manageris not a reachablesys_member.rolevalue —mapMembershipRoleinpackages/spec/src/identity/eval-user.zod.tsnormalizes owner / admin / member and nothing else — whereassales_manageris the canonical example position name inpackages/spec/src/identity/position.zod.ts.The two readings
sys_member/sys_team_member. Cheap fix, no behaviour change.role:resolving against positions — thatRecipientResolvernever implemented. Then the tenancy argument the index rests on is about a graph the code does not read, and the real question is which of the two should move.The per-organization unique index is not in question under either reading:
sys_memberandsys_team_memberrows are tenant-scoped too, so the index is right regardless of which object backs the selector.Worth resolving because this comment is load-bearing for exactly the reader who comes looking: it is the most detailed prose in the repo about what a notification
principaldenotes, and it is what a docs or spec agent verifying the principal set will find first.Verified on
origin/main(ed4ca59).