Pattern 15: Enrollment Process¶
Purpose¶
Use this pattern when the adapter must capture a new credential or template for a person.
The overall process should stay the same regardless of what is being enrolled:
- card
- fingerprint
- face
- another opaque credential blob
What changes between adapters is mainly:
- which PQ command starts the process
- what device action or event completes it
- what payload is sent to
IEnrollmentService.Complete(...)
Core Rule¶
Treat enrollment as a short-lived process owned by the framework:
- start session on command
- set reader status to enrollment in progress
- wait for device capture or scan
- transform captured data to PQ enrollment payload
- complete via
IEnrollmentService - let framework publish completion event and clear status
Do not build custom enrollment state machines in transport code unless the device protocol truly requires it.
Recommended Shape¶
Keep enrollment in a small handler, similar to command handlers in existing adapters.
public static async Task<DeviceCommandResult> EnrollX(
Thing thing,
Access.Enroll.Card command,
Protocol protocol,
IEnrollmentService enrollment,
CancellationToken ct)
{
enrollment.Begin(thing);
try
{
var deviceEvent = await protocol.WaitForEvent(
MatchesTargetThing,
TimeSpan.FromSeconds(60),
ct: ct);
var data = new CardEnrollmentData
{
EnrollmentId = command.EnrollmentId,
PersonId = command.PersonId,
CardCode = ExtractCardCode(deviceEvent),
Technology = "vendor.card",
BitLength = ExtractBitCount(deviceEvent), // set when the reader/SDK reports a bit count
};
return await enrollment.Complete(data, ct);
}
catch (TimeoutException)
{
await thing.SetStatus("enrollment", null, CancellationToken.None);
return new DeviceCommandResult(CommandResult.Timeout);
}
catch (OperationCanceledException)
{
await thing.SetStatus("enrollment", null, CancellationToken.None);
return new DeviceCommandResult(CommandResult.Timeout);
}
}
For biometrics, the same process applies, but the completion payload is EnrollmentData instead of CardEnrollmentData.
Framework Contract¶
IEnrollmentService owns the framework side of the lifecycle.
Begin(thing)- marks the thing as enrolling
Complete(CardEnrollmentData)- completes card enrollment and publishes success event with
card_code(andcard_bitswhenBitLengthis set) Complete(EnrollmentData)- completes biometric enrollment and uploads template/blob to server
The adapter should focus on capture and transformation, not on persistence.
Card Enrollment Variant¶
Use this when the device produces a card identifier but PQ does not yet know the credential.
Typical sources:
- explicit scan API call
- intercepted unknown-card event
- reader transaction event
Typical output:
CardEnrollmentData.CardCodeCardEnrollmentData.TechnologyCardEnrollmentData.BitLength— set it when the reader/SDK reports the wire bit count (see the Card Wire Bit-Length concept and RULE-053); leave it null only when no bit count is available
Examples in current adapters:
- reader-transaction adapter: waits for a reader card transaction and completes with raw card hex
- denial-driven adapter: waits for an unknown-card denial on the target door reader and completes with raw serial hex
- scan-API adapter: calls a scan API and completes with card bytes converted to hex
Biometric Enrollment Variant¶
Use this when the device returns an opaque template or blob.
Typical output:
EnrollmentData.TemplateDataEnrollmentData.Technology- optional quality score and metadata
The framework stores the blob. The adapter should not interpret template internals unless the protocol requires it.
Target Resolution¶
Enrollment must be scoped to the correct thing.
Common strategies:
- wait for event already addressed to the target reader
- match protocol address or ACR number
- match door address derived from reader topology
- match device ID from scan response
Prefer a small predicate over global mutable state.
Where To Put The Logic¶
Preferred layout:
Capabilities/Enrollment/Card/CardEnrollment.csCapabilities/Enrollment/Face/FaceEnrollment.cs- similar feature-local handlers for other credential types
Keep transport/protocol generic. Enrollment-specific orchestration belongs close to the capability, not buried in shared connection code.
Error Handling¶
Expected outcomes:
- success:
enrollment.Complete(...) - timeout: return
CommandResult.Timeout - cancellation: usually same external result as timeout unless protocol needs something else
- no usable captured data: return
CommandResult.Failure
If cleanup must happen after timeout/cancellation, do not reuse an already-cancelled token for clearing enrollment status.
Technology Strings¶
Use stable vendor-specific technology strings.
Examples:
acme.cardbeta.cardacme.csnvendor.fingerprint.v2
Technology should identify the credential family or template format, not UI wording.
Verification Checklist¶
- command is exposed only on things that can actually complete enrollment
- handler calls
Begin(...)before waiting for capture - completion payload contains the intended raw identifier or template
- timeout path clears enrollment state
- project builds and generated command wiring matches expected thing type
Relationship To Pattern 8¶
Pattern 8 remains the biometric-specific variant. This document is the general enrollment process pattern that should be used first, then specialized for card or biometric capture.