- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
---Please consult security and risk organizations before proceeding with the design below. In an ideal scenario, we don't want retirees to access employee information. The following approach was socialized ONLY if the organization accepted the risk----
Executive Summary
This design replaces a legacy alumni directory application with a ServiceNow-native solution. A single custom table (u_people_directory) holds both active employee records and opted-in retiree/alumni records. A nightly scheduled sync job populates employee data from sys_user. Retirees manage their own directory preferences through the existing ASC profile page. A dedicated People Directory Search widget on the ASC portal provides the lookup experience.
Key design decisions:
- One unified custom table holds both populations, with a record type field (employee vs. alumni/retiree) and a record source field (sync vs. self-service) governing ownership.
- Alumni access the ASC portal only. No EC Pro access, no direct queries to sys_user, sn_employee_profile, or sn_hr_core_profile.
- A scheduled sync job (Import Set + Transform Map) copies approved employee fields from sys_user nightly. Alumni never touch the sync infrastructure.
- Retirees manage their directory opt-in and contact preferences from the existing ASC profile page via an embedded Directory Preferences widget. No new pages or navigation entries are needed.
- A dedicated People Directory Search widget on a new ASC portal page provides purpose-built search for finding employees and opted-in retirees. This is intentionally separate from the ASC portal’s general search bar.
- AI Search is not used. The directory search is a distinct user intent ("find a person") that requires consent-aware field display and clean data segregation from platform search infrastructure.
Solution Overview
What Gets Built
|
Component |
Type |
Purpose |
|
u_people_directory |
Custom table |
Unified directory holding employee records (system-synced) and alumni/retiree records (self-managed with opt-in consent). |
|
Employee Sync Job |
Import Set + Transform Map |
Nightly sync from sys_user to u_people_directory for active employees matching defined criteria. |
|
People Directory Search |
Custom SP widget + ASC page |
Dedicated search interface for looking up employees and opted-in retirees. Embedded on a new "Directory" page in the ASC portal. |
|
Directory Preferences |
Lightweight custom SP widget |
Consent toggles and contact fields for retirees. Embedded as a section on the existing ASC profile page. |
What Stays Unchanged
- Employee Center Pro (/esc): Active employees continue using EC Pro’s native people finder, org chart, and profiles. EC Pro and the alumni directory share no widgets, tables, or security model.
- ASC portal core: Existing ASC content (knowledge articles, HR cases, targeted communications, general search) is unaffected. The directory is additive.
- ASC profile page: The existing profile page (accessible via the user avatar/name in the top right of the ASC portal) continues to surface sys_user and HR profile fields. The Directory Preferences widget is added as a new section alongside existing profile content.
Why Not AI Search
AI Search was evaluated and intentionally excluded from this design for three reasons.
Different user intent: The ASC portal’s search bar serves a general purpose: finding knowledge articles, HR services, and catalog items. "Find a person" is a distinct intent with a distinct UX. Mixing people results into the general search creates confusion. An alumni searching "John Smith" expects a person card, not a knowledge article about an employee named John Smith. A dedicated widget with purpose-built search, filtering by record type (employee vs. retiree), and consent-aware profile cards delivers a cleaner experience.
Consent-aware display is not supported: AI Search renders result cards with a static field layout configured at the Search Source level. It cannot conditionally show or hide fields per record based on consent booleans. An alumni who opted to share their email and one who did not would receive identical result cards. The consent model requires per-record field evaluation at render time, which only a custom widget can provide.
Data segregation principle: The core architectural decision in this design is to keep alumni interactions fully contained within a single custom table, with no dependencies on platform-level system tables. AI Search introduces dependencies on sys_ais_index, sys_ais_search_source, indexed source configurations, and the search indexing pipeline. This reintroduces the coupling we specifically designed away from. The People Directory Search widget queries u_people_directory directly via GlideRecord with no intermediary.
Data Model: u_people_directory
Single custom table holding both populations. One record per person. The u_record_type and u_record_source fields govern behavior and ownership.
Core Fields (All Records)
|
Field Name |
Type |
Purpose |
Source |
|
u_user |
Reference (sys_user) |
Link to user record (if exists) |
sys_user / alumni record |
|
u_record_type |
Choice |
Values: employee, alumni, retiree |
Sync job or self-service |
|
u_record_source |
Choice |
Values: sync, self-service. Determines ownership. |
Set on creation, never changed |
|
u_display_name |
String (150) |
Name as displayed in directory |
sys_user.name / self-entered |
|
u_department |
String (100) |
Current or last department |
sys_user.department / self-entered |
|
u_title |
String (100) |
Current or former job title |
sys_user.title / self-entered |
|
u_work_phone |
Phone Number |
Work phone (employees only) |
sys_user.phone |
|
u_work_location |
String (200) |
Office location (employees only) |
sys_user.location |
|
u_is_active |
True/False |
Whether record appears in search results |
Sync job / consent toggle |
Alumni/Retiree-Specific Fields
|
Field Name |
Type |
Purpose |
Source |
|
u_personal_email |
|
Personal email address |
Self-entered via profile |
|
u_personal_phone |
Phone Number |
Personal phone number |
Self-entered via profile |
|
u_mailing_address |
String (500) |
Mailing address |
Self-entered via profile |
|
u_linkedin_url |
URL |
LinkedIn profile link |
Self-entered via profile |
|
u_departure_year |
Integer |
Year departed the organization |
HR profile or self-entered |
|
u_consent_listed |
True/False |
Master opt-in: appear in directory |
Default: false |
|
u_consent_email |
True/False |
Opt-in: show personal email |
Default: false |
|
u_consent_phone |
True/False |
Opt-in: show personal phone |
Default: false |
|
u_consent_address |
True/False |
Opt-in: show mailing address |
Default: false |
|
u_last_updated |
Date/Time |
Last profile edit timestamp |
Auto-set on save |
Ownership Rules
u_record_source = "sync" (employee records): Created and maintained exclusively by the scheduled sync job under a service account. No user can edit these records through the portal. The sync job upserts on each run: creates records for new employees, updates changed fields, and sets u_is_active = false for departed employees. Records are deactivated, not deleted, for audit purposes.
u_record_source = "self-service" (alumni/retiree records): Created and maintained by the individual retiree through the Directory Preferences section on the ASC profile page. The sync job skips any record where u_record_source = "self-service." Even if a retiree has an inactive sys_user record, the sync job will not create a duplicate or overwrite their self-managed profile.
Consent Model: u_consent_listed must be true for the record to appear in any search results. Individual field consents (email, phone, address) control which contact details are visible. LinkedIn URL and display name are always visible when the master consent is granted, since the retiree explicitly populated those fields. For employee records, consent fields are not evaluated — u_is_active is controlled directly by the sync job based on employment status.
Scheduled Sync Job
Source Query
The sync job queries sys_user with configurable filter criteria stored as system properties. Recommended defaults:
- active = true
- employee_type IN [configurable list, e.g., "regular", "home-based"]
- Optional: exclude VIP users via custom flag or group membership
- Optional: u_exclude_from_directory = true (new boolean on sys_user for individual employee opt-out)
Field Mapping
|
Source (sys_user) |
Target (u_people_directory) |
Notes |
|
sys_id |
u_user |
Reference field; coalesce key for upsert |
|
name |
u_display_name |
Full name |
|
department.name |
u_department |
Dot-walked to department name string |
|
title |
u_title |
Current job title |
|
phone |
u_work_phone |
Business phone only; never home or mobile |
|
location.name |
u_work_location |
Dot-walked to location name string |
The transform map sets u_record_type = "employee," u_record_source = "sync," and u_is_active = true for all upserted records. An onBefore script skips any target record where u_record_source = "self-service." After the sync completes, a post-sync script deactivates any sync-managed record whose corresponding sys_user no longer matches the source query criteria.
Frequency and Performance
Nightly by default. For organizations with high turnover, configurable to every 4 hours. The job processes only changed records using delta detection (comparing sys_updated_on against the last sync timestamp). A directory of 10,000–50,000 employees typically completes a full sync in under 5 minutes.
Widget Architecture
Widget 1: People Directory Search
Location: New "Directory" page added to the ASC portal, accessible via a new navigation link in the ASC menu (e.g., under "Company" or as a top-level item). This is the only new page in the design.
Server Script: Queries u_people_directory where u_is_active = true. Uses addField() to explicitly select only the approved display columns — defense in depth beyond ACLs. Applies typeahead search against u_display_name, u_department, u_title, and u_departure_year. For alumni/retiree records, the server script evaluates per-field consent booleans before including contact details in the response. For employee records, all mapped fields are returned (they contain only pre-approved data). Pagination at 20 results per page.
Client UX: Search bar with typeahead at the top. Filter options: "All," "Employees," "Alumni/Retirees" (maps to u_record_type). Results rendered as profile cards. Employee cards show: name, department, title, work phone, work location. Retiree/alumni cards show: name, last department, departure year, plus any consented contact fields (personal email, phone, address, LinkedIn). Cards that have no consented contact fields still appear in results (with name, department, departure year) so directory presence itself has value even without contact sharing.
Security: The server script is the primary enforcement layer. It queries only u_people_directory (never sys_user), selects only specific fields via addField(), and evaluates consent booleans per record. Table ACLs provide the second layer. The widget never returns data from any table other than u_people_directory.
Widget 2: Directory Preferences
Location: Embedded as a new section on the existing ASC profile page, below the standard profile fields. No new page, no new navigation. Retirees access it by clicking their avatar/name in the top-right corner of the ASC portal, which is the same action they would take to view or update their profile today.
Server Script: On page load, queries u_people_directory where u_user = current session user AND u_record_source = "self-service." If no record exists, returns an empty state (triggers the "Join the Directory" prompt in the client). If a record exists, returns the editable fields and current consent toggle values. On save, validates field formats (email, phone, URL), writes to the record, and auto-sets u_last_updated. On create, sets u_record_source = "self-service" and u_record_type based on the user’s selection (alumni or retiree).
Client UX: When no directory record exists, shows a card with a brief explanation: "Join the people directory to let other alumni and employees find you. You control exactly what information is shared." A single "Join" button creates the record with all consents set to false. When a record exists, shows the editable contact fields (personal email, phone, mailing address, LinkedIn) and a consent toggle next to each. A master "Listed in Directory" toggle controls u_consent_listed. A live preview panel shows exactly how the retiree’s card will appear in search results, updating dynamically as toggles change. A "Remove from Directory" link hard-deletes the record with a confirmation dialog. A "Pause Listing" toggle sets u_consent_listed = false without deleting data.
Security: The server script enforces u_user = current session user on all operations. A retiree cannot view or modify another person’s directory preferences. The write ACL on u_people_directory provides the second enforcement layer.
Design Decision: Embedding Directory Preferences on the existing profile page rather than creating a standalone page eliminates a new navigation entry, reduces page count, and leverages a familiar UX pattern. Retirees already know where their profile is. Adding a "People Directory" section to that page is the lowest-friction way to drive adoption.
ACL Strategy
Alumni interact with exactly one table: u_people_directory. No ACLs are created on sys_user, sn_employee_profile, or sn_hr_core_profile for the alumni role. The sync job runs under a service account with its own access.
|
ACL Type |
Operation |
Role |
Condition / Script |
|
Table-level |
Read |
sn_hr_asc.alumni |
Condition: u_is_active = true. Alumni see only active directory records. |
|
Table-level |
Create |
sn_hr_asc.alumni |
Script: u_user must equal gs.getUserID() AND u_record_source must be "self-service." Prevents alumni from creating employee-type records. |
|
Table-level |
Write |
sn_hr_asc.alumni |
Condition: u_user = current user AND u_record_source = "self-service." Alumni edit only their own self-managed record. |
|
Table-level |
Delete |
sn_hr_asc.alumni |
Condition: u_user = current user AND u_record_source = "self-service." Alumni delete only their own record. |
|
Field-level |
Write (deny) |
sn_hr_asc.alumni |
Fields: u_record_source, u_record_type, u_user, u_is_active. Prevents alumni from modifying governance fields directly. u_is_active is derived from u_consent_listed via business rule. |
Security Principle: The attack surface for alumni is one table containing only pre-approved directory data. Even a complete ACL failure exposes nothing beyond names, departments, titles, work phones, and voluntarily shared contact details. No manager chains, no personal emails from HR profiles, no compensation data, no emergency contacts.
Business Rules
|
Rule Name |
When |
Condition |
Action |
|
Set Active from Consent |
Before Insert/Update |
u_record_source = "self-service" |
Set u_is_active = u_consent_listed. Ensures retiree visibility is driven by master consent toggle. |
|
Protect Sync Records |
Before Update |
u_record_source = "sync" AND current user is not the sync service account |
Abort with message. Prevents any user from editing sync-managed records through the portal. |
|
Detect Rehire Conflict |
Before Insert |
u_record_source = "sync" AND existing record with same u_user AND u_record_source = "self-service" |
Skip insert. Create HR task for admin review. |
|
Set Last Updated |
Before Update |
u_record_source = "self-service" |
Set u_last_updated = gs.nowDateTime(). |
Privacy and Data Lifecycle
Consent Model: All retiree directory participation is opt-in with all consent flags defaulting to false. No retiree data is visible until the individual enables it through the Directory Preferences section on their ASC profile page.
Right to Erasure: "Remove from Directory" hard-deletes the u_people_directory record. Confirmation dialog includes: "This will permanently remove your contact information from the people directory." For employee records, the sync job handles lifecycle by setting u_is_active = false when the employee departs.
Data Minimization: The sync job copies only six fields from sys_user. The custom table physically cannot contain data it was never designed to hold. No personal email, home address, compensation, or manager chain data exists in u_people_directory for employee records.
Stale Record Cleanup: Monthly scheduled job flags retiree records where u_last_updated is older than 24 months and u_consent_listed = true. Flagged retirees receive an email to confirm continued participation. No response within 30 days sets u_consent_listed = false (hidden, not deleted). The retiree can re-enable by logging in and toggling the setting back on.
Employee Opt-Out (Optional): If the organization wants individual employees to exclude themselves, add u_exclude_from_directory (boolean) to sys_user. The sync job filters on this field. This is a policy decision, not a technical requirement.
Sample Implementation Roadmap
Phase 1: Table, ACLs, Business Rules (Weeks 1–2)
- Create u_people_directory table with all fields.
- Build table-level and field-level ACLs for the alumni role.
- Build all four business rules (consent activation, sync protection, rehire detection, last-updated stamp).
- Create sync service account with read access to sys_user.
- Validate alumni role cannot access EC Pro, backend, or any system table.
Phase 2: Sync Job (Weeks 2–3)
- Build Import Set and Transform Map for sys_user → u_people_directory.
- Configure source query filters as system properties.
- Implement upsert with coalesce on u_user and self-service record protection.
- Implement post-sync deactivation for departed employees.
- Test with production-representative data volume.
Phase 3: Widgets (Weeks 3–5)
- Build People Directory Search widget with typeahead, record type filters, consent-aware field display, and pagination.
- Create "Directory" page on ASC portal and add navigation link.
- Build Directory Preferences widget with consent toggles, live preview, join/pause/remove actions.
- Embed Directory Preferences widget on the existing ASC profile page.
- UX review for consistency with ASC portal styling.
Phase 4: Testing & Hardening (Weeks 5–7)
- Security: ACL enforcement, privilege escalation testing, field-level restriction validation, sync job access verification.
- Privacy: consent toggle behavior, hard-delete verification, stale record cleanup validation.
- Edge cases: rehire conflict, legacy data import, employee who was previously a retiree.
- UAT with pilot group of alumni/retirees.
- Performance: search at scale (10K+ records), sync job timing.
Phase 5: Migration & Launch (Weeks 7–9)
- Import legacy alumni directory data as "self-service" records with u_consent_listed = false.
- Run initial full employee sync.
- Communicate to alumni/retiree community with instructions and benefits.
- Parallel run with legacy app (2–4 weeks).
- Decommission legacy app.
Licensing Considerations
This design consumes one custom table entitlement (u_people_directory). Verify available allocation against App Engine entitlements or bundled custom tables with HRSD Enterprise. Alumni portal access uses the existing ASC licensing model (part of HRSD Enterprise Onboarding and Transitions). No EC Pro licensing implications exist because alumni never access any EC Pro component, table, or widget. The sync job runs as a system process and does not consume user entitlements.
Risks and Mitigations
|
Risk |
Impact |
Mitigation |
|
Sync job freshness: employee changes not reflected until next sync |
Low – directory data is not time-critical |
Nightly sync is sufficient. Increase to every 4 hours if needed. Real-time sync is not warranted for a people directory. |
|
Low retiree opt-in adoption |
Medium – directory has limited value with few participants |
Pre-populate records from legacy app with non-PII data (name, departure year, department). Set all consent flags to false. Communicate value during migration. |
|
Rehire creates data conflict |
Low – small number of users affected |
Business rule detects conflict and creates HR task. No automatic merge. |
|
Custom table entitlement not available |
Medium – blocks implementation |
Confirm entitlement in Phase 1. Evaluate App Engine subscription if needed. |
Appendix: Design Evolution
This design went through three iterations, each simplifying the architecture based on deeper analysis of requirements and platform capabilities.
|
Dimension |
V1: Direct EC Pro Query |
V2: Unified Table |
V3: Final (This Document) |
|
Custom tables |
1 (alumni directory only) |
1 (unified) |
1 (unified) |
|
Custom widgets |
3 |
2 |
1 full + 1 lightweight |
|
New portal pages |
2 (directory + profile mgmt) |
1 (directory) |
1 (directory) |
|
Tables alumni query |
2 (sn_employee_profile + custom) |
1 (u_people_directory) |
1 (u_people_directory) |
|
Licensing risk |
High (EC Pro data layer) |
None |
None |
|
Search approach |
Custom widget |
Custom widget |
Custom widget (AI Search evaluated and excluded) |
The final design achieves the original requirements (alumni-to-alumni directory with opt-in contact sharing, alumni-to-employee lookup) with the smallest possible custom footprint: one table, one sync job, one search page, and one profile section. Every platform component it touches (ASC portal, Service Portal widgets, Import Sets, Transform Maps, ACLs, business rules) is standard ServiceNow with no dependencies on premium features beyond the existing HRSD Enterprise license.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
