Skip to content

PRD: User identifiers & configuration management

ModuleUser Management (CORE-01)PRD IDPRD-USR-002
StatusShippedOwnerIdentity squad
Date2026-06-15Versionv1.0
Packages@nx/identity · @nx/coreURDUSR · 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 AUTH area (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

MetricTarget / signal
Identifier ownership safety100% of created identifiers are owned by the requesting user; no cross-user attachment is possible
Verification safety100% of created email / phone identifiers start unverified; none bypass OTP via a pre-set flag
Pre-merchant readabilityA signed-in user can read their configurations before selecting a merchant
Config key integrityA configuration is uniquely resolvable by code within a user
Custom-view correctnessA saved view is rejected on duplicate code, and refused when its base table layout is missing

4. Personas & Use Cases

PersonaGoal in this feature
Owner / end userAdd and manage their own emails / phones, and save personal table layouts
Authorized operatorBrowse and maintain identifiers within their scope under explicit permissions
Client appRead 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

#RequirementURD ref
FR-1Every identifier declares a scheme that types it - username, email, phone number, plus system-issued schemes - drawn from a fixed, validated setURD-USR-012
FR-2Identifiers are a managed resource: list, search, count, and single-record read, scoped to the caller's accessURD-USR-013
FR-3Adding an identifier always binds it to the requesting user and creates it unverified; a supplied user reference or verified flag cannot override thisURD-USR-014 · URD-USR-005
FR-4Identifiers can be updated and removed (single or by filter) via soft-delete; identifier data is preservedURD-USR-015 · URD-USR-009
FR-5Every identifier operation (read / create / update / delete) is individually permission-gated and merchant-scopedURD-USR-016
FR-6Configurations belong to a fixed group set (system, table, integration) and are uniquely identified by code per userURD-CFG-004 · URD-CFG-002
FR-7Configuration reads are available to any authenticated user before a merchant is selected; create / update / delete are permission-gatedURD-CFG-005
FR-8A 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 groupURD-CFG-006
FR-9Creating a custom view rejects a duplicate (user + code + name) view and requires the referenced base table configuration to existURD-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

AreaRequirement
Ownership integrityIdentifier-create derives the owner from the authenticated caller, never from the request body
Verification integrityA created identifier is always unverified; the verified state is reachable only through the OTP flow
AuthorizationIdentifier operations are individually permission-gated; configuration reads are authenticate-only (user-level), writes permission-gated
Pre-merchant accessConfiguration reads succeed before a merchant scope exists, so the client can hydrate at launch
Soft-deleteRemoving an identifier or configuration preserves the row; physical deletion is never performed
i18nUser-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

EntityRole in this increment
UserIdentifierA typed login value - scheme (username / email / phone, plus system schemes), identifier, a verified flag, owned by a user; managed as a resource
UserConfigurationA 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 / questionMitigation / status
An identifier attached to the wrong userOwner is taken from the authenticated caller, never the request body
A contact pre-marked verified to skip OTPCreate always forces the unverified state; verification is reachable only via the OTP flow
Configuration reads blocked before a merchant is chosenReads are authenticate-only at the user level, so the client can hydrate at launch
Two custom views colliding on the same codeA duplicate (user + code + name) view is refused before anything is saved
A view cloned from a missing base layoutCreation requires the referenced base table configuration to exist, else it is refused

12. Release Plan & Launch Criteria

AspectPlan
PhaseP1 (foundation) - extends USR and CFG in the URD feature catalog
RolloutAll users; no feature flag
MigrationNone - the surfaces operate on existing identifier and configuration records
Launch criteriaIdentifiers 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
MonitoringIdentifier-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

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.