You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase 8a (Node) of the invitations↔org decouple epic. Plan Phase 8a. Referral substrate for #5 — schema/event only, no credit math. Pairs with P8b (Vue, #4282).
⚠️ E14 — add the schema first, else the field is silently dropped/rejected.
modules/users/models/users.model.mongoose.js — add referredBy: { type: ObjectId, ref:'User', default:null } (index if queried). users.schema.js (Zod) allows it. Server-set only (from invitation.invitedBy) — keep OUT of the client signup whitelist (safeBody).
⚠️ E20 — block referredBy on EVERY user-update path (Phase-0 audit):rg the user-update controllers/repos (PATCH /api/users/me, admin user-update, profile update) and ensure referredBy is NOT in their writable whitelist (strip it). Test: PATCH /api/users/me with referredBy ignored. Not just the signup whitelist.
⚠️ E22 — shared finalize helper used by BOTH the token two-phase path AND the OAuth path (checkOAuthUserProfile via findValidByEmail): set referredBy = invitation.invitedBy on the created user + acceptedUserId on the invitation, then invitationEvents.emit('invitation.accepted', { invitationId, email, invitedBy, acceptedUserId }). Without it, OAuth-invited users aren't credited (adapting to front #5 gap).
modules/invitations/lib/events.js doc-block — finalize the payload.
Acceptance:referredBy persists server-side on accept (token AND OAuth paths); client-supplied referredBy in signup body AND PATCH /api/users/me ignored; event emitted; billing listener fires (spy) no-op. Zero credit-grant logic.
🛑 Fable review corrections (2026-06-10, code-verified vs origin/master)
E20 endpoint corrected: there is NO PATCH /api/users/me — it's PUT /api/users with a service-level whitelistconfig.whitelists.users.update (+ updateAdmin) in users.development.config.js. referredBy is blocked by simply NOT adding it to those arrays; the deliverable is the negative test (PUT /api/users + admin update with referredBy → ignored), not a strip.
File separately (pre-existing hole):email IS in the update whitelist and changing it does NOT reset emailVerified → a verified user swaps to an unverified email keeping emailVerified:true, trusted by OAuth linkProviderByEmail. Open an ERRORS.md/issue; out of scope here.
E22 shared finalize helper (token + OAuth both set referredBy) confirmed correct and necessary.
Phase 8a (Node) of the invitations↔org decouple epic. Plan Phase 8a. Referral substrate for #5 — schema/event only, no credit math. Pairs with P8b (Vue, #4282).
modules/users/models/users.model.mongoose.js— addreferredBy: { type: ObjectId, ref:'User', default:null }(index if queried).users.schema.js(Zod) allows it. Server-set only (frominvitation.invitedBy) — keep OUT of the client signup whitelist (safeBody).referredByon EVERY user-update path (Phase-0 audit):rgthe user-update controllers/repos (PATCH /api/users/me, admin user-update, profile update) and ensurereferredByis NOT in their writable whitelist (strip it). Test:PATCH /api/users/mewithreferredByignored. Not just the signup whitelist.checkOAuthUserProfileviafindValidByEmail): setreferredBy = invitation.invitedByon the created user +acceptedUserIdon the invitation, theninvitationEvents.emit('invitation.accepted', { invitationId, email, invitedBy, acceptedUserId }). Without it, OAuth-invited users aren't credited (adapting to front #5 gap).modules/billing/billing.init.js—invitationEvents.on('invitation.accepted', …)listener = no-op +// TODO(#5): grant credits.modules/invitations/lib/events.jsdoc-block — finalize the payload.Acceptance:
referredBypersists server-side on accept (token AND OAuth paths); client-suppliedreferredByin signup body ANDPATCH /api/users/meignored; event emitted; billing listener fires (spy) no-op. Zero credit-grant logic.🛑 Fable review corrections (2026-06-10, code-verified vs origin/master)
PATCH /api/users/me— it'sPUT /api/userswith a service-level whitelistconfig.whitelists.users.update(+updateAdmin) inusers.development.config.js.referredByis blocked by simply NOT adding it to those arrays; the deliverable is the negative test (PUT /api/users+ admin update withreferredBy→ ignored), not a strip.emailIS in the update whitelist and changing it does NOT resetemailVerified→ a verified user swaps to an unverified email keepingemailVerified:true, trusted by OAuthlinkProviderByEmail. Open an ERRORS.md/issue; out of scope here.referredBy) confirmed correct and necessary.