Skip to content

ADR-011: Multi-option conditional parent answers on schema-v0 projects

Status

In-Review — the compatibility implementation and automated tests exist in an isolated feature worktree. No environment has been deployed or modified.

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.

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 exposes supportsMultiOptionConditionalParentAnswers. Its backing feature flag defaults to false. The API refuses schema-v0 multi-option upsert/copy operations and the legacy designer remains single-select until an operator enables the flag. API clients can use the live capability value to refuse unsupported schema-v0 multi-option writes. Schema v1 remains supported without the compatibility flag.

Enable FeatureFlags__SchemaV0MultiOptionConditionalParentAnswers=true on the API only after deployment verification proves that all project-writing API and project-management instances have been upgraded and old instances drained. The enabled response is therefore an explicit operator attestation as well as a server capability handshake. Once hybrid data exists, do not disable the flag or roll a project writer below the compatibility release.

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 default-off, operator-enabled capability through the generated API and Angular clients; the write endpoints enforce the same gate.
  4. The legacy Angular question designer uses a checklist for schema-v0 option parents only when the live capability is enabled, 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; default-off capability and write gate agree; stale stage hashes conflict
Evaluation scalar and array parent answers activate a child if any selected value matches
UI enabled schema-v0 option parent emits an array; disabled capability stays single-select; boolean remains scalar

Deployment and staging plan

  1. Review and merge the compatibility release; require green domain, Mongo, API, generated-client and Angular tests.
  2. Take an environment before-image and confirm that a selective restore has been tested. For Atlas, retain a point-in-time recovery window covering deployment and the first hybrid writes.
  3. Deploy the API, project-management service and web client to staging with the schema-v0 feature flag left false. Drain all old API and project-management instances.
  4. Enable FeatureFlags__SchemaV0MultiOptionConditionalParentAnswers=true on the staging API and verify the live project response advertises the capability. Then create a disposable schema-v0 parent and child through the supported API, read it back, exercise both matching and non-matching parent answers, edit an unrelated project field and confirm the condition survives the full-document replacement.
  5. Roll the compatibility release to production with the feature flag left false. Confirm all writers report the expected version and no old instances remain; then enable the API flag and verify the capability response.
  6. Exercise the first approved production schema-v0 multi-option question as a canary through the supported API or UI. Read it back, verify matching and non-matching parent answers, and confirm the condition survives an unrelated project edit before broader use.

Rollback and restore

Before hybrid data exists, the compatibility release can be rolled back normally. After it exists, the compatibility release is the minimum safe writer version. Do not roll API or project-management writers below it while hybrid schema-v0 questions remain.

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 and peer review have not yet approved the compatibility release.
  • Staging deployment, mixed-version drain proof, restore rehearsal and end-to-end staging smoke tests remain outstanding.
  • Production deployment needs its normal release approval.
  • This ADR does not authorise deployment, feature-flag enablement or any live write.
  • A general schema-v1 migration remains deliberately out of scope.

References