Connector Resource Sync¶
Overview¶
The connector resource sync system enables Floh to maintain a local cache of upstream connector data — users, groups, roles, and other resources — so that workflow steps and UI screens can query connector data without making real-time API calls on every request.
This is particularly important for connectors that manage large directories (e.g. hundreds of thousands of users, hundreds of groups) where live fetches on every action would be prohibitively slow or rate-limited.
Architecture¶
The sync system uses a hybrid tiered approach:
| Tier | Storage | Use Case | Examples |
|---|---|---|---|
| Tier 1 — DB Sync | PostgreSQL | Small-to-medium reference data that changes infrequently | Groups, roles, documents |
| Tier 2 — Redis Cache | Redis (TTL) | Individual high-volume lookups during workflow execution | Single-user lookups |
| Tier 3 — Opt-in Bulk Sync | PostgreSQL | Large directories, enabled explicitly with filter rules | Full user directory |
┌─────────────────────────────────────────────────────┐
│ Sync Scheduler │
│ (BullMQ repeatable jobs) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ full sync │ │ incremental │ │
│ │ (cron) │ │ (cron) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────┐ │
│ │ ConnectorSyncService │ │
│ │ │ │
│ │ • Pages through connector │ │
│ │ • Computes SHA-256 hash │ │
│ │ • Upserts with hash comparison │ │
│ │ • Marks unseen records stale │ │
│ │ • Purges beyond retention │ │
│ └────────────┬─────────────────────┘ │
│ │ │
│ ┌────────────▼─────────────────────┐ │
│ │ connector_resource table (PG) │ │
│ │ connector_sync_config table │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ ConnectorCacheService (Redis) │◄── Tier 2 │
│ │ TTL-based per-resource cache │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ Webhooks (near-real-time) │ │
│ │ Invalidates cache + upserts │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
Database Schema¶
connector_resource¶
Stores synced resource records from upstream connectors.
| Column | Type | Description |
|---|---|---|
id |
UUID PK | Auto-generated |
connector_id |
UUID FK | References connector_definition |
resource_type |
VARCHAR(64) | user, group, role, etc. |
external_id |
VARCHAR(255) | ID from the upstream system |
display_name |
VARCHAR(255) | Human-readable name |
email |
VARCHAR(255) | Optional email address |
attributes |
JSONB | Arbitrary upstream attributes |
sync_hash |
VARCHAR(64) | SHA-256 of the canonical record |
stale_since |
TIMESTAMPTZ | Set when the record was not refreshed during a sync pass |
synced_at |
TIMESTAMPTZ | Last time the record was confirmed upstream |
created_at |
TIMESTAMPTZ | Row creation time |
A unique constraint on (connector_id, resource_type, external_id) prevents duplicates.
connector_sync_config¶
Stores per-connector, per-resource-type synchronization configuration.
| Column | Type | Description |
|---|---|---|
id |
UUID PK | Auto-generated |
connector_id |
UUID FK | References connector_definition |
resource_type |
VARCHAR(64) | Resource type this config applies to |
enabled |
BOOLEAN | Whether scheduled sync is active |
strategy |
VARCHAR(32) | full or incremental |
cron_schedule |
VARCHAR(64) | Cron expression for scheduled syncs |
filter_rules |
JSONB | Optional filter rules (see below) |
stale_retention |
VARCHAR(32) | How long to keep stale records (e.g. 7d, 24h) |
last_sync_at |
TIMESTAMPTZ | Timestamp of last sync completion |
last_sync_cursor |
TEXT | Resume cursor on error |
last_sync_status |
VARCHAR(32) | idle, running, success, error |
last_sync_error |
TEXT | Error message if last sync failed |
last_sync_stats |
JSONB | Statistics from the last sync run |
attribute_mappings |
JSONB | Declarative mappings from connector resources to profile fields (see Attribute mapping) |
user_match_strategy |
VARCHAR(32) | Primary strategy: email, email_and_issuer, upstream_identity, or external_id (default email) |
user_match_fallback_strategies |
JSONB | Ordered list of additional strategies tried when the primary finds no unique user |
issuer_source_path |
TEXT | Dot-path to issuer field on resource (for email_and_issuer); defaults to attributes.identityIssuer at runtime |
post_sync_workflow_id |
UUID FK | Optional workflow_definition to run after sync (nullable; ON DELETE SET NULL) |
sync_trigger_mode |
VARCHAR(32) | How post-sync workflow automation is scoped: none, per_record, or summary (default none) |
create_users |
BOOLEAN | When true, post-sync may create Floh users for rows that remain unmatched (default false) |
deactivate_users |
BOOLEAN | When true, post-sync may soft-delete users whose matched connector row has gone stale (default false) |
create_user_defaults |
JSONB | Profile attribute payload applied to users auto-created via create_users |
connector_sync_match¶
One row per synced connector resource (for configured user resource types), used to reconcile upstream identities to Floh users and drive profile updates.
| Column | Type | Description |
|---|---|---|
id |
UUID PK | Auto-generated |
connector_id |
VARCHAR(36) FK | References connector_definition |
resource_type |
VARCHAR(64) | Same resource type as the sync config (e.g. user) |
connector_resource_id |
UUID FK | References connector_resource |
external_id |
VARCHAR(255) | Upstream identifier |
external_display_name |
VARCHAR(255) | Cached display name |
external_email |
VARCHAR(255) | Cached email |
user_id |
UUID FK | Linked Floh user when matched or resolved (nullable) |
match_status |
VARCHAR(32) | matched, unmatched, ambiguous, manual_override, skipped, create_pending |
match_confidence |
VARCHAR(32) | exact, probable, ambiguous, or none |
match_strategy |
VARCHAR(32) | Strategy that produced the current outcome, when applicable |
candidate_user_ids |
JSONB | Candidate list when status is ambiguous |
resolution_notes |
TEXT | Optional operator notes |
resolved_by |
UUID FK | User who resolved the row |
resolved_at |
TIMESTAMPTZ | Resolution timestamp |
created_at / updated_at |
TIMESTAMPTZ | Row metadata |
Unique on (connector_id, resource_type, connector_resource_id).
Sync Strategies¶
Full Sync¶
A full sync fetches all resources from the upstream connector, upserts them into connector_resource, and marks any records that were not seen during this pass as stale. Stale records are purged after the retention period.
Best for: Reference data that should always reflect the upstream truth (groups, roles).
Incremental Sync¶
An incremental sync requests only records modified since the last successful sync (modifiedSince parameter). It does not mark unseen records as stale — since it only fetches changes, absence does not imply deletion.
Best for: Large datasets where full enumeration is expensive and the upstream supports modification timestamps.
Filter Rules¶
Filter rules restrict which upstream records are synced. They are stored in the filter_rules JSONB column and applied in-memory after each page is fetched.
interface SyncFilterRules {
groupNamePattern?: string; // Glob-like pattern (e.g. "proj-*")
groupIds?: string[]; // Whitelist of specific group IDs
emailDomains?: string[]; // Only sync users from these domains
memberOfSyncedGroups?: boolean; // Only sync users in already-synced groups
maxRecords?: number; // Hard cap on total synced records
}
Group Filters¶
groupNamePattern— Matches thedisplayNameusing glob syntax (*= any chars,?= single char). Case-insensitive.groupIds— Explicit whitelist; only groups with matchingexternalIdare synced.- Both filters compose with AND: a record must pass all specified filters.
User Filters¶
emailDomains— Only syncs users whose email domain matches one of the listed domains. Case-insensitive.maxRecords— Stops syncing after this many records have been processed (across all pages).
Sync Hash and Change Detection¶
Each upstream record is serialized into a canonical JSON form:
A SHA-256 hash of this JSON is computed and stored as sync_hash. On subsequent syncs, the hash is compared:
- Match → Record is unchanged; only
synced_atis updated. - Mismatch → Record is updated with new data and hash.
- New → Record is inserted.
This minimizes database writes for large datasets where most records haven't changed.
Stale Management¶
After a full sync completes, any records whose synced_at is older than the sync start time (meaning they were not seen upstream) are marked stale by setting stale_since to the current time.
Stale records are retained for a configurable period (default 7d) before being purged. This protects against transient upstream issues — if a group temporarily disappears from an API response, it won't be immediately deleted.
Redis Cache (Tier 2)¶
The ConnectorCacheService provides a TTL-based Redis cache for individual resource lookups. This is used during workflow execution to avoid repeated API calls when checking a single user or group.
Key format: cr:{connectorId}:{resourceType}:{externalId}
Default TTL: 300 seconds (5 minutes)
Cache-Aside Pattern¶
getOrFetch(connectorId, type, externalId, fetcher):
1. Check Redis → if hit, return cached value
2. Call fetcher() → if null, return null
3. Store result in Redis with TTL
4. Return result
Cache Invalidation¶
Cache entries are invalidated:
- On webhook events (created/updated/deleted)
- On manual cache clear
- Automatically via TTL expiry
Webhook Integration¶
The webhook handler at POST /api/webhooks/:connectorId has been extended to handle resource sync events:
| Action | Cache | DB |
|---|---|---|
created |
Invalidate key | Upsert record with hash |
updated |
Invalidate key | Upsert record with hash |
deleted |
Invalidate key | Delete record |
The webhook body must include a resourceType field to trigger resource sync handling. Without it, the event falls through to the existing entitlement reconciliation handler.
{
"action": "updated",
"resourceId": "user-123",
"resourceType": "user",
"data": {
"displayName": "Jane Doe",
"email": "jane@acme.com"
}
}
Post-sync processing¶
After a sync run finishes writing connector_resource rows, post-sync processing may run. It reconciles users, optionally creates or deactivates accounts, applies attribute mappings to profiles, and can feed post-sync workflow automation.
Post-sync runs only when the sync pass had at least one added or updated resource (not on a purely unchanged run). Failures in post-sync are logged and do not fail the overall sync status.
The pipeline (in order):
- User matching — Recompute
connector_sync_matchrows from current resources and Floh users. - Auto-create users — If
createUsersis enabled, create minimal unconfirmed users for rows stillunmatchedthat have an email, then link the match. - Auto-deactivation — If
deactivateUsersis enabled, soft-delete users who are matched only through this connector/resource type and whose linked resource hasstale_sinceset (and who have no other sync match rows on other connectors). - Attribute mapping — For matches in
matchedormanual_overridewith auser_id, apply configured mappings into the user profile.
User matching strategies¶
Matching compares each synced resource to v_user using a primary strategy (userMatchStrategy) and an optional fallback chain (userMatchFallbackStrategies). Duplicates in the fallback list are ignored; the primary is always tried first.
| Strategy | Connector side | Floh user field |
|---|---|---|
email |
Normalized resource email | email (case-insensitive) |
email_and_issuer |
Normalized email + issuer from configurable dot-path | email + upstream_issuer (compound key) |
upstream_identity |
Resource externalId |
upstream_id |
external_id |
Resource externalId |
sub |
The email_and_issuer strategy resolves the issuer from the resource using issuerSourcePath (defaults to attributes.identityIssuer when not set). The compound key is lower(email)|lower(issuer). This is useful in multi-IdP environments where the same email may appear under different identity providers.
For each strategy in order, the engine looks up users by that key. No match moves to the next strategy. Exactly one user produces a match for that step. Two or more users stop the chain with status ambiguous and populate candidate_user_ids.
All lookup queries use the withTempLookup helper (db/temp-lookup.ts) which inserts lookup keys into a temporary table and uses WHERE column IN (SELECT value FROM _lk_...) instead of parameterized IN (...) clauses. This avoids PostgreSQL's ~65,535 parameter limit and produces efficient query plans for large directory syncs.
Match confidence¶
| Confidence | Meaning |
|---|---|
exact |
Unique hit on the primary strategy |
probable |
Unique hit on a fallback strategy |
ambiguous |
Multiple users matched for the current strategy |
none |
No user matched after exhausting the chain |
Match reconciliation¶
Operators resolve ambiguous rows or correct bad links via the Match reconciliation API: link to a specific userId, mark a row as skipped, unlink to reset to unmatched, or use create (single-row resolve) to create an unconfirmed user from the cached resource email and link. Resolved rows can move to manual_override or matched depending on the action.
Attribute mapping¶
attributeMappings on the sync config is a declarative list of rules that copy values from a connector resource (after sync) into user profile fields. Each entry includes:
| Field | Description |
|---|---|
sourcePath |
Dot path into the resource, rooted at top-level fields and attributes (e.g. email, attributes.departmentCode). Reads use own-property rules and block unsafe path segments. |
targetField |
Profile field name: built-in keys such as title, department, phoneNumber, location, employeeId, costCenter, startDate, or a custom attribute name. |
writeMode |
How to treat an existing profile value (see below). |
transform |
Reserved for future expression-based transforms; non-empty values are currently skipped. |
enabled |
When false, the rule is ignored. |
Write modes:
| Mode | Behavior |
|---|---|
overwrite_always |
Write the source value whenever it is present on the resource. |
overwrite_if_empty |
Write only when the current profile value is empty (null, undefined, or blank string). |
never_overwrite |
Never write; useful for keeping a rule disabled without deleting it. |
Updates go through ProfileRepository.updateAttributes with source connector. The profile DTO’s attributeSources map records which fields were last written by manual, oidc, workflow, or connector, so UIs and policies can distinguish connector-driven values.
Workflow variables: Steps that resolve the subject user’s profile (or variables derived from it) see values produced by mapping on the next run after a successful post-sync pass. Connector data does not bypass the profile layer—sync → match → profile update → workflows consume the same profile model as the rest of Floh.
User lifecycle management¶
| Option | Default | Behavior |
|---|---|---|
createUsers |
false |
After matching, for each unmatched row with a non-empty external_email, create an unconfirmed Floh user, link the match, and set confidence to exact. Rows without email are skipped. |
deactivateUsers |
false |
After matching, soft-delete users who are matched only through this connector/resource pair and whose resource row is stale (stale_since set). Users with sync matches on other connectors are not deactivated. |
createUserDefaults |
null |
JSON object of profile fields applied once to each user created via createUsers (same shape as profile attribute updates, stored with source connector). |
Email collisions during auto-create are handled as errors for that row; the resolve API’s create action similarly returns a conflict if the email already exists.
Post-sync workflow triggers¶
Sync configuration can reference a postSyncWorkflowId (a workflow_definition id) together with syncTriggerMode:
| Mode | Role |
|---|---|
none |
No post-sync workflow automation (default). |
per_record |
Intended for automation scoped to individual changed records; job payloads may include a records collection for the workflow. |
summary |
Intended for a single post-sync run with aggregate context; job payloads may include a stats object. |
The integrations worker registers a sync-workflow-trigger job handler. Job data is validated to include connectorId, resourceType, workflowId, and triggerMode (the configured mode). Optional records and stats are passed through when present. The handler resolves the workflow definition and builds the initial variable bag:
| Variable | Description |
|---|---|
connectorId |
Connector instance id |
resourceType |
Synced resource type |
workflowId |
Target workflow definition id |
triggerMode |
Same as syncTriggerMode |
records |
Present when the dispatcher sends changed-record detail |
stats |
Present when the dispatcher sends aggregate post-sync / sync stats |
Exact enqueue timing and payload shape for each mode are defined by the dispatcher that enqueues sync-workflow-trigger jobs; the sync config fields store the operator’s chosen workflow and mode.
Match reconciliation API¶
All paths are under /api/connectors and require authentication.
List sync matches¶
Query parameters
| Parameter | Required | Description |
|---|---|---|
type |
Yes | Resource type (e.g. user) |
status |
No | Filter: matched, unmatched, ambiguous, manual_override, skipped, create_pending |
search |
No | Search term for external id, email, or display name |
page, pageSize |
No | Pagination (same conventions as other list APIs; pageSize up to 2000) |
Response: Paginated list of match records (data, total, page, pageSize, totalPages).
Example item:
{
"id": "b2c0c4d8-…",
"connectorId": "…",
"resourceType": "user",
"connectorResourceId": "…",
"externalId": "upstream-42",
"externalDisplayName": "Jane Doe",
"externalEmail": "jane@acme.com",
"userId": null,
"matchStatus": "ambiguous",
"matchConfidence": "ambiguous",
"matchStrategy": "email",
"candidateUserIds": [
{
"userId": "…",
"email": "jane@acme.com",
"confidence": "ambiguous",
"reason": "email"
}
],
"resolutionNotes": null,
"resolvedBy": null,
"resolvedAt": null,
"createdAt": "…",
"updatedAt": "…"
}
Sync match statistics¶
Response:
Resolve a sync match¶
POST /api/connectors/:id/sync-matches/:matchId/resolve
Content-Type: application/json
{
"action": "link",
"userId": "uuid-of-floh-user",
"notes": "Verified with HR"
}
Body
action |
userId |
Behavior |
|---|---|---|
link |
Required | Attach the match to the given Floh user |
skip |
— | Mark the row skipped (no user link) |
create |
— | Create an unconfirmed user from the cached resource email and link (fails with 400 if email missing, 409 if email exists) |
Response: Updated match record (200).
Unlink a sync match¶
Clears the user link and resolution metadata so the row can be matched again on a later post-sync pass.
Response: Updated match record (200).
Bulk resolve sync matches¶
POST /api/connectors/:id/sync-matches/bulk-resolve
Content-Type: application/json
{
"matchIds": ["uuid-1", "uuid-2"],
"action": "link",
"userId": "uuid-of-floh-user",
"notes": "Bulk link after directory review"
}
Body: matchIds (non-empty array), action link or skip. userId is required when action is link. Only matches belonging to the connector :id are updated.
Response:
API Reference¶
All endpoints require authentication and are scoped under /api/connectors.
List Synced Resources¶
Returns paginated resources from the local sync cache. Supports filtering by type, search term, and stale status.
List Sync Configurations¶
Returns all sync configurations for a connector.
Get Sync Configuration¶
Returns the sync configuration for a specific resource type.
Create or Update Sync Configuration¶
PUT /api/connectors/:id/sync-config/:type
Content-Type: application/json
{
"enabled": true,
"strategy": "full",
"cronSchedule": "0 */6 * * *",
"filterRules": {
"groupNamePattern": "proj-*"
},
"staleRetention": "7d",
"userMatchStrategy": "email_and_issuer",
"issuerSourcePath": "attributes.identityIssuer",
"userMatchFallbackStrategies": ["email"],
"attributeMappings": [
{
"sourcePath": "attributes.department",
"targetField": "department",
"writeMode": "overwrite_if_empty",
"enabled": true
},
{
"sourcePath": "displayName",
"targetField": "title",
"writeMode": "overwrite_always",
"enabled": true
}
],
"createUsers": false,
"deactivateUsers": false,
"createUserDefaults": {
"location": "Remote"
},
"postSyncWorkflowId": null,
"syncTriggerMode": "none"
}
Creates a new sync configuration or updates an existing one. When enabled with a cron schedule, a BullMQ repeatable job is registered automatically. On update, include only fields to change. On create, omitted keys use the defaults in the table below.
Sync configuration fields¶
| Field (API) | Default (create) | Description |
|---|---|---|
enabled |
false |
Turns scheduled sync on or off |
strategy |
full |
full or incremental |
cronSchedule |
null |
Cron for repeatable sync jobs |
filterRules |
null |
In-memory filters (see Filter rules) |
staleRetention |
7d |
Stale purge window |
attributeMappings |
null |
List of attribute mapping rules |
userMatchStrategy |
email |
Primary match strategy |
userMatchFallbackStrategies |
null |
Extra strategies in order after the primary |
issuerSourcePath |
null |
Dot-path for issuer on resource (for email_and_issuer; defaults to attributes.identityIssuer at runtime when null) |
postSyncWorkflowId |
null |
Workflow definition UUID for post-sync automation |
syncTriggerMode |
none |
none, per_record, or summary |
createUsers |
false |
Auto-create users for unmatched rows with email |
deactivateUsers |
false |
Soft-delete users tied only to stale matches on this connector |
createUserDefaults |
null |
Profile patch applied to auto-created users |
Delete Sync Configuration¶
Removes the sync configuration and its associated scheduled job.
Trigger Manual Sync¶
Immediately runs a sync for the specified resource type. Returns the sync statistics on completion.
Response:
{
"message": "Sync completed",
"stats": {
"added": 12,
"updated": 3,
"staled": 0,
"unchanged": 485,
"removed": 0,
"durationMs": 8420,
"pagesProcessed": 1,
"totalUpstreamRecords": 500
}
}
Get Sync Status¶
Returns the last sync status, error, and statistics without the full configuration.
Connector Implementation¶
For a connector to support resource sync, it must implement paginated list commands. The connector's configSchema should declare these commands with syncCapable: true.
Required Commands¶
| Resource Type | Command | Description |
|---|---|---|
group |
listGroups |
Paginated group listing |
user |
listUsers |
Paginated user listing |
role |
listRoles |
Paginated role listing |
Command Interface¶
Each list command receives:
{
command: 'listGroups',
cursor?: string, // Resume cursor from previous page
pageSize?: number, // Requested page size (default 500)
filter?: object, // Optional filter from sync config
modifiedSince?: string // ISO timestamp for incremental sync
}
And must return:
{
success: true,
payload: {
resources: [
{
externalId: 'grp-123',
displayName: 'Engineering',
email: null,
attributes: { description: 'Engineering team', memberCount: 45 }
}
],
nextCursor: 'page-2-token', // undefined when no more pages
totalEstimate: 150 // optional, for progress indication
}
}
Authifi Example¶
The Authifi connector implements listGroups and listUsers out of the box. Both commands use cursor-based pagination and map upstream API responses to the standard ConnectorResourceRecord format.
Background Scheduling¶
When a sync configuration is enabled with a cronSchedule, a BullMQ repeatable job is registered with the id cr-sync-{connectorId}-{resourceType}. The worker handler invokes ConnectorSyncService.runSync().
On application startup, all enabled sync configs are loaded from the database and their corresponding jobs are registered with the scheduler. This ensures schedules survive server restarts.
Permissions¶
| Endpoint | Required Permission |
|---|---|
GET .../resources |
connector:read |
GET .../sync-config |
connector:read |
GET .../sync-config/:type/status |
connector:read |
PUT .../sync-config/:type |
connector:manage |
DELETE .../sync-config/:type |
connector:manage |
POST .../sync-config/:type/trigger |
connector:manage |
GET .../sync-matches |
connector:read |
GET .../sync-matches/stats |
connector:read |
POST .../sync-matches/:matchId/resolve |
connector:manage |
POST .../sync-matches/:matchId/unlink |
connector:manage |
POST .../sync-matches/bulk-resolve |
connector:manage |
Shared Types¶
All sync-related types are defined in packages/shared/src/connector-sync.types.ts and exported from @floh/shared. Key interfaces:
ConnectorResourceRecord— Upstream resource shapeConnectorListResult— Paginated list response from connectorsConnectorSyncConfigDto— Sync configuration DTOSyncFilterRules— Filter rule schemaSyncStats— Sync run statisticsPostSyncStats— Post-sync run aggregates (users created/deactivated, profiles updated, match stats)AttributeMapping/AttributeWriteMode— Declarative profile mapping rulesUserMatchStrategy—email|email_and_issuer|upstream_identity|external_idSyncTriggerMode—none|per_record|summaryMatchConfidence,MatchStatus,MatchCandidate,SyncMatchRecordDto— Reconciliation modelResolveMatchDto,BulkResolveMatchDto— Match API payloadsConnectorResourceDto— Resource as returned by the APIResourceWebhookEvent— Webhook event for resource changes
Profile DTOs (UserProfileDto, ProfileAttributeSource) in packages/shared/src/profile.types.ts describe attributeSources used with connector-driven updates.
File Layout¶
packages/shared/src/
connector-sync.types.ts # Shared type definitions
profile.types.ts # Profile DTOs and attribute source enum
packages/server/src/
db/migrations/
003_connector_resource_sync.ts # Initial resource sync tables
012_sync_profile_integration.ts # Sync config extensions + connector_sync_match
013_compound_match_strategy.ts # issuer_source_path column for email_and_issuer
db/
temp-lookup.ts # withTempLookup helper for large IN-clause replacement
db/schema/
operational-tables.ts # Table type definitions
database.ts # Database interface registration
modules/connectors/
sync-repository.ts # ConnectorResourceRepository, ConnectorSyncConfigRepository
sync-service.ts # ConnectorSyncService (orchestration)
sync-routes.ts # API routes for sync management
sync-match-*.ts # Sync match routes, schemas, repository, service, runner
sync-post-processor*.ts # Post-sync pipeline (match, users, attributes, lifecycle)
connector-resource-dto-mapper.ts
connector-cache.ts # ConnectorCacheService (Redis)
authifi-client.ts # Extended with listGroupsPaginated, listUsersPaginated
authifi-connector.ts # Extended with listGroups, listUsers commands
seed-connectors.ts # Updated schema with syncCapable commands
modules/roles/routes/
webhook-routes.ts # Extended for resource webhook events
modules/scheduler/handlers/
sync-workflow-trigger.ts # Post-sync workflow job handler (integrations queue)
route-registry.ts # Sync + sync-match routes registration
app.ts # Startup job scheduling
worker-handlers.ts # Sync job handler
worker.ts # Worker deps with sync service
packages/server/test/unit/
connector-cache.test.ts # Cache service tests
connector-sync-repository.test.ts # Repository tests
connector-sync-service.test.ts # Sync orchestration tests
connector-sync-routes.test.ts # API route tests
connector-sync-filter.test.ts # Filter rules engine tests
authifi-connector-sync.test.ts # Authifi list commands tests
connector-resource-webhook.test.ts # Webhook extension tests
sync-match-mappers.test.ts # Match DTO / row mapping
sync-post-processor-mapping.test.ts # Attribute mapping behavior
temp-lookup.test.ts # Temp table lookup utility tests