Symptom
When DcvEnabled=true but no IDomainValidatorFactory has been injected (e.g. the gateway host doesn't supply one, or no DNS provider plugin is installed), CERTInext already logs a clear startup warning (CERTInextCAPlugin.cs:261-272, tagged SOC2 CC7.1) and the per-order check at CERTInextCAPlugin.cs:1374 causes DCV to be silently skipped for that order. The order is then left parked at EXTERNALVALIDATION (per-domain dcvStatus="0") indefinitely — the sync-retry path (TryRunDcvDuringSyncAsync) keeps re-checking it every pass but can never complete it without a factory.
The operator has no way to see this from Keyfactor Command. The only signal is the plugin's own log file (which most Command operators enrolling certificates never look at). Nothing in the enrollment result surfaced "here is the TXT record you need to publish manually" — the order just sat pending with no actionable guidance in the UI.
Severity
Low/Medium — operator-experience gap, not a functional defect.
How this was found
Found during a cross-repo DCV design comparison (2026-07-22) against cscglobal-caplugin's in-flight (unmerged) DNS-plugin branch (Keyfactor/cscglobal-caplugin#12). That branch handles this via EnrollResult.EnrollmentContext — a Dictionary<string,string> of record-name → target value that gets returned to Command even when auto-publish succeeds or fails, so an operator without a DNS provider factory wired up still gets the record instructions surfaced directly in Command's enrollment result, rather than having to go dig through plugin logs.
Fix
Fixed by #13, scoped to the "no factory wired" case specifically (the common one — new gateway host, DNS provider plugin not yet installed), rather than every DCV failure mode. When PerformDcvIfNeededAsync can't proceed because no factory is wired, the plugin now best-effort looks up the pending DCV challenge(s) for the order and populates the enrollment result's EnrollmentContext/StatusMessage with the exact TXT record name and expected value, mirroring the EnrollmentContext pattern above. Falls back cleanly to the original pending-only message if the lookup itself fails.
Symptom
When
DcvEnabled=truebut noIDomainValidatorFactoryhas been injected (e.g. the gateway host doesn't supply one, or no DNS provider plugin is installed), CERTInext already logs a clear startup warning (CERTInextCAPlugin.cs:261-272, tagged SOC2 CC7.1) and the per-order check atCERTInextCAPlugin.cs:1374causes DCV to be silently skipped for that order. The order is then left parked atEXTERNALVALIDATION(per-domaindcvStatus="0") indefinitely — the sync-retry path (TryRunDcvDuringSyncAsync) keeps re-checking it every pass but can never complete it without a factory.The operator has no way to see this from Keyfactor Command. The only signal is the plugin's own log file (which most Command operators enrolling certificates never look at). Nothing in the enrollment result surfaced "here is the TXT record you need to publish manually" — the order just sat pending with no actionable guidance in the UI.
Severity
Low/Medium — operator-experience gap, not a functional defect.
How this was found
Found during a cross-repo DCV design comparison (2026-07-22) against
cscglobal-caplugin's in-flight (unmerged) DNS-plugin branch (Keyfactor/cscglobal-caplugin#12). That branch handles this viaEnrollResult.EnrollmentContext— aDictionary<string,string>of record-name → target value that gets returned to Command even when auto-publish succeeds or fails, so an operator without a DNS provider factory wired up still gets the record instructions surfaced directly in Command's enrollment result, rather than having to go dig through plugin logs.Fix
Fixed by #13, scoped to the "no factory wired" case specifically (the common one — new gateway host, DNS provider plugin not yet installed), rather than every DCV failure mode. When
PerformDcvIfNeededAsynccan't proceed because no factory is wired, the plugin now best-effort looks up the pending DCV challenge(s) for the order and populates the enrollment result'sEnrollmentContext/StatusMessagewith the exact TXT record name and expected value, mirroring theEnrollmentContextpattern above. Falls back cleanly to the original pending-only message if the lookup itself fails.