ADR-011: Multi-option conditional parent answers on schema-v0 projects¶
Status¶
In-Review — the compatibility release is merged on main. This revision records the retirement of
its temporary rollout gate; it remains subject to normal pull-request review and deployment.
Decision summary¶
Support multi-option conditional parent answers on schema-v0 projects with a narrowly scoped,
additive compatibility representation. Keep existing schema-v0 boolean and single-option
conditions in their legacy fields. Store only schema-v0 conditions containing two or more parent
option values in the already-supported ConditionalParentAnswers subdocument used by schema v1.
After every project writer reached the compatibility release, make this behavior unconditional.
Retire the temporary SchemaV0MultiOptionConditionalParentAnswers runtime flag, API write gates,
designer gate, application configuration and Helm environment wiring. Retain the public
supportsMultiOptionConditionalParentAnswers response property as an always-true compatibility
contract for existing clients. This retirement is application-only: it requires no MongoDB schema,
index or data migration.
This is preferable to migrating schema-v0 projects solely for this capability. The API and annotation runtime already use the typed collection and “any selected parent answer” semantics. The missing behaviour was limited to schema-v0 domain validation, conditional BSON emission and the legacy designer control.
Context and proven facts¶
Before this change, schema v0 failed in
Target.V0SetConditionalParentAnswers: it rejected an option condition containing more than one
value. The public API contract already models option conditions as an array. Both server-side
relationship validation and the annotation designer already evaluate an option condition as
active when the parent answer is any member of that array. Schema v1 already persists the same
typed object.
Read-only production metadata established that both target projects are schema v0 and currently have no custom annotation questions or stored annotations. That evidence reduces import rollback complexity, but it is not a substitute for a fresh preflight immediately before a later write.
Representation¶
For schema-v0 custom questions:
- no condition: retain the existing empty legacy representation;
- boolean condition: retain
ConditionalplusBoolChoice; - one option: retain
Conditionalplus the parent's stableOptionId; - two or more options: emit
ConditionalParentAnswerswithTargetParentOptionsvalues and do not emit a contradictory legacy condition.
The additive subdocument deliberately reuses a field and discriminator family understood by the
existing codebase. Introducing a new OptionIds field would be less compatible because older
writers do not have a mapping for it. Persisting values is also consistent with the existing v1
model. Legacy option IDs are retained for unchanged values when a schema-v0 parent question is
edited, and a referenced value cannot be removed or renamed while a child condition uses it.
The compatibility getter prefers the additive representation when it is present. Updating or clearing a condition clears both representations first, preventing stale legacy fields from changing the meaning of the condition.
Compatibility boundary¶
MongoDB project writes replace the full project document. Consequently, every API and project-management service instance capable of writing projects must run this compatibility release before the first hybrid schema-v0 question is created. An older writer could deserialize the known subdocument but suppress it during schema-v0 serialization, erasing the condition on a later full replacement.
The project summary continues to expose supportsMultiOptionConditionalParentAnswers, but every
summary, standard and full response now returns true. The field remains in OpenAPI and generated
clients so older consumers do not experience a contract break. The API accepts schema-v0
multi-option upsert and recursive or non-recursive copy operations without a runtime gate, and the
legacy designer always uses its checklist for option parents. Boolean conditions remain scalar.
The deployment floor is permanent even though the temporary flag is not: once hybrid data exists, do not roll a project writer below the compatibility release. Reintroducing the flag would not make an older writer safe and could falsely advertise that a supported persisted shape is unavailable.
Implementation scope¶
The compatibility release changes only these paths:
- Domain creation and update preserve multi-option conditions, validate selected values and types, clear stale representations, and protect referenced parent options.
- Mongo mappings emit the additive subdocument for schema-v0 multi-option conditions and retain the legacy BSON shape for boolean and single-option conditions.
- The project summary advertises the capability as always true through the existing API and Angular client contract; the temporary write gates and resolver are removed.
- The legacy Angular question designer always uses a checklist for option parents and continues to use a single value for boolean parents.
No project, membership, role, group, permission, stage, security, living-search, study, systematic-search or investigator document needs migration for this capability.
Alternatives considered¶
A. Additive schema-v0 compatibility — selected¶
This changes the smallest failing surface, preserves all legacy shapes, avoids a live data rewrite and matches the existing API/runtime semantics. Its main cost is a mixed-version deployment floor once hybrid data exists. The capability handshake and drain gate make that cost explicit. This is maintainable because the extension reuses the canonical v1 conditional type rather than creating a third domain model.
B. Project-scoped v0-to-v1 migration¶
A project-scoped migration could make the question model uniformly v1, but no production-ready, idempotent runner or supported project upgrade endpoint was found. Existing schema-version branches show that such a migration is not question-only: it can affect the project aggregate, registrations/memberships, roles/groups/permissions, stage settings, security, living searches, questions, studies, systematic searches and investigator-linked records. Building, rehearsing and operating that machinery is disproportionate to six conditional questions.
C. Full schema-v1 rollout¶
A full rollout additionally changes default schema creation and service-wide compatibility. No active schema-v1 production precedent was found during the read-only investigation. This has the largest blast radius and must remain a separately designed programme, not a prerequisite for these templates.
Validation matrix¶
The implementation must remain covered at all of these boundaries:
| Boundary | Required regression |
|---|---|
| Domain | schema-v0 boolean, single option, multiple options, schema-v1 multiple options |
| Validation | empty/null, duplicate, unknown value and parent-type mismatch refuse |
| Updates | change multi to single, clear condition, retain unchanged option IDs, refuse removal/type changes referenced by a child |
| Copying | copied schema-v0 child retains every selected option |
| Persistence | real Mongo insert/read/replace retains hybrid subdocument; legacy single remains legacy; v1 remains modern |
| API | JSON converter accepts a multi-value array; every project response advertises true; schema-v0 upsert and copy succeed; stale stage hashes conflict |
| Evaluation | scalar and array parent answers activate a child if any selected value matches |
| UI | schema-v0 option parents render a checklist and emit an array even if a stale response says false; boolean remains scalar |
| Helm | API and project-management renders omit the retired environment variable even if a stale values key is supplied |
Deployment and staging plan¶
- Require green domain, Mongo, API, OpenAPI/generated-code, Angular and Helm-render validation on the retirement pull request.
- In the pull-request preview, verify a schema-v0 project response advertises the capability, create and read a multi-option conditional child, exercise matching and non-matching scalar and array parent answers, copy the child, and confirm an unrelated project update preserves the condition.
- Review and merge through the normal release path. No flag toggle or data migration accompanies the application deployment.
- After deployment, verify API and project-management pods run the new release and the obsolete environment variable is absent. Production canary writes require their normal separate approval.
Rollback and restore¶
The compatibility release remains the minimum safe writer version. Do not roll API or project-management writers below it while hybrid schema-v0 questions remain. The retirement release itself can be rolled back to the compatibility release as long as the rollback configuration keeps the former gate enabled; that rollback must never be used to justify an older writer.
Application rollback is therefore:
- stop or disable project writes;
- keep the compatibility release serving reads;
- decide whether to forward-fix or remove only the journalled imported questions under fresh destructive approval;
- validate affected project documents and annotations;
- only then consider rolling writers below the compatibility floor.
If integrity cannot be proven, restore the affected collection/database from the verified before-image or Atlas point-in-time backup into an isolated recovery database first, compare the two project aggregates, and perform a separately approved selective recovery. Never use an untested production overwrite as the first restore attempt.
Remaining gaps and approvals¶
- CI, preview evidence and peer review of the retirement pull request remain required.
- Production deployment needs its normal release approval and is outside this pull request.
- This ADR does not authorise deployment, a live feature-flag change or a production write.
- A general schema-v1 migration remains deliberately out of scope.