Manual verification call flow - #306
Draft
leowilkin wants to merge 13 commits into
Draft
Conversation
Verification cases with document collection, cal.com-scheduled review calls, backend review UI with four-eyes escalation, and persona capture fallthrough.
…ion-calls # Conflicts: # app/views/backend/identities/show.html.erb # config/initializers/active_record_encryption.rb
The previous dump came from the shared dev database and carried churn from other branches (dropped pg_trgm/slack_idp_groups, stray ahoy/yoti/two_factor columns, and a version pointing at a migration that doesn't exist). Rebuilt from origin/main's schema + this branch's migration.
…-escalation - replace the slack thread integration with native staff comments on cases (new verification_case_comments table, comment action + UI) - staff can open a skip-persona case for users who refuse persona: document capture happens live via getUserMedia on our page, no file picker, JPEG/PNG enforced server-side; the persona-selfie checklist item records as n/a on cases with no persona capture - BOOKING_CANCELLED now returns the case to docs_submitted and emails the user a rebook link instead of stranding them on a stale page - remove the hard auto-escalation rules; first-reviewer judgment stands, manual escalation + four-eyes stay
…ncellation email - skip-persona submissions now capture a selfie (front camera widget) alongside the document, stored as a 'selfie' document kind - the decision checklist item generalizes to 'document matches selfie (persona or live capture)'; still records n/a when a case has no selfie from either source - drop the auth-side cancellation email — cal.com already emails the attendee with a rebook path; we only reset the case state
Marking a no-show reopens the case for booking (like a cancellation) and logs call_no_show to the audit trail; un-marking is a no-op. The no-show payload carries bookingUid and no metadata, so case lookup now also matches on the stored booking uid.
…ifications/manual - /verify/manual* becomes /verifications/manual* to match the site's route nomenclature (path helpers unchanged) - both VerificationsController and Portal::VerificationsController now redirect to the open case, so 'ID Verification' in the portal lands users on their case instead of the legacy resubmission flow
Second opinions happen through case comments + a DM, not a state machine. Drops the escalated state/event, the four-eyes decide policy, escalated_to/escalation_reason on verifications, the escalate action + route + UI. Decisions now strictly require call_held. Also collapse the actions row when no action applies (stray gap between separators).
…ipient BreakGlassRecord's tracked recipient calls break_glassable.identity; VerificationCase::Document reaches its identity through the case, so delegate it.
Removes the post-decision retention stamp, the nightly purge job + cron, and the retention/purged columns. User-facing copy no longer promises deletion timelines (biometric consent + recording disclosure now describe encrypted storage with logged access; needs privacy review before prod). Account deletion via DeletionService now purges case document files — previously uncovered, and now the only deletion path for them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A staff-initiated fallback for identity verification when Persona doesn't work out: staff open a case, the user gets a single-use link to submit documents, books a video call on our self-hosted cal.com (cal.hackclub.com), and a reviewer decides on the call. The durable outcome is a
Verification::ManualVerificationCall— the same polymorphic verification type as Persona approvals, so the rest of auth treats it identically.The flow
/verifications/manual(login + flag + one-time token). Two document tiers (government ID / alternative docs with a nudge back toward gov ID). Capture paths:CALCOM_MANUAL_VERIFICATION_BOOKING_URL+metadata[casePublicId]+ email prefill)./webhooks/calcom, HMAC viaX-Cal-Signature-256) drive state: created/rescheduled →call_scheduled; cancelled & no-show → back todocs_submittedso the user can rebook (cal.com owns those emails).Design decisions (deliberate, argued over)
call_held.DeletionService) purges case document files.verification_case_events).Env vars (undocumented elsewhere, ask Leo)
CALCOM_MANUAL_VERIFICATION_BOOKING_URL,CALCOM_WEBHOOK_SECRET,PERSONA_MANUAL_CAPTURE_TEMPLATE(+DEV_HOSTif testing webhooks through ngrok). Webhook events to subscribe: booking created / rescheduled / cancelled / no-show updated.Testing
CI doesn't run rspec — run the feature specs locally (~75 examples, all green):
Note:
.rspec's--patternloads the whole suite alongside your named files; add-P "<same files>"to isolate. ~39 persona/identity spec failures exist on main and are unrelated.Known TODOs (help welcome)
call_recordingdocument kind + consent exist, but nothing ingests recordings yet (google meet recordings land in Drive; ingestion would go via the Drive API, or cal.comRECORDING_READYif using cal video)expires_atis stamped andexpired?exists, butIdentity#verification_statusnever consults it/backend/verification_cases) is only reachable via per-identity linkssampled_at,sample_reviewer_id) are schema-onlyEnd-to-end tested against cal.hackclub.com through an ngrok tunnel (booking, reschedule, cancel, decision) on 2026-07-27.