Goal
Make BaseUser the sole persistence source for user reads and writes while preserving legacy user API behavior through a compatibility mask.
Normal application flows must no longer query, create, update, or delete records in the legacy User collection.
Dependency
Depends on the BaseOrg migration ticket. User membership and admin status must be resolved from BaseOrg.users and BaseOrg.admins.
Background
BaseUserRepository currently supports legacy-shaped operations, but it still reads and dual-writes the old User collection.
Legacy user data stores organization and admin information on the user:
org_UUID
active
authority.active_roles
time.created / time.modified
The new data model stores account data in BaseUser, but organization membership and admin state in BaseOrg:
BaseUser.status
BaseOrg.users
BaseOrg.admins
Proposed approach
Create a LegacyUserMaskRepository backed only by BaseUser and BaseOrg.
It must expose the legacy UserRepository contract while deriving legacy fields:
| Source |
Legacy field |
| BaseOrg user membership |
org_UUID |
| BaseOrg admin membership |
authority.active_roles: ["ADMIN"] |
BaseUser.status |
active |
BaseUser.created |
time.created |
BaseUser.last_updated |
time.modified |
| BaseUser identity/name/secret |
Same-named legacy fields |
Update RepositoryFactory.getUserRepository() to return this compatibility repository so legacy CVE/CVE-ID consumers continue receiving legacy-shaped data without using User.
Refactor BaseUserRepository so legacy-format options mask BaseUser data rather than accessing User.
Required work
- Extract and test pure helpers:
toLegacyUser(baseUser, org)
toBaseUserPatch(legacyPayload)
- BaseOrg membership and admin lookups
- legacy query and projection translation where needed
- Move legacy reads and listings to BaseUser plus BaseOrg membership.
- Remove legacy
User writes from create, update, full update, reset-secret, and delete flows.
- Ensure create, reassignment, deletion, and admin-role changes atomically update:
BaseUser
- source
BaseOrg.users / BaseOrg.admins
- destination
BaseOrg.users / BaseOrg.admins
- Derive
org_UUID and authority.active_roles rather than persisting duplicate values in BaseUser.
- Preserve legacy responses for
/api/users and /api/org/:shortname/user* endpoints.
- Preserve registry user behavior, including hydrated
role: "ADMIN" responses.
- Remove production dependencies on
src/model/user.js and src/repositories/userRepository.js.
Important decision
BaseOrg.admins must be the authoritative source for ADMIN membership.
The BaseUser Mongoose model does not persist a dedicated role field, and current registry controllers already hydrate admin status from BaseOrg.admins. This migration should formalize that model.
Data migration and rollout
- Verify every legacy User has a matching BaseUser UUID.
- Verify every legacy
org_UUID corresponds to exactly one BaseOrg membership.
- Verify legacy ADMIN users occur in the matching BaseOrg
admins array.
- Report and resolve:
- orphaned users
- users with no org or multiple orgs
- mismatched admin state
- mismatched username, status, name, or secret data
- Add temporary telemetry proving runtime code no longer accesses
User.
Acceptance criteria
- Legacy user endpoints work with only BaseUser and BaseOrg records present.
- No normal request path reads or writes the
User collection.
- Legacy responses retain
org_UUID, active, authority.active_roles, and time.
- Registry responses retain
status and return role: "ADMIN" for BaseOrg admins.
- User create, update, move, delete, grant/revoke-admin, and reset-secret keep BaseUser/BaseOrg consistent.
- CVE and CVE-ID flows continue to resolve requester users and names correctly.
- Integration coverage includes legacy and registry user CRUD, reassignment, admin grant/revoke, authorization, reset-secret, and membership cleanup.
- Existing integration suite passes.
Goal
Make
BaseUserthe sole persistence source for user reads and writes while preserving legacy user API behavior through a compatibility mask.Normal application flows must no longer query, create, update, or delete records in the legacy
Usercollection.Dependency
Depends on the BaseOrg migration ticket. User membership and admin status must be resolved from
BaseOrg.usersandBaseOrg.admins.Background
BaseUserRepositorycurrently supports legacy-shaped operations, but it still reads and dual-writes the oldUsercollection.Legacy user data stores organization and admin information on the user:
org_UUIDactiveauthority.active_rolestime.created/time.modifiedThe new data model stores account data in
BaseUser, but organization membership and admin state inBaseOrg:BaseUser.statusBaseOrg.usersBaseOrg.adminsProposed approach
Create a
LegacyUserMaskRepositorybacked only byBaseUserandBaseOrg.It must expose the legacy
UserRepositorycontract while deriving legacy fields:org_UUIDauthority.active_roles: ["ADMIN"]BaseUser.statusactiveBaseUser.createdtime.createdBaseUser.last_updatedtime.modifiedUpdate
RepositoryFactory.getUserRepository()to return this compatibility repository so legacy CVE/CVE-ID consumers continue receiving legacy-shaped data without usingUser.Refactor
BaseUserRepositoryso legacy-format options maskBaseUserdata rather than accessingUser.Required work
toLegacyUser(baseUser, org)toBaseUserPatch(legacyPayload)Userwrites from create, update, full update, reset-secret, and delete flows.BaseUserBaseOrg.users/BaseOrg.adminsBaseOrg.users/BaseOrg.adminsorg_UUIDandauthority.active_rolesrather than persisting duplicate values in BaseUser./api/usersand/api/org/:shortname/user*endpoints.role: "ADMIN"responses.src/model/user.jsandsrc/repositories/userRepository.js.Important decision
BaseOrg.adminsmust be the authoritative source for ADMIN membership.The BaseUser Mongoose model does not persist a dedicated role field, and current registry controllers already hydrate admin status from
BaseOrg.admins. This migration should formalize that model.Data migration and rollout
org_UUIDcorresponds to exactly one BaseOrg membership.adminsarray.User.Acceptance criteria
Usercollection.org_UUID,active,authority.active_roles, andtime.statusand returnrole: "ADMIN"for BaseOrg admins.