7.7 KiB
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
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
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
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:
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:
- provider
CatalogQuestionDTO→ student-safe projection or practice-session creation; - persisted
PracticeQuestionDOsnapshot → 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, andimageas objective/option-backed and requires at least two options. - Legacy source recognizes
readingas 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
choicequestions 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_choiceis retained as a target compatibility alias because current submit tests use it, while the legacy canonical type ismulti.
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;
readingfails 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, andorder; - 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.