Reproduction
pnpm tsx app/database/seed-marketing.ts
- Log in as
marc.dupont@demo.unitae.app / demo1234.
- Open a programme event → Assign a part → the speaker (and reader) combobox is empty. Same for service roles.
Root cause
app/database/seed-marketing.ts:687 creates every publisher via prisma.member.create(...) directly, bypassing the Member aggregate at app/features/publishers/server/member.aggregate.ts.
The aggregate is the only place that calls syncBuiltInRoleAssignments (app/shared/domain/built-in-roles.server.ts:76), which reads a member's flags (isPublisher, isMale, baptismDate, isHelder, isServant, …) and populates MemberRoleAssignment rows for the built-in identity roles (member, publisher, brother, sister, elder, pioneer, …).
Net effect: seeded members have zero MemberRoleAssignment rows — not even the always-true member role.
The assign-part loader chain then falls over:
app/features/events/routes/programs/events/assign-part.tsx loader
- →
loadPartAssignmentCandidates (app/features/events/server/assign-part-loader.server.ts)
- →
resolveEligibleUserIds(db, allowedRoleIds=[], congregationId) (app/features/events/server/allowed-roles.server.ts:23-38)
With no explicit allowed roles on the template part, the fallback looks up the built-in member role and returns members with that role assignment. That query returns [], so speakerCandidates and readerCandidates end up empty.
Additional risk: if the marketing seed runs on a fresh DB (no prior pnpm prisma db seed), the built-in Role rows don't exist for the congregation either. seed.ts:47 calls seedBuiltInRoles(prisma, defaultCongregation.id); seed-marketing.ts doesn't.
Fix
Two additions to seed-marketing.ts:
- Call
seedBuiltInRoles(prisma, congId) right after the congregation is created / updated so the built-in Role rows exist regardless of whether the regular seed ran first.
- Call
syncBuiltInRoleAssignments(prisma, member.id, congId, null) after each prisma.member.create(...) so the MemberRoleAssignment rows exist.
import { seedBuiltInRoles } from '~/shared/domain/setup.server'
import { syncBuiltInRoleAssignments } from '~/shared/domain/built-in-roles.server'
// …after congregation exists
await seedBuiltInRoles(prisma, congId)
// …after each member.create
const member = await prisma.member.create({ data: { … } })
await syncBuiltInRoleAssignments(prisma, member.id, congId, null)
Bonus: CI hole
scripts/check-aggregate-boundaries.ts:30 regex matches db.member.* writes but not prisma.member.*. That's why this bypass never showed up as a boundary violation. Worth a follow-up to either widen the regex or explicitly allowlist app/database/** in the script (today the seed exemption lives in prose in docs/development/architecture-conventions.md but not in the enforcement).
Reproduction
pnpm tsx app/database/seed-marketing.tsmarc.dupont@demo.unitae.app/demo1234.Root cause
app/database/seed-marketing.ts:687creates every publisher viaprisma.member.create(...)directly, bypassing theMemberaggregate atapp/features/publishers/server/member.aggregate.ts.The aggregate is the only place that calls
syncBuiltInRoleAssignments(app/shared/domain/built-in-roles.server.ts:76), which reads a member's flags (isPublisher,isMale,baptismDate,isHelder,isServant, …) and populatesMemberRoleAssignmentrows for the built-in identity roles (member,publisher,brother,sister,elder,pioneer, …).Net effect: seeded members have zero
MemberRoleAssignmentrows — not even the always-truememberrole.The assign-part loader chain then falls over:
app/features/events/routes/programs/events/assign-part.tsxloaderloadPartAssignmentCandidates(app/features/events/server/assign-part-loader.server.ts)resolveEligibleUserIds(db, allowedRoleIds=[], congregationId)(app/features/events/server/allowed-roles.server.ts:23-38)With no explicit allowed roles on the template part, the fallback looks up the built-in
memberrole and returns members with that role assignment. That query returns[], sospeakerCandidatesandreaderCandidatesend up empty.Additional risk: if the marketing seed runs on a fresh DB (no prior
pnpm prisma db seed), the built-inRolerows don't exist for the congregation either.seed.ts:47callsseedBuiltInRoles(prisma, defaultCongregation.id);seed-marketing.tsdoesn't.Fix
Two additions to
seed-marketing.ts:seedBuiltInRoles(prisma, congId)right after the congregation is created / updated so the built-inRolerows exist regardless of whether the regular seed ran first.syncBuiltInRoleAssignments(prisma, member.id, congId, null)after eachprisma.member.create(...)so theMemberRoleAssignmentrows exist.Bonus: CI hole
scripts/check-aggregate-boundaries.ts:30regex matchesdb.member.*writes but notprisma.member.*. That's why this bypass never showed up as a boundary violation. Worth a follow-up to either widen the regex or explicitly allowlistapp/database/**in the script (today the seed exemption lives in prose indocs/development/architecture-conventions.mdbut not in the enforcement).