1. Purpose

A User is a person with access to oxFlow. Users are synced from Microsoft 365 (the identity source of truth) — they cannot be created directly in oxFlow. M365 brings identity (name, email, M365 ID); oxFlow assigns access via a Role (Admin / Lead Estimator / Estimator) post-sync.

The split is deliberate: IT manages identity (M365); oxFlow manages access (Role). Adding or removing people happens in M365; reassigning what they can do happens in oxFlow’s Admin page.

Analogy (glossary cooking model): the User is the kitchen staff — IT decides who’s on payroll; the head chef (Admin) decides who works the line versus the prep station.


2. Attributes

AttributeTypeRequiredDefaultNotes
idUUIDgeneratedSystem-managed
namestring (text)Display name as synced from M365 (e.g., “Alice Brown”)
emailstring (email)M365 primary email; used for login + display. Unique workspace-wide
m365_idstring (unique)M365 object ID (immutable). Anchors the sync; prevents duplicates
role_idRole refAssigned Role: Admin · Lead Estimator · Estimator (BR-084)
is_activebooleantrueMirrors M365 enabled status; falsified when M365 disables/deletes the account
last_login_attimestampnullLast successful login; populated by auth subsystem
created_at / updated_attimestampsystemAudit. created_by is always system (M365 sync); updated_by tracks Role changes in oxFlow
created_by / updated_byUser refsystemAudit

Derived attributes (computed, not stored) — see §8.


3. Roles

Every User has exactly one Role (BR-084). Multi-role Users are deferred to v2.

RoleScopeSummary
Admin 🟢Full workspaceSystem owner. Manages Users, Roles, lookups, branding, integrations
Lead Estimator 🟢Tender / Estimate controlCreates Tenders/Estimates, manages Commercials, runs Adjudications, locks/submits
Estimator 🟢Item-level workEdits Items, Worksheets, Worksheet Resources, Variables, Calculations, Content Blocks

Roles are hierarchical — Admin ⊇ Lead Estimator ⊇ Estimator. If a capability is granted to Estimator, the higher Roles automatically have it.

Full capability matrix: see roles-permissions.md §3.

Role assignment authority (BR-085, BR-086):

  • Only Admins can assign or change Roles
  • Role assignment happens in oxFlow’s Admin page after M365 sync brings in identity
  • Default Role on first sync: Estimator (lowest privilege; Admin elevates as needed)

4. Lifecycle States & Sync

Users have no lifecycle state machine beyond is_active. The sync mechanics are:

Sync direction: M365 → oxFlow only. oxFlow does not push User changes back to M365.

Sync triggers:

  • Scheduled — periodic background sync (cadence configurable; admin-managed)
  • On-demand — Admin clicks “Sync M365 Users” in Admin page
  • On login — first-time login from a new M365 account triggers a sync record (Estimator default)

What sync brings in:

  • name, email, m365_id, is_active — sourced from M365 every sync
  • New Users are added with Role = Estimator (default); Admin elevates as needed
  • Removed/disabled M365 accounts → is_active = false (User remains for audit/history but cannot log in)

What sync does NOT touch:

  • role_id — Role assignment is oxFlow-only. M365 group membership does not influence Role
  • Audit trail — historical references to a User remain even after deactivation

Re-activation: if a previously deactivated M365 account is re-enabled, the next sync flips is_active back to true. Role is preserved (was held throughout deactivation).


5. Relationships

Inbound (things referring to User)

FromCardinalityRole contextNotes
EstimateEstimate M:1 UserLead EstimatorRequired (BR-093); nominal accountability, not access gate
ItemItem M:0..1 UserAssigned EstimatorOptional informational tag (BR-095)
Audit log entriesmany M:1 UserVariousAll write actions stamp updated_by

Outbound (things User references)

ToCardinalityRequiredNotes
RoleUser M:1 RoleExactly one Role per User (BR-084)

6. Validation / Invariants

Rules that must hold at all times:

  1. M365 ID unique. No two Users may share the same m365_id. Enforced at sync time.
  2. Email unique. No two Users may share the same email (M365 enforces this upstream; oxFlow re-validates).
  3. Exactly one Role. role_id cannot be null; multi-role assignment is rejected (BR-084).
  4. Immutable M365 ID. Once synced, m365_id cannot be changed or transferred.
  5. Cannot delete with references. A User with inbound references (Lead Estimator on Estimates, audit log entries) cannot be hard-deleted; deactivation only.
  6. Role enum. role_id must reference a valid Role (Admin / Lead Estimator / Estimator). No other values.
  7. Inactive User cannot perform writes. is_active = false blocks all write actions; read access is admin-configurable.

7. Derived / Computed Attributes

AttributeDerivationNotes
is_adminrole_id references Admin RoleConvenience flag for UI gating
is_lead_estimator_or_aboveRole ∈ {Admin, Lead Estimator}Convenience flag for capability checks
assigned_estimate_countCount of Estimates where lead_estimator_id = this.idFor workload reporting
assigned_item_countCount of Items where estimator_id = this.idFor workload reporting

8. Worked Examples

Example A — New User from M365 sync (default Estimator role)

A new hire is added to M365’s “oxFlow Users” group. Next scheduled sync:

User (created by sync):
  name: "Charlie Davis"
  email: "charlie.davis@oxcon.local"
  m365_id: "M365-7F2E-..."
  role_id: <Estimator Role>     ← default on first sync
  is_active: true
  created_by: system (M365 sync)
  created_at: 2026-04-15T08:30:00Z

Admin then logs in, navigates to Admin → Users, sees Charlie listed,
elevates Role to "Lead Estimator" because Charlie is replacing a departing Lead.

Updated:
  role_id: <Lead Estimator Role>
  updated_by: admin@oxcon.local
  updated_at: 2026-04-15T09:12:00Z

Example B — User deactivated in M365

An employee leaves; their M365 account is disabled. Next sync:

User (before sync):
  name: "Bob Roberts"
  email: "bob.roberts@oxcon.local"
  is_active: true
  role_id: <Lead Estimator>
  assigned_estimate_count: 4

Sync detects M365 status = disabled →

User (after sync):
  is_active: false
  role_id: <Lead Estimator>     ← preserved for audit
  
Effects:
  - Bob can no longer log in
  - Estimates Bob led remain assigned to him (audit trail preserved)
  - Lead Estimator field on those Estimates still shows "Bob Roberts"
    (any Lead Estimator can still act on them per the flat model — see BR-093)
  - Items Bob was assigned (estimator_id) remain tagged for history

Example C — Role change preserves audit history

Admin demotes a Lead Estimator back to Estimator (e.g., role rotation):

Before:
  User "Alice Brown"
  role_id: <Lead Estimator>
  assigned_estimate_count: 7

After Admin role change:
  role_id: <Estimator>
  updated_by: admin@oxcon.local
  
Effects:
  - Alice's Lead Estimator capabilities are revoked immediately (next request)
  - Estimates where Alice is the named Lead Estimator: lead_estimator_id remains "Alice"
    (the field is nominal, not an access gate per BR-093)
  - Admin should reassign Lead Estimator on those Estimates if they want
    accountability to follow the Role change
  - Audit log preserves the role change with timestamp + acting Admin