# Provider-neutral question content safety contract > Status: proposed for EDU-P0-S1 > > Evidence basis: current Education providers and services plus the legacy question import and learning rules. This document defines the implementation contract; it does not report tests as executed. ## Decision Student-visible question content is validated by one provider-neutral contract before it is projected as a safe question or persisted/restored as a practice snapshot. Provider-specific parsing may reject malformed transport data earlier, but switching between Scalar and Java must not change whether the same logical question is considered safe. Invalid or unsupported content fails closed. It must not be normalized into an apparently valid question with an empty option list. ## Question-type families Type matching is case-insensitive after trimming. Persisted and returned canonical values remain an implementation concern; validation uses the following families. ### Option-backed ```text choice multi multi_choice judge image ``` An option-backed question requires: - at least two options; - every option object to be non-null; - a non-blank string `label`; - a non-blank string `content`; - labels unique after trimming; - each `order`, when present, to be a finite number; - no duplicate non-null order value; - no answer-bearing field in the student-safe projection or snapshot. Options are emitted in deterministic order: numeric `order` first, preserving source order only when order is absent. This slice does not infer or repair missing labels, contents, or order values. ### Optionless ```text fill text terms short_answer composition discuss translation case_analysis brief_analysis calculation analysis_design combination solution ``` An optionless question may have null or empty options. If options are supplied, the content is inconsistent with its type and fails closed rather than silently discarding them. This slice only establishes safe display and snapshot behavior. It does not add text-answer submission or scoring semantics. Existing answer-saving behavior must not pretend an optionless question is option-backed. ### Composite ```text reading ``` A composite question requires sub-questions in the legacy model. The current target `CatalogQuestionDTO`, safe response, and practice snapshot do not carry a supported sub-question contract. Therefore a top-level `reading` question is unsupported by EDU-P0-S1 and fails closed rather than appearing as an optionless standalone question. Supporting composite questions requires a later explicit model and API contract. ### Unknown or missing type A null, blank, or unknown type fails closed. The target must not default unknown content to `choice`, because doing so can turn malformed content into a different assessment. ## Common option shape The provider-neutral safe option shape is: ```text label: non-blank string, unique after trim content: non-blank string order: optional finite number, unique when present ``` Provider DTOs may temporarily contain `isCorrect` for internal scoring or migration needs, but that field and all answer-bearing fields are discarded before creation of a Safe Question or the student-visible Question Snapshot JSON. A separately stored Protected Answer Key may retain correctness and explanation data for stable server-side scoring, but it is never part of the safe snapshot projection or a pre-submit response. The safety validator must not require `isCorrect`, because safe restored snapshots intentionally do not store it. Correct-answer completeness is a content-authoring/scoring concern and is outside this read-only display contract. ## Failure semantics ### Fresh provider content Any of the following makes the provider result unsafe: - malformed options transport or JSON; - null option element; - wrong field types; - fewer than two options for an option-backed question; - options on an optionless question; - blank or duplicate labels; - blank content; - non-finite or duplicate explicit order; - unsupported composite content; - null, blank, or unknown type. A single-question request returns the existing controlled unsafe/malformed content error. Page and collection requests fail the response closed rather than silently changing totals or returning a partial assessment set. ### Persisted practice snapshot A null/empty options value is valid only for an optionless type. Malformed JSON or a shape that violates the type family makes the snapshot unavailable. Session restoration must return a controlled error for the session/question; it must not convert parse failure into `[]`. No automatic repair is performed during read or restore. Historical repair, quarantine, or backfill requires a separately reviewed migration or administrative process. ## Boundary placement The contract is applied at two domain boundaries: 1. provider `CatalogQuestionDTO` → student-safe projection or practice-session creation; 2. persisted `PracticeQuestionDO` snapshot → practice-session response. Both boundaries use the same type classification and option-shape rules. Scalar and Java adapters remain responsible only for transport/database parsing and mapping; they must not define divergent business validity. ## Compatibility notes - Legacy source evidence identifies `choice`, `multi`, `judge`, and `image` as objective/option-backed and requires at least two options. - Legacy source recognizes `reading` as a composite type with sub-questions. - Legacy source recognizes the optionless types listed above and requires answer text for authoring; answer text is intentionally not exposed by the safe read contract. - Current target tests sometimes construct `choice` questions with null, one, or empty options. Those fixtures describe previous permissive behavior and must be corrected where they cross a student-visible or snapshot boundary. - `multi_choice` is retained as a target compatibility alias because current submit tests use it, while the legacy canonical type is `multi`. ## Required tests ### Shared contract - each recognized type is classified correctly; - type matching trims and ignores case; - null, blank, and unknown types fail; - option-backed types reject null, empty, and one-option lists; - optionless types accept null/empty and reject supplied options; - `reading` fails as unsupported composite content; - null elements, wrong field types, blank labels, duplicate labels, blank contents, non-finite orders, and duplicate explicit orders fail; - valid options preserve safe fields and never expose `isCorrect`. ### Provider paths Run the same logical contract cases against Scalar and Java mappings. Transport-specific malformed data may fail earlier, but no provider may turn malformed input into an empty valid list. ### Service paths - single-question browsing fails closed for unsafe content; - page browsing fails the whole response for unsafe content; - collection browsing fails the whole response for unsafe content; - unpublished, hidden, inactive, disabled-source, and unavailable-source behavior remains fail-closed; - successful output contains no answer-bearing fields. ### Practice paths - session creation rejects unsafe source questions before persisting snapshots; - a valid option-backed snapshot restores its options; - a valid optionless snapshot restores an empty option list; - malformed or type-inconsistent snapshots fail closed; - snapshot JSON contains only `label`, `content`, and `order`; - cross-tenant and ownership protections remain unchanged. ## Out of scope - choosing Scalar or Java as the authoritative provider; - adding composite/sub-question APIs; - adding subjective answer submission or scoring; - validating that correct answers exist or are unique; - repairing historical snapshots; - changing database schema or Flyway migrations; - submit-idempotency redesign.