Consent v2
We intend to revise our consent system for a few purposes:
To provide a server model that can drive re-usable or dynamically-rendered user interfaces to design and to present informed consent;
To better track where people fail to enroll (eligibility requirements, the informed consent process);
Internally we are going to insert a layer between apps and consents in the form of a study. Consents will be reusable elements that are explicitly associated to studies.
In an alternative universe where we don’t intend to provide #1 (in particular), we could refactor Subpopulation
to be this new top-level Consent
object, and reuse almost all of our existing consent infrastructure with a new association between Study
(originally Substudy
) and Consent
(originally Subpopulation
). So it’s worth checking again that we really want to perform this refactor.
Consent v2
A study can be associated to zero or more consents, defined globally within an app context. Only one consent in a given language can be marked as the required consent. (Another alternative would be to push this down into the model and filter things like DocumentElements by language). The consent can 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;
}
[TODO: Is this all just submitted in one graph, a little like Account
, or will there be separate endpoints to work with these component parts?]
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)
These calls need to work with a StudyConsent
associative record. This record will record if the consent is the required one for this study. (Because StudyConsent
is already taken internal to the system, we should rename those first to ConsentDocuments
).
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 |
Sign-In Workflow
This would be the new workflow around signing in to a Bridge app:
Once authenticated, we would load enrollment records. If there is an enrollment record, the user is signed in with no further steps needed (session v2 is constructed and returned with a status of 200);
If there are no enrollment records, we look at the apps consent version. In v1 we proceed as before. In v2, we return the same session as in step #1, but with a v1 session and a 412 response);
The client should know what study the user should be enrolled in, but if they don’t, there is a list of studies API that can be used to present options to the user;
The user goes through the consent for a study, and signs its required consent, adding an enrollment record to that user’s session;
Calls to participant-facing endpoints no longer return 412 (consented == enrollments.length > 0).
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;
}
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.
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.
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.