# EDU-017 — Tenant appearance, public settings, and theme lifecycle - **Status:** done — bounded appearance/theme slice implemented and verified - **Type:** tenant administration / public presentation configuration - **Phase:** 4 / tenant operations - **Blockers:** EDU-003, System Tenant authority, Vben admin foundation ## Decision System Tenant remains authoritative for tenant identity, display name, lifecycle, and bound websites. Education owns only presentation-specific configuration: branding extensions, student/admin feature flags, public runtime configuration, and draft/published theme state. The public tenant locator response remains the minimal EDU-003 contract; appearance is exposed separately under the normal request tenant context. Platform theme templates are global trusted configuration and are explicitly excluded from MyBatis tenant injection. Tenant appearance rows extend `TenantBaseDO`, retain one live row per tenant, and use optimistic versions for every update. RuoYi System RBAC, tenant validation, admin projection, API access/operation logging, and the normal `tenant-id` security filter remain authoritative. ## Delivered contract - Admin read, branding write, settings write, theme preview, and theme publish endpoints with independent permissions. - `GET /education/tenant-appearance/public` is anonymous but not tenant-ignored; it requires the normal validated tenant header/context and returns no admin flags or draft data. - System tenant name is the lazy-row default and fallback; no duplicate Education tenant identity or domain authority is created. - Three legacy-compatible templates are seeded: `classic`, `focus`, and `high-contrast`. - Theme preview is a shallow template/override merge. Publication revalidates the persisted draft and clears it atomically. - Recursively rejects secret/password/token/private-key/API-key-like public keys except `secretRef`; theme tokens, CSS variables, icons, assets, colors, radii, modes, and density are allowlisted and unsafe renderable strings fail closed. - Vben page `education/tenant-appearance/index` manages branding, JSON settings, templates, draft preview, and explicit publication with client-side preflight checks. ## Verification - Policy unit tests cover recursive secret rejection, closed theme/asset key sets, colors/radii/CSS, URLs, and shallow merge. - Service unit tests cover System-name fallback, optimistic conflict, sanitized draft persistence, and public projection. - Method-security contract tests prove query, branding, settings, and theme permissions are independent. - Real PostgreSQL service tests prove lazy defaults, shared templates, settings, draft/publish, public projection, stale-version rejection, and cross-tenant isolation through the production MyBatis interceptor. - Flyway tests prove the V4290 schema, exact template seeds, menu shape, unique tenant row, and draft/publish database state. ## Explicitly open - Domains stay in System Tenant `websites`; no Education domain CRUD authority is planned. - Payment-account configuration must compose Pay rather than copy the legacy table. - Authentication-provider configuration must reuse System/Member authentication seams. - Tenant secret storage/rotation needs a dedicated encrypted private-storage decision and must never be added to the public appearance table. - Activation codes and coupons need a Mall Promotion/Member entitlement ownership and idempotent redemption decision.