AI Scribe — Feature Access & Gating
Audience: Backend / full-stack engineers touching AI Scribe code paths. Companion docs:
general/ai-scribe-feature-access.md(product),testing/ai-scribe-qa.md(QA).
Summary
AI Scribe access is gated entirely at the facility level by two boolean columns on identity.facility_ai_settings:
| Column | Purpose |
|---|---|
is_enabled | Master toggle for all facility-level AI features |
transcription_enabled | Specific capability toggle for AI Scribe transcription sessions |
Both must be true for a facility to create transcription sessions. There is no feature flag in this path — ENABLE_AI_SCRIBE was retired because it duplicated is_enabled without adding control.
Resolver
Shared::AiSettingsResolver (backend/domains/shared/services/ai_settings_resolver.rb) is the only entry point for "is AI X allowed for this caller?" questions. Construct one per request:
resolver = Shared::AiSettingsResolver.for(
facility: encounter.facility,
practitioner: current_practitioner # optional
)
resolver.ai_enabled? # master toggle (facility-only)
resolver.transcription_enabled? # AI Scribe gate (facility-only)
resolver.suggestions_enabled? # AI suggestions (facility-only)
resolver.confidence_threshold # practitioner-overridable
resolver.language # practitioner-overridableIdentity::FacilityAiSettings#ai_enabled? exists for places that already hold the row (blueprints, internal helpers). Anything that needs to consider practitioner overrides must go through the resolver.
Practitioner overrides — narrow by design
Identity::PractitionerAiSettings exists to let individual clinicians tune workflow preferences. It does not give a practitioner the power to flip facility-level gates.
PRACTITIONER_OVERRIDABLE_KEYS (in the resolver) is the single source of truth:
PRACTITIONER_OVERRIDABLE_KEYS = %i[
language medical_terminology_enabled confidence_threshold
].freezeAnything outside that list — is_enabled, transcription_enabled, suggestions_enabled, auto_documentation_enabled, ai_provider, ai_model, retention/limit columns — is read straight from facility settings, even if the practitioner row has a non-null value. If you need to add a new overridable key, update this constant and the doc here in the same PR.
Lifecycle
-
Identity::Facilityrunsafter_create :build_default_ai_settings, which callsIdentity::FacilityAiSettings.find_or_create_for_facility!. Every facility created from now on starts with a settings row (defaults: everything off). -
Facilities created before this callback existed may not have a row. The resolver tolerates a missing row (treats every key as
nil/default), but you should backfill via:Identity::Facility.find_each { |f| Identity::FacilityAiSettings.find_or_create_for_facility!(f) } -
The
FacilityAiSettingsController(GET/PATCH /identity/facilities/:facility_id/ai_settings) is what clinic admins call to flip the toggles. Auth is enforced viaFacilityAiSettingsPolicy:tenant_owner?,tenant_admin?, or any membership with themanage_facilitiespermission. (An earlier version also accepted a "facility-level practitioner admin" viapractitioner_roles.role— that column never existed on the new schema and the branch was dropped, see commitb44e4712b.) -
The corresponding CMS admin surface is Settings → Practice Settings → AI Scribe (
apps/cms/src/features/settings/practiceSettings/components/tabs/AiScribeTab.tsx), which renders the master toggle + per-capability switches.
Controller gate
Clinical::TranscriptionSessionsController#ai_scribe_enabled? is the runtime check:
def ai_scribe_enabled?
encounter = Clinical::Encounter.find_by(id: session_params[:encounter_id] || @session&.encounter_id)
return false unless encounter&.facility
Shared::AiSettingsResolver.for(facility: encounter.facility).transcription_enabled?
endWhen this returns false, the API responds with 403 ai_scribe_disabled. That error is config-driven, not a bug — see the QA guide for how to enable a facility.
Why two flags and not three (or one)
is_enabledis the billing/entitlement toggle. Off = AI bundle not active for this facility, no AI features anywhere.transcription_enabled/suggestions_enabled/auto_documentation_enabledlet an admin keep the bundle on but disable individual capabilities (e.g., transcription on, auto-doc off).- A feature-flag layer on top would only duplicate
is_enabled— engineering can already toggle a single row when needed. We dropped it.
If we ever need an engineering-only kill switch (e.g., shut off AI globally during an incident), introduce that as a global feature flag (see Feature Flags), not per-facility.
Testing
See testing/ai-scribe-qa.md for the dev/staging enablement recipe and end-to-end checks.