The project already maps `as_asset_device` through `AssetDeviceEntity` and `AssetDeviceMapper`, but it has no device Controller, Service, DTOs, page, image upload path, route, or menu. The table keeps a device name, two optional image attachment fields, a logical `user_person_id`, two text status fields, and the common `delete_time` soft-delete marker.
Enterprise WeChat asset work is currently in progress in the same repository. Its files are already dirty, including the enterprise WeChat Controller, Service, DTOs, frontend module, CSS, and tests. Device management must therefore be independently buildable and defer the two shared frontend registrations until final integration.
There is no existing upload service. This change stores original image files locally and records controlled, application-relative file identifiers in `image_attachment_1` and `image_attachment_2`. The browser renders a fixed-size preview of the original image; it does not generate or store a second thumbnail file.
## Goals / Non-Goals
**Goals:**
- Provide a complete, safe device-asset CRUD workflow over existing `as_asset_device` rows.
- Support up to two JPG, PNG, or GIF images per device, with a 20 MB maximum for each image.
- Provide searchable company-person selection and readable person names in the list response.
- Keep active device names unique, apply approved dropdown values, and protect referenced devices from deletion.
- Add a dedicated Device Asset Management page, route, and menu without changing enterprise WeChat business behavior.
- Allow parallel implementation by isolating device work to new files and assigning final shared-file integration to one owner.
**Non-Goals:**
- Do not modify database schema, run migrations, or directly operate the database.
- Do not add device selection to the enterprise WeChat creation form; that is a later change.
- Do not generate physical thumbnail files, convert image formats, or add an object-storage dependency.
- Do not permanently remove original image files during normal device soft deletion.
- Do not introduce authentication or a general-purpose file-management module.
## Decisions
### Device API and data contract
Use `/api/device-assets` for list, creation, update, and deletion. `GET` accepts `page`, `size`, `deviceName`, `userPersonId`, `userUsageStatus`, and `assetRelationStatus`; it filters `delete_time = 0` and orders by descending ID. `POST` and `PUT /{id}` accept multipart form data so device fields and both optional image files are saved together. `DELETE /{id}` performs a soft deletion.
The list and detail response return `id`, device fields, original-image access URLs, `userPersonId`, `userPersonName`, and audit times, but never return `deleteTime` or the physical storage path. This follows the current asset API envelope and pagination shape.
### Validation and reference handling
`deviceName` is required and is unique among active records. Save operations reject values outside the fixed approved status lists. A selected `userPersonId` must resolve to a non-deleted company person; it can be omitted.
Before a device is soft-deleted, the service checks active `as_phone_asset`, `as_wecom_account`, `as_wechat_account`, and `as_douyin_account` rows for the device ID. If any exists, deletion fails with a readable list of reference sources. This preserves logical association integrity because the database intentionally has no foreign keys.
### Image storage and access
The server validates extension and image content for JPG, PNG, and GIF and limits each file to 20 MB. It saves each original file beneath a configurable local root, defaulting to `./uploads/device-assets`, with a generated opaque file identifier. Attachment database columns store only that identifier.
An application endpoint resolves an identifier to a file only after constraining it to the upload root; response DTOs expose the endpoint URL rather than a disk path. On update, omitted image parts preserve the current image; an explicit remove flag clears its database reference. A successful replacement saves the new file before updating the row, while failed requests clean up newly written temporary files. Soft deletion keeps files for recoverability.
Browser-sized preview was selected over server-generated thumbnail files because it adds no image-processing dependency or duplicate storage. The trade-off is that a list may download larger original files; lazy image loading will reduce initial page work.
### Frontend behavior
`DeviceAssetView` provides filters, a paged table, a create/edit dialog, remote company-person search, the two status dropdowns, two image selectors, inline browser previews, and a delete confirmation. Each slot supports retain, replace, or explicit removal during edit. The page sets `loading` and `saving` states and surfaces API errors through the existing Element Plus message pattern.
Use a dedicated `device-asset.css`, imported by the device module, rather than editing the currently modified shared stylesheet. The final integration adds the `设备资产管理` navigation item and maps `#/device-assets` to the new view.
### Parallel ownership and integration
Device work owns only newly added `DeviceAsset*` backend files, device frontend module files, device CSS, and device tests. It reuses but does not edit `AssetDeviceEntity`, `AssetDeviceMapper`, or enterprise WeChat code. Enterprise WeChat work retains ownership of all current dirty files. A single integration owner changes `App.js` and `router/index.js` after both feature branches are ready.
This ownership model avoids merge conflicts. The device branch is developed in a separate Git worktree from the current dirty enterprise-WeChat worktree; final integration runs all relevant tests after both changes are present.
## Risks / Trade-offs
-[Original files can be 20 MB] → Lazy-load list previews, display a loading state, and never generate duplicate thumbnail files.
-[Local disk is not shared across multiple application instances] → Keep the upload root configurable and document that a future multi-instance deployment must move to shared/object storage.
-[Soft deletion retains image files] → Retention is deliberate for recovery; any physical cleanup must be a separately authorized maintenance workflow.
-[Database has no foreign keys] → Service-level active-reference checks block deletion, and tests cover each referencing asset type.
-[User-provided file extension can be misleading] → Validate both allowed type and decodable image content; serve files only through opaque identifiers constrained to the upload root.
-[Shared menu and router files are collision points] → Make their two-line integration a separately owned final commit only.
## Migration Plan
1. Create the device feature branch/worktree from the agreed base without resetting or stashing the current enterprise-WeChat working tree.
2. Implement and test only device-owned new files; do not change enterprise-WeChat files, shared CSS, or shared navigation during this phase.
3. Configure the multipart request maximum to accommodate two 20 MB files plus form metadata, without changing database schema.
4. Deploy with a writable device upload directory and verify that its access endpoint can read only files under the configured root.
5. Run device API tests, frontend build, device Playwright tests, then the existing phone and enterprise-WeChat smoke tests after final integration.
6. Roll back code by reverting the device feature and route/menu integration commits. Existing rows and retained files remain recoverable; no database rollback is required.
## Open Questions
None. The approved status values, per-image 20 MB limit, browser-sized previews, local storage, deletion protection, route, menu label, and deferred enterprise-WeChat selection are all fixed for this change.
`as_asset_device` already has an Entity and Mapper, but the asset console cannot browse, create, edit, upload images for, or safely delete device assets. Completing this closed management loop is needed before later work can let enterprise WeChat assets choose a device.
## What Changes
- Add a device-asset REST API for paged listing, creation, editing, image upload and protected soft deletion against `as_asset_device`.
- Add a Device Asset Management workspace at `#/device-assets`, including filters, paged table, creation/edit dialog, two-image upload and browser-sized thumbnail preview.
- Limit each uploaded JPG, PNG, or GIF image to 20 MB. Store only original files locally; the list renders a fixed-size browser preview and does not create a second thumbnail file.
- Add fixed dropdown options for user usage status (`使用中`, `闲置`, `维修中`, `停用`) and asset relation status (`已关联`, `未关联`, `待确认`).
- Resolve `user_person_id` to a readable company-person name and prohibit deletion while an active phone, enterprise WeChat, WeChat, or Douyin asset still references the device.
- Add the `设备资产管理` menu item and the `#/device-assets` route as a final, isolated integration change so this work can proceed in parallel with enterprise WeChat asset work.
-`device-asset-workspace`: Provides the Device Asset Management Vue workspace, its filters, forms, image previews, and menu/route entry.
### Modified Capabilities
- None.
## Impact
- Backend: adds device-specific Controller, Service, request/response DTOs, local file-storage support, and API tests; reuses the existing `AssetDeviceEntity`, `AssetDeviceMapper`, `CompanyPersonMapper`, and referencing asset Mappers without changing database schema.
- Frontend: adds an isolated device module, API client, dedicated CSS, and Playwright coverage. Only the final integration changes `frontend/src/App.js` and `frontend/src/router/index.js`.
- API and files: introduces `/api/device-assets` CRUD, company-person lookup, and controlled image-file access endpoints. Images are stored under a configurable local directory, defaulting to `./uploads/device-assets`; each image is limited to 20 MB.
- Database: no DDL, migration, or direct database operation is included. Existing `delete_time` semantics remain the soft-delete mechanism.
The system SHALL provide `GET /api/device-assets` with validated `page` and `size` parameters and optional `deviceName`, `userPersonId`, `userUsageStatus`, and `assetRelationStatus` filters. It MUST return only rows whose `delete_time` is `0`, ordered by descending ID, in the existing `records`, `total`, `page`, and `size` pagination shape.
#### Scenario: Filtered active-device page
-**WHEN** a user requests page 1 with `deviceName` and an approved status filter
-**THEN** the response contains only matching non-deleted device records and their pagination metadata
### Requirement: Readable device response and company-person lookup
The system SHALL return each device's ID, device name, two image access URLs when present, `userPersonId`, resolved `userPersonName`, both status fields, and creation and update times. It MUST NOT return `deleteTime` or a physical file path. The system SHALL provide a read-only company-person lookup endpoint that returns active matching person IDs and names for the device form.
#### Scenario: Missing or deleted device user
-**WHEN** a device has no user person or its referenced person is not active
-**THEN** the device record retains `userPersonId` when present and returns a null user-person name without failing the page
### Requirement: Device creation and update
The system SHALL provide multipart `POST /api/device-assets` and `PUT /api/device-assets/{id}` endpoints. Creation and update MUST require a nonblank device name, reject duplicate active device names, accept only the approved status values, and require an active company person when `userPersonId` is supplied. Update MUST preserve an existing image when no replacement or explicit removal is supplied.
#### Scenario: Create a device with valid approved values
-**WHEN** a user submits a unique device name, optional active user person, and approved usage and relation statuses
-**THEN** the system creates an active device record with creation and update timestamps and returns its readable response
#### Scenario: Reject invalid status or duplicate name
-**WHEN** a user submits an unsupported status value or a device name already used by an active device
-**THEN** the system rejects the request without writing a new or changed device row
### Requirement: Device image attachment handling
The system SHALL accept at most two optional device images, one for each attachment slot. Each file MUST be JPG, PNG, or GIF, MUST be decodable as that image type, and MUST not exceed 20 MB. The system MUST store original files under a configurable local root and expose them only through an opaque application file URL; it MUST NOT return local disk paths or create separate thumbnail files.
#### Scenario: Display image by browser-sized preview
-**WHEN** a device response contains an image access URL
-**THEN** the client can load the original image through the controlled URL and render it in a fixed-size preview without requesting a separately generated thumbnail
#### Scenario: Replace or remove an image while editing
-**WHEN** a user updates one image slot with a valid replacement or an explicit remove flag
-**THEN** the system respectively records the new opaque identifier or clears that slot while leaving the other slot unchanged
The system SHALL provide `DELETE /api/device-assets/{id}` as a soft delete. Before deletion it MUST check active phone, enterprise WeChat, WeChat, and Douyin assets for the target device ID. If any active reference exists, it MUST reject deletion and identify the referencing asset types; otherwise it MUST update `delete_time` and `update_time` without physically deleting stored image files.
#### Scenario: Reject deletion of a referenced device
-**WHEN** an active phone or account asset references the requested device ID
-**THEN** the system returns a readable failure and the device remains active
#### Scenario: Soft-delete an unreferenced device
-**WHEN** no active supported asset references the requested device ID
-**THEN** the system marks the device deleted and it no longer appears in the device list
### Requirement: Device Asset Management route and menu
The frontend SHALL provide a Device Asset Management workspace at `#/device-assets` and a sidebar menu item labelled `设备资产管理` that opens it. The route and menu integration MUST preserve the existing enterprise-WeChat route and page behavior.
#### Scenario: Open Device Asset Management
-**WHEN** a user selects `设备资产管理` from the sidebar
-**THEN** the application navigates to `#/device-assets` and renders the device list workspace
### Requirement: Device list filters and pagination
The workspace SHALL render device name, user person, usage status, relation status, and audit-time columns with filters for name, user person, usage status, and relation status. It MUST expose pagination and show loading, empty, and request-error states.
#### Scenario: Reset a filtered device list
-**WHEN** a user clears the filters through the reset control
-**THEN** the workspace requests the first unfiltered page and displays its returned records
### Requirement: Device create and edit form
The workspace SHALL provide create and edit dialogs with a required device name, remote company-person selector, the approved usage-status dropdown, and the approved relation-status dropdown. It MUST prevent duplicate save submissions while a request is pending and show validation or API failures to the user.
#### Scenario: Submit a valid device form
-**WHEN** a user completes a valid create or edit dialog and selects save
-**THEN** the workspace submits multipart form data, closes the dialog after success, and refreshes the list
### Requirement: Two-image browser preview
The workspace SHALL allow each device form to select up to two JPG, PNG, or GIF files, each no larger than 20 MB. It MUST render fixed-size previews of existing and newly selected original images, allow an existing slot to be retained, replaced, or marked for removal, and allow a user to open the original image preview.
#### Scenario: Reject an oversized or unsupported image before save
-**WHEN** a user selects an image larger than 20 MB or outside the supported formats
-**THEN** the workspace displays an error and does not include that file in the save request
### Requirement: Protected delete interaction
The workspace SHALL require delete confirmation and refresh the list after a successful soft delete. If the API reports active references, it MUST display the returned reason and keep the device row visible.
#### Scenario: Attempt to delete a referenced device
-**WHEN** a user confirms deletion of a device that is still referenced
-**THEN** the workspace shows the API's reference warning and does not remove the row from the table
| `backend/src/main/java/com/xyw/console/asset/service/DeviceAssetService.java` | Implements device CRUD, list filtering, person resolution, and reference checks. | Device feature |
| `backend/src/main/java/com/xyw/console/asset/controller/DeviceAssetController.java` | Exposes browser endpoints for devices, images, and person lookup. | Device feature |
| `backend/src/test/java/com/xyw/console/asset/service/DeviceAssetServiceTest.java` | Verifies device business rules without a live database. | Device feature |
| `frontend/tests/authenticated-test.js` | Provides a shared developer-session mock so protected asset-page tests can exercise their own API fixtures. | Test integration |
| `frontend/src/App.js` | Renders global sidebar navigation. | Final integration owner only |
| `frontend/src/router/index.js` | Registers global Hash routes. | Final integration owner only |
-[x] 0.1 Create a dedicated device-asset branch/worktree from the approved base; preserve the current enterprise-WeChat working tree and do not reset, stash, or edit its files.
-[x] 0.2 Keep `AssetDeviceEntity.java`, `AssetDeviceMapper.java`, all enterprise-WeChat files, and `frontend/src/styles/app.css` read-only for the device feature; record the final integration owner for `App.js` and `router/index.js`.
## 1. Device request, response, and error contract
-[x] 1.1 Add `DeviceAssetPageQuery.java` with validated page/size and device filter fields. Add beginner comments to `resolvedPage` and `resolvedSize`: code purpose (plain language), related files, and request-to-pagination data flow.
-[x] 1.2 Add `DeviceAssetSaveRequest.java` for required device name, optional active user person, approved statuses, two optional multipart images, and explicit image-removal flags; document its file purpose.
-[x] 1.3 Add `DeviceAssetResponse.java`, `DeviceAssetPageResponse.java`, and `DevicePersonLookupResponse.java`; document each file purpose and exclude physical paths and `deleteTime` from browser responses.
-[x] 1.4 Add device-specific not-found and validation exceptions, plus focused exception-to-HTTP-response handling if the existing handler cannot safely support them; document each new method and error data flow.
## 2. Local image-file support
-[x] 2.1 Add `DeviceAssetFileStorageService.java` with a configurable `./uploads/device-assets` default and an application multipart limit that permits two 20 MB files plus form data, without exposing physical paths.
-[x] 2.2 Implement and fully annotate every storage method: constructor, `store`, `resolve`, `replace`, `removeReference`, `validateImage`, `createOpaqueIdentifier`, `resolveInsideRoot`, and temporary-file cleanup helpers. Each comment MUST state code purpose (plain language), related files, and upload-to-database-to-preview data flow.
-[x] 2.3 Validate JPG, PNG, and GIF extension plus decodable image content, reject any image over 20 MB, and test path traversal, unsupported type, corrupted content, and oversized file rejection.
-[x] 2.4 Preserve original files through normal soft deletion; ensure failed create/update operations remove only files written by that failed request and never remove an existing referenced original.
## 3. Backend device CRUD and integrity protection
-[x] 3.1 Add `DeviceAssetService.java`, injecting existing device, company-person, phone, enterprise-WeChat, WeChat, and Douyin Mappers without changing their source files.
-[x] 3.2 Implement and fully annotate every service method: constructor, `page`, `create`, `update`, `softDelete`, `searchCompanyPersons`, `findImage`, `activeQuery`, `requireActiveDevice`, `validateSaveRequest`, `validateUserPerson`, `validateStatus`, `checkActiveReferences`, `collectIds`, `personNames`, `toResponse`, `hasText`, and any added helper. Each comment MUST state code purpose (plain language), related files, and Controller-to-Service-to-Mapper/file data flow.
-[x] 3.3 Enforce nonblank active-unique device names, approved values `使用中/闲置/维修中/停用` and `已关联/未关联/待确认`, optional active company-person ownership, audit-time initialization, and `delete_time = 0` list filtering.
-[x] 3.4 Enforce soft-delete protection across active phone, enterprise-WeChat, WeChat, and Douyin records; return a readable list of source types when deletion is blocked.
-[x] 3.5 Add `DeviceAssetController.java` for `GET/POST/PUT/DELETE /api/device-assets`, company-person lookup, and controlled opaque-file access. Fully annotate its constructor and every endpoint method with code purpose (plain language), related files, and HTTP-to-Service-to-response data flow.
## 4. Device Asset Management frontend
-[x] 4.1 Add `device-api-client.js`, including `request`, `listDeviceAssets`, `createDeviceAsset`, `updateDeviceAsset`, `deleteDeviceAsset`, and `searchDeviceCompanyPersons`. Add required beginner comments to each function describing purpose, related files, and view-to-API-to-Controller flow.
-[x] 4.2 Add `DeviceAssetView.js` and document the component file purpose. Implement and fully annotate `setup`, `loadPage`, `openCreate`, `openEdit`, `resetForm`, `submitForm`, `confirmDelete`, `fetchPersonSuggestions`, `validateImageBeforeSelect`, image preview callbacks, image-removal callbacks, `scheduleSearch`, `submitSearch`, `resetSearch`, `changePage`, `changePageSize`, and the mount callback. Every annotation MUST contain code purpose (plain language), related files, and user-action-to-API-to-rendered-state flow.
-[x] 4.3 Render the approved filters, paged table, create/edit dialog, remote person selector, fixed status dropdowns, save/loading/error states, and soft-delete confirmation with reference-error feedback.
-[x] 4.4 Implement two image slots that accept JPG/PNG/GIF up to 20 MB each, preview the original image in a fixed-size browser frame, allow retain/replace/remove on edit, and do not request or create a generated thumbnail file.
-[x] 4.5 Add `device-asset.css` and import it only from the device module; use device-prefixed selectors and preserve `app.css` unchanged.
## 5. Final route and menu integration
-[x] 5.1 After enterprise-WeChat work is ready, have the designated integration owner add only the `设备资产管理` menu link in `frontend/src/App.js`; preserve all existing links and add the required file/method comment if any logic changes.
-[x] 5.2 Have the same integration owner add only `/device-assets` → `DeviceAssetView` in `frontend/src/router/index.js`; preserve the enterprise-WeChat route and add the required file/function comment if any logic changes.
-[x] 5.3 Rebase or merge the device branch only after both feature branches are independently verified; resolve no business changes in shared files outside the dedicated integration commit.
## 6. Verification and handoff
-[x] 6.1 Add `DeviceAssetServiceTest.java` coverage for default pagination, every filter, active-name uniqueness, approved-status validation, person validation, all four deletion-reference sources, safe soft deletion, image preservation, replacement, explicit removal, and failed-upload cleanup. Add required comments to every test method and business callback.
-[x] 6.2 Add `DeviceAssetControllerTest.java` coverage for GET pagination, multipart create/update, image validation and controlled image reads, error responses, and hidden `deleteTime`/physical paths. Add required comments to every test method and callback.
-[x] 6.3 Add `frontend/tests/device-asset.spec.js` coverage for the new route/menu, filters, pagination, create/edit, status dropdowns, image type/20 MB checks, browser-sized preview, retained/replaced/removed images, and deletion-protection feedback. Add required comments to every test callback.
-[x] 6.4 Run `openspec validate add-device-asset-management --strict`, backend compile and focused tests, frontend build, device Playwright tests, then existing phone and enterprise-WeChat smoke tests after integration. Analyze any failure before changing code.
-[x] 6.5 Record the implementation file list, route/menu effect, API/DTO/configuration impact, database non-impact, executed verification, and any unexecuted check in the completion handoff.
## Completion handoff (2026-08-03)
### Implementation files
- Backend: `DeviceAssetMultipartConfig`, `DeviceAssetController`, `DeviceAssetExceptionHandler`, five `DeviceAsset*` DTOs, two device exceptions, `DeviceAssetFileStorageService`, and `DeviceAssetService`.
- Backend tests: `DeviceAssetServiceTest`, `DeviceAssetFileStorageServiceTest`, and `DeviceAssetControllerTest`.
- Frontend: `device-api-client.js`, `DeviceAssetView.js`, `device-asset.css`, and `playwright.device.config.js`.
- Test integration: `device-asset.spec.js` plus `authenticated-test.js`; the existing phone, enterprise-WeChat, and legacy-reference specifications now import the shared authenticated test object.
### Product and contract effect
- Route and menu: `#/device-assets` renders Device Asset Management and the sidebar exposes the Device Asset Management entry. The integration preserves the enterprise-WeChat route.
- API and DTOs: `GET/POST/PUT/DELETE /api/device-assets`, company-person lookup, and controlled opaque image reads are available. Multipart saves accept two original JPG/PNG/GIF files, each at most 20 MB; response DTOs omit physical paths and `deleteTime`.
- Configuration: `DeviceAssetMultipartConfig` permits two 20 MB uploads plus form data and `DeviceAssetFileStorageService` defaults to `./uploads/device-assets`.
- Database: no DDL, migration, or direct database operation was performed for device management; the existing `as_asset_device` table and its soft-delete fields are reused.
-`npm run build` passed. Vite retained its existing third-party PURE-comment and bundle-size warnings.
-`npx playwright test tests/device-asset.spec.js --config=playwright.device.config.js --reporter=list --timeout=30000` passed: 5/5.
-`npx playwright test --config=playwright.device.config.js --reporter=list --timeout=30000` passed: 13/13, including device, phone, enterprise-WeChat, and legacy-reference tests.
### Resolution of the prior test blocker
The newly integrated authentication guard redirected protected asset pages before their business request fixtures were registered. The shared `authenticated-test.js` fixture now returns a deterministic developer session and CSRF preflight response. It changes test setup only; production authentication, permissions, APIs, and asset business behavior remain unchanged.
系统 MUST 使用 BCrypt 保存密码哈希,且 MUST NOT 在数据库、API 响应、日志、审计文本或前端持久化存储中保存或回显明文密码。只有 `DEVELOPER` 可以通过系统接口设置或重置非开发者账号密码;固定开发者 `Jeddy` 的密码哈希只允许通过受控数据库操作维护。系统 MUST NOT 提供任何账号的自助改密接口或页面。
The workspace SHALL restrict the phone input to 11 digits after trimming whitespace and removing a leading `+86`. It SHALL display `cardType` as “运营商”, `realNameOwner` as “实名主体”, `managementType` as “管理模式”, `disposalStatus` as “使用状态”, and `deviceId` as “关联设备(ID)” consistently in the list and create/edit form. The form SHALL explain that the real-name subject is the individual or organization registered to the phone number, and that the device field accepts only an asset-record ID. Management type SHALL display empty for null and disposal status is required with initial value `正常使用`.
#### Scenario: User views a record in the list
-**WHEN** the workspace renders phone-asset records
-**THEN** it shows the columns “运营商、实名主体、管理模式、使用状态、关联设备(ID)” and does not expose the prior ambiguous labels for those fields
#### Scenario: User creates or edits a record
-**WHEN** a user opens the create or edit form
-**THEN** the field labels match the list names and the form explains the real-name subject and device-ID meanings without changing the submitted property names
#### Scenario: User saves confirmed dropdown values
-**WHEN** a user creates or edits using existing dropdown values
-**THEN** the workspace submits the existing `cardType`, `managementType`, and `disposalStatus` properties unchanged while the visible labels remain “运营商、管理模式、使用状态”
The frontend identifies the current user through `GET /api/auth/me`, which reads the current database row. Protected backend writes use the role stored in the signed login token. `SystemUserAdminService.updateUser` currently changes role, status, and page permissions without increasing `auth_version`; therefore an old token can retain a lower role while the UI renders the newly promoted role.
`phone-api-client.js`, `wecom-api-client.js`, and `system-user-api-client.js` use the shared authentication request helper. `device-api-client.js` uses a separate raw `fetch`, so it does not request the CSRF cookie or send the `X-XSRF-TOKEN` header. `DeviceAssetController` also lacks server-side administrator checks, unlike phone and enterprise-WeChat controllers; frontend route metadata cannot stop a direct API call. Spring Security maps both a missing CSRF token and role denial to a generic 403 response, which conceals the cause during diagnosis.
## Goals / Non-Goals
**Goals:**
- Make a role, status, or page-permission change invalidate the account's existing login token before its new authorization state is shown as usable.
- Preserve the existing fixed roles and server-side authorization checks; a developer remains the highest-privilege role but must reauthenticate after an authorization change.
- Use one authenticated request path for device GET, multipart create/update, and delete operations, including cookies and CSRF headers for every write.
- Enforce the existing administrator-only device rule in the backend for device rows, lookups, and controlled image reads.
- Remove stale browser identity on 401 and reacquire CSRF input if its Cookie is absent.
- Return a safe, actionable distinction between expired authentication, CSRF failure, and ordinary authorization denial.
**Non-Goals:**
- Do not add a database migration, create more developer accounts, weaken CSRF, or change device asset fields, upload validation, or response DTOs.
- Do not add self-service role changes or browser-stored login tokens.
- Do not modify the enterprise-WeChat or phone asset business rules.
## Decisions
### 1. Invalidate on every authorization-state mutation
`SystemUserAdminService.updateUser` will compare the persisted and requested role, status, and normalized page permissions. If any effective authorization value changes, it will increment `auth_version` in the same database update. `AuthTokenFilter` already rejects a token whose version differs from the database value, so the next request with that old cookie returns 401 and the user must sign in again.
`auth_version`(登录票据版本号:服务端用来立即作废旧登录票据的整数) is used because the role embedded in a signed JWT cannot safely be altered in-place. Relying only on `/api/auth/me` would keep the UI and API authorization sources inconsistent; rereading the role from the database on each request would reduce this mismatch but would not reliably revoke sessions after status or page-permission changes.
### 2. Reuse the existing authenticated request helper for multipart safely
Exported `request` in `auth-api-client.js` will remain the single browser request entry point. It will preserve caller headers, request CSRF only for non-GET non-login methods, and omit `Content-Type` when the body is `FormData` so the browser can supply the multipart boundary.
`device-api-client.js` will call this helper for list, lookup, create, update, delete, and image access requests as appropriate. The alternative of disabling CSRF for device endpoints is rejected because it would permit forged writes from a third-party page that can use a user's login cookie.
### 3. Keep safe 401/403 diagnostic classes
The security error handler will retain generic authorization wording for ordinary role/page denial, return a session-expired/login-required message for an invalidated token, and return a refresh-and-retry message for missing or invalid CSRF input. It will not reveal account existence, target roles, permission maps, token values, or internal exception details.
CSRF(跨站请求伪造防护:要求浏览器把服务端发出的随机值同时放在 Cookie 和请求头中) remains mandatory for all non-login writes. Without it, another website could submit a write using the user's ambient login Cookie.
### 4. Make administrator-only device access a backend rule
`PagePermissionService` will expose one reusable administrator requirement that reads the authenticated principal and accepts only `DEVELOPER` and `SUPER_ADMIN`. `DeviceAssetController` will call it before every list, lookup, image-read, create, update, and delete endpoint. This keeps the approved device access model unchanged while making the API enforce it.
Adding a new device entry to the configurable page-permission map was considered but rejected for this change. The approved product behavior is administrator-only; adding `READ`/`EDIT` assignment for ordinary roles would be a product-scope expansion and must be proposed separately.
### 5. Expire the browser view and verify actual CSRF Cookie presence
When the shared request helper receives a 401, it will signal the authentication state to clear the in-memory user and redirect to `/login`; it will not retry a potentially non-idempotent write. The CSRF helper will treat the Cookie as the source of truth: it skips initialization only when `XSRF-TOKEN` is actually present, otherwise it requests `/api/auth/csrf` again.
### 6. Test the real failure paths before declaring the fix
Backend tests will prove that a promoted user token becomes invalid, a fresh developer token can manage users, and a non-developer token remains denied. Browser tests will verify that the first device write initializes CSRF, sends cookies and the token header, preserves multipart boundaries, and shows the distinct safe error messages.
## Risks / Trade-offs
-[An administrator changes their own authorization state] -> Their next request becomes 401 by design; the frontend redirects to login with a clear reauthentication message.
-[A change increments `auth_version` unnecessarily] -> Compare the persisted role, status, and normalized permission JSON before incrementing; no-op edits retain the current session.
-[Setting JSON content type for multipart] -> Detect `FormData` and let the browser add the boundary; otherwise uploads would reach the server as malformed data.
-[Detailed 403 messages disclose security state] -> Restrict messages to three generic client actions: login again, refresh/retry, or no permission.
-[A logged-in ordinary user bypasses the device route] -> Require an administrator in every device controller endpoint, including the opaque image endpoint.
-[The CSRF Cookie disappears while the JavaScript flag remains true] -> Test Cookie absence and reacquisition rather than trusting an in-memory readiness flag.
-[Concurrent account updates] -> Use the existing single-row update path and increment from the persisted version; the current project has no optimistic-lock column, so concurrent administrator updates remain outside this targeted fix.
## Migration Plan
1. Deploy the backend and frontend together; no data migration or schema change is required.
2. Existing sessions continue until a managed account's authorization state changes. That change invalidates its previous session at the next protected request.
3. Verify a device create, multipart device update, device image read, phone write, enterprise-WeChat write, and system-user write using a fresh developer login; verify an ordinary user cannot call any device endpoint.
4. Roll back by reverting the application changes. Existing `auth_version` values and asset data remain intact; already-invalidated sessions simply need a new login.
## Open Questions
None. Reauthentication after a role, status, or page-permission change is the selected security behavior.
An account promoted to `DEVELOPER` can receive full permissions from `/api/auth/me` while its already-issued login token still contains the prior role. The UI then identifies the user as an administrator but protected writes, such as `POST /api/system-users`, return 403. Separately, device-asset writes bypass the existing CSRF protection flow, and the device controller relies on a frontend-only administrator route so any authenticated user can call its CRUD endpoints directly.
## What Changes
- Invalidate an existing login session whenever a managed account's role, enabled status, or page permissions change. The next protected request must require a fresh login so the token role and database role cannot diverge.
- Keep the current rule that `DEVELOPER` and `SUPER_ADMIN` are administrators; do not relax role checks or allow creation of extra developer accounts.
- Route every device-asset write request through the shared authenticated request helper so it includes cookies and the CSRF header, including multipart `POST` and `PUT` requests.
- Enforce the approved administrator-only device access rule in the backend for list, lookup, image read, create, update, and delete endpoints; do not rely on hidden menus or route metadata as authorization.
- Clear local browser identity and redirect to login after a 401 session-invalid response. Reacquire CSRF input whenever its Cookie is absent instead of trusting an in-memory initialization flag.
- Make 403 verification distinguish an authorization failure from a missing/invalid CSRF token in automated tests and developer diagnostics, without exposing sensitive account information.
## Capabilities
### New Capabilities
-`authorization-session-consistency`: Keeps the role and permissions used by a protected API request consistent with the currently effective account state.
-`authenticated-device-writes`: Makes device-asset create, update, and delete requests use the same authenticated CSRF-safe request flow as the existing asset modules.
### Modified Capabilities
- None. The related authentication and device specifications are still in unarchived changes rather than the repository's main OpenSpec specification set; this change records the corrective requirements as standalone capabilities.
## Impact
- Backend: `SystemUserAdminService`, `PagePermissionService`, `DeviceAssetController`, authentication-token filtering, and focused authorization tests. No database schema change or migration is required because `auth_version` already exists.
- Frontend: the shared auth request utility and state, device API client, and browser tests. The page layout, role names, and asset data contract remain unchanged.
- Security: CSRF(跨站请求伪造防护:阻止第三方网站借用登录 Cookie 发起写操作) continues to protect every non-login write. Not preserving it would make the 403 disappear at the cost of allowing forged writes.
### Requirement: Device write requests use the shared authenticated CSRF flow
The browser SHALL send device-asset create, update, and delete requests through the shared authenticated request helper. It MUST include login cookies and obtain/send the CSRF header for each protected non-login write. Multipart create and update requests MUST allow the browser to set the multipart boundary and MUST NOT force a JSON content type.
#### Scenario: Create a device with an image
-**WHEN** an authenticated administrator submits a device create form with a valid image
-**THEN** the client obtains CSRF input when needed, sends the login cookie and CSRF header with multipart form data, and the backend receives a valid device create request
#### Scenario: Update a device with multipart data
-**WHEN** an authenticated administrator edits a device name, image, or remove-image flag
-**THEN** the client sends the protected multipart update without overriding the browser-generated multipart boundary
#### Scenario: Missing device CSRF input
-**WHEN** a device write is attempted without a valid CSRF token
-**THEN** the backend rejects it before asset or file mutation and the browser shows the safe refresh-and-retry message
The device list, company-person lookup, and controlled opaque image access SHALL retain their existing request paths and response shapes. This change MUST NOT expose physical file paths, disable reference-protected deletion, or relax the existing image validation rules.
#### Scenario: Read device list after security fix
-**WHEN** an authenticated authorized user loads the device asset page
-**THEN** the client receives the existing paged list shape and renders it without a write-oriented CSRF requirement
### Requirement: Device APIs enforce administrator access on the server
The system SHALL require an authenticated `DEVELOPER` or `SUPER_ADMIN` before executing device list, company-person lookup, controlled image read, create, update, or delete operations. A non-administrator MUST receive authorization denial even when directly calling the API without using the sidebar or route.
#### Scenario: Ordinary account bypasses the device page route
-**WHEN** an authenticated non-administrator directly sends `POST`, `PUT`, or `DELETE` to `/api/device-assets`
-**THEN** the system returns authorization denial and does not write device rows or files
#### Scenario: Ordinary account reads a device attachment URL
-**WHEN** an authenticated non-administrator requests `/api/device-assets/files/{identifier}`
-**THEN** the system returns authorization denial and does not stream the image
#### Scenario: Administrator uses any device endpoint
-**WHEN** an authenticated `DEVELOPER` or `SUPER_ADMIN` calls a device list, lookup, image, or CRUD endpoint with otherwise valid input
-**THEN** the server permits the request to continue to the existing device service behavior
### Requirement: Authorization-state changes revoke stale login sessions
The system SHALL increment `auth_version` whenever a managed account's effective role, enabled status, or validated page-permission map changes. A token whose embedded version differs from the current account version MUST be rejected before a protected controller or service method executes.
#### Scenario: Promoted account uses an old token
-**WHEN** an active account is changed from a non-administrator role to `DEVELOPER` or `SUPER_ADMIN` while it still holds an earlier login token
-**THEN** its next protected request is rejected as requiring a fresh login, and a new login receives a token with the current role
#### Scenario: Permission map changes without a role change
-**WHEN** an administrator changes an account's validated page permissions or enabled status
-**THEN** the prior token is rejected on its next protected request and the account's next login uses the new effective permissions and status
#### Scenario: No-op account edit
-**WHEN** an administrator saves an account with the same effective role, status, and validated page permissions
-**THEN** the account's `auth_version` is unchanged and its current session remains valid
### Requirement: Protected write failures provide safe actionable categories
The system SHALL preserve generic authorization denial for a valid authenticated user without the required role or page permission. It SHALL return a login-required response for an invalidated or absent session, and a refresh-and-retry response for a missing or invalid CSRF token. Responses MUST NOT disclose token values, account existence, roles, or permission maps.
#### Scenario: Valid non-administrator creates an account
-**WHEN** a valid non-administrator token submits `POST /api/system-users`
-**THEN** the system returns a generic authorization-denied response and does not write an account row
#### Scenario: Browser omits CSRF input
-**WHEN** an authenticated browser sends a protected non-login write without a valid CSRF token
-**THEN** the system returns a safe refresh-and-retry response and does not execute the controller business method
### Requirement: Browser session state follows authentication failure
The shared browser request helper SHALL clear the in-memory authenticated user and navigate to the login route when a protected request returns 401. It MUST NOT automatically repeat the failed request. It SHALL request CSRF input again whenever the `XSRF-TOKEN` Cookie is absent for a protected non-login write.
#### Scenario: Authorization change invalidates the active browser session
-**WHEN** an already-open browser sends a protected request with a token invalidated by an authorization-state change
-**THEN** the browser clears its local identity, navigates to login, and does not retry the failed write
#### Scenario: CSRF Cookie is missing after prior initialization
-**WHEN** a protected browser write begins after the `XSRF-TOKEN` Cookie has been removed or expired
-**THEN** the client requests fresh CSRF input before sending the write
| `backend/src/main/java/com/xyw/console/auth/SystemUserAdminService.java` | Changes an account and invalidates its old login only when effective authorization changes. | Authentication change; coordinate with current account-permission work. |
| `backend/src/main/java/com/xyw/console/config/SecurityConfig.java` | Maps security-layer authentication, CSRF, and authorization failures to safe browser responses. | Authentication change; do not relax CSRF or global protection. |
| `backend/src/main/java/com/xyw/console/asset/controller/DeviceAssetController.java` | Requires an administrator before list, lookup, image, and CRUD device requests reach the service. | Device integration; preserve service and DTO contracts. |
| `backend/src/test/java/com/xyw/console/auth/SystemUserAdminServiceTest.java` | Proves version changes and account-management role boundaries without a live database. | New focused test file. |
| `backend/src/test/java/com/xyw/console/config/SecurityConfigTest.java` | Proves safe response categories for expired sessions, CSRF failures, and ordinary permission denial. | New focused test file. |
| `backend/src/test/java/com/xyw/console/asset/controller/DeviceAssetControllerTest.java` | Proves administrators pass and ordinary accounts cannot bypass device API protection. | Extend existing device test file. |
| `frontend/src/modules/auth/auth-api-client.js` | Is the shared browser helper that sends cookies, fetches CSRF input, and preserves multipart requests. | Authentication change; all API clients depend on it. |
| `frontend/src/modules/auth/auth-store.js` | Clears browser identity and navigates to login after a shared request reports an invalid session. | Authentication change; avoid circular module dependencies. |
| `frontend/src/modules/device/device-api-client.js` | Sends device list, lookup, CRUD, and image requests through the shared authenticated helper. | Device integration only; do not change device view behavior. |
| `frontend/tests/auth-session-consistency.spec.js` | Exercises stale-session, CSRF, and multipart browser request behavior. | New focused browser test. |
| `frontend/tests/device-asset.spec.js` | Extends device workflow tests with real request-header and multipart-boundary assertions. | Device integration test; preserve existing scenarios. |
-[x] 0.1 Confirm the active authentication and device working-tree changes, preserve unrelated edits, and designate one integration owner for the four shared files above. Integration owner: current authorization/session change.
-[x] 0.2 Add a plain-language file-purpose annotation to each new test file. For every method, callback, or helper added or changed in the scoped files, add a beginner comment describing its purpose, related files, and request-to-security-to-response data flow.
-[x] 0.3 Do not run a database migration or modify `as_system_user` schema; verify `auth_version` is already readable and writable before implementation.
## 1. Authorization-session consistency
-[x] 1.1 Add focused helpers in `SystemUserAdminService` that compare persisted and requested role, status, and normalized page permissions, and calculate the next `auth_version` safely from the persisted value.
-[x] 1.2 Update `SystemUserAdminService.updateUser` so a true authorization-state change increments `auth_version` in the same row update, while a no-op update does not invalidate the current session.
-[x] 1.3 Keep existing developer/super-administrator creation boundaries unchanged; a fresh `DEVELOPER` token can manage accounts, a valid non-administrator token remains denied, and no additional developer account can be created.
-[x] 1.4 Update `SecurityConfig` to return safe distinct messages for invalidated/absent login sessions, missing-or-invalid CSRF input, and valid-session authorization denial, without returning account, role, permission, token, or exception details.
-[x] 1.5 Add and annotate a reusable administrator requirement in `PagePermissionService`; apply it in every `DeviceAssetController` endpoint, including company-person lookup and opaque image reads, while preserving the approved administrator-only device scope.
## 2. Shared device write authentication
-[x] 2.1 Update `auth-api-client.js` request handling to preserve caller headers, send credentials, initialize CSRF for protected writes, and omit a forced JSON `Content-Type` when the request body is `FormData`.
-[x] 2.1a Change CSRF initialization to verify the real `XSRF-TOKEN` Cookie instead of trusting an in-memory ready flag, and have a 401 signal `auth-store.js` to clear local identity and navigate to login without repeating the request.
-[x] 2.2 Refactor every `device-api-client.js` operation to use the shared authenticated request helper; retain existing URLs, query parameter behavior, and API response shape.
-[x] 2.3 Verify device multipart create and update retain the browser-generated boundary, and device delete includes the CSRF header and cookie.
-[x] 2.4 Ensure a 401 session-invalid response clears local authentication state and leads the user to sign in again; do not silently retry a write that might repeat a user action.
## 3. Focused verification
-[x] 3.1 Add `SystemUserAdminServiceTest` coverage for role promotion, status change, permission change, no-op update, developer management success, and non-administrator denial. Annotate every test method and business callback.
-[x] 3.2 Add `SecurityConfigTest` coverage for 401 expired-session handling, CSRF 403 handling, ordinary authorization 403 handling, and the absence of sensitive details. Extend `DeviceAssetControllerTest` with administrator allow and ordinary-user denial coverage for list, lookup, image, and all CRUD operations. Annotate every test method and callback.
-[x] 3.3 Add browser coverage for the first device write obtaining CSRF, Cookie-absence reacquisition, multipart request header/boundary behavior, device delete CSRF behavior, 401 login redirect, and the safe refresh-and-retry message. Annotate every test method and route callback.
-[x] 3.4 Run `mvn -q test`, `npm run build`, focused authentication/device Playwright tests, and the existing full frontend Playwright suite. Diagnose a failing check before changing code.
-[x] 3.5 Record changed files, no-database-impact confirmation, security behavior, executed verification, and any unexecuted check in the completion handoff.
## Completion handoff (2026-08-03)
### Changed files
- Backend authorization: `SystemUserAdminService`, `PagePermissionService`, `SecurityConfig`, and `DeviceAssetController`.
- Backend tests: `SystemUserAdminServiceTest`, `SecurityConfigTest`, and extended `DeviceAssetControllerTest`.
- Frontend authentication/device requests: `auth-api-client.js`, `auth-store.js`, and `device-api-client.js`.
- Frontend tests: `authenticated-test.js` and `device-asset.spec.js`.
### Security behavior delivered
- Account role, status, or normalized page-permission changes increment `auth_version`; the previous token fails on its next protected request and the browser returns to login.
- Device list, lookup, opaque image read, create, update, and delete operations require `DEVELOPER` or `SUPER_ADMIN` in the backend, not only a protected route or hidden menu.
- Device multipart writes use the shared authenticated CSRF request helper. The helper preserves browser multipart boundaries, reacquires a missing `XSRF-TOKEN` Cookie, and does not retry a rejected write.
- No database migration, DDL, direct database write, or configuration-file change was performed. The existing `auth_version` entity field and migration definition are reused.
- No live production write was performed. After backend restart, manually log out and log in once, then verify account creation and device create/update/delete using the developer account.
`origin/master` at `27c7b6d` contains device, phone, and WeCom management but not the authentication module. The source chain is `1cd0087`, `9d5953f`, `3f7c655`, `52071f0`, and `7a452f3`; `52071f0` changes only a historical OpenSpec document and is intentionally excluded under the approved documentation policy.
### Scope-file purpose notes
| Files | Purpose |
|---|---|
| `backend/src/main/java/com/xyw/console/auth/**` and `config/{SecurityConfig,WebConfig}.java` | Login, token/session validation, authorization, system-user administration, and HTTP security policy. |
| `backend/src/main/resources/db/migration/V1__system_user_auth_permissions.sql` | Adds permission/session fields required by authentication. |
| `backend/src/main/java/com/xyw/console/asset/**` changed files | Applies authentication and CSRF checks to existing asset APIs. |
| Changed device, phone, WeCom, system-user frontend files and test files | Keep protected workspaces compatible and prove regressions are avoided. |
| `openspec/changes/integrate-authentication-to-master/**` | Sole planning record for this integration. |
## Goals / Non-Goals
**Goals:**
- Use a neutral first paint while the router validates the session.
- Send unauthenticated visitors to login without showing a protected shell.
- Keep a network failure at the requested URL with a retry path.
- Preserve asset management workflows for permitted users.
**Non-Goals:**
- Do not push, merge to `master`, deploy, or run a database migration without a further user approval.
- Do not include historical source-branch OpenSpec records.
- Do not redesign unrelated asset features.
## Decisions
1. Integrate four runtime commits on a new branch based on `origin/master`; omit `52071f0` because it affects only excluded historical documentation.
2. The router owns the initial current-user decision. The API client handles later expired sessions, avoiding competing redirects.
3.`index.html` provides a neutral boot screen and `main.js` waits for router readiness, avoiding the dashboard-to-login flash.
4. Backend and frontend are a paired release. A database without production data lowers data-risk now, but the inserted administrator's password hash, role, and page permissions must match backend rules before release.
5. Every new or modified method, object method, arrow function, and business callback in scope retains a plain-language comment describing its purpose, related files, and call/data flow.
## Risks / Trade-offs
-[Administrator cannot log in] -> verify password hash, administrator role, and page permissions against the implemented backend before release.
-[Asset regression] -> retain current `master` behavior and run device, phone, WeCom, and frontend regression suites.
-[Refresh flash returns] -> preserve the neutral boot contract and run the dedicated browser checks.
-[Session/CSRF mismatch] -> test authenticated and rejected writes in backend and browser suites.
-[Future production migration] -> approve migration execution separately; application rollback must not blindly delete user/role/permission data.
## Migration Plan
1. Use the isolated integration branch and verify its test evidence.
2. Before any release, insert a verified administrator using the backend-compatible password hash, role, and permissions.
3. Review and run the database migration only after explicit approval.
4. Deploy matching backend and frontend artifacts together.
5. If authentication or core asset behavior fails, roll back application artifacts together; preserve database data for review.
## Open Questions
1. Which exact administrator username, password hash process, role, and page permissions will be inserted before the first release?
2. When the branch is reviewed, do you approve a remote push and a new MR? Those actions remain out of scope for the current execution.
Authentication is a dependent feature chain outside `master`. The refresh-only fix cannot work on its own because `master` lacks the login, session, permission, and database code it needs.
## What Changes
- Integrate the authentication foundation, session/CSRF correction, login UI, and refresh redirect fix on a branch based on current `master`.
- Retain current device, phone, and WeCom asset behavior while adding authentication and page permissions.
- Exclude historical source-branch OpenSpec records; retain only this integration plan.
-**BREAKING**: management pages require a valid authenticated session after release.
## Capabilities
### New Capabilities
-`authentication-master-integration`: safe integration, verification, and release readiness for the full authentication feature.
### Modified Capabilities
<!-- None. -->
## Impact
- Backend authentication, authorization, CSRF protection, system-user data, and the related migration.
The integration SHALL include the authentication foundation, session/CSRF correction, login experience, and refresh redirect fix as one feature branch based on current `master`. Historical source-branch OpenSpec records MUST NOT be present in the final integration diff.
#### Scenario: Integration branch is prepared
-**WHEN** maintainers inspect the integration branch
-**THEN** it contains the required runtime code and tests but excludes historical source-branch OpenSpec records
### Requirement: Safe initial routing
The frontend SHALL show an identity-neutral startup state until initial session validation completes. The router MUST own the initial current-user route decision. An unauthenticated visitor MUST NOT see a protected workspace before reaching login.
#### Scenario: Unauthenticated protected refresh
-**WHEN** an unauthenticated visitor refreshes a protected URL
-**THEN** the neutral boot state is shown before the login route, without a dashboard shell or dashboard-shaped placeholder
#### Scenario: Initial network failure
-**WHEN** initial current-user validation fails from a network error rather than a 401 response
-**THEN** the browser remains on the requested URL and provides a retry state
### Requirement: Protected asset compatibility
Permitted authenticated users SHALL retain their device, phone, WeCom, and system-user workflows. Unauthenticated or unauthorized protected writes MUST be rejected by the backend security policy.
#### Scenario: Permitted protected write
-**WHEN** a permitted signed-in user performs an allowed asset write with valid CSRF data
-**THEN** the backend accepts the operation
#### Scenario: Rejected protected write
-**WHEN** a visitor lacks a valid session or required permission for a protected write
-**THEN** the backend rejects the operation and the frontend does not report success
-[x] 1.1 Confirm `origin/master` base `27c7b6d` and source commits `1cd0087`, `9d5953f`, `3f7c655`, `52071f0`, and `7a452f3`.
-[x] 1.2 Confirm that the database has no production data, no release is scheduled, and the first administrator will be inserted directly into the database before release.
-[x] 1.3 Create an isolated branch from current `master` without changing source branches or the user's dirty worktree.
-[x] 1.4 Exclude historical source-branch OpenSpec records; retain only this integration plan.
## 2. Runtime integration
-[x] 2.1 Apply `1cd0087`, `9d5953f`, `3f7c655`, and `7a452f3` in dependency order.
-[x] 2.2 Omit `52071f0` because it changes only historical documentation excluded by task 1.4.
-[x] 2.3 Confirm no code conflicts occurred with current device, phone, or WeCom changes.
-[x] 2.4 Retain the required beginner comments for new or modified backend and frontend methods/callbacks.
## 3. Verification
-[x] 3.1 Run the backend test suite.
-[x] 3.2 Run the frontend production build.
-[x] 3.3 Run the dedicated login and refresh browser tests.
-[x] 3.4 Run the complete frontend browser regression suite for device, phone, WeCom, and legacy pages.
-[x] 3.5 Review the final staged diff for accidental source documents, secrets, generated files, and omitted runtime files.
## 4. Deferred release actions
-[] 4.1 Verify the first inserted administrator's password hash, role, and page permissions against the backend implementation.
-[] 4.2 Obtain explicit approval before pushing this branch, creating an MR, merging to `master`, deploying, or running the database migration.