Files
ruoyi-vue-pro/docs/education/migration/11-question-content-safety-contract.md

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:

  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.