Migration
The biggest change in the system will be that participant-facing APIs will no longer throw a 412 if the user is enrolled in any study in the app. This is different than the Subpopulation model where some subpopulations require consent, and some do not.
In the first version of consent, we matched subpopulations based on criteria, and then returned 412 if any of the required consents were not signed. To disable consent, you created a new, optional subpopulation, the only purpose of which was to turn off the 412 responses without having to sign consent.
In the new consent system, you would model this as two studies that shared the same protocols, and enrollment state, where one had a consent assigned to it and one did not. (There’s no use of criteria to select which one a user should enroll in…creating an external ID for an account is one way to enroll someone in a study.)
Refactor subpopulations to use a single String guid, so an account’s consent signatures can continue to work to persist consents under the new consent system;
For every required subpopulation, create a study, and change the subpopulation to enroll the user in that study when the subpopulation consent is signed. (Possibly require this going forward.)
Migrate all existing accounts with consents by adding enrollment records for these.
Going forward, consents will merge with the presence of an enrollment record, in many cases (if a study enrolled people but didn’t require them to consent or use an external ID, the reality is, we don’t know that they are enrolled).
The App object should include a consent version number, and existing studies should be marked as v1. Subpopulations, consents, and study consents should continue to work for v1 applications. The v2 consent system is described below.
Consent v2
Each study has one (and only one) required consent that must be signed to enroll in the study (with the caveat that the required consent may be in multiple languages). The consent can also be flagged if the client should force the user to reconsent (the user enrolled with a prior consent). There can be any number of optional, supplemental consents that can be presented to the user.
Users will no longer have to consent based on criteria selection… I want to make this more deterministic because right now, we have a hard time telling administrators the state that users are in. We’ll think about it in terms of study selection and we should be able to tell unambiguously what studies a user is in.
The consent includes a model for presenting the consent, and a model for verifying comprehension of the consent. The consent document will be assembled through the use of the content fields of all the document sections, with a signature block at the end of the consent which can be tailored for individual consents. Finally, any functionality we want to maintain from Subpopulations, like adding a data group to a participant when they consent, should be carried over to this new Consent record.
class Consent { // Consents are owned by organizations that have write permissions, // but other organizations can use the consent if needed. String ownerId; String name; String description; String language; // more than one consent can be marked required, // but only if they all have different languages boolean required; boolean requiresReconsent; String appId; // if you sign this consent, you will be enrolled in this study. String studyId; String guid; // subpopulation functionality maintained Set<String> dataGroupsAssignedWhileConsented // Name of the approving IRB String approvedBy; DateTime approvedOn; DateTime approvalExpiresOn; ComprehensionType type; // summative, formative List<DocumentSection> sections; // timestamps, deleted, version } class DocumentSection { int order; String title; String content; // markdown or HTML String summary; // markdown or HTML ComprehensionQuestion question; // optional } class ComprehensionQuestion { String question; List<Answer> answers; } class Answer { String text; boolean correct; // assuming it was either selected correctly, or in error, the // response will be an affirmation or a correction/further explanation String response; }
The enrollment record which indicates that a participant has been enrolled in a study, will include the GUID of the consent that was used when the participant consented. It may be the same or different as the current required consent guid. If it’s different and the current consent requiresReconsent
, the app should act like the user has never consented and consent the user again (however, the user is consented and nothing will fail while this is happening, like pending uploads). If it’s not required, no action needs to be taken by the client. (Maybe we show somewhere that there’s a newer version of the consent document, I don’t know).
Researcher Consent APIs (devs and researchers)
Method | Path | Description |
---|---|---|
GET | /v4/consents | All consents in this app. These may be summaries. |
POST | /v4/consents | Create a new consent |
GET | /v4/consents/{guid} | Get a complete consent record with its component parts |
POST | /v4/consents/{guid} | Update a consent |
DELETE | /v4/consents/{guid} | Delete a consent (logical or physical) |
A consent cannot be physically deleted if it is referenced by a study. Consents are owned by an organization, and only developers or researchers from that organization can edit or delete consents. However, any organization can associate a consent to a study they sponsor.
Study Consent APIs (devs and researchers)
Method | Path | Description |
---|---|---|
GET | /v5/studies/{studyId}/consents | Get all consents (possibly in summary) that are associated to the study. Only one consent of a given language can be required. This is available to all authenticated users. |
POST | /v5/studies/{studyId}/consents/{guid} | Add a consent to a study |
DELETE | /v5/studies/{studyId}/consents/{guid} | Remove a consent from a study |
Participant consent APIs
Finally, users have APIs to sign these consents, which creates (or removes) enrollment records and signatures. All these APIs are from the perspective of the authenticated caller.
Method | Path | Description |
---|---|---|
GET | /v5/studies/{studyId}/consents/signatures | Get all signatures for this study |
GET | /v5/studies/{studyId}/consents/{guid}/signature | Get the signature for a specific consent (is this needed though?) |
POST | /v5/studies/{studyId}/consents/{guid}/signature | Sign this consent. If this is the required consent, enroll caller in study |
DELETE | /v5/studies/{studyId}/consents/{guid}/signature | Withdraw consent. If this is the required consent, withdraw the user from the study (delete enrollment record). |
POST | /v5/studies/{studyId}/consents/{guid}/signature/send | Send a copy of the consent to the caller via email or SMS. We propose to disable sending by default, as it is now rarely used. |
DELETE | /v5/studies/{studyId}/consents/signatures | Withdraw this caller entirely from the study, deleting the enrollment record |
Enrollments
Rather than loading and examining consent signatures to determine active enrollees in a study, there will be an enrollment record when an account is actively enrolled in a study. When consent is withdrawn, the enrollment record will be set a flag and behave like a logically deleted record. These records allow us to quickly determine the participant’s status, and they answer two questions for governance: how many people have we enrolled in the study, and how many have been withdrawn from the study? (Note that the questions I received from Vanessa also include “how many people have you removed from the study” which we’ll need to consider.)
class Enrollment { String studyId; // optional String externalId; // the consent signed to enrollment String consentGuid; // true if consentGuid != the study's required consent GUID, // and the required consent requires reconsent. User is // still considered to be enrolled in study, e.g. uploads // will not fail. boolean reconsentRequired; // behaves similarly to logical deletion boolean withdrawn; }
UserSession v2
These changes to consent lead to some significant changes to the user session. In v2 apps, I would like to clean up the session in the following ways:
Remove these fields:
authenticated (always true)
environment (always production)
username (always email)
dataSharing (redundant with sharing_scope)
consented (true if there are any enrollment records… we could keep this if helpful)
signedMostRecentConsent (now has to be tied to a specific study)
consentStatuses (no more trying to communicate consent signatures or available consents in the session)
substudyIds (covered by enrollments)
externalIds (covered by enrollments)
And in place of many of these, I would add one field: enrollments
. This would be an array of enrollment records without withdrawn records (see above). A user is considered “consented” to access participant-facing APIs without receiving a 412 from the API if they have at least one enrollment record.
SQL
The new consent objects will be created in SQL and will replace StudyConsent, Subpopulation. ConsentSignature will continue to be stored in SQL as it is now, repurposing the AccountConsents table (the content of a signature is not changing).
The consent signatures table may be able to store signatures for the v2 version of consent, if the consent GUID can be stored in the subpopulation GUID column (the values are in fact globally unique).
Candidates API
Candidate { String anonymousId; DateTime firstContactOn; DateTime mostRecentContactOn; String anonSessionToken; // should map 1:1 with a consent, but we can include consentGuid String studyId; String userId; String stepCompleted; // eligibility, comprehension, consented, demographics Enum eligibility; // eligible, ineligible List<RejectedAnswer> eligibilityRejections; JsonNode clientState; }
Users engage in some important activities before they create an account in Bridge, such as checking their eligibility to participate in a study, or agreeing to join a study before they create an account.
Currently we have an “intent to participate” API to help cover some of these cases, but it is lacking insofar as we would like the following information for governance and recruiting:
What eligibility criteria are causing candidates to be rejected?
What aspect of informed consent are people not understanding?
Do we have people who consented, but never created an account?
The candidate history APIs is a proposal to track this domain-specific information. It is intended to track the required consent for a study (if we need to track more than this, the API should change). Each API will look for the Bridge-Session header to recover a session, or if it doesn’t exist, a token in a cookie. If neither exists, it will set a new token in a cookie and use it to track a record during anonymous consent. This should allow browser applications to save consent while working.
Method | URL | Description |
---|---|---|
GET | /v1/studies/{studyId}/candidates | Return the current state of consent, if not, return a new session cookie with a new anonymous session in the JSON. |
POST | /v1/studies/{studyId}/candidates | If session cookie exists, update the current state of consent (such as UI state, or the step of consent the user has just completed). |
GET | /v1/candidates/{consentId} | Paginated list of candidate records for a consent |
On sign up, the candidate session token can be included, and we will associate this candidate record with the user.
Because this doesn’t require authentication and can be used to generate a denial-of-service attack, we should consider the design of this API carefully. For example, when setting a cookie and returning a blank state object, we should not persist anything on the server. We may also wish to use rate limiting, IP address locking, and other mechanisms, or accept that we cannot track eligibility before people authenticate with our system.