Skip to content

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 Conditional plus BoolChoice;
  • one option: retain Conditional plus the parent's stable OptionId;
  • two or more options: emit ConditionalParentAnswers with TargetParentOptions values 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:

  1. Domain creation and update preserve multi-option conditions, validate selected values and types, clear stale representations, and protect referenced parent options.
  2. Mongo mappings emit the additive subdocument for schema-v0 multi-option conditions and retain the legacy BSON shape for boolean and single-option conditions.
  3. 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.
  4. 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

  1. Require green domain, Mongo, API, OpenAPI/generated-code, Angular and Helm-render validation on the retirement pull request.
  2. 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.
  3. Review and merge through the normal release path. No flag toggle or data migration accompanies the application deployment.
  4. 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:

  1. stop or disable project writes;
  2. keep the compatibility release serving reads;
  3. decide whether to forward-fix or remove only the journalled imported questions under fresh destructive approval;
  4. validate affected project documents and annotations;
  5. 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.

References