forked from wangziqi/ruoyi-vue-pro
feat(education): complete Flyway migration and atomic submit
This commit is contained in:
37
docs/education/migration/issues/EDU-000-phase-0-inventory.md
Normal file
37
docs/education/migration/issues/EDU-000-phase-0-inventory.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# EDU-000 — Phase 0 inventory and architecture map
|
||||
|
||||
- **Status:** done
|
||||
- **Type:** discovery
|
||||
- **Phase:** 0
|
||||
- **Blockers:** none
|
||||
|
||||
## Outcome
|
||||
|
||||
A verified static map of the source system, target capabilities, historical commits, database objects, reusable modules, unresolved decisions, and vertical-slice roadmap exists under `docs/education/migration/`.
|
||||
|
||||
## Delivered artifacts
|
||||
|
||||
- `00-current-state.md`
|
||||
- `01-capability-matrix.md`
|
||||
- `02-api-mapping.md`
|
||||
- `03-database-object-mapping.md`
|
||||
- `04-module-reuse-map.md`
|
||||
- `05-commit-review-11e9cc6.md`
|
||||
- `06-commit-review-0f846fd.md`
|
||||
- `07-decisions.md`
|
||||
- `08-slice-roadmap.md`
|
||||
- `09-first-slice.md`
|
||||
- `10-documentation-corrections.md`
|
||||
|
||||
## Evidence and caveats
|
||||
|
||||
- Investigation was read-only and multi-agent.
|
||||
- No PostgreSQL migration was executed during discovery.
|
||||
- The source baseline is provisionally `main` at `033701a`; no source `feature/education-core-loop` ref was found.
|
||||
- The target worktree is dirty and must remain protected.
|
||||
- Completing this ticket did not resolve the product and architecture decisions recorded in `07-decisions.md`.
|
||||
|
||||
## Verification
|
||||
|
||||
- Artifacts generated and inspected.
|
||||
- `git diff --check -- docs/education/migration` passed at delivery time.
|
||||
@@ -0,0 +1,55 @@
|
||||
# EDU-001 — Provider-neutral safe question content
|
||||
|
||||
- **Status:** done with recorded follow-up coverage
|
||||
- **Type:** implementation
|
||||
- **Phase:** 0 / core-loop prerequisite
|
||||
- **Blockers:** EDU-000
|
||||
|
||||
## Student outcome
|
||||
|
||||
A student cannot receive or restore an apparently valid question when its type, visibility, or options are malformed. Student-visible question content and practice snapshot JSON do not expose answer-bearing fields.
|
||||
|
||||
## Scope delivered
|
||||
|
||||
- Shared question-type and option-shape contract.
|
||||
- Option-backed, optionless, unsupported composite, and unknown type handling.
|
||||
- Fail-closed single/page/collection safe projection.
|
||||
- Fail-closed practice creation for disabled/unavailable provider, invisible question, and unsafe options.
|
||||
- Strict practice snapshot restoration.
|
||||
- Answer-free option snapshot JSON.
|
||||
|
||||
## Relevant files
|
||||
|
||||
- `docs/education/migration/11-question-content-safety-contract.md`
|
||||
- `yudao-module-education/CONTEXT.md`
|
||||
- `service/question/QuestionContentSafety.java`
|
||||
- `service/question/QuestionCatalogServiceImpl.java`
|
||||
- `service/practice/PracticeSessionServiceImpl.java`
|
||||
- `service/practice/SessionResponseAssembler.java`
|
||||
- corresponding focused tests
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] Invalid option-backed content fails closed.
|
||||
- [x] Valid optionless content may have no options.
|
||||
- [x] `reading` and unknown types fail closed until modeled.
|
||||
- [x] Safe responses and option snapshot JSON exclude correctness and explanation fields.
|
||||
- [x] Malformed persisted snapshots do not become empty valid options.
|
||||
- [x] Disabled/unavailable providers and invisible questions cannot create sessions.
|
||||
- [x] Focused safety tests pass.
|
||||
- [x] Required compile and diff checks pass.
|
||||
|
||||
## Follow-up coverage
|
||||
|
||||
- Add a clean JavaCatalogProvider public-seam/PostgreSQL contract test when the native catalog test harness is established.
|
||||
- Add explicit cross-tenant and `tenant_id=0` PUBLIC graph tests in the native catalog/graph-integrity slice.
|
||||
- Do not test private provider parsing through reflection.
|
||||
|
||||
## Verification recorded
|
||||
|
||||
```text
|
||||
Focused tests: 112 run, 0 failures, 0 errors
|
||||
git diff --check: passed
|
||||
yudao-server clean compile: BUILD SUCCESS
|
||||
PostgreSQL migration: not applicable and not executed
|
||||
```
|
||||
@@ -0,0 +1,104 @@
|
||||
# EDU-002 — Restore the full Practice regression baseline
|
||||
|
||||
- **Status:** completed as test-context repair; PostgreSQL persistence coverage moved to EDU-016
|
||||
- **Type:** test-enablement vertical slice
|
||||
- **Phase:** 0 / Phase 2 prerequisite
|
||||
- **Blockers:** EDU-001
|
||||
|
||||
## Outcome
|
||||
|
||||
The complete Practice test set starts reliably and distinguishes test-context failures from real behavior regressions across create, answer, restore, submit, report, wrong-question, and favorite flows.
|
||||
|
||||
## Why this is next
|
||||
|
||||
The direct EDU-001 tests pass, but broader Practice tests currently fail during Spring test-context creation because test configurations that import `PracticeSessionServiceImpl` do not consistently provide its current `ScoringService` dependency. Some tests also use name-based `@Resource` injection against Mapper proxies, producing type mismatches. Continuing core-loop work without this feedback loop would hide regressions.
|
||||
|
||||
## Existing code and data
|
||||
|
||||
- No legacy capability is being newly migrated.
|
||||
- No database object changes are required.
|
||||
- Existing Education test SQL and Mapper test infrastructure are reused.
|
||||
|
||||
## Scope
|
||||
|
||||
1. Inventory every test that imports, instantiates, or indirectly creates `PracticeSessionServiceImpl`.
|
||||
2. For each test context, choose one explicit dependency strategy:
|
||||
- import the real `ScoringServiceImpl` when scoring behavior is under test; or
|
||||
- provide `@MockitoBean ScoringService` when the test is outside the scoring seam.
|
||||
3. Replace ambiguous name-based Mapper injection only where it currently prevents the target tests from starting.
|
||||
4. Run the complete focused Practice regression set.
|
||||
5. Classify remaining failures as:
|
||||
- test assembly defect;
|
||||
- existing product defect;
|
||||
- expected contract change from EDU-001;
|
||||
- unrelated dirty-worktree issue.
|
||||
6. Fix only test-assembly defects in this ticket. Create separate tickets for product defects.
|
||||
|
||||
## Reuse boundaries
|
||||
|
||||
- Reuse `BaseDbUnitTest`, existing Education test SQL, Spring `@Import`, and `@MockitoBean`.
|
||||
- Do not create a parallel test framework.
|
||||
- Do not modify System, Member, database schema, or production state machines.
|
||||
- Do not weaken assertions merely to make tests green.
|
||||
|
||||
## Target test set
|
||||
|
||||
```text
|
||||
PracticeSessionServiceImplTest
|
||||
PracticeAnswerServiceImplTest
|
||||
PracticeSubmitServiceImplTest
|
||||
PracticeSubmitProjectionIntegrationTest
|
||||
PracticeSessionControllerHttpTest
|
||||
PracticeAnswerControllerHttpTest
|
||||
PracticeSessionControllerSubmitHttpTest
|
||||
WrongQuestionServiceImplTest
|
||||
FavoriteServiceImplTest
|
||||
```
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Every target class starts its Spring/JUnit context.
|
||||
- [ ] No target class fails because `ScoringService` is missing.
|
||||
- [ ] No target class fails from avoidable Mapper bean-name/type injection ambiguity.
|
||||
- [ ] EDU-001 safe-content assertions remain green.
|
||||
- [ ] Any actual behavior failure is documented with reproducible command and assigned a separate ticket.
|
||||
- [ ] No production behavior or database schema is changed unless a failing regression proves it is necessary and the ticket is explicitly amended.
|
||||
|
||||
## Test command
|
||||
|
||||
```bash
|
||||
mvn -pl yudao-module-education \
|
||||
-Dtest='PracticeSessionServiceImplTest,PracticeAnswerServiceImplTest,PracticeSubmitServiceImplTest,PracticeSubmitProjectionIntegrationTest,PracticeSessionControllerHttpTest,PracticeAnswerControllerHttpTest,PracticeSessionControllerSubmitHttpTest,WrongQuestionServiceImplTest,FavoriteServiceImplTest' \
|
||||
-Dsurefire.failIfNoSpecifiedTests=false \
|
||||
test
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
mvn -pl yudao-server -am -DskipTests clean compile
|
||||
```
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** Low production risk; medium risk of exposing pre-existing behavior defects.
|
||||
- **Rollback:** Revert only this ticket's test assembly changes. There is no database rollback.
|
||||
|
||||
## Completion result
|
||||
|
||||
Test-context assembly was repaired:
|
||||
|
||||
- `ScoringService` is now explicitly mocked in Practice contexts that are not testing scoring itself.
|
||||
- `PracticeQuestionMapper` fields use type-based injection where name-based `@Resource` resolved the wrong MyBatis proxy.
|
||||
- Controller tests and `PracticeSessionServiceImplTest` start and pass.
|
||||
|
||||
The expanded regression run then exposed a separate infrastructure limitation rather than a remaining Spring context defect: H2 cannot execute the production PostgreSQL `ON CONFLICT` statements, and the unified `education_idempotency` test table was missing. A temporary H2 table definition was added so table absence no longer masks the dialect issue, but PostgreSQL conflict semantics cannot be made truthful on H2. The required follow-up is [`EDU-016`](EDU-016-postgresql-persistence-tests.md).
|
||||
|
||||
## Verification result
|
||||
|
||||
- Controller Practice tests: 39 passed.
|
||||
- `PracticeSessionServiceImplTest`: 24 passed before PostgreSQL-dialect persistence paths were included.
|
||||
- Full targeted suite starts after dependency/injection repair, then fails on confirmed H2/PostgreSQL dialect mismatch and downstream assertions.
|
||||
- No production behavior was changed by EDU-002.
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
# EDU-003 — Decide tenant resolution and student-principal policy
|
||||
|
||||
- **Status:** done — corrected policy and EDU-004 test seams are implementation-ready
|
||||
- **Type:** decision
|
||||
- **Phase:** 1
|
||||
- **Blockers:** EDU-000
|
||||
|
||||
## Decision outcome
|
||||
|
||||
Public tenant resolution accepts caller-supplied locator claims and is not an authentication boundary. Browser `Origin`/`Referer` evidence improves browser-context consistency but is forgeable by non-browser clients. The accepted contract therefore documents tenant-existence disclosure, uses one redacted unavailable response for unknown/disabled/expired tenants, requires abuse controls, and reserves authenticated/signed locators for deployments that require spoof resistance. Authenticated Education context is Member-only. This ticket records policy and tests-to-write only; it changes no production behavior.
|
||||
|
||||
## Domain language
|
||||
|
||||
- A **Tenant Locator Claim** is an unauthenticated pre-login value used to request tenant selection: either a browser-context hostname claim or an explicit Public Tenant Handle. It is not proof of caller identity or tenant authorization.
|
||||
- **Browser-context evidence** is a normalized host derived from `Origin`, falling back to `Referer`. It can bind browser UX inputs consistently, but any HTTP client can forge it.
|
||||
- A **Public Tenant Handle** is the current System tenant's unique `name` used as an exact public lookup key because the target has no separate stable tenant-code capability. It is not called a Tenant Code. It is case-sensitive, must match `^[A-Za-z0-9._-]{2,64}$`, and administrators must treat it as immutable after publication. A future mutable display label must be a separate field.
|
||||
- A **Student Principal** is an authenticated identity whose `LoginUser.userType` is `UserTypeEnum.MEMBER`. A generic authenticated account is not necessarily a Student Principal.
|
||||
- **Public Tenant Resolution** maps a Tenant Locator Claim to minimal login-routing fields. It intentionally discloses existence when a claim succeeds; it does not disclose whether a failed tenant is unknown, disabled, or expired.
|
||||
|
||||
## Evidence reviewed
|
||||
|
||||
- Legacy `apps/api/src/features/tenant/locator.ts` parses and compares `Origin`, `Referer`, request hosts, and a tenant code, but does not authenticate header provenance.
|
||||
- Legacy `resolver.ts` compares the source `slug` with the explicit code, proving the source Tenant Code was distinct from its display name.
|
||||
- The target has no System-owned stable tenant-code field or API. `system_tenant.name` is unique and mutable through administration; it is the only current exact generic lookup key.
|
||||
- Current `EducationTenantController` accepts arbitrary public `hostname` or `tenantName`, preserves ports, returns status and Education-configured `loginMethods`, and exposes distinct unknown/disabled/expired errors.
|
||||
- `EducationProperties.hostnameTenantMap` documents lowercase host-only keys without ports, while current implementation and tests preserve ports.
|
||||
- `system_tenant.websites` is an exact string-list lookup and existing target tests demonstrate values containing a scheme. No normalization seam currently makes those values host-only.
|
||||
- `EducationContextController` derives IDs from framework contexts but reads only the user ID and therefore does not reject an authenticated ADMIN principal.
|
||||
- `TenantSecurityWebFilter` already fills a missing request tenant from the authenticated principal, rejects authenticated principal/request-tenant mismatch, requires a tenant for non-ignored URLs, and validates tenant availability.
|
||||
- `TenantCommonApi` exposes generic System-owned tenant lookup methods; `TenantApiImpl` implements them, but the interface currently hides missing adapters behind `UnsupportedOperationException` defaults and has no focused owning-module contract test.
|
||||
- No verified Member public interface advertises enabled login methods. `MemberConfigApi` currently exposes points configuration only.
|
||||
|
||||
## ADR: public tenant-resolution and student-principal policy
|
||||
|
||||
### Status
|
||||
|
||||
Accepted for EDU-004.
|
||||
|
||||
### Context and trade-off
|
||||
|
||||
The resolver is public and cannot authenticate `Origin`, `Referer`, or ordinary query/header values. Browser headers are useful for consistent browser routing, not identity. A successful lookup necessarily distinguishes an available tenant from a failed candidate when it returns routing fields. The contract can hide lifecycle state among failures, but cannot honestly promise general non-enumeration without an unguessable or signed locator.
|
||||
|
||||
The target also lacks the source system's distinct stable tenant code. Adding one would require a separately designed System-owned capability and likely data work. For the current slice, the existing unique System tenant `name` is explicitly exposed as a constrained Public Tenant Handle; it is no longer mislabeled as a Tenant Code.
|
||||
|
||||
### Decision
|
||||
|
||||
1. **Production browser-context binding, not trusted identity**
|
||||
- Derive browser-context evidence from a valid HTTP(S) `Origin`; if absent, use a valid HTTP(S) `Referer`.
|
||||
- A supplied `hostname` may only confirm that evidence. A mismatch is a public locator conflict.
|
||||
- `Origin` and `Referer` are untrusted caller claims. Proxy preservation and forwarding-header controls do not make them authentic and are not cited as spoofing protection.
|
||||
- A non-browser/headless caller can forge either header and probe hostnames. This accepted threat is handled through the public disclosure policy and abuse controls below.
|
||||
- A deployment requiring spoof resistance must replace this public mode with an authenticated/signed locator or a host value supplied through a separately designed trusted-proxy boundary. That stronger mode is not implemented by EDU-004.
|
||||
|
||||
2. **Explicit headless handle and legacy query compatibility**
|
||||
- A headless client may submit `tenantHandle`, defined above as the existing System tenant unique `name` under a constrained public contract.
|
||||
- The legacy public `tenantName` query is unsupported and must be rejected, not silently aliased. EDU-004 adds a compatibility test for its rejection/removal.
|
||||
- A browser domain claim and explicit `tenantHandle` may be supplied together only when both resolve to the same tenant; disagreement is a public locator conflict.
|
||||
|
||||
3. **Local-development activation seam**
|
||||
- The sole authority is `yudao.education.tenant-resolution.local-development-enabled`.
|
||||
- Its secure default is `false`; absence means production-safe behavior. Spring profile names and environment names do not implicitly enable it.
|
||||
- Only developer workstations and automated tests may set it to `true`; shared, staging, and production deployments must keep it `false`.
|
||||
- When enabled, a configured local request host (`localhost`, `*.localhost`, loopback IPv4, `0.0.0.0`, or `::1`) may resolve without a handle. If `tenantHandle` is also present, the explicit handle takes precedence.
|
||||
- EDU-004 tests code-less local host, local host plus handle, and rejection of local/request-host fallback when the flag is absent or false.
|
||||
|
||||
4. **Hostname identity and normalization**
|
||||
- Tenant hostname identity is host-only: trim whitespace, lowercase, remove one trailing dot, remove IPv6 brackets, and discard default or non-default ports.
|
||||
- Accept valid DNS hosts, IPv4, and IPv6; reject credentials, paths, comma-separated/multi-value input, malformed authorities, and unsupported schemes.
|
||||
- `localhost:48080` normalizes to `localhost`.
|
||||
- Canonical `system_tenant.websites` entries used for this resolver are host-only values in the same normalized form. Entries containing a scheme, path, credentials, comma-separated values, or a port are legacy/non-canonical configuration and are not matched by Public Tenant Resolution.
|
||||
- EDU-004 implements canonical exact lookup and focused tests; it does not silently normalize legacy stored candidates at read time. Tenant administrators must correct non-canonical website configuration before enabling domain resolution. If later inventory requires automated data correction, that becomes a separately scoped Flyway/data ticket using `flyway-postgresql`; EDU-004 must not claim such correction.
|
||||
|
||||
5. **Authenticated Education context**
|
||||
- `/education/context` obtains the full `LoginUser`, rejects missing authentication, and rejects `userType != UserTypeEnum.MEMBER`.
|
||||
- User and tenant IDs continue to come only from security and tenant contexts.
|
||||
- EDU-004 preserves and does not duplicate or bypass `TenantSecurityWebFilter` mismatch and availability checks.
|
||||
|
||||
6. **Login-method metadata ownership**
|
||||
- Login-method metadata belongs to Member authentication, not System tenant metadata and not Education.
|
||||
- EDU-004 removes `loginMethods` from Education resolution and deprecates Education configuration/documentation that presents it as authoritative.
|
||||
- If later routing proves it necessary, introduce only a minimal Member-owned public interface with focused Member tests; do not create tenant-specific auth configuration in Education.
|
||||
|
||||
7. **Exact external wire contract**
|
||||
- The target framework represents business failures as HTTP `200 OK` with a `CommonResult` envelope. EDU-004 keeps that convention; tests assert both transport status and envelope.
|
||||
- Malformed, missing, locally forbidden, or otherwise unsupported locator claim: HTTP `200`; `CommonResult.code = 1005001003`; `msg = "租户识别请求无效"`; `data = null`.
|
||||
- Domain/handle or browser-evidence/requested-host conflict: HTTP `200`; `CommonResult.code = 1005001008` (new stable Education business code); `msg = "租户识别信息冲突"`; `data = null`.
|
||||
- Unknown, disabled, or expired tenant: HTTP `200`; `CommonResult.code = 1005001004`; `msg = "当前租户不可用"`; `data = null`.
|
||||
- Messages contain no rejected host/handle, lifecycle status, System exception text, or lookup detail. Logs may record a reason category and correlation metadata but must not log secrets or echo unsanitized header values.
|
||||
- Unknown, disabled, and expired paths must have identical status, code, message, JSON field set, null-data shape, and no intentional timing distinction. System errors remain internal.
|
||||
- Success is HTTP `200`, `code = 0`, `msg = ""`, and data contains only `tenantId` and `displayName`. `displayName` currently comes from the System tenant `name`; because that same field is the current Public Tenant Handle, an exact handle lookup necessarily returns the submitted handle as `displayName`. A future non-echoing mutable label requires a separate System-owned public display field. The response contains no separate handle field, raw status, websites, expiry, package, private configuration, internal lifecycle detail, or `loginMethods`.
|
||||
|
||||
8. **Disclosure and abuse threat model**
|
||||
- The resolver is not generally non-enumerating: a valid Public Tenant Handle or domain claim yields success with tenant ID/display name, while an unavailable candidate yields the generic failure.
|
||||
- The accepted guarantee is only unknown/disabled/expired indistinguishability.
|
||||
- EDU-004 must attach the public resolver to the repository's existing public API rate-limiting/ingress mechanism where available, emit structured success/failure-category security metrics, and document alerting for sustained candidate probing. If no reusable limiter seam exists, EDU-004 records that operational blocker rather than inventing an Education-only limiter.
|
||||
|
||||
9. **System seam**
|
||||
- Retain `TenantCommonApi` as the generic System-owned seam; do not add Education-specific locator, branding, redaction, or login-method concepts.
|
||||
- Replace `UnsupportedOperationException` lookup defaults with required abstract methods and add focused `TenantApiImpl` contract tests.
|
||||
- `EducationTenantController` remains the public adapter applying claim consistency, canonical website policy, availability coarsening, exact errors, and redaction.
|
||||
|
||||
### Rejected alternatives
|
||||
|
||||
- Treating `Origin` or `Referer` as authenticated tenant identity.
|
||||
- Claiming forwarding-header ingress controls authenticate browser headers.
|
||||
- Claiming general non-enumeration while successful lookup returns identifying fields.
|
||||
- Silently aliasing System tenant `name` to the distinct Tenant Code domain term.
|
||||
- Continuing the legacy `tenantName` public query.
|
||||
- Silently normalizing scheme/path/port-bearing stored website values during lookup.
|
||||
- Port-sensitive tenant identity.
|
||||
- ADMIN accepted as Student Principal.
|
||||
- Education-owned login methods or an Education-specific System API.
|
||||
|
||||
### Consequences
|
||||
|
||||
- EDU-004 intentionally changes current query, response, error, local-mode, website, and port behavior.
|
||||
- Published Public Tenant Handles use the System tenant unique `name`; renaming one is a breaking login-routing change until a genuine stable System-owned code exists.
|
||||
- Non-canonical website entries require configuration correction before domain resolution is enabled; no database change is authorized here.
|
||||
- Public existence disclosure is accepted and must be monitored and throttled. Strong spoof resistance requires a future signed/authenticated locator design.
|
||||
|
||||
## EDU-004 exact test matrix
|
||||
|
||||
| Scenario | Exact expected behavior | Owning test seam |
|
||||
|---|---|---|
|
||||
| Valid production `Origin` | Resolve normalized domain claim; success HTTP 200/code 0 | Education controller HTTP test |
|
||||
| Missing `Origin`, valid `Referer` | Resolve normalized Referer host; success HTTP 200/code 0 | Education controller HTTP test |
|
||||
| Forged but syntactically valid browser header | Documented as accepted untrusted claim; no authenticity assertion | Education controller test name/documentation |
|
||||
| Malformed `Origin` | HTTP 200/code 1005001003/generic message/null data; no fallback | Education controller HTTP test |
|
||||
| Origin/requested-host mismatch | HTTP 200/code 1005001008/generic conflict/null data | Education controller HTTP test |
|
||||
| Arbitrary production hostname without browser evidence | HTTP 200/code 1005001003 | Education controller HTTP test |
|
||||
| Explicit `tenantHandle` | Exact case-sensitive constrained System-name lookup | Education controller HTTP test |
|
||||
| Legacy `tenantName` query | Rejected/unsupported; HTTP 200/code 1005001003 | Education controller compatibility HTTP test |
|
||||
| Unknown, disabled, expired | Identical HTTP 200/code 1005001004/message/JSON/null data | Education controller HTTP parameterized test |
|
||||
| Host case/trailing dot/IPv4/IPv6/ports | Canonical host-only identity; all ports discarded | Education normalization/HTTP tests |
|
||||
| Non-canonical stored website candidate | Not matched; generic unavailable response | System adapter fixture plus Education HTTP test |
|
||||
| Local flag absent/false | Local/request-host fallback rejected with code 1005001003 | Education controller HTTP test |
|
||||
| Local flag true, code-less local host | Configured local host may resolve | Education controller HTTP test |
|
||||
| Local flag true, local host plus handle | Explicit handle takes precedence | Education controller HTTP test |
|
||||
| Domain/handle agreement | Resolve one tenant | Education controller HTTP test |
|
||||
| Domain/handle conflict | HTTP 200/code 1005001008 | Education controller HTTP test |
|
||||
| Anonymous `/education/context` | Existing unauthorized contract | Education context HTTP test |
|
||||
| Missing tenant or authenticated mismatch | Existing filter behavior remains active | Framework `TenantSecurityWebFilter` tests |
|
||||
| ADMIN/MEMBER principal | ADMIN rejected; MEMBER accepted with context-derived IDs | Education context HTTP tests |
|
||||
| Public field redaction | Success has only tenantId/displayName; failure has code/msg/data only | Education controller HTTP test |
|
||||
| `TenantCommonApi` adapters | Required methods delegate/map; no unsupported defaults | System `TenantApiImpl` contract test |
|
||||
| Abuse controls | Reused limiter/ingress attachment and structured category metric proven, or blocker recorded | Configuration/integration test where seam exists |
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] Browser headers are described as forgeable consistency evidence, not trusted identity.
|
||||
- [x] Public existence disclosure and the narrower lifecycle-indistinguishability guarantee are explicit.
|
||||
- [x] Public Tenant Handle is distinguished from the source Tenant Code and has exact mutability/case/format semantics.
|
||||
- [x] Local behavior has one named, secure-default configuration seam and unambiguous precedence.
|
||||
- [x] Canonical stored website compatibility policy is selected without claiming data migration.
|
||||
- [x] Every error category has exact HTTP status, stable `CommonResult` code, message, data shape, and redaction rules.
|
||||
- [x] EDU-004 has exact production/test seams and legacy `tenantName` compatibility coverage.
|
||||
|
||||
## Verification
|
||||
|
||||
Static design review only. Reviewed legacy `locator.ts`/`resolver.ts`, current Education controller/properties/error codes, `CommonResult` and global error handling, `TenantSecurityWebFilter`, `TenantCommonApi`/`TenantApiImpl`, System tenant name/website storage and tests, Member public interfaces, the completed EDU-016 ticket, tracker, and dirty working tree. No production implementation, build, database connection, Flyway execution, or migration was performed by EDU-003.
|
||||
@@ -0,0 +1,139 @@
|
||||
# EDU-004 — Enforce tenant resolution and student identity boundaries
|
||||
|
||||
- **Status:** done — accepted tenant-locator and Member-principal policy implemented and verified; ingress/IP-only probing throttle remains an operational blocker
|
||||
- **Type:** implementation
|
||||
- **Phase:** 1
|
||||
- **Blockers:** EDU-003
|
||||
|
||||
## Selected policy from EDU-003
|
||||
|
||||
- `Origin` then `Referer` supplies forgeable browser-context evidence, not trusted identity. It binds browser UX inputs but does not prevent non-browser spoofing.
|
||||
- Headless clients use `tenantHandle`, the existing System tenant unique `name` under an exact constrained public contract; do not call it a Tenant Code.
|
||||
- Reject/remove the legacy public `tenantName` query as a separate compatibility behavior.
|
||||
- Production domain/handle agreement is checked; disagreement is a locator conflict.
|
||||
- Host identity removes case, whitespace, trailing dot, IPv6 brackets, and all ports.
|
||||
- Local fallback is controlled only by `yudao.education.tenant-resolution.local-development-enabled`, default `false`; profiles do not implicitly enable it.
|
||||
- Canonical System website values for this resolver are normalized host-only strings. Scheme/path/port-bearing stored candidates are not silently normalized and require configuration correction or a separate future Flyway/data ticket.
|
||||
- `/education/context` accepts only an authenticated `UserTypeEnum.MEMBER` Student Principal.
|
||||
- Unknown/disabled/expired failures are identical, but successful resolution still discloses tenant existence. Reuse rate limiting/ingress controls and emit abuse-monitoring metrics.
|
||||
- Login-method metadata is Member-owned; remove/deprecate Education `loginMethods` unless a minimal Member public interface is first proven necessary.
|
||||
- Retain generic `TenantCommonApi`, make its lookup methods required, and add System-owned `TenantApiImpl` contract tests.
|
||||
|
||||
See [`EDU-003-tenant-resolution-decision.md`](EDU-003-tenant-resolution-decision.md) for rationale and exact threat model.
|
||||
|
||||
## Implementation result
|
||||
|
||||
Implemented the accepted public HTTP contracts for tenant resolution and Member-only Education context. The resolver treats `Origin`, `Referer`, `hostname`, and `tenantHandle` only as forgeable locator claims; successful responses expose only `tenantId` and `displayName`, and unknown/disabled/expired tenants share the exact unavailable envelope. Local fallback is controlled exclusively by the secure-default property, including browser-derived loopback hosts, and only configured local hosts use the Education mapping. Production domains always use the canonical System website lookup. `TenantCommonApi` lookup methods are now required and System-owned adapter tests prove exact delegation and DTO mapping. Existing `TenantSecurityWebFilter` production behavior was unchanged and is covered by focused filter-boundary regression tests.
|
||||
|
||||
No adequate reusable candidate-probing rate-limit attachment was found. The existing `ClientIpRateLimiterKeyResolver` includes attacker-controlled method arguments in its key, so attaching it would create per-candidate limits rather than an IP-only probing limit. No Education-only limiter was introduced; ingress/IP-only throttling and alerting remain an operational blocker. The Education module also has no direct generic Micrometer dependency seam, so structured resolver metrics remain part of the same operational blocker rather than adding an Education-only dependency or abstraction.
|
||||
|
||||
System currently has no separate public tenant display field: its unique `name` is both the Public Tenant Handle and the only public label available through `TenantCommonApi`. Therefore handle-based success returns that same value as `displayName`; tests now model this real exact-name adapter behavior. Suppressing that value requires a future generic System-owned display-field capability, not an Education workaround.
|
||||
|
||||
No database or Flyway change was made or executed.
|
||||
|
||||
## Student outcome
|
||||
|
||||
A student receives minimal login-routing data for an available tenant, while authenticated Education endpoints reject wrong tenants and non-Member principals. The public resolver does not claim that caller-supplied locator headers authenticate tenant identity.
|
||||
|
||||
## Scope
|
||||
|
||||
- browser-context domain claim selection from `Origin`, then `Referer`;
|
||||
- requested-host confirmation and domain/handle conflict handling;
|
||||
- explicit `tenantHandle` exact lookup and legacy `tenantName` rejection/removal;
|
||||
- host-only normalization and canonical stored-website behavior;
|
||||
- secure-default local-development configuration seam and precedence;
|
||||
- Member/student principal enforcement for `/education/context`;
|
||||
- exact public `CommonResult` contract and safe response fields;
|
||||
- minimal generic `TenantCommonApi` adjustment with System-owned tests;
|
||||
- reuse of public resolver rate limiting/ingress controls and structured abuse metrics where an existing seam is available.
|
||||
|
||||
## Exact external contract
|
||||
|
||||
All business outcomes use the framework convention of HTTP `200 OK` with `CommonResult`:
|
||||
|
||||
| Category | HTTP | `CommonResult.code` | `msg` | `data` |
|
||||
|---|---:|---:|---|---|
|
||||
| Malformed/missing/untrusted/locally forbidden locator | 200 | `1005001003` | `租户识别请求无效` | `null` |
|
||||
| Requested-host/domain/handle conflict | 200 | `1005001008` | `租户识别信息冲突` | `null` |
|
||||
| Unknown, disabled, or expired tenant | 200 | `1005001004` | `当前租户不可用` | `null` |
|
||||
| Success | 200 | `0` | empty string | object containing only `tenantId`, `displayName`; current `displayName` is System tenant `name` and therefore equals a successful handle claim |
|
||||
|
||||
Failure messages and JSON shape never contain the rejected host/handle, lifecycle state, System exception detail, or lookup reason. Unknown, disabled, and expired paths must be byte-shape equivalent after normal serialization and have no intentional timing distinction.
|
||||
|
||||
## Reuse boundaries
|
||||
|
||||
- Reuse `TenantSecurityWebFilter`, `TenantContextHolder`, System Tenant public APIs, Member/System security context, `UserTypeEnum`, and an existing public rate-limit/ingress seam if present.
|
||||
- Do not duplicate tenant tables, token logic, login methods, RBAC, or a generic rate-limiter in Education.
|
||||
- Education must not depend on System internal Services, Mappers, or DOs.
|
||||
- A non-Education change must be generic, minimal, backward-compatible, and tested in its owning module.
|
||||
- If no reusable abuse-control seam exists, record the operational blocker; do not invent an Education-only infrastructure abstraction.
|
||||
|
||||
## Required TDD tests
|
||||
|
||||
### Education tenant controller HTTP tests
|
||||
|
||||
- valid production `Origin`; valid `Referer` fallback;
|
||||
- syntactically valid forged header is treated only as an untrusted claim, with no authenticity assertion;
|
||||
- malformed Origin and no attacker-selected fallback: exact HTTP/code/msg/data;
|
||||
- Origin/requested-host mismatch: exact conflict contract;
|
||||
- arbitrary production hostname without browser evidence: exact invalid contract;
|
||||
- explicit case-sensitive `tenantHandle` with `^[A-Za-z0-9._-]{2,64}$` validation;
|
||||
- legacy `tenantName` query rejected/removed independently;
|
||||
- domain/handle agreement and conflict;
|
||||
- unknown/disabled/expired exact identical wire shape;
|
||||
- case, trailing dot, DNS, IPv4, bracketed IPv6, default and non-default port normalization;
|
||||
- local flag absent/false rejects local/request-host fallback;
|
||||
- local flag true permits code-less configured local host;
|
||||
- local flag true plus handle gives the handle precedence;
|
||||
- non-canonical stored website candidate does not match;
|
||||
- success exposes only `tenantId` and System tenant `name` as `displayName`; for handle lookup this necessarily equals the submitted handle until System owns a separate public display field; no separate handle field, status, websites, expiry, package, private config, or `loginMethods`.
|
||||
|
||||
### Education context HTTP tests
|
||||
|
||||
- unauthenticated context rejected;
|
||||
- ADMIN principal rejected;
|
||||
- MEMBER principal accepted and IDs derived only from security/tenant contexts.
|
||||
|
||||
### System contract tests
|
||||
|
||||
- `TenantCommonApi` lookup methods are required, not optional unsupported defaults;
|
||||
- `TenantApiImpl` exact name and canonical website lookups delegate and map DTOs;
|
||||
- non-canonical website candidates are not silently normalized by the Public Tenant Resolution path.
|
||||
|
||||
### Framework/configuration tests
|
||||
|
||||
- existing missing-tenant and authenticated tenant/header mismatch filter behavior remains active;
|
||||
- local-development flag defaults false and is not inferred from a Spring profile;
|
||||
- reusable limiter/ingress attachment and structured success/failure-category metric are verified where an existing seam is identified.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] Client `tenantId` and `userId` are never authoritative.
|
||||
- [x] Browser headers are documented and implemented as forgeable context claims, not authentication.
|
||||
- [x] Public existence disclosure is accepted; only unknown/disabled/expired status is indistinguishable.
|
||||
- [x] `tenantHandle` is not mislabeled as Tenant Code; mutability, case, and format match EDU-003.
|
||||
- [x] Legacy `tenantName` is rejected/removed and covered by a compatibility test.
|
||||
- [x] Local fallback uses the named secure-default property and unambiguous precedence.
|
||||
- [x] Canonical website compatibility policy is implemented without silent legacy normalization.
|
||||
- [x] Exact HTTP/CommonResult code/message/data contracts are asserted.
|
||||
- [x] Authenticated Student context enforces Member principal type.
|
||||
- [x] Existing framework mismatch checks remain active and are not bypassed.
|
||||
- [x] Public abuse-control gap is truthfully recorded; no inadequate or Education-only limiter was introduced.
|
||||
- [x] Every non-Education modification has a necessity explanation and focused owning-module tests.
|
||||
- [x] API documentation matches implementation and tests.
|
||||
|
||||
## Verification
|
||||
|
||||
Run focused Education, System, framework, and configuration tests as applicable, then:
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
mvn -pl yudao-server -am -DskipTests clean compile
|
||||
```
|
||||
|
||||
No database change is in scope. If investigation proves automated website data correction is required, stop that part, create a separate blocked data/Flyway ticket, and invoke `flyway-postgresql` before any schema/data work.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** High security, disclosure, and login-routing impact.
|
||||
- **Rollback:** Application/configuration rollback to the previous resolver adapter; keep local mode disabled by default and do not weaken authenticated tenant checks. No destructive tenant-data operation.
|
||||
@@ -0,0 +1,160 @@
|
||||
# EDU-005 — Decide PostgreSQL/Flyway takeover strategy
|
||||
|
||||
- **Status:** done — forward-only takeover and EDU-006 version plan accepted
|
||||
- **Type:** decision
|
||||
- **Phase:** 1 / Phase 2 prerequisite
|
||||
- **Blockers:** EDU-002
|
||||
|
||||
## Decision outcome
|
||||
|
||||
Education schema ownership moves exclusively to module-owned PostgreSQL Flyway migrations under `yudao-module-education/src/main/resources/db/migration/education/`. The existing root `sql/postgresql/education/` files are classified as manual bootstrap/design history, not Flyway history. The MySQL files are obsolete archival artifacts and must not remain in operational instructions.
|
||||
|
||||
`V4010__initialize_education_flyway.sql` and `V4020__create_native_catalog.sql` are uncommitted working-tree resources in this checkout, and the inspected local disposable PostgreSQL database has no `flyway_schema_history` table and no Education tables. This proves neither migration ran in that database, but it does not prove they never ran in another environment. To avoid assigning a second meaning to a potentially distributed version, EDU-006 must preserve both files byte-for-byte and allocate new work from `V4030`.
|
||||
|
||||
No migration or production schema change was executed by EDU-005.
|
||||
|
||||
## Evidence and classification
|
||||
|
||||
| Artifact | Verified state | Classification | Forward disposition |
|
||||
|---|---|---|---|
|
||||
| `V4010__initialize_education_flyway.sql` | Untracked module resource; contains only `SELECT 1`; packaged in current `target/classes` | Potentially distributed Flyway history; execution unverified | Freeze byte-for-byte; do not repurpose |
|
||||
| `V4020__create_native_catalog.sql` | Untracked module resource; owns 11 native catalog tables; differs materially from manual `008` | Potentially distributed Flyway history; execution unverified | Freeze byte-for-byte; do not replace with manual `008` |
|
||||
| `sql/postgresql/education/002`–`005`, `007`, `009` | Untracked manual scripts implementing the current Practice/report/wrong/favorite/unified-idempotency model in stages | Manual bootstrap/design history, not active Flyway | Consolidate the approved final state into higher Flyway versions; do not copy the obsolete intermediate tables as fresh schema |
|
||||
| `sql/postgresql/education/008` | Manual native-catalog design predating/diverging from V4020 | Superseded manual design | V4020 remains the only intended Flyway owner of native catalog schema |
|
||||
| `sql/postgresql/education/000`–`001` | Placeholder schema plus menu/tenant seed scripts | Manual bootstrap/seed history | Do not create a separate `education` schema; evaluate the still-used `education:capability` menu seed separately |
|
||||
| `sql/mysql/education/**` | Historical MySQL schema, seeds, and rollback scripts | Obsolete archive | Remove from runbooks; retain only if explicitly labeled non-operational archive |
|
||||
| `src/test/resources/sql/postgresql/create_tables.sql` | EDU-016 disposable test bridge; 140 PostgreSQL persistence tests passed against it | Temporary test fixture | Replace with Flyway-driven test setup after EDU-006 proves equivalent schema |
|
||||
| Docker init mounts for manual Education SQL | Dirty Docker configuration mounts `000`–`009` directly | Obsolete delivery path | Remove Education manual mounts after Flyway takeover; the server owns migration execution |
|
||||
|
||||
## Approved schema owner map
|
||||
|
||||
### V4020 owner — unchanged
|
||||
|
||||
V4020 exclusively owns the native catalog tables:
|
||||
|
||||
- `education_region`, `education_school`, `education_major`, `education_subject`, `education_category`;
|
||||
- `education_content_entry`, `education_content_node`;
|
||||
- `education_question_collection`, `education_question`, `education_practice_blueprint`;
|
||||
- `education_question_collection_question`.
|
||||
|
||||
EDU-006 does not fold Practice schema into V4020 and does not silently substitute manual `008`.
|
||||
|
||||
### EDU-006 new owner — Practice final state
|
||||
|
||||
The new Practice migration owns the final runtime shape of:
|
||||
|
||||
- `education_practice_session` and `education_practice_question`;
|
||||
- `education_practice_report` and `education_practice_report_detail`;
|
||||
- `education_wrong_question` and `education_wrong_question_idempotency`;
|
||||
- `education_favorite`;
|
||||
- `education_idempotency`.
|
||||
|
||||
The approved fresh schema does **not** create `education_answer_idempotency` or `education_submit_idempotency`. Current production services use `IdempotencyStoreMapper` and `education_idempotency`; the old DOs/Mappers are unused compatibility residue and must be removed or explicitly isolated during EDU-006.
|
||||
|
||||
The migration must include all columns already required by runtime code and the proven EDU-016 bridge, including `client_sequence`, `last_client_sequence`, `review_fingerprint`, protected answer snapshots, report content snapshots, JSONB fields, tenant IDs, logical-delete fields, and observed conflict/query indexes.
|
||||
|
||||
## Version plan for EDU-006
|
||||
|
||||
Version numbers are project-wide. With V4020 frozen, the next allocated version is:
|
||||
|
||||
1. **V4030 — Practice core-loop final schema and adoption**
|
||||
- Create the final tables, columns, constraints, and indexes listed above.
|
||||
- Be adoption-aware for databases that contain manually bootstrapped Practice tables.
|
||||
- Validate existing column types and required uniqueness before treating existing objects as compatible; fail closed on incompatible shapes rather than silently accepting them.
|
||||
- Backfill the unified idempotency table from legacy answer/submit tables when those tables exist.
|
||||
- Preserve old idempotency tables during the initial adoption migration; do not make data destruction a prerequisite for application rollout.
|
||||
2. **V4040 — deterministic Education capability seed, only if still approved**
|
||||
- Seed menu IDs `6800`/`6801` idempotently if the existing `EducationCapabilityController` remains an exposed administrator capability.
|
||||
- Keep role assignment outside the migration.
|
||||
- If the capability endpoint/menu is retired before EDU-006, omit this migration rather than seeding dead UI.
|
||||
3. **Later forward cleanup migration**
|
||||
- Drop legacy `education_answer_idempotency` and `education_submit_idempotency` only after every adopted environment has verified backfill counts, the application no longer contains active references, and a separately reviewed forward cleanup is approved.
|
||||
|
||||
If repository-wide migration inventory changes before implementation, EDU-006 must re-run the version scan and use the next unused project-wide version instead of blindly taking V4030/V4040.
|
||||
|
||||
## Existing-environment takeover classes
|
||||
|
||||
`baseline-on-migrate=true` with baseline `4009` is an adoption aid, not proof that Education objects match Flyway.
|
||||
|
||||
1. **Empty or platform-only database, no Education tables**
|
||||
- Use baseline `4009` only when the non-empty platform schema requires adoption.
|
||||
- Run V4010, V4020, then V4030+ normally.
|
||||
2. **Manual Practice tables exist, native catalog tables do not**
|
||||
- Baseline `4009` may be used.
|
||||
- V4020 creates catalog objects; V4030 validates/adopts Practice objects and performs required backfills.
|
||||
3. **Manual catalog tables equivalent to V4020 already exist, no Flyway history**
|
||||
- Do not run V4020 into colliding tables.
|
||||
- First compare the actual schema with the frozen V4020 contract.
|
||||
- For a verified equivalent environment, use a one-time environment-specific baseline at `4020`, then run V4030+. This records adoption, not execution of V4020, and must be documented per environment.
|
||||
- If the schema is not equivalent, correct it through an explicit higher-version adoption path; do not falsify history or edit V4020.
|
||||
4. **Flyway history already contains V4010 and/or V4020**
|
||||
- Compare script/checksum/success with the frozen resources.
|
||||
- Never edit an executed script. Any mismatch or failed row blocks rollout until an environment-specific repair decision is reviewed.
|
||||
5. **Unknown shared environment**
|
||||
- No migration rollout is authorized until its Education tables and `flyway_schema_history` are inventoried.
|
||||
|
||||
After all existing environments carry an explicit baseline/history record, changing `baseline-on-migrate` to `false` is a separate reviewed configuration ticket. `validate-on-migrate=true`, `clean-disabled=true`, and `out-of-order=false` remain mandatory.
|
||||
|
||||
## Data compatibility and backfill rules
|
||||
|
||||
- Copy legacy answer and submit idempotency rows into `education_idempotency` with deterministic operation values and `ON CONFLICT ... DO NOTHING` only after verifying duplicate-key/request-hash compatibility.
|
||||
- Preserve original IDs only if required by references; otherwise allow identity allocation and verify semantic row counts by operation.
|
||||
- Existing Practice tables must be compared with the final DO/Mapper contract, not merely checked for table-name existence.
|
||||
- JSON snapshot fields use PostgreSQL `JSONB` where current runtime/test behavior expects JSONB normalization.
|
||||
- Tenant-scoped uniqueness includes `tenant_id` where the business key is tenant-local. `education_practice_report` uses `(tenant_id, session_id)` as the final unique report key.
|
||||
- Do not copy Supabase RLS. Framework tenant isolation remains primary; database constraints enforce integrity and idempotency.
|
||||
- No destructive rollback SQL is delivered. Recovery is application rollback plus a higher-version forward correction.
|
||||
|
||||
## Documentation and operational corrections
|
||||
|
||||
EDU-006 must update operational documentation in the same slice:
|
||||
|
||||
- replace `yudao-module-education/README.md` MySQL apply/rollback commands with Flyway/PostgreSQL forward-only instructions;
|
||||
- remove the manual Education SQL mounts from `script/docker/docker-compose.yml` so a fresh Docker database is not initialized outside Flyway before server startup;
|
||||
- label `sql/postgresql/education/` and `sql/mysql/education/` as non-operational history or move them to an explicitly archival location without rewriting history;
|
||||
- replace the EDU-016 temporary PostgreSQL schema bridge with Flyway-driven setup after equivalence is proven;
|
||||
- state per environment whether Flyway was actually run, validated, or only packaged/compiled.
|
||||
|
||||
## Required EDU-006 verification
|
||||
|
||||
Static and build gates:
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
mvn -pl yudao-module-education -am -DskipTests clean package
|
||||
find yudao-module-education/target/classes/db/migration/education -type f -print
|
||||
mvn -pl yudao-server -am -DskipTests clean compile
|
||||
```
|
||||
|
||||
Real disposable PostgreSQL gate:
|
||||
|
||||
1. initialize a disposable platform database or approved baseline fixture;
|
||||
2. run Flyway migrate using the server's exact migration locations and PostgreSQL driver;
|
||||
3. run Flyway validate;
|
||||
4. inspect `flyway_schema_history` with version, script, checksum, and success;
|
||||
5. inspect all approved tables, columns, constraints, indexes, and backfill counts;
|
||||
6. run the EDU-016 PostgreSQL persistence suite against the migrated schema;
|
||||
7. test at least the empty/platform-only path and one representative manually bootstrapped adoption path.
|
||||
|
||||
Only these real successful executions may be reported as migration success.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] No published or potentially distributed migration is authorized for editing.
|
||||
- [x] Every required table/index/constraint/seed has one intended Flyway owner and version range.
|
||||
- [x] Manual SQL is not silently treated as executed history.
|
||||
- [x] The plan includes migration packaging and real PostgreSQL execution evidence requirements.
|
||||
- [x] Documentation correction scope is explicit.
|
||||
|
||||
## Verification performed by EDU-005
|
||||
|
||||
- Read project Flyway rules, local/dev Flyway configuration, server dependencies, all active migration locations, manual PostgreSQL/MySQL artifacts, core-loop DOs/Mappers, PostgreSQL test bridge, Docker initialization, Git history, and dirty-tree state.
|
||||
- Inspected the reachable disposable `postgresdb` container. Target identity was database `postgres`, user `postgres`, schema `public`; it contained no `flyway_schema_history` relation and no Education tables. Container startup logs state that `/docker-entrypoint-initdb.d/*` was ignored because the volume was already initialized.
|
||||
- Confirmed V4010/V4020 are currently packaged under `target/classes/db/migration/education/` from a prior build.
|
||||
- Did not modify migration SQL, application code, server configuration, Docker configuration, or any database object.
|
||||
- Did not run Flyway migrate/validate and does not claim a successful database migration.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** High. Existing manually initialized databases may have partially overlapping or divergent table shapes, and a false baseline can hide incompatibility.
|
||||
- **Rollback:** No rollback is required for this decision-only ticket. EDU-006 uses forward migrations, preserves legacy idempotency tables during first adoption, and supports application rollback without `flyway clean` or destructive down scripts.
|
||||
@@ -0,0 +1,83 @@
|
||||
# EDU-006 — Deliver Practice schema through module-owned Flyway
|
||||
|
||||
- **Status:** done — V4030 delivered and verified on an isolated disposable PostgreSQL test database
|
||||
- **Type:** database implementation
|
||||
- **Phase:** 2 prerequisite
|
||||
- **Blockers:** EDU-005
|
||||
|
||||
## Implementation result
|
||||
|
||||
`V4030__create_and_adopt_practice_schema.sql` now owns the final Practice session/question, report/detail, wrong-question/idempotency, favorite, and unified idempotency schema. Fresh databases do not create legacy answer/submit idempotency tables. Compatible manually initialized Practice tables receive missing final columns and JSONB alignment; legacy answer/submit idempotency rows are backfilled into `education_idempotency` while the source tables remain untouched. A conflicting request hash fails the migration transactionally.
|
||||
|
||||
The EDU-016 test seam now runs the real Flyway chain in a random disposable PostgreSQL schema. Its temporary `create_tables.sql` bridge was removed. The established 140 persistence tests and five migration-contract scenarios pass together.
|
||||
|
||||
Manual Education SQL mounts were removed from Docker Compose, and the active Education README and Pilot runbook now describe PostgreSQL/Flyway forward-only delivery. V4010/V4020 remained byte-for-byte unchanged. V4040 was not created because the capability menu still lacks a complete admin UI/product decision; no empty placeholder version was introduced.
|
||||
|
||||
No shared or production database was migrated. Successful migration evidence applies only to the isolated no-volume PostgreSQL container and random schemas used by the tests.
|
||||
|
||||
## Scope
|
||||
|
||||
Implement only the objects approved in EDU-005, potentially covering:
|
||||
|
||||
- practice sessions and question snapshots;
|
||||
- answer and submit idempotency;
|
||||
- reports and report details;
|
||||
- wrong questions and favorites;
|
||||
- tenant-scoped unique constraints;
|
||||
- indexes required by verified query paths;
|
||||
- necessary menu/permission seed data;
|
||||
- compatible backfills for existing development data.
|
||||
|
||||
Exact objects and versions are determined by EDU-005 and `flyway-postgresql`.
|
||||
|
||||
## Architecture rules
|
||||
|
||||
- Use PostgreSQL dialect only.
|
||||
- Tenant-scoped uniqueness includes `tenant_id` where required.
|
||||
- Database uniqueness is the final idempotency guard.
|
||||
- Use `ON CONFLICT` where approved by the design.
|
||||
- Do not copy Supabase auth/RLS as the application isolation model.
|
||||
- Do not add destructive rollback migrations.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] New migrations use versions allocated by `flyway-postgresql`.
|
||||
- [x] V4010/V4020 remain unchanged as potentially distributed history.
|
||||
- [x] Migrations are packaged under `target/classes/db/migration/education/`.
|
||||
- [x] Annotated SQL and Mapper behavior match PostgreSQL constraints.
|
||||
- [x] Focused repository/integration tests cover uniqueness and tenant scope.
|
||||
- [x] Real PostgreSQL Flyway migrate/validate succeeds in an isolated disposable test environment.
|
||||
- [x] Operational docs no longer instruct users to apply MySQL or manual Education SQL for these objects.
|
||||
|
||||
## Verification
|
||||
|
||||
At minimum:
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
mvn -pl yudao-module-education -am -DskipTests clean package
|
||||
find yudao-module-education/target/classes/db/migration/education -type f
|
||||
mvn -pl yudao-server -am -DskipTests clean compile
|
||||
```
|
||||
|
||||
Also run the PostgreSQL commands prescribed by `flyway-postgresql` when an authorized database is available.
|
||||
|
||||
## Verification performed
|
||||
|
||||
Against an isolated PostgreSQL container bound only to `127.0.0.1`, the focused Flyway suite exercised fresh migration, baseline-4009 adoption, compatible session/question plus legacy-idempotency adoption, conflict with existing unified history, and conflicting duplicate legacy keys. Together with the existing persistence suite: 145 tests passed, 0 failures, 0 errors, 0 skipped. Every test schema was dropped and the no-volume container was stopped.
|
||||
|
||||
Also completed:
|
||||
|
||||
```text
|
||||
mvn -pl yudao-module-education -am -DskipTests clean package — BUILD SUCCESS
|
||||
V4010, V4020, V4030 present under target/classes/db/migration/education/
|
||||
mvn -pl yudao-server -am -DskipTests clean compile — BUILD SUCCESS
|
||||
git diff --check — passed
|
||||
```
|
||||
|
||||
V4010 and V4020 retained their pre-ticket SHA-256 values. No shared or production PostgreSQL database was changed.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** High data compatibility and deployment-order risk.
|
||||
- **Rollback:** Forward correction migration plus application rollback. Never use `clean` or destructive rollback in shared environments.
|
||||
@@ -0,0 +1,57 @@
|
||||
# EDU-007 — Verify tenant-scoped practice creation and restoration
|
||||
|
||||
- **Status:** done — bounded create/restore contract verified against V4030 PostgreSQL
|
||||
- **Type:** implementation/verification
|
||||
- **Phase:** 2
|
||||
- **Blockers:** EDU-004, EDU-006
|
||||
|
||||
## Implementation result
|
||||
|
||||
The existing create/restore aggregate was retained and re-verified rather than rebuilt. Practice endpoints now require a `UserTypeEnum.MEMBER` Student Principal and continue deriving user/tenant only from the authenticated principal. Concurrent creation now uses the existing PostgreSQL `ON CONFLICT DO NOTHING` mapper seam, avoiding a query inside an aborted duplicate-key transaction. Real PostgreSQL tests prove identical concurrent requests return one session and conflicting fingerprints produce one winner plus one idempotency mismatch.
|
||||
|
||||
Focused tests also prove provider failure leaves no partial state and restore uses the persisted Question Snapshot after source content changes. The service create/restore suite now runs on the V4030 Flyway-owned PostgreSQL schema.
|
||||
|
||||
PUBLIC catalog read predicates and provider-neutral safety remain unchanged. Cross-scope PUBLIC graph-integrity semantics remain an explicit later architecture decision; EDU-007 does not claim or invent those constraints. Legacy entitlement/quota, timed practice, rich blueprint/random/review modes, and discovery of multiple active sessions are outside this bounded ticket.
|
||||
|
||||
## Scope
|
||||
|
||||
- Verify or complete session creation idempotency.
|
||||
- Verify session ownership and tenant isolation.
|
||||
- Restore immutable safe question snapshots.
|
||||
- Preserve provider-neutral question safety from EDU-001.
|
||||
- Verify PUBLIC catalog reads continue using the existing explicit scope predicates; tenant-consistent PUBLIC graph constraints remain blocked on the graph decision.
|
||||
- Remove no existing core-loop behavior unless a regression proves it invalid.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] Current user and tenant derive from a Member security principal at the controller boundary.
|
||||
- [x] Duplicate client session ID with identical fingerprint returns the existing session.
|
||||
- [x] Conflicting fingerprint, user, or tenant does not expose the existing session.
|
||||
- [x] Underfilled, invisible, malformed, disabled, and unavailable content fails closed.
|
||||
- [x] Restored content remains stable after source content changes.
|
||||
- [x] Cross-tenant and wrong-user access is denied.
|
||||
- [x] Provider-neutral question safety and existing PUBLIC read predicates are preserved; graph-integrity enforcement remains blocked on the recorded architecture decision.
|
||||
- [x] PostgreSQL uniqueness and transaction behavior are tested against the EDU-006 schema.
|
||||
|
||||
## Verification
|
||||
|
||||
Focused create/restore service, Mapper, controller, and PostgreSQL tests; then required diff/compile gates.
|
||||
|
||||
## Verification performed
|
||||
|
||||
Against an isolated PostgreSQL database using the V4010/V4020/V4030 Flyway chain:
|
||||
|
||||
```text
|
||||
PracticeSessionControllerHttpTest: 15 passed
|
||||
PracticeSessionServiceImplTest: 25 passed
|
||||
PracticeSessionServicePostgreSqlIntegrationTest: 2 passed
|
||||
Total: 42 passed
|
||||
Failures/errors/skipped: 0
|
||||
```
|
||||
|
||||
The PostgreSQL concurrency tests use bounded latches and prove both identical and conflicting fingerprint races. No database migration was added or changed by EDU-007.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** Medium session-ownership and compatibility risk.
|
||||
- **Rollback:** Application rollback; retain forward-compatible schema.
|
||||
@@ -0,0 +1,53 @@
|
||||
# EDU-008 — Verify idempotent answer saving
|
||||
|
||||
- **Status:** done — answer idempotency and rollback contract verified on PostgreSQL
|
||||
- **Type:** implementation/verification
|
||||
- **Phase:** 2
|
||||
- **Blockers:** EDU-007
|
||||
|
||||
## Implementation result
|
||||
|
||||
The existing unified PostgreSQL idempotency claim remains the final guard. Matching keys replay only a complete, valid stored response; null, blank, malformed, or structurally incomplete replay data now fails closed without re-executing the answer mutation. Completion of a newly claimed response must update exactly one idempotency row before session/question state changes.
|
||||
|
||||
Answer saving remains limited to Option-backed Questions. Optionless/free-text behavior is explicitly rejected until a separate subjective-answer contract is designed. The answer HTTP seam now uses the EDU-007 Member principal and TenantContextHolder boundary and rejects ADMIN principals.
|
||||
|
||||
PostgreSQL tests prove same-key/same-payload replay, same-key/different-payload conflict including concurrent requests, different-key CAS serialization, stale version/sequence rejection, user/tenant/state/question ownership checks, full rollback of answer/session/claim state, refresh recovery, and no answer-key/explanation leakage. No migration was added or executed by EDU-008.
|
||||
|
||||
## Scope
|
||||
|
||||
- Same key and same canonical payload replays the original result.
|
||||
- Same key and different payload returns a conflict.
|
||||
- Database uniqueness is the final idempotency guard.
|
||||
- Session version and client sequence prevent stale updates.
|
||||
- Selected options are validated against the safe snapshot contract.
|
||||
- Optionless answer behavior remains blocked until explicitly designed; do not pretend it is option-backed.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] Same-payload duplicate semantics are explicit and tested.
|
||||
- [x] Conflicting payload is rejected.
|
||||
- [x] Concurrent same-key and different-key behavior is tested on PostgreSQL.
|
||||
- [x] Stale session version and stale per-session command sequence are rejected.
|
||||
- [x] Wrong user, tenant, session state, or question membership is rejected.
|
||||
- [x] Responses and restored sessions contain no answer key or explanation.
|
||||
- [x] Failure does not partially update answer, sequence, session version, or the idempotency claim.
|
||||
|
||||
## Verification
|
||||
|
||||
Focused answer service/controller/Mapper tests, PostgreSQL concurrency tests, EDU-001 regressions, and required diff/compile gates.
|
||||
|
||||
## Verification performed
|
||||
|
||||
```text
|
||||
PracticeAnswerControllerHttpTest: 16 passed
|
||||
PracticeAnswerServiceImplTest: 37 passed on PostgreSQL/V4030
|
||||
Total focused: 53 passed
|
||||
Failures/errors/skipped: 0
|
||||
```
|
||||
|
||||
The service suite includes concurrent same-key identical and conflicting payloads plus exact different-key winner/loser assertions. Incomplete replay rows fail closed, and rollback assertions cover session version, session sequence, question state, and claim removal.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** Medium-to-high concurrency and offline-retry risk.
|
||||
- **Rollback:** Application rollback with schema retained; forward migration for any constraint correction.
|
||||
@@ -0,0 +1,69 @@
|
||||
# EDU-009 — Atomic submit, immutable report, wrong questions, and favorites
|
||||
|
||||
- **Status:** done
|
||||
- **Type:** implementation
|
||||
- **Phase:** 2
|
||||
- **Blockers:** EDU-008 (done)
|
||||
|
||||
## Student outcome
|
||||
|
||||
A student can retry submission safely, receive exactly one immutable report, and see consistent wrong-question and favorite projections.
|
||||
|
||||
## Core defect to resolve
|
||||
|
||||
The current submit path has evidence of check-then-insert idempotency. This ticket must define and implement an atomic initial claim with crash recovery before treating submission as complete.
|
||||
|
||||
## Scope
|
||||
|
||||
- Atomic submit-key reservation using PostgreSQL uniqueness/`ON CONFLICT` or the approved project mechanism.
|
||||
- Explicit processing/completed/failed or equivalent recovery semantics.
|
||||
- Same-key replay and conflicting-payload behavior.
|
||||
- Single state transition from active session to submitted.
|
||||
- Immutable scoring/report snapshot.
|
||||
- Duplicate-safe wrong-question projection.
|
||||
- Favorite behavior remains independent and tenant/user scoped.
|
||||
- No external calls inside a long database transaction.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] Concurrent same-key same-payload requests converge on one report.
|
||||
- [x] Same key with different payload is rejected.
|
||||
- [x] Different keys racing on one session produce at most one committed submit.
|
||||
- [x] A crash after claim has a documented retry/recovery result.
|
||||
- [x] Report content remains stable after question mutation.
|
||||
- [x] Wrong-question projection is idempotent.
|
||||
- [x] Report and history access enforce user and tenant ownership.
|
||||
- [x] Pre-submit responses never expose protected answer data; post-submit response follows the approved report contract.
|
||||
|
||||
## Recovery contract
|
||||
|
||||
- The submit key is serialized with a PostgreSQL transaction-level advisory lock, then reserved with
|
||||
`INSERT ... ON CONFLICT DO NOTHING` before session/report writes.
|
||||
- The claim uses `PROCESSING` with a unique token and lease timestamp; report, report detail,
|
||||
wrong-question projection, session CAS, and token-checked claim completion run in the same transaction.
|
||||
- A normal processing failure rolls the full transaction back, including a newly inserted claim. If a previously
|
||||
committed/manual `PROCESSING` claim exists (for example after legacy partial persistence), a retry can take over
|
||||
the matching claim after the 120-second lease expires. A mismatched payload can never take over the claim.
|
||||
- A completed claim stores `report_id` and the complete immutable response as `COMPLETED`; same-key retries
|
||||
replay only a structurally complete matching response. Malformed or incomplete committed rows fail closed.
|
||||
- A different key that loses the session race is completed against the immutable winner report, so retries of
|
||||
either accepted key remain stable.
|
||||
|
||||
No external provider call occurs in the submit transaction: scoring uses the persisted session question snapshot.
|
||||
V4040 adds the claim token/timestamp columns and normalizes migrated successful `SUBMIT_SESSION` rows from
|
||||
legacy `ACCEPTED` to `COMPLETED` without changing V4030 release history.
|
||||
|
||||
## Verification
|
||||
|
||||
PostgreSQL concurrency tests are mandatory, along with service/controller/projection tests and required diff/compile gates.
|
||||
|
||||
Focused PostgreSQL verification on 2026-07-30:
|
||||
|
||||
- `PracticeSubmitServiceImplTest`: 38 passed.
|
||||
- Submit/report controller, projection, wrong-question, and favorite suites: 129 passed total.
|
||||
- Failures, errors, skipped: 0.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** High state-machine and data-consistency risk.
|
||||
- **Rollback:** Disable practice writes or roll back application version; repair through forward migration only.
|
||||
@@ -0,0 +1,33 @@
|
||||
# EDU-010 — Tenant content publication and graph integrity
|
||||
|
||||
- **Status:** blocked
|
||||
- **Type:** implementation program
|
||||
- **Phase:** 3
|
||||
- **Blockers:** EDU-004, EDU-009, provider-authority decision, PUBLIC graph-semantics decision
|
||||
|
||||
## Tenant-admin outcome
|
||||
|
||||
Authorized tenant administrators can author, classify, publish, archive, and retire education content without creating cross-tenant or invalid PUBLIC/tenant relationships, and student reads remain consistent with publication state.
|
||||
|
||||
## Scope
|
||||
|
||||
- Question banks, questions, versions, classifications, catalogs, collections, blueprints, and bindings.
|
||||
- Draft/published/archived lifecycle.
|
||||
- System RBAC/DataPermission enforcement.
|
||||
- Tenant-consistent graph constraints or equivalent transactional enforcement.
|
||||
- Provider consistency between authoring source and student reads.
|
||||
- Safe projections preserved from EDU-001.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Admin permission and data-scope matrix is explicit.
|
||||
- [ ] Cross-tenant graph relationships cannot be persisted.
|
||||
- [ ] PUBLIC and tenant-owned reference rules are enforced and tested.
|
||||
- [ ] Unpublished/archived content is never student-visible.
|
||||
- [ ] Publication is transactional and auditable.
|
||||
- [ ] Database changes use `flyway-postgresql` and forward migrations.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** High content-integrity and authorization risk.
|
||||
- **Rollback:** Disable authoring/publishing and roll back application; preserve data and correct forward.
|
||||
@@ -0,0 +1,32 @@
|
||||
# EDU-011 — Content import, export, assets, and scanning
|
||||
|
||||
- **Status:** blocked
|
||||
- **Type:** implementation program
|
||||
- **Phase:** 3 / 6
|
||||
- **Blockers:** EDU-010, Infra File contract, scanner ownership decision, durable job claim decision
|
||||
|
||||
## Tenant-admin outcome
|
||||
|
||||
Administrators can import and export education content through durable, duplicate-safe jobs, with secure files, malware scanning, tenant propagation, audit, retries, and partial-failure reporting.
|
||||
|
||||
## Reuse
|
||||
|
||||
- Education owns import/export business state and content validation.
|
||||
- Infra owns File, Job/MQ, locks, logging, and audit primitives.
|
||||
- Scanner integration sits behind a clear adapter; Education does not implement generic storage or scheduling.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Preview and execute are distinct states.
|
||||
- [ ] Jobs use atomic claim/lease/heartbeat/recovery semantics.
|
||||
- [ ] At-least-once retries are duplicate-safe.
|
||||
- [ ] File type, size, object key, access, and retention are enforced.
|
||||
- [ ] Scanning fails closed.
|
||||
- [ ] Tenant context propagates into asynchronous handlers.
|
||||
- [ ] Exports redact answers and private fields according to authorization.
|
||||
- [ ] Partial failures and dead letters are visible and auditable.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** High operational and file-security risk.
|
||||
- **Rollback:** Disable job handlers and preserve job/business state for forward recovery.
|
||||
@@ -0,0 +1,33 @@
|
||||
# EDU-012 — Classes and education relationships
|
||||
|
||||
- **Status:** blocked
|
||||
- **Type:** implementation program
|
||||
- **Phase:** 4
|
||||
- **Blockers:** EDU-004, education relationship model decision, Member relationship contract, System data-scope policy
|
||||
|
||||
## Tenant-admin outcome
|
||||
|
||||
Tenant administrators manage classes, student education relationships, invitations, and supervision within explicit tenant and row-level scopes while Member/System remain the owners of generic users and roles.
|
||||
|
||||
## Scope
|
||||
|
||||
- Education class entity and membership relationships.
|
||||
- Student/teacher/class domain roles without duplicating System RBAC.
|
||||
- Invitations and duplicate-safe acceptance.
|
||||
- Education profile extensions.
|
||||
- Supervision relationships and data scopes if retained.
|
||||
- Audit and operation logging.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Generic account, password, token, tenant, and role tables are not duplicated.
|
||||
- [ ] Student/teacher/class permission matrix is documented and tested.
|
||||
- [ ] Cross-class and cross-tenant access is denied.
|
||||
- [ ] Invitation acceptance is idempotent and auditable.
|
||||
- [ ] Platform-admin tenant-ignore operations are explicit and permission guarded.
|
||||
- [ ] Database changes use `flyway-postgresql`.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** High authorization and relationship-integrity risk.
|
||||
- **Rollback:** Disable management endpoints and correct relationships through audited forward operations.
|
||||
33
docs/education/migration/issues/EDU-013-commercialization.md
Normal file
33
docs/education/migration/issues/EDU-013-commercialization.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# EDU-013 — Education commercialization binding
|
||||
|
||||
- **Status:** blocked
|
||||
- **Type:** decision and implementation program
|
||||
- **Phase:** 5
|
||||
- **Blockers:** product/entitlement model, Mall/Pay API assessment, Member entitlement decision, EDU-004
|
||||
|
||||
## Outcome
|
||||
|
||||
Education products and access rights are connected to Mall, Pay, Member, and CRM without creating a parallel product, order, payment, refund, membership, or financial ledger in Education.
|
||||
|
||||
## Education ownership
|
||||
|
||||
Education may own only domain bindings and fulfillment orchestration, such as:
|
||||
|
||||
- education product to course/exam/content binding;
|
||||
- entitlement scope and education-resource association;
|
||||
- duplicate-safe fulfillment event state where no platform facility exists.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Mall/Pay/Member/CRM public contracts are mapped before implementation.
|
||||
- [ ] Payment callbacks and refunds remain in Pay.
|
||||
- [ ] Generic products/orders remain in Mall where applicable.
|
||||
- [ ] Entitlement issuance, revocation, expiry, and refund effects are explicit and idempotent.
|
||||
- [ ] Paid/private practice remains inaccessible until entitlement checks are complete.
|
||||
- [ ] Reconciliation and commission/referral ownership is explicit.
|
||||
- [ ] Financial and authorization tests cover duplicate callbacks and cross-tenant access.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** Very high financial and access-control risk.
|
||||
- **Rollback:** Disable fulfillment handlers and paid access; preserve financial ledgers in their owning modules.
|
||||
37
docs/education/migration/issues/EDU-014-extended-learning.md
Normal file
37
docs/education/migration/issues/EDU-014-extended-learning.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# EDU-014 — Extended student and secondary learning waves
|
||||
|
||||
- **Status:** blocked
|
||||
- **Type:** decision map followed by implementation tickets
|
||||
- **Phase:** 5
|
||||
- **Blockers:** explicit product scope, EDU-004, entitlement model, AI/File/Member/System/Infra contract assessment
|
||||
|
||||
## Outcome
|
||||
|
||||
Every legacy Auth/Profile/extended Learning, scoreline, vocabulary, video, AI, notification, badge, feedback, and exam-date capability receives a traceable conclusion: replaced, migrated, retired, deferred, or product decision required.
|
||||
|
||||
## Required decomposition
|
||||
|
||||
Do not implement this as one large ticket. Create one child ticket per selected capability family after ownership is decided. At minimum assess:
|
||||
|
||||
- Auth compatibility and phone/OAuth binding;
|
||||
- profile and education profile extensions;
|
||||
- vocabulary learning/review;
|
||||
- scoreline and admissions content;
|
||||
- video entitlement and progress;
|
||||
- recommendation and AI generation;
|
||||
- notifications and reminders;
|
||||
- points, badges, check-ins, feedback, exam countdowns;
|
||||
- learning analytics, leaderboard, trends, and reports.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Each family has target owner, reusable public capability, data disposition, API conclusion, priority, and tests.
|
||||
- [ ] Member/System/Infra/AI capabilities are reused rather than copied.
|
||||
- [ ] Sensitive reports and exports are redacted.
|
||||
- [ ] Media and AI access follows entitlement and tenant rules.
|
||||
- [ ] Retired capabilities have compatibility and data-retention conclusions.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** Medium-to-high scope and entitlement risk.
|
||||
- **Rollback:** Per child ticket; this parent is a planning gate.
|
||||
@@ -0,0 +1,42 @@
|
||||
# EDU-015 — Operational independence and legacy exit
|
||||
|
||||
- **Status:** blocked
|
||||
- **Type:** integration and deployment program
|
||||
- **Phase:** 6
|
||||
- **Blockers:** EDU-011, EDU-013, EDU-014 child decisions, all temporary-adapter owners and exit plans
|
||||
|
||||
## Outcome
|
||||
|
||||
The target backend runs its selected education capabilities without depending on the old NestJS API, worker, Supabase auth/storage, or asset-scanner deployment, except for explicitly time-bounded adapters with owners and exit dates.
|
||||
|
||||
## Scope
|
||||
|
||||
- Replace selected Worker jobs with Infra Job/MQ and owning-domain handlers.
|
||||
- Complete retries, dead letters, audit, notifications, and observability.
|
||||
- Complete file/scanner deployment or approved alternative.
|
||||
- Remove or disable temporary Scalar/legacy adapters according to provider strategy.
|
||||
- Reconcile migrated data and operational runbooks.
|
||||
- Prove deployment, startup, Flyway, and core user flows.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Every temporary legacy dependency has an owner, telemetry, failure policy, and exit date.
|
||||
- [ ] At-least-once consumers are duplicate-safe.
|
||||
- [ ] Job retries/dead letters and scanner health are observable.
|
||||
- [ ] PostgreSQL migrations are actually executed and validated in an authorized environment.
|
||||
- [ ] Student and selected admin E2E flows pass against the target only.
|
||||
- [ ] Runbooks contain no active MySQL/manual-SQL or obsolete NestJS startup requirement.
|
||||
- [ ] Rollback and incident procedures are documented.
|
||||
|
||||
## Verification
|
||||
|
||||
- Application startup and health.
|
||||
- Flyway history and migration execution.
|
||||
- Worker/job deployment smoke tests.
|
||||
- Playwright student harness and selected admin flows.
|
||||
- Logs, metrics, traces, retry/dead-letter, and scanner health checks.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** High deployment and production reliability risk.
|
||||
- **Rollback:** Per capability using application/configuration rollback while preserving forward database history.
|
||||
@@ -0,0 +1,64 @@
|
||||
# EDU-016 — Move PostgreSQL-specific Education persistence tests off H2
|
||||
|
||||
- **Status:** done — focused PostgreSQL persistence suite passes against the reachable `postgresdb` seam
|
||||
- **Type:** test infrastructure / PostgreSQL integration
|
||||
- **Phase:** 0 / database prerequisite
|
||||
- **Blockers:** EDU-002, EDU-005 for final schema ownership
|
||||
|
||||
## Confirmed defect
|
||||
|
||||
Education unit tests use H2 with `MODE=MYSQL`, while production Mapper SQL intentionally uses PostgreSQL `ON CONFLICT`. H2 rejects the annotated SQL for favorites, wrong-question idempotency, unified answer/submit idempotency, and review-session conflict handling. Switching H2 to PostgreSQL mode does not solve this: H2 still rejects `ON CONFLICT` and also exposes Boolean/integer compatibility differences.
|
||||
|
||||
This prevents the full Practice/Wrong/Favorite regression suite from exercising production persistence semantics.
|
||||
|
||||
## Outcome
|
||||
|
||||
PostgreSQL-specific persistence behavior runs against real ephemeral PostgreSQL in the test suite, while fast database-independent tests may remain on H2 where their SQL is portable.
|
||||
|
||||
## Scope
|
||||
|
||||
- Select the repository-standard PostgreSQL integration-test mechanism, preferably Testcontainers or an existing project fixture.
|
||||
- Move tests that execute `ON CONFLICT`, PostgreSQL JSON/JSONB, identity, Boolean, or concurrency semantics onto PostgreSQL.
|
||||
- Keep controller and pure domain tests database-independent.
|
||||
- Align test schema with module-owned Flyway after EDU-005/EDU-006; avoid maintaining a divergent hand-written full schema long term.
|
||||
- Cover at least:
|
||||
- `IdempotencyStoreMapper.insertIgnore`;
|
||||
- wrong-question idempotency insert;
|
||||
- favorite upsert;
|
||||
- review-session insert-ignore;
|
||||
- concurrent answer and submit claims.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] No test relies on H2 to validate PostgreSQL `ON CONFLICT` semantics.
|
||||
- [x] PostgreSQL tests execute the same Mapper SQL as production.
|
||||
- [x] Test database is isolated and disposable.
|
||||
- [x] Schema setup uses Flyway or an explicitly temporary bridge with an exit ticket.
|
||||
- [x] Concurrency tests are stable and prove unique-constraint behavior.
|
||||
- [x] CI prerequisites and local commands are documented.
|
||||
|
||||
## Verification
|
||||
|
||||
Completed against the existing reachable `postgresdb` service with credentials supplied only through `EDU_TEST_POSTGRES_*` environment variables:
|
||||
|
||||
```bash
|
||||
mvn -pl yudao-module-education \
|
||||
-Dtest=PracticeSessionMapperTest,FavoriteServiceImplTest,PracticeAnswerServiceImplTest,PracticeSubmitServiceImplTest,PracticeSubmitProjectionIntegrationTest,WrongQuestionServiceImplTest test
|
||||
```
|
||||
|
||||
Result: 140 tests passed, 0 failures, 0 errors. The same 140-test suite also passed with JUnit class parallelism explicitly enabled, proving the shared schema resource lock prevents class-level collisions. The suite executes production Mapper SQL on PostgreSQL, including `ON CONFLICT` and JSONB behavior. A JUnit resource lock serializes PostgreSQL test classes that share one random JVM-scoped schema; each class closes its Spring context before the inherited lifecycle drops and recreates that schema, preventing cached contexts from reusing a dropped schema.
|
||||
|
||||
The temporary schema bridge was removed by EDU-006. PostgreSQL persistence tests now create their disposable random schema through the module-owned Flyway chain (`V4010`, `V4020`, `V4030`) and retain only `clean.sql` for per-test data isolation.
|
||||
|
||||
Local/CI prerequisites:
|
||||
|
||||
- a reachable disposable PostgreSQL database;
|
||||
- PostgreSQL JDBC connectivity from the Maven process;
|
||||
- non-blank `EDU_TEST_POSTGRES_HOST`, `EDU_TEST_POSTGRES_PORT`, `EDU_TEST_POSTGRES_DB`, `EDU_TEST_POSTGRES_USER`, and `EDU_TEST_POSTGRES_PASSWORD` values.
|
||||
|
||||
No Testcontainers or other external dependency was added. No PostgreSQL Flyway migration was executed or authorized by this ticket.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** Medium CI/runtime cost; high value for persistence confidence.
|
||||
- **Rollback:** Keep prior fast tests temporarily, but do not restore false H2 coverage claims for PostgreSQL SQL.
|
||||
59
docs/education/migration/issues/README.md
Normal file
59
docs/education/migration/issues/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Education Migration Tickets
|
||||
|
||||
This directory turns [`GOAL.md`](../GOAL.md) into executable, blocker-aware vertical slices.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Work only tickets whose blockers are complete.
|
||||
2. Start each implementation ticket in a fresh context after reading `GOAL.md`, the ticket, relevant decisions, and current Git status.
|
||||
3. Preserve the dirty working tree. Implementation is serial unless an isolated worktree and integration plan are explicit.
|
||||
4. Apply TDD at the ticket's declared seams.
|
||||
5. Use `flyway-postgresql` for every schema, index, constraint, seed, baseline, backfill, or Flyway configuration change.
|
||||
6. Close with focused tests, `git diff --check`, and `mvn -pl yudao-server -am -DskipTests clean compile`.
|
||||
7. Report exact commands and results. Never report PostgreSQL migration success without an actual successful PostgreSQL run.
|
||||
|
||||
## Status vocabulary
|
||||
|
||||
- `done`: implemented and verified to the ticket's current acceptance criteria.
|
||||
- `in-progress`: currently being implemented.
|
||||
- `ready`: all blockers complete and no unresolved decision prevents work.
|
||||
- `blocked`: depends on another ticket or product decision.
|
||||
- `decision`: produces a recorded decision rather than production behavior.
|
||||
|
||||
## Ticket graph
|
||||
|
||||
```text
|
||||
EDU-000 Phase 0 artifacts done
|
||||
├── EDU-001 Safe question content done, follow-up coverage remains
|
||||
├── EDU-002 Practice regression baseline done
|
||||
│ ├── EDU-016 PostgreSQL persistence tests done; temporary bridge blocked on EDU-005/EDU-006
|
||||
│ └── EDU-006 Practice schema Flyway done
|
||||
│ ├── EDU-007 Create/restore practice done
|
||||
│ ├── EDU-008 Idempotent answer save done
|
||||
│ └── EDU-009 Atomic submit/report done
|
||||
├── EDU-003 Tenant resolution decision done
|
||||
│ └── EDU-004 Tenant/identity security done; ingress/IP-only probing throttle remains operational blocker
|
||||
└── EDU-005 PostgreSQL/Flyway takeover decision done
|
||||
|
||||
EDU-009 + provider/content decisions
|
||||
└── EDU-010 Tenant content publication blocked
|
||||
└── EDU-011 Import/export/assets/scanning blocked
|
||||
|
||||
EDU-004
|
||||
└── EDU-012 Classes and education relationships blocked
|
||||
|
||||
Commerce ownership decisions
|
||||
└── EDU-013 Education commercialization blocked
|
||||
|
||||
All owner/contract decisions
|
||||
└── EDU-014 Extended learning waves blocked
|
||||
└── EDU-015 Operational independence blocked
|
||||
```
|
||||
|
||||
## Recommended execution order
|
||||
|
||||
1. Select the next unblocked content-management decision/ticket after EDU-009.
|
||||
|
||||
## Phase 0 completion caveat
|
||||
|
||||
Phase 0 artifacts exist, but several architecture and product decisions remain open. `EDU-000` is considered complete as an inventory deliverable, not as resolution of every decision it discovered.
|
||||
Reference in New Issue
Block a user