forked from wangziqi/ruoyi-vue-pro
feat(education): complete Flyway migration and atomic submit
This commit is contained in:
174
docs/education/migration/11-question-content-safety-contract.md
Normal file
174
docs/education/migration/11-question-content-safety-contract.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user