feat(education): add tenant question placement
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# Native question authoring follows the active catalog authority
|
||||
|
||||
Education exposes local question authoring only when `catalog-mode=JAVA_READ`; `SCALAR_READ` and other modes fail with an explicit business error before any database access. Tenant questions start as `DRAFT`, retain immutable content versions, move only through `DRAFT → PUBLISHED → ARCHIVED`, and append an Education-owned lifecycle audit in the same transaction, because a local write under Scalar authority would be invisible to students and the platform's asynchronous operation log cannot prove atomic publication.
|
||||
Education exposes local question authoring only when `catalog-mode=JAVA_READ`; `SCALAR_READ` and other modes fail with an explicit business error before any database access. Tenant questions start as `DRAFT`, retain immutable content versions, are placed optimistically on an allowed Content Node, move only through `DRAFT → PUBLISHED → ARCHIVED`, and append an Education-owned lifecycle audit in the same transaction, because a local write under Scalar authority would be invisible to students and the platform's asynchronous operation log cannot prove atomic publication.
|
||||
|
||||
`ARCHIVED` is terminal for the current command surface, and `RETIRED` is not introduced until its distinct business and restoration semantics are decided. Direct question visibility follows the question's own publication state; entry, node, and collection state gates discovery through those routes rather than rewriting the question lifecycle.
|
||||
Question Placement has its own optimistic version, is available only to current-tenant `TENANT_OWNED` drafts, and becomes immutable at publication. Publication requires a stable active, visible, selectable PUBLIC or same-tenant Content Node. `ARCHIVED` is terminal for the current command surface, and `RETIRED` is not introduced until its distinct business and restoration semantics are decided. Direct question visibility follows the question's own publication state; entry, node, and collection state gates discovery through those routes rather than rewriting the question lifecycle.
|
||||
|
||||
@@ -29,7 +29,7 @@ The durable artifact classification, adoption matrix, version allocation, backfi
|
||||
3. V4030 owns the final Practice core-loop schema, including sessions/questions, reports/details, wrong questions, favorites, and unified `education_idempotency`. Fresh schema does not create legacy answer/submit idempotency tables.
|
||||
4. Existing manually bootstrapped databases require explicit schema comparison and adoption. A verified V4020-equivalent catalog may use an environment-specific 4020 baseline; incompatible environments require a higher-version correction, never falsified history.
|
||||
5. Legacy idempotency data is backfilled into the unified table before any later forward cleanup. Legacy tables are preserved during initial adoption.
|
||||
6. The earlier V4040 capability-seed plan was superseded after V4040 was allocated to the submit-claim lease. V4080 conditionally seeds the approved capability plus question author/publish/archive permissions when `system_menu` exists; role assignment is not seeded.
|
||||
6. The earlier V4040 capability-seed plan was superseded after V4040 was allocated to the submit-claim lease. V4080/V4090 conditionally seed the approved capability plus question author/classify/publish/archive permissions when `system_menu` exists; role assignment is not seeded.
|
||||
7. Docker/manual SQL initialization and MySQL rollback runbooks must be removed from active operations when EDU-006 lands. EDU-016's temporary test bridge becomes Flyway-driven after equivalence is proven.
|
||||
8. The inspected local disposable `postgresdb` had no Flyway history and no Education tables. EDU-005 ran no migration and makes no migration-success claim.
|
||||
|
||||
@@ -107,24 +107,28 @@ The durable artifact classification, adoption matrix, version allocation, backfi
|
||||
**Decision:** Local tenant question authoring is available only under explicit `JAVA_READ` authority and fails before persistence access in `SCALAR_READ`. New questions are `DRAFT`; the current command surface permits only `DRAFT → PUBLISHED → ARCHIVED`, with no restore or retire command.
|
||||
|
||||
**Rules:**
|
||||
1. Create, publish, and archive are explicit commands with separate System RBAC permissions; clients cannot submit tenant, scope, lifecycle, or actor fields.
|
||||
1. Create, place, publish, and archive are explicit commands with separate System RBAC permissions; clients cannot submit tenant, scope, lifecycle, or actor fields.
|
||||
2. Tenant authoring always creates `TENANT_OWNED` content for the current framework tenant. PUBLIC/platform-curator authoring is not part of this slice.
|
||||
3. Question Content Versions are immutable. Publication changes lifecycle state for the current version and never changes `tenant_id`, `scope`, or content version.
|
||||
4. Education appends a lifecycle audit in the same transaction as each state transition. The asynchronous platform operation log remains supplementary, not proof of atomic publication.
|
||||
5. Direct question visibility follows the question's Publication State. Entry, node, and collection availability gates their own discovery routes and does not mutate the question lifecycle or historical practice snapshots.
|
||||
6. Historical `HIDDEN`, `INACTIVE`, and `PUBLISHED/is_published=false` rows map to `ARCHIVED`; historical `DRAFT` rows become non-published; only consistent published rows remain `PUBLISHED`.
|
||||
7. V4080 adds Question Version → Question as the twentieth guarded catalog edge. A version must have exactly the same tenant and scope as its question, and the version table becomes the twelfth catalog table whose ownership cannot change after insert.
|
||||
8. Question Placement means assigning a DRAFT question's `node_id`; it does not mean Category CRUD. The `education_category` table has no Question relationship in the current target model.
|
||||
9. Placement targets must be visible, active, selectable PUBLIC or same-tenant Content Nodes. Placement has an independent optimistic version, becomes immutable after publication, and publication CAS includes that version.
|
||||
10. Node-route student visibility is evaluated atomically in the Question/Content Node query. Node availability gates that route but does not change direct question visibility or historical snapshots.
|
||||
|
||||
**Permission and data-scope matrix:**
|
||||
|
||||
| Actor capability | Permission | Allowed rows | Data scope |
|
||||
|---|---|---|---|
|
||||
| Author | `education:question:author` | Create current-tenant `TENANT_OWNED` drafts | Current tenant; no request-supplied owner or tenant |
|
||||
| Classifier | `education:question:classify` | Place or re-place current-tenant `TENANT_OWNED` drafts on allowed Content Nodes | Tenant-wide shared catalog asset; PUBLIC/same-tenant targets only |
|
||||
| Publisher | `education:question:publish` | Current-tenant `TENANT_OWNED` drafts | Tenant-wide shared catalog asset; department/self DataPermission does not expand access |
|
||||
| Archiver | `education:question:archive` | Current-tenant `TENANT_OWNED` published questions | Tenant-wide shared catalog asset; department/self DataPermission does not expand access |
|
||||
| Platform curator | none in this slice | No PUBLIC or tenant-owned writes through these endpoints | Fail closed |
|
||||
|
||||
V4080 conditionally seeds the four Education permissions (`education:capability` plus author/publish/archive) when the adopted platform schema contains `system_menu`. It fails when a fixed ID is already occupied by a different permission and deliberately does not assign the permissions to any role.
|
||||
V4080/V4090 conditionally seed the five Education permissions (`education:capability` plus author/classify/publish/archive) when the adopted platform schema contains `system_menu`. They fail when a fixed ID is already occupied by a different permission and deliberately do not assign the permissions to any role.
|
||||
|
||||
**Rationale:** A successful PostgreSQL write while Scalar remains authoritative would be student-invisible. Department/self DataPermission has no truthful meaning for tenant-wide shared catalog rows without a separate ownership model, so action permissions plus framework tenant isolation are explicit rather than applying a misleading annotation.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# EDU-010 — Tenant content publication and graph integrity
|
||||
|
||||
- **Status:** in progress — catalog graph-integrity and the verified JAVA_READ tenant-question lifecycle slice are delivered; broader classification/collection authoring remains
|
||||
- **Status:** in progress — catalog graph-integrity, the verified JAVA_READ tenant-question lifecycle, and Question Placement slices are delivered; category, collection, and blueprint authoring remain
|
||||
- **Type:** implementation program
|
||||
- **Phase:** 3
|
||||
- **Blockers:** EDU-004 ✓ (done), EDU-009 ✓ (done), provider-authority decision ✓ (resolved 2026-07-30, see decisions.md), PUBLIC graph-semantics decision ✓ (resolved 2026-07-30, see decisions.md)
|
||||
@@ -26,7 +26,8 @@ Authorized tenant administrators can author, classify, publish, archive, and ret
|
||||
- [x] V4080 extends the graph guard to the new Question Version → Question edge and makes the version table the twelfth ownership-protected catalog table.
|
||||
- [x] Unpublished/archived content is never student-visible.
|
||||
- [x] Publication is transactional and auditable.
|
||||
- [x] Database graph-integrity changes use `flyway-postgresql` and forward migration V4070.
|
||||
- [x] Tenant Question Placement is permission-separated, tenant-safe, optimistic, and frozen after publication.
|
||||
- [x] Database graph-integrity changes use `flyway-postgresql` and forward migrations V4070/V4090.
|
||||
|
||||
## Delivery progress — graph-integrity slice (2026-07-30)
|
||||
|
||||
@@ -67,22 +68,35 @@ Delivered:
|
||||
- When the adopted platform schema contains `system_menu`, V4080 conditionally seeds the Education capability plus author/publish/archive permissions. A fixed ID already occupied by a different permission aborts migration, and no role assignment is seeded.
|
||||
- Publication and archive use tenant/scope/expected-state CAS updates and append audit facts in the same transaction. Audit failure rolls back the state change.
|
||||
- PostgreSQL integration-test coverage exercises the student read-after-write seam: draft is invisible, published content is returned only through the existing Safe Question projection, and archived content is invisible while stored snapshots remain independent.
|
||||
- Direct question visibility is controlled by the question lifecycle. Container availability is a route-level discovery gate; classification, collection, and platform-curator write paths remain outside this bounded slice.
|
||||
- Direct question visibility is controlled by the question lifecycle. Container availability is a route-level discovery gate; Question Placement is delivered separately below, while Category, Collection, and platform-curator write paths remain outside the lifecycle slice.
|
||||
|
||||
Permission and data-scope matrix is recorded in `07-decisions.md`. The three lifecycle endpoints manage tenant-wide shared catalog assets within the current framework tenant; department/self DataPermission does not grant additional row access, PUBLIC authoring fails closed, and the V4080 seed intentionally grants no role.
|
||||
|
||||
The listed acceptance criteria are satisfied for the native catalog graph and tenant-question lifecycle slice. EDU-010 remains in progress because its tenant-admin outcome also includes classification, collection, blueprint, retire, and platform-curator workflows that are not part of this bounded slice.
|
||||
The listed acceptance criteria are satisfied for the native catalog graph, tenant-question lifecycle, and Question Placement slices. EDU-010 remains in progress because its tenant-admin outcome also includes Category, Collection, Blueprint, Retire, and platform-curator workflows that are not part of these bounded slices.
|
||||
|
||||
## Delivery progress — JAVA_READ Question Placement slice (2026-07-30)
|
||||
|
||||
Delivered:
|
||||
|
||||
- `PUT /admin-api/education/questions/{id}/placement` uses the independent `education:question:classify` permission. The request contains only `nodeId` and `expectedPlacementVersion`; tenant, scope, actor, lifecycle, and ownership remain server-controlled.
|
||||
- Only current-tenant `TENANT_OWNED` drafts can be placed. A target must be a visible, active, selectable PUBLIC or same-tenant Content Node. `SCALAR_READ` fails before Mapper access, and tenant endpoints cannot manage PUBLIC questions.
|
||||
- Placement uses a monotonic optimistic version and tenant/scope/status/version CAS. Repeating the current placement is rejected as a conflict; two writers using the same expected version have one winner.
|
||||
- Publication requires a stable available placement and includes the validated placement version in its lifecycle CAS. V4090 prevents direct PUBLIC placement, requires exact placement-version advancement, freezes placement after publication, and rejects publication without an available node.
|
||||
- Student node-route reads join Question and Content Node in one PostgreSQL statement. A hidden, inactive, non-selectable, cross-scope, or deleted node cannot expose questions through that route; direct question visibility still follows the question Publication State.
|
||||
- V4090 conditionally seeds `education:question:classify` at fixed ID 6805, fails closed on conflicting rows, and grants no role.
|
||||
|
||||
Category CRUD, Content Node authoring, Collection/Blueprint authoring, PUBLIC curator workflows, and a distinct Retire state remain outside this slice. In the target domain, `education_category` is not connected to Question; the delivered classification seam is explicitly Question Placement through `question.node_id`.
|
||||
|
||||
## Verification evidence (2026-07-30)
|
||||
|
||||
- `mvn -pl yudao-module-education test` with the five `EDU_TEST_POSTGRES_*` variables pointed at the local Docker PostgreSQL: **485 tests passed**, including 18 Flyway migration tests and five lifecycle PostgreSQL integration tests. V4080 was actually executed in disposable PostgreSQL schemas.
|
||||
- `mvn -pl yudao-module-education clean test` with the five `EDU_TEST_POSTGRES_*` variables pointed at the local Docker PostgreSQL: **502 tests passed**, including 20 Flyway migration tests and nine lifecycle/placement PostgreSQL integration tests. V4090 was actually executed in disposable PostgreSQL schemas.
|
||||
- The lifecycle integration tests prove concurrent double-publish has one success and one lifecycle conflict with exactly one publish audit, cross-tenant and PUBLIC management fail closed, and `draft → publish → archive` matches student visibility.
|
||||
- The real Spring Method Security contract tests prove each of author/publish/archive requires its own permission and rejected calls do not reach the lifecycle service.
|
||||
- The real Spring Method Security contract tests prove each of author/classify/publish/archive requires its own permission and rejected calls do not reach the lifecycle service.
|
||||
- `mvn -pl yudao-server -am -DskipTests clean compile`: **22 reactor modules passed**.
|
||||
- `git diff --check`: passed.
|
||||
- `target/classes/db/migration/education/V4080__add_question_publication_lifecycle.sql`: present after the module build.
|
||||
- `target/classes/db/migration/education/V4080__add_question_publication_lifecycle.sql` and `V4090__add_question_placement.sql`: present after the module build.
|
||||
|
||||
## Risk and rollback
|
||||
|
||||
- **Risk:** High content-integrity and authorization risk.
|
||||
- **Rollback:** Disable authoring/publishing and roll back application; preserve data and correct forward.
|
||||
- **Rollback:** Before rolling the application back behind V4090, disable native authoring by switching away from `JAVA_READ` (or disable Education when no alternate read authority is configured). Preserve V4090 data and schema, then correct forward. A V4080 application left writable in `JAVA_READ` is intentionally rejected when it attempts to publish an unplaced draft.
|
||||
|
||||
Reference in New Issue
Block a user