PRD: User identifiers & configuration management
| Module | User Management (CORE-01) | PRD ID | PRD-USR-002 |
| Status | Shipped | Owner | Identity squad |
| Date | 2026-06-15 | Version | v1.0 |
| Packages | @nx/identity · @nx/core | URD | USR · CFG |
TL;DR
Turns two things the account already holds - its login identifiers and its personal settings - into surfaces a user can actually manage. Each identifier carries a scheme that types it (username, email, phone number, plus system-issued schemes), and adding one is safe by construction: it is always bound to the requesting user and always starts unverified, no matter what the request claims. Configurations are exposed the same way, grouped and uniquely keyed by code per user, and readable by any signed-in user before a merchant is even selected - so the app can hydrate a user's preferences at launch. On top of that, a user can save a custom table view by name, cloned from a base table layout, to keep their own columns and filters.
1. Context & Problem
PRD-USR-001 established the account: a user can hold several identifiers and a default set of configurations is seeded at registration. It defined what those records are - it did not define how a user manages them afterwards as standalone resources.
Without a managed surface, a signed-in user cannot add a second phone to an existing account, list the identifiers they already hold, or save their own table layout - and the platform has no guard against the two ways an identifier-create goes wrong: someone attaching an identifier to another user, or attaching one pre-marked verified to skip OTP. Configurations have a subtler gap: they must be readable before a merchant is chosen (the very first thing the client needs to render a user's preferences), yet the standard resource pattern gates reads behind a merchant scope.
This increment closes both gaps. It exposes identifiers and configurations as first-class, permission-gated resources, makes identifier-create safe by construction, opens configuration reads to any authenticated user, and adds a per-user custom table view built by cloning a base table layout.
2. Goals & Non-Goals
Goals
- Expose identifiers as a managed resource - list, search, count, single read, update, remove - scoped to the caller.
- Make adding an identifier safe by construction: it is always bound to the requesting user and always created unverified.
- Type every identifier with a scheme (username, email, phone number, plus system-issued schemes) drawn from a fixed set.
- Expose configurations as a managed resource, grouped and uniquely keyed by code per user.
- Allow configuration reads before a merchant is selected (user-level resource), while gating create / update / delete.
- Let a user save a custom table view from a code + display name, cloned from the base table layout, rejecting duplicates.
Non-Goals
- The account-bootstrap path itself (registration creating account, profile, identifiers, default configs) - specified in PRD-USR-001.
- OTP request / delivery / verification flows - they live in the
AUTHarea (Authentication); this PRD only sets an identifier's initial unverified state. - Password and credential schemes (URD-AUTH).
- Custom role / permission definition → Permissions.
- The contents of any specific configuration payload (SMS provider config, search pipeline, finance defaults) - this PRD governs the management surface, not each payload's schema.
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Identifier ownership safety | 100% of created identifiers are owned by the requesting user; no cross-user attachment is possible |
| Verification safety | 100% of created email / phone identifiers start unverified; none bypass OTP via a pre-set flag |
| Pre-merchant readability | A signed-in user can read their configurations before selecting a merchant |
| Config key integrity | A configuration is uniquely resolvable by code within a user |
| Custom-view correctness | A saved view is rejected on duplicate code, and refused when its base table layout is missing |
4. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Owner / end user | Add and manage their own emails / phones, and save personal table layouts |
| Authorized operator | Browse and maintain identifiers within their scope under explicit permissions |
| Client app | Read a user's configurations at launch, before any merchant is chosen, to hydrate preferences |
Core scenario: a signed-in user opens identifier management → sees the identifiers they hold, each shown by its scheme → adds a new phone; it is stored against their own account and marked unverified, awaiting OTP. Separately, the app loads the user's configurations the moment they sign in - before a merchant is picked - and the user saves a custom "Products" table view; the system clones the base product table layout into a new per-user view, refusing a second view that reuses the same code.
5. User Stories
- As a user, I want to add an email or phone to my existing account, so I can later verify it and use it to sign in.
- As a user, I want any identifier I add to belong to me and start unverified, so no one can pre-trust a contact or attach it elsewhere.
- As a user, I want to see and manage the identifiers I already hold, each labelled by type, so I know my account's login surface.
- As a user, I want my preferences to load the moment I sign in, before I pick a store, so the app feels personalised from the first screen.
- As a user, I want to save my own table layout by name, so my columns and filters persist across sessions.
- As a user, I want a duplicate-named view refused, so my saved views stay unambiguous.
6. Functional Requirements
| # | Requirement | URD ref |
|---|---|---|
| FR-1 | Every identifier declares a scheme that types it - username, email, phone number, plus system-issued schemes - drawn from a fixed, validated set | URD-USR-012 |
| FR-2 | Identifiers are a managed resource: list, search, count, and single-record read, scoped to the caller's access | URD-USR-013 |
| FR-3 | Adding an identifier always binds it to the requesting user and creates it unverified; a supplied user reference or verified flag cannot override this | URD-USR-014 · URD-USR-005 |
| FR-4 | Identifiers can be updated and removed (single or by filter) via soft-delete; identifier data is preserved | URD-USR-015 · URD-USR-009 |
| FR-5 | Every identifier operation (read / create / update / delete) is individually permission-gated and merchant-scoped | URD-USR-016 |
| FR-6 | Configurations belong to a fixed group set (system, table, integration) and are uniquely identified by code per user | URD-CFG-004 · URD-CFG-002 |
| FR-7 | Configuration reads are available to any authenticated user before a merchant is selected; create / update / delete are permission-gated | URD-CFG-005 |
| FR-8 | A user can create a custom table view from a code + display name; the system clones the base table configuration's stored layout into a new per-user view in the table group | URD-CFG-006 |
| FR-9 | Creating a custom view rejects a duplicate (user + code + name) view and requires the referenced base table configuration to exist | URD-CFG-007 |
Full requirement text and acceptance criteria live in the User Management URD - USR and CFG. This PRD references them rather than restating them. The account-bootstrap requirements (URD-USR-001..011, URD-CFG-001..003) are specified in PRD-USR-001.
7. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Ownership integrity | Identifier-create derives the owner from the authenticated caller, never from the request body |
| Verification integrity | A created identifier is always unverified; the verified state is reachable only through the OTP flow |
| Authorization | Identifier operations are individually permission-gated; configuration reads are authenticate-only (user-level), writes permission-gated |
| Pre-merchant access | Configuration reads succeed before a merchant scope exists, so the client can hydrate at launch |
| Soft-delete | Removing an identifier or configuration preserves the row; physical deletion is never performed |
| i18n | User-facing labels (identifier-type and configuration labels) are bilingual ({ en, vi }) |
8. UX & Flows
Key screens (in apps/client): identifier management (add / list emails and phones by type), preferences hydration at sign-in, and the per-table "save view" control that persists a user's columns and filters.
9. Data & Domain
| Entity | Role in this increment |
|---|---|
UserIdentifier | A typed login value - scheme (username / email / phone, plus system schemes), identifier, a verified flag, owned by a user; managed as a resource |
UserConfiguration | A per-user setting - group (system / table / integration), code, display name, stored value - uniquely keyed by code per user; a custom view is a table-group row cloned from a base layout |
Conceptual only - full schema and invariants live in the identity domain model. Identifier ownership and uniqueness are enforced in the service layer, not by cross-table database constraints.
10. Dependencies & Assumptions
Depends on
- User account & configuration (PRD-USR-001, URD-USR-001..011, URD-CFG-001..003) - this increment manages the identifiers and configurations that account bootstrap creates.
- Authentication (URD-AUTH) - the OTP flow flips an identifier from unverified to verified; this PRD only sets the initial state.
- Roles & Scoping (URD-ROLE) - identifier operations are permission-gated and scoped to the caller.
@nx/core- shared identifier and configuration models, scheme/group constants, and base table layouts.
Assumptions
- A user is authenticated before managing identifiers or configurations (except where reads are intentionally open at the user level).
- Base table layouts exist as seeded table-group configurations that a custom view can clone from.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| An identifier attached to the wrong user | Owner is taken from the authenticated caller, never the request body |
| A contact pre-marked verified to skip OTP | Create always forces the unverified state; verification is reachable only via the OTP flow |
| Configuration reads blocked before a merchant is chosen | Reads are authenticate-only at the user level, so the client can hydrate at launch |
| Two custom views colliding on the same code | A duplicate (user + code + name) view is refused before anything is saved |
| A view cloned from a missing base layout | Creation requires the referenced base table configuration to exist, else it is refused |
12. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P1 (foundation) - extends USR and CFG in the URD feature catalog |
| Rollout | All users; no feature flag |
| Migration | None - the surfaces operate on existing identifier and configuration records |
| Launch criteria | Identifiers are listable and addable; a created identifier is owned by the caller and unverified; configurations are readable before a merchant is selected; a custom view clones the base layout and refuses duplicates / missing base |
| Monitoring | Identifier-create ownership/verified invariants, configuration read latency at sign-in, custom-view rejection rate by reason |
13. FAQ
Can I add an identifier to another user's account? No - the owner is always the authenticated caller; any user reference in the request is ignored.
Why does a new email I add show as unverified? Every added identifier starts unverified by design; it becomes a usable login only after the OTP flow verifies it.
Why can I read my settings before choosing a store? Configurations are a user-level resource, so reads are open to any signed-in user - that lets the app hydrate your preferences the moment you sign in, before a merchant is selected.
What is a custom view? A personal table layout (columns and filters) saved under your account, cloned from a base table layout and identified by a code and name.
What stops two views clashing? A view reusing an existing (user + code + name) combination is refused, and a view whose base table layout does not exist is refused as well.
References
- URD: User Management - User Account · User Configuration
- Builds on: PRD-USR-001 - User account & configuration
- Related: Authentication · Roles & Scoping
- Module: User Management - overview + capabilities
- Developer: @nx/identity · domain model · @nx/core