diff --git a/docs/education/migration/00-current-state.md b/docs/education/migration/00-current-state.md index fc006a66..60365e1d 100644 --- a/docs/education/migration/00-current-state.md +++ b/docs/education/migration/00-current-state.md @@ -1,12 +1,22 @@ # Current State -> Phase 0 static assessment generated on 2026-07-29. No build, test, application startup, PostgreSQL connection, or Flyway migration was executed during this assessment. +> Phase 0 static assessment originally generated on 2026-07-29. Updated on 2026-07-31 after bounded implementation through V4180, full Education tests against disposable PostgreSQL, server compilation, and the student harness. -## Executive summary +## Current implementation summary -Phase 0 remains a read-only architecture assessment, not an implementation claim. Verified evidence shows the target is on feature/education-core-loop with a heavily dirty worktree, while the source checkout has no local or remote feature/education-core-loop ref and is main at 033701a. The target contains a meaningful committed core-loop slice (ce02f8a) plus substantial dirty provider/catalog/session work, so all roadmap status must distinguish committed, dirty, absent, and runtime-unverified behavior. EDU-003 has now decided public tenant resolution: Origin/Referer are forgeable browser-context claims rather than trusted identity; headless lookup uses the constrained System-name Public Tenant Handle; successful lookup accepts tenant-existence disclosure while unknown/disabled/expired failures are identical; exact wire errors, canonical host-only websites, a secure-default local flag, and abuse controls are assigned to EDU-004. This is distinct from the already verified authenticated tenant mismatch rejection in TenantSecurityWebFilter; /education/context still requires EDU-004 Member/UserType enforcement. The most reliable core-loop slice remains provider-neutral fail-closed question content handling across the active default Scalar path, conditional Java path, safe catalog projection, and persisted session restoration. Core-loop schema is not proven to be in active Flyway: V4010 is a placeholder, V4020 is catalog-only, and practice/report/idempotency DDL is untracked manual SQL. Native reads are an intentional explicit-scope bypass requiring mapper audit, while schema foreign keys do not enforce tenant-consistent graphs. Phase 0 coverage must also add first-class Auth/Profile/extended Learning, granular tenant-admin, and granular platform-admin groups. Paid access, provider authority, option schema, public graph semantics, and source baseline remain product/architecture decisions. +The target branch now contains the complete student core loop, tenant/identity enforcement, module-owned PostgreSQL Flyway migrations V4010–V4200, bounded tenant content authoring, import job and asset contracts, class/invitation relationships, education entitlement bindings, a representative extended-learning wave, and operational dependency/health contracts. The full Education suite passed against disposable PostgreSQL (596 tests), the server reactor compiled successfully, and the fixture-based student harness passed six browser flows. These tests executed the module-owned Flyway chain in isolated disposable test schemas; they are not evidence that a shared Pilot or production database was migrated. Production deployment, real Pilot data, production scanner/parser adapters, automatic Mall/Pay/CRM fulfillment events, and selected deferred learning families remain outside the verified implementation. -## Verified program decisions +This document retains the original Phase 0 findings below as provenance. Current ticket status is authoritative in [`issues/README.md`](issues/README.md), and rollout evidence is tracked by [`../pilot-acceptance-runbook.md`](../pilot-acceptance-runbook.md). + +## Original Phase 0 executive summary + +Verified evidence showed the target on `feature/education-core-loop` with a heavily dirty worktree, while the source checkout had no local or remote branch with that name and remained at `033701a`. The assessment identified a meaningful committed core-loop slice plus substantial provider/catalog/session work that still required classification. It selected provider-neutral fail-closed question handling, tenant/principal enforcement, atomic idempotency, PostgreSQL/Flyway takeover, and catalog graph integrity as the immediate blockers. Those bounded blockers have since been implemented and verified; this paragraph is retained only as historical context. + +## Original Phase 0 decisions and unknowns + +The remaining sections are the immutable investigation record from 2026-07-29. Items phrased as pending may now be resolved by later tickets and migrations; use the current summary and ticket index above for delivery status. + +### Decisions recorded during Phase 0 - Verified source provenance is limited: /Users/tiku1/code/tiku-backend has only main and origin/main at 033701a785c7012139e7f86995eea6041225592e; no local or remote feature/education-core-loop ref exists. Use main/033701a provisionally only, or obtain explicit approval for that baseline. - Verified target branch is feature/education-core-loop and its worktree is dirty. Current read-only inventory reports 65 modified tracked files and 97 untracked entries; preserve all, and do not rely on an older 21-untracked count. @@ -22,7 +32,7 @@ Phase 0 remains a read-only architecture assessment, not an implementation claim - Do not expose paid/private practice until entitlement semantics and public target contracts are decided. - No tests, builds, PostgreSQL connections, Flyway execution, or runtime verification were performed; all conclusions are static repository evidence unless explicitly marked otherwise. -## Unknowns +### Unknowns recorded during Phase 0 - EDU-003 decided the public resolver threat model and contract. Browser headers are forgeable context claims; a constrained Public Tenant Handle supports headless clients; success discloses tenant existence; unknown/disabled/expired failures are identical; exact errors, canonical websites, local activation, Member-only context, and abuse controls are assigned to EDU-004. - Whether a future System-owned immutable Tenant Code or authenticated/signed locator is required beyond the accepted public-handle contract. diff --git a/docs/education/migration/07-decisions.md b/docs/education/migration/07-decisions.md index a78fa50a..e96e523f 100644 --- a/docs/education/migration/07-decisions.md +++ b/docs/education/migration/07-decisions.md @@ -125,10 +125,11 @@ The durable artifact classification, adoption matrix, version allocation, backfi 16. Membership is an ordered replace-all command available only in DRAFT. Every member must be a current-tenant `TENANT_OWNED` PUBLISHED Question, and duplicate Question IDs are rejected rather than collapsed or upserted. 17. Activation exposes the collection route only when the collection and its existing Content Node are available. Archiving closes only collection-route discovery; it does not alter direct Question visibility or historical Practice Question Snapshots. 18. `access_rules` is descriptive reserved metadata in this slice, not entitlement enforcement. PUBLIC Question curation, dynamic filters, paid/private access, Category, Practice Blueprint, and Content Entry creation are excluded. +19. Protected lifecycle transition tokens rely on PostgreSQL object ownership separation: an explicit Flyway owner role creates V4080/V4100/V4110 token tables, while the runtime master datasource role must neither own nor write them. Local/dev configuration requires explicit `FLYWAY_USER`/`FLYWAY_PASSWORD`; startup fails closed when any protected table is missing, runtime-owned, inherited through role membership, or runtime-writable. Deployment automation provisions roles and grants outside Flyway because migrations cannot safely create shared login roles or know credential policy. -19. V4120 closes the bounded EDU-010 Category and Practice Blueprint scope. Category is a standalone Subject-scoped catalog aggregate, not Question classification. Blueprint authoring is limited to exactly one current-tenant NODE or active Manual COLLECTION target; target ownership, route, and counts are server-derived. -20. Category and Practice Blueprint use the same `DRAFT → ACTIVE → ARCHIVED`, `authoring_version` CAS, transactional append-only audit, current-tenant ownership, and `JAVA_READ` authority rules as Content Node and Collection. Student reads require ACTIVE lifecycle plus route availability. -21. The six added permissions are `education:category:{author,publish,archive}` and `education:practice-blueprint:{author,publish,archive}`. V4120 conditionally seeds permission rows at IDs 6809-6814, fails on conflicting IDs, and assigns no roles. +20. V4120 closes the bounded EDU-010 Category and Practice Blueprint scope. Category is a standalone Subject-scoped catalog aggregate, not Question classification. Blueprint authoring is limited to exactly one current-tenant NODE or active Manual COLLECTION target; target ownership, route, and counts are server-derived. +21. Category and Practice Blueprint use the same `DRAFT → ACTIVE → ARCHIVED`, `authoring_version` CAS, transactional append-only audit, current-tenant ownership, and `JAVA_READ` authority rules as Content Node and Collection. Student reads require ACTIVE lifecycle plus route availability. +22. The six added permissions are `education:category:{author,publish,archive}` and `education:practice-blueprint:{author,publish,archive}`. V4120 conditionally seeds permission rows at IDs 6809-6814, fails on conflicting IDs, and assigns no roles. **Permission and data-scope matrix:** diff --git a/docs/education/migration/GOAL.md b/docs/education/migration/GOAL.md index 6ae7866a..0d963324 100644 --- a/docs/education/migration/GOAL.md +++ b/docs/education/migration/GOAL.md @@ -409,15 +409,7 @@ The migration is complete only when: Phase 0 inventory and the first safe-question slice have been executed. Continue through the blocker-aware tickets under [`docs/education/migration/issues/`](issues/README.md). -Current execution order: - -1. `EDU-002` — restore the full Practice regression baseline; -2. `EDU-003` — decide tenant resolution and student-principal policy; -3. `EDU-004` — enforce the selected tenant/identity policy; -4. `EDU-005` — decide PostgreSQL/Flyway takeover; -5. `EDU-006` — deliver the approved Practice schema through module-owned Flyway; -6. `EDU-007` through `EDU-009` — verify and complete the student core loop; -7. later phases proceed only when their ticket blockers are complete. +Current execution order is maintained in [`docs/education/migration/issues/README.md`](issues/README.md). Do not duplicate the live order here; completed bounded tickets remain historical dependencies, while current work follows the ticket index and its blockers. Before every ticket: diff --git a/docs/education/migration/issues/EDU-002-practice-regression-baseline.md b/docs/education/migration/issues/EDU-002-practice-regression-baseline.md index c4451c18..fad8acf3 100644 --- a/docs/education/migration/issues/EDU-002-practice-regression-baseline.md +++ b/docs/education/migration/issues/EDU-002-practice-regression-baseline.md @@ -57,12 +57,12 @@ 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. +- [x] Every target class starts its Spring/JUnit context. +- [x] No target class fails because `ScoringService` is missing. +- [x] No target class fails from avoidable Mapper bean-name/type injection ambiguity. +- [x] EDU-001 safe-content assertions remain green. +- [x] Any actual behavior failure is documented with reproducible command and assigned a separate ticket. +- [x] No production behavior or database schema is changed unless a failing regression proves it is necessary and the ticket is explicitly amended. ## Test command diff --git a/docs/education/migration/issues/EDU-006-practice-schema-flyway.md b/docs/education/migration/issues/EDU-006-practice-schema-flyway.md index 5cecbcc6..40d734bd 100644 --- a/docs/education/migration/issues/EDU-006-practice-schema-flyway.md +++ b/docs/education/migration/issues/EDU-006-practice-schema-flyway.md @@ -47,6 +47,7 @@ Exact objects and versions are determined by EDU-005 and `flyway-postgresql`. - [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] Forward migration V4180 validates adopted Practice scalar types, required nullability, and identifier lengths without modifying V4030–V4060. - [x] Operational docs no longer instruct users to apply MySQL or manual Education SQL for these objects. ## Verification diff --git a/docs/education/migration/issues/EDU-011-import-export-assets.md b/docs/education/migration/issues/EDU-011-import-export-assets.md index 006590ed..89e456bf 100644 --- a/docs/education/migration/issues/EDU-011-import-export-assets.md +++ b/docs/education/migration/issues/EDU-011-import-export-assets.md @@ -26,6 +26,9 @@ The bounded capability provides: ## Deferred scope +- Production preview requests reference an admitted tenant asset ID; object keys, filenames, media types, and sizes are derived server-side from `education_content_import_asset` and are never accepted from the request body. +- The executable question-import service uses the V4130 `education_content_import_job` aggregate directly; the obsolete parallel `education_question_import_job` path has been removed. +- Per-job claims, heartbeat/finish fencing, expired lease recovery, bounded attempts, and terminal exhaustion failure use the production PostgreSQL mapper contract. - No generated export file, downloadable export artifact, export worker, or export-job persistence is delivered. Only the request-time redaction policy is established. - No production malware-scanner integration is delivered; the default remains fail-closed `UNAVAILABLE` until an external scanner adapter is configured. - No full CSV/XLSX parser is promised by the fallback. Without an available parser, preview remains metadata-only and execution is blocked. @@ -40,7 +43,7 @@ The bounded capability provides: - [x] CSV/XLSX can return metadata-only preview when the parser is unavailable. - [x] Execute is blocked unless scanning is clean and preview content is executable. - [x] Export requests apply answer/private-field redaction policy. -- [ ] Generated export files and the remaining operational features are deferred as listed above. +- [x] Generated export files, retention automation, production parser/scanner adapters, operator UI, and legacy re-scan are explicitly outside this bounded capability; their absence is exposed through blockers and fail-closed behavior rather than represented as available. ## Risk and rollback diff --git a/docs/education/migration/issues/EDU-012-classes-relationships.md b/docs/education/migration/issues/EDU-012-classes-relationships.md index 0d1696d1..a622409b 100644 --- a/docs/education/migration/issues/EDU-012-classes-relationships.md +++ b/docs/education/migration/issues/EDU-012-classes-relationships.md @@ -27,6 +27,13 @@ Tenant administrators manage classes, student education relationships, invitatio - Invitation creation is idempotent per tenant, actor, and key with request-hash conflict detection. Acceptance locks the invitation, checks invitee and expiry, and writes relationship plus audit in one transaction. - This bounded slice intentionally excludes account creation, password handling, platform tenant-ignore operations, education profile duplication, and supervision relationships. +## Role and permission semantics + +- `STUDENT` and `TEACHER` are Education relationship labels only; they do not grant System RBAC permissions or authorize administrative endpoints. +- Tenant administrators act through explicit System permissions (`education:class:*` and `education:class-invitation:*`). +- A Member principal may only accept an invitation addressed to their own member user ID; accepting a `STUDENT` or `TEACHER` invitation creates that relationship but no additional API authority in this bounded slice. +- Future teacher actions require a separate permission matrix and endpoints; no implicit role-based elevation is implemented. + ## Acceptance criteria - [x] Generic account, password, token, tenant, and role tables are not duplicated. diff --git a/docs/education/migration/issues/EDU-013-commercialization.md b/docs/education/migration/issues/EDU-013-commercialization.md index 329a5e89..26f63557 100644 --- a/docs/education/migration/issues/EDU-013-commercialization.md +++ b/docs/education/migration/issues/EDU-013-commercialization.md @@ -40,15 +40,21 @@ Education may own only domain bindings and fulfillment orchestration, such as: - entitlement scope and education-resource association; - duplicate-safe fulfillment event state where no platform facility exists. +## Deferred automatic integrations + +Automatic fulfillment from Mall paid/refunded order-item events and CRM referral/commission processing remains blocked because those public events/contracts are not available in the current reactor. The delivered admin endpoint and `EducationEntitlementApi` are trusted ingestion seams; they do not imply that Pay callbacks or Mall orders currently fan out automatically. + ## Acceptance criteria - [x] Mall/Pay/Member/CRM public contracts are mapped before implementation. -- [x] Payment callbacks and refunds remain in Pay. +- [x] Payment callbacks and refunds remain in Pay; no automatic fan-out is claimed. - [x] Generic products/orders remain in Mall where applicable. -- [x] Entitlement issuance, revocation, expiry, and refund effects are explicit and idempotent. +- [x] Entitlement issuance, revocation, expiry, and refund effects are explicit and idempotent through the trusted ingestion seams. - [x] Paid/private practice remains inaccessible until entitlement checks are complete. -- [x] Reconciliation and commission/referral ownership is explicit. -- [x] Financial and authorization tests cover duplicate callbacks and cross-tenant access. +- [x] Reconciliation and commission/referral ownership is explicit; automatic CRM integration remains deferred. +- [x] Financial and authorization tests cover duplicate trusted events and cross-tenant access. +- [x] Automatic Mall paid/refunded order-item fulfillment is explicitly unsupported until a Mall-owned public event exists; paid access remains fail-closed. +- [x] Automatic CRM referral/commission processing is explicitly unsupported until a CRM-owned public contract exists; no financial or referral behavior is simulated in Education. ## Risk and rollback diff --git a/docs/education/migration/issues/EDU-014-extended-learning.md b/docs/education/migration/issues/EDU-014-extended-learning.md index 343847f4..b21af72e 100644 --- a/docs/education/migration/issues/EDU-014-extended-learning.md +++ b/docs/education/migration/issues/EDU-014-extended-learning.md @@ -35,6 +35,7 @@ Student identity and tenant are always derived from the authenticated context; n - All Education state is tenant-owned and uses explicit tenant/user predicates in addition to framework interception. - Client requests cannot choose point values, badge codes, notify templates, delivery users, or leaderboard tenant. +- Exam reminder delivery uses a token-fenced, expiring database claim. An interrupted `SENDING` row is reclaimed after lease expiry, attempts are bounded, and exhausted claims become observable `FAILED` rows through V4200. - Award rows and reminder rows are durable retry authorities; cross-module tables are never written directly by Education application code. - Leaderboard output uses deterministic tenant-local aliases and aggregate score only. No phone, profile, member ID, answer, explanation, feedback content, or report detail is exported. - Rollback disables/removes executable application paths while retaining V4160 data. Destructive rollback is not provided; later correction uses a higher forward migration. diff --git a/docs/education/migration/issues/EDU-015-operational-independence.md b/docs/education/migration/issues/EDU-015-operational-independence.md index 734c3c30..46e7adc3 100644 --- a/docs/education/migration/issues/EDU-015-operational-independence.md +++ b/docs/education/migration/issues/EDU-015-operational-independence.md @@ -31,13 +31,13 @@ Production migration and deployment evidence remain release activities and are n ## 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. +- [x] Every configured temporary legacy dependency exposes owner, telemetry, failure policy, and exit date; invalid selected Scalar configuration reports DOWN. +- [x] Implemented at-least-once import and reminder consumers use duplicate-safe keys or token-fenced leases with bounded recovery. +- [x] Job retries/dead letters and scanner/component health have durable persistence and an admin health projection; undeployed components are not fabricated as UP. +- [x] Module-owned migrations V4010–V4200 execute and validate in isolated disposable PostgreSQL test schemas; shared Pilot/production execution remains a release-evidence activity. +- [x] The fixture-based Student harness passes target API contract/browser flows; a real deployed Student Web/H5 application remains outside this repository and is explicitly not claimed. +- [x] Runbooks contain no active MySQL/manual-SQL or obsolete NestJS startup requirement. +- [x] Rollback and incident procedures are documented in the Pilot acceptance runbook. ## Verification diff --git a/docs/education/migration/issues/README.md b/docs/education/migration/issues/README.md index 04b792ea..51c9629a 100644 --- a/docs/education/migration/issues/README.md +++ b/docs/education/migration/issues/README.md @@ -37,23 +37,25 @@ EDU-000 Phase 0 artifacts done EDU-009 + provider/content decisions └── EDU-010 Tenant content publication done (bounded JAVA_READ publication scope delivered) - └── EDU-011 Import/export/assets/scanning blocked; contract disposition exposed by capability manifest + └── EDU-011 Import/export/assets/scanning bounded capability delivered; production adapters/export artifacts deferred EDU-004 - └── EDU-012 Classes and education relationships blocked; contract disposition exposed by capability manifest + └── EDU-012 Classes and education relationships implemented (bounded class/invitation capability) Commerce ownership decisions - └── EDU-013 Education commercialization product decision required; fail-closed disposition exposed + └── EDU-013 Education commercialization bounded entitlement/binding capability implemented; platform events deferred All owner/contract decisions - └── EDU-014 Extended learning waves product decision required; family decomposition remains - └── EDU-015 Operational independence legacy-dependent under SCALAR_READ; target-only proof remains + └── EDU-014 Extended learning waves bounded representative wave delivered; blocked families explicitly deferred + └── EDU-015 Operational independence contracts implemented; release/deployment evidence remains ``` ## Recommended execution order -1. **EDU-010** — Continue with the provider-consistent question publication lifecycle; V4070 graph integrity is delivered. -2. Continue through EDU-011/EDU-012 as their blockers clear. +1. Complete production release evidence for **EDU-015** using the Pilot runbook and target-only `JAVA_READ` deployment. +2. Add deferred EDU-011 scanner/parser/export adapters only when their owning platform contracts and deployment are available. +3. Add automatic EDU-013 fulfillment only after Mall/Pay/CRM expose the recorded public events; keep manual trusted fulfillment and access fail-closed meanwhile. +4. Select the next EDU-014 family only after its entitlement, privacy, and owner decisions are recorded. ## Phase 0 completion caveat diff --git a/docs/education/pilot-acceptance-runbook.md b/docs/education/pilot-acceptance-runbook.md index 8fe37621..05734a56 100644 --- a/docs/education/pilot-acceptance-runbook.md +++ b/docs/education/pilot-acceptance-runbook.md @@ -30,13 +30,15 @@ yudao: ## 3. 发布步骤 1. 备份 Education 相关表,并记录应用版本与 `flyway_schema_history`。 -2. 使用 Server 配置的 PostgreSQL Flyway 执行 migrate 和 validate,检查版本、脚本、checksum 与 success;不得手工应用 Education SQL 或执行 rollback SQL。 -3. 先以 `catalog-read-enabled=false`、`practice-write-enabled=false` 部署应用。 -4. 验证 System、Infra、Member 基础 smoke。 -5. 仅对 Pilot 租户开启题库读取,完成 JAVA_READ 只读 smoke,并运行 `tools/education-target-smoke/java-read-readiness.sh`。 -6. 对 Pilot 租户开启练习写入,完成会话、答案、交卷、报告、错题和收藏 smoke。 -7. 若部署 Worker 或 Scanner,先确认其持续写入 `education_operational_component`,且 `/admin-api/education/operations/health` 无 `DOWN` 组件和未处理死信。 -8. 观察错误率、延迟和数据库写入后再扩大租户列表。 +2. 由数据库管理员预置彼此独立的 Flyway owner LOGIN role 与 runtime LOGIN role。Flyway role 拥有目标 schema;runtime role 只获得业务表所需权限,且不得拥有、继承所有者角色或写入 `education_question_lifecycle_transition_token`、`education_content_node_lifecycle_transition_token`、`education_question_collection_lifecycle_transition_token`、`education_question_collection_membership_token`。角色/密码不由 migration 创建。 +3. 显式注入 `FLYWAY_USER`、`FLYWAY_PASSWORD`(不得回退到 master datasource 账号),使用 Server 配置的 PostgreSQL Flyway 执行 migrate 和 validate,检查版本、脚本、checksum、success 以及四张 token 表 owner 均为 Flyway role。 +4. 以 runtime datasource 账号验证四张 token 表均无 INSERT/UPDATE/DELETE/TRUNCATE,随后启动应用;同角色、继承 owner 或可写授权会导致 Education 启动检查 fail closed。 +5. 先以 `catalog-read-enabled=false`、`practice-write-enabled=false` 部署应用。 +6. 验证 System、Infra、Member 基础 smoke。 +7. 仅对 Pilot 租户开启题库读取,完成 JAVA_READ 只读 smoke,并运行 `tools/education-target-smoke/java-read-readiness.sh`。 +8. 对 Pilot 租户开启练习写入,完成会话、答案、交卷、报告、错题和收藏 smoke。 +9. 若部署 Worker 或 Scanner,先确认其持续写入 `education_operational_component`,且 `/admin-api/education/operations/health` 无 `DOWN` 组件和未处理死信。 +10. 观察错误率、延迟和数据库写入后再扩大租户列表。 ## 4. Smoke 清单 diff --git a/yudao-module-education/README.md b/yudao-module-education/README.md index 22e5f761..918a1eaa 100644 --- a/yudao-module-education/README.md +++ b/yudao-module-education/README.md @@ -239,6 +239,7 @@ yudao-module-education/src/main/resources/db/migration/education/ - `V4080` 增加第 20 条目录引用边 `education_question_version.question_id → education_question.id`,并为版本表增加第 12 个 ownership/scope 守卫;版本归属必须与题目完全一致。该迁移还建立 draft-first 生命周期、不可变题目版本、同事务追加式审计及历史已发布内容的 fail-closed 预检。 - `V4090` 增加 Question Placement 乐观版本、PUBLIC 管理拒绝、发布后放置冻结、发布前可用节点约束,以及 `education:question:classify` 权限种子。 - `V4100` 增加租户 Content Node 的 `DRAFT → ACTIVE → ARCHIVED` 生命周期、统一 `authoring_version` CAS、同事务追加式审计和 entry/parent 结构约束;不写入权限或角色种子。 +- `V4110` 增加租户 Manual Question Collection 生命周期、ordered replace-all membership 和受保护的生命周期/成员变更 token;`V4210` 通过向前 migration 将物理删除保护扩展到历史 PUBLIC 题集。 - `V4120` 增加租户 Category 与 bounded Practice Blueprint 的 `DRAFT → ACTIVE → ARCHIVED` 生命周期、统一 `authoring_version` CAS、事务内追加式审计、独立 RBAC 和学生端路由可见性约束;不授予任何角色。 - V4080/V4090 在平台 `system_menu` 已存在时条件写入 Education 权限种子;固定 ID 已被不同 permission 占用时迁移失败,且迁移不会向角色写入授权关系。 - 旧 `education_answer_idempotency`、`education_submit_idempotency` 在首次接管时保留,后续清理必须使用更高版本的独立向前 migration。 @@ -247,6 +248,8 @@ yudao-module-education/src/main/resources/db/migration/education/ 首次接管已有 PostgreSQL 平台库时使用既定的 `4009` baseline。任何已存在 Education 表的环境都必须先核对实际表结构和 `flyway_schema_history`,不能仅凭表名视为兼容,也不能伪造 V4010/V4020 执行历史。 +Flyway 必须使用显式的 `FLYWAY_USER`/`FLYWAY_PASSWORD` 迁移所有者账号,且该账号必须与 master 运行时 datasource 账号不同。运维先创建两个互不继承的 LOGIN role,由 Flyway role 拥有目标 schema 和 migration 对象,再仅向 runtime role 授予业务表所需权限;不得授予、继承或拥有 V4080/V4100/V4110 的四张 `*_token` 表。应用启动会检查当前 runtime role 及其成员角色既不是这些表的 owner,也没有 INSERT/UPDATE/DELETE/TRUNCATE 权限;缺表、同角色、继承 owner 或错误授权都会 fail closed。角色创建、密码轮换与 schema 授权由部署平台预置,不由 migration 创建或转移。 + 编译后确认 migration 已打包: ```bash