# Database Guidelines

> MySQL data and DAO conventions for the library-management system.

---

## Overview

MySQL is the project data layer. DAO classes perform CRUD and query operations
against MySQL. The initial scaffold schema exists at
`src/main/resources/db/schema.sql`; future module tables should follow the same
DDL style and DAO boundaries.

---

## Core Tables

Use primary keys for every table and foreign keys for cross-entity integrity.

Implemented scaffold tables:

- `roles`: administrator, librarian, reader, and future role definitions.
- `permissions`: permission definitions for protected actions.
- `role_permissions`: role-to-permission mapping.
- `users`: login accounts for administrator, librarian, and reader roles.
- `system_logs`: key operation logs, backup events, and exception traces.
- `book_categories`: category names and descriptions for catalog grouping.
- `books`: book information, category reference, inventory counts, and catalog
  status.
- `readers`: reader profiles, optional login-account linkage, borrowing
  eligibility, contact information, and management status.
- `borrow_records`: book-reader borrowing, return, renew, and overdue data.

Record new schema changes in `src/main/resources/db/schema.sql` and update this
spec with exact table names, key columns, and DAO/service contracts.

---

## DAO Responsibilities

- DAOs own database CRUD and query details.
- Use parameterized SQL or prepared-statement style access; never concatenate
  raw request parameters into SQL.
- Keep transaction boundaries in the service layer for workflows that span
  multiple DAO calls, such as borrow/return operations that also update
  inventory status.
- Return entities or small query result objects to services, not HTML or
  servlet response objects.
- Keep MySQL connection details in `src/main/resources/db.properties` loaded by
  `JdbcUtil`. The required keys are `db.driver`, `db.url`, `db.username`, and
  `db.password`.

---

## Query Guidance

- Book search must support combined lookup by title, author, category, and ID.
- Statistics queries should cover borrowing rankings, inventory reports, and
  overdue reports.
- Borrowing records should preserve enough dates/status fields for borrow,
  return, renew, overdue calculation, and automatic collection status updates.
- Permission queries should support role-based checks for administrator,
  librarian, and reader workflows.

---

## Integrity Constraints

- `books.category_id` should reference `book_categories`.
- `borrow_records.book_id` should reference `books`.
- `borrow_records.reader_id` should reference `readers`.
- Administrator-role and role-permission mapping tables should use foreign keys
  to preserve authorization integrity.
- `users.role_code` must reference `roles.code`.
- `role_permissions.role_code` must reference `roles.code`.
- `role_permissions.permission_code` must reference `permissions.code`.
- `books.status` must match `BookStatus` enum codes: `available`,
  `unavailable`, and `archived`.
- `readers.user_id` may reference `users.id` when a reader profile is linked to
  a login account.
- `readers.status` must match `ReaderStatus` enum codes: `active`,
  `suspended`, and `inactive`.
- `readers.max_borrow_count` must stay between 1 and 50.

## Scenario: Book Catalog And Management Slice

### 1. Scope / Trigger

- Trigger: the first concrete business module introduced catalog search and
  basic book management across MySQL, DAO, service, Servlet, and JSP layers.
- Schema path: `src/main/resources/db/schema.sql`.
- JSP paths: `WEB-INF/jsp/books/catalog.jsp`, `books/manage.jsp`, and
  `books/form.jsp`.

### 2. Signatures

- DAO signatures: `BookDao.findAllCategories()`,
  `BookDao.search(BookSearchCriteria criteria)`, `findById(long id)`,
  `findByIdentifier(String identifier)`, `create(Book book)`,
  `update(Book book)`, and `delete(long id)`.
- Entity/search signatures: `Book` fields are `id`, `identifier`, `title`,
  `author`, `categoryId`, `categoryName`, `totalCopies`, `availableCopies`,
  `status`, `createdAt`, and `updatedAt`; `BookSearchCriteria` fields are
  `identifier`, `title`, `author`, and nullable `categoryId`.
- Service signatures: `BookService.listCategories()`,
  `searchBooks(BookSearchCriteria criteria)`, `findBook(long id)`,
  `createBook(AuthenticatedUser actor, Book book)`,
  `updateBook(AuthenticatedUser actor, Book book)`, and
  `deleteBook(AuthenticatedUser actor, long id)`, all returning
  `ServiceResult<T>`.
- Read route: `GET /catalog` with optional `identifier`, `title`, `author`,
  and `categoryId` query fields.
- Management routes: `GET /books`, `GET /books/new`, `GET /books/edit?id=...`,
  `POST /books`, `POST /books/update`, and `POST /books/delete`.
- Protected permissions: `/catalog` requires `VIEW_CATALOG`; `/books*`
  requires `MANAGE_BOOKS`.
- DB signatures:
  - `book_categories(id, name, description, created_at, updated_at)`, with
    unique key `uk_book_categories_name(name)`.
  - `books(id, book_identifier, title, author, category_id, total_copies,
    available_copies, status, created_at, updated_at)`, with unique key
    `uk_books_identifier(book_identifier)`, indexes on title, author, category,
    and status, foreign key `fk_books_category`, and checks for non-negative
    copy counts and allowed status values.

### 3. Contracts

- `book_categories.name` is unique and displayed through category selectors.
- `books.book_identifier` is the unique user-facing book ID.
- `books.category_id` references `book_categories.id`.
- `books.total_copies` and `books.available_copies` are non-negative, and
  available copies cannot exceed total copies.
- `books.status` stores the Java `BookStatus` code exactly.
- Servlet controllers parse request fields and set JSP attributes such as
  `criteria`, `categories`, `books`, `book`, `statuses`, `errors`,
  `errorMessage`, and `successMessage`.
- `ServiceResult<T>` is the service-to-controller response contract:
  `successful`, nullable `data`, nullable `message`, and field-level
  `errors`. Controllers must pass validation errors to JSPs so form/search
  redisplay can highlight the exact field, for example `errors.categoryId`.
- JSP pages render JavaBean properties only; they must not call DAOs or embed
  SQL.

### 4. Validation & Error Matrix

- Missing identifier, title, author, category, copy counts, or status -> return
  to `books/form.jsp` with field errors.
- Negative total or available copies -> return a field error.
- Available copies greater than total copies -> return
  `Available copies cannot exceed total copies.`
- Duplicate `book_identifier` -> return a field error on `identifier`.
- Reader or unauthenticated actor attempts write -> HTTP 403 through
  authorization filter or service denial.
- DAO failure during list/search/write -> log server-side details and return
  `Book service is temporarily unavailable. Please try again later.`
- Successful create/update/delete -> redirect to `/books` with a short flash
  message.

### 5. Good/Base/Bad Cases

- Good: a librarian creates `BK-1002`, sees it in `/books`, and readers can
  find it from `/catalog` without write controls.
- Base: catalog search with no filters lists available records ordered by
  title, author, and identifier.
- Bad: a JSP opens JDBC, builds SQL from request parameters, or renders a stack
  trace from a failed DAO call.

### 6. Tests Required

- Run `BookServiceCheck` or equivalent assertions for invalid inventory,
  duplicate identifiers, reader write denial, successful librarian CRUD, search,
  and DAO failure fallback.
- Run `PermissionPolicyCheck` to confirm readers lack `MANAGE_BOOKS` and retain
  `VIEW_CATALOG`.
- Scan JSPs for scriptlets and SQL/JDBC references.
- When Maven/Tomcat dependencies are installed, run `mvn clean package` to
  compile Servlets and package JSP resources.

### 7. Wrong vs Correct

#### Wrong

```text
books/form.jsp -> JDBC -> INSERT INTO books using request parameters
```

#### Correct

```text
books/form.jsp -> BookManagementServlet -> BookService -> BookDao -> books
```

## Scenario: Book Category Maintenance Slice

### 1. Scope / Trigger

- Trigger: category maintenance completes the book-management core requirement
  by adding staff-managed CRUD for `book_categories`, while existing book forms
  and catalog searches continue to consume the same category source.
- Schema path: `src/main/resources/db/schema.sql`.
- JSP paths: `WEB-INF/jsp/books/categories.jsp` and
  `WEB-INF/jsp/books/category-form.jsp`.

### 2. Signatures

- DAO signatures: `BookDao.findAllCategories()`, `findCategoryById(long id)`,
  `findCategoryByName(String name)`, `createCategory(BookCategory category)`,
  `updateCategory(BookCategory category)`, `deleteCategory(long id)`, and
  `countBooksByCategoryId(long categoryId)`.
- Entity signature: `BookCategory(id, name, description)`.
- Service signatures: `BookService.listCategories()`,
  `findCategory(long id)`, `createCategory(AuthenticatedUser actor,
  BookCategory category)`, `updateCategory(AuthenticatedUser actor,
  BookCategory category)`, and `deleteCategory(AuthenticatedUser actor,
  long id)`, all returning `ServiceResult<T>`.
- Routes: `GET /book-categories`, `GET /book-categories/new`,
  `GET /book-categories/edit?id=...`, `POST /book-categories`,
  `POST /book-categories/update`, and `POST /book-categories/delete`.
- Protected permission: `/book-categories*` requires `MANAGE_BOOKS`.

### 3. Contracts

- `book_categories.name` is unique and is the display value used in book forms,
  catalog filters, and management filters.
- `book_categories.description` is optional and limited to the database column
  size.
- Book category deletes must check `books.category_id` usage before deletion
  and return a safe validation result when the category is in use.
- Servlet controllers set JSP attributes such as `categories`, `category`,
  `formTitle`, `formAction`, `formValues`, `errors`, `errorMessage`, and
  `successMessage`.
- JSP pages render JavaBean properties only; they must not call DAOs or embed
  SQL.

### 4. Validation & Error Matrix

- Missing category name -> field error on `name`.
- Category name longer than 96 characters -> field error on `name`.
- Description longer than 255 characters -> field error on `description`.
- Duplicate category name -> field error on `name`.
- Missing or non-positive category id for edit/delete -> `Select a valid
  category.`
- Delete category used by any `books` row -> `Category is used by existing
  books and cannot be deleted.`
- Reader or unauthenticated actor attempts mutation -> permission denial through
  filter/service.
- DAO failure during list/search/write -> log server-side details and return
  `Book service is temporarily unavailable. Please try again later.`

### 5. Good/Base/Bad Cases

- Good: a librarian creates `Architecture`, selects it on a book form, and sees
  it in catalog filters.
- Base: `/book-categories` lists seed categories ordered by name.
- Bad: deleting a category with existing books surfaces a MySQL foreign-key
  stack trace or lets JSP code perform the delete.

### 6. Tests Required

- Run `BookServiceCheck` assertions for reader category-write denial, duplicate
  category names, successful create/update/delete, and used-category delete
  rejection.
- Run `PermissionPolicyCheck` to confirm `MANAGE_BOOKS` remains staff-only.
- Scan category JSPs for scriptlets and SQL/JDBC references.
- When Maven/Tomcat dependencies are installed, run `mvn clean package` to
  compile Servlets and package JSP resources.

### 7. Wrong vs Correct

#### Wrong

```text
categories.jsp -> JDBC -> DELETE FROM book_categories WHERE id = request.id
```

#### Correct

```text
categories.jsp -> BookManagementServlet -> BookService -> BookDao -> book_categories
```

## Scenario: Reader Information Management Slice

### 1. Scope / Trigger

- Trigger: the reader profile foundation was implemented after login,
  permissions, catalog search, and book management.
- Schema path: `src/main/resources/db/schema.sql`.
- JSP paths: `WEB-INF/jsp/readers/manage.jsp` and `readers/form.jsp`.

### 2. Signatures

- DAO signatures: `ReaderDao.search(ReaderSearchCriteria criteria)`,
  `findById(long id)`, `findByIdentifier(String identifier)`,
  `findByUserId(long userId)`, `create(Reader reader)`,
  `update(Reader reader)`, and `deactivate(long id)`.
- Entity/search signatures: `Reader` fields are `id`, `identifier`,
  nullable `userId`, nullable `username`, `fullName`, `phone`, `email`,
  `status`, `maxBorrowCount`, `createdAt`, and `updatedAt`;
  `ReaderSearchCriteria` fields are `identifier`, `name`, `contact`, and
  `statusCode`.
- Status signature: `ReaderStatus` enum codes are `active`, `suspended`, and
  `inactive`.
- Service signatures: `ReaderService.searchReaders(ReaderSearchCriteria)`,
  `findReader(long id)`, `createReader(AuthenticatedUser actor, Reader reader)`,
  `updateReader(AuthenticatedUser actor, Reader reader)`, and
  `deactivateReader(AuthenticatedUser actor, long id)`, all returning
  `ServiceResult<T>`.
- Management routes: `GET /readers`, `GET /readers/new`,
  `GET /readers/edit?id=...`, `POST /readers`, `POST /readers/update`, and
  `POST /readers/delete`.
- Protected permission: `/readers*` requires `MANAGE_READERS`.
- DB signature: `readers(id, reader_identifier, user_id, full_name, phone,
  email, status, max_borrow_count, created_at, updated_at)`, with unique keys
  on `reader_identifier` and nullable `user_id`, indexes on name/contact/status,
  a foreign key to `users(id)`, and checks for supported status values and
  borrow-limit range.

### 3. Contracts

- `readers.reader_identifier` is the unique user-facing reader ID.
- `readers.user_id` is optional; when present, only one reader profile may link
  to the same login account.
- `readers.status` stores the Java `ReaderStatus` code exactly. Soft-delete
  behavior deactivates the profile by setting status to `inactive`.
- Reader searches support partial identifier, full-name, and phone/email
  contact matching plus exact status filtering.
- Servlet controllers parse request fields and set JSP attributes such as
  `criteria`, `readers`, `reader`, `statuses`, `formValues`, `errors`,
  `errorMessage`, and `successMessage`.
- JSP pages render JavaBean properties only; they must not call DAOs or embed
  SQL.
- Navigation should expose reader management to administrator/librarian users,
  matching the current `MANAGE_READERS` policy.

### 4. Validation & Error Matrix

- Missing identifier or full name -> return to `readers/form.jsp` with field
  errors.
- Missing both phone and email -> return a field error on `phone`.
- Invalid phone or email format -> return field errors on `phone` or `email`.
- Unsupported status -> return a field error on `status`.
- Max borrow count outside 1 to 50 -> return a field error on
  `maxBorrowCount`.
- Non-positive linked `user_id` -> return a field error on `userId`.
- Duplicate `reader_identifier` -> return a field error on `identifier`.
- Duplicate linked `user_id` -> return a field error on `userId`.
- Reader or unauthenticated actor attempts write -> HTTP 403 through
  authorization filter or service denial.
- DAO failure during list/search/write -> log server-side details and return
  `Reader service is temporarily unavailable. Please try again later.`
- Successful create/update/deactivate -> redirect to `/readers` with a short
  flash message.

### 5. Good/Base/Bad Cases

- Good: a librarian creates `RD-1003`, searches by email, edits the borrow
  limit, and can later deactivate the profile without deleting future borrowing
  history.
- Base: `/readers` with no filters lists reader records ordered by full name
  and reader identifier.
- Bad: normal readers access `/readers`, a JSP opens JDBC, or deleting a reader
  profile removes rows that future borrow records need to reference.

### 6. Tests Required

- Run `ReaderServiceCheck` or equivalent assertions for invalid contact,
  invalid borrow limit, duplicate identifiers, duplicate linked users, reader
  write denial, successful librarian CRUD-like operations, search, deactivate,
  and DAO failure fallback.
- Run `PermissionPolicyCheck` to confirm librarians have `MANAGE_READERS` and
  readers do not.
- Scan reader JSPs for scriptlets and SQL/JDBC references.
- When Maven/Tomcat dependencies are installed, run `mvn clean package` to
  compile Servlets and package JSP resources.

### 7. Wrong vs Correct

#### Wrong

```text
readers/form.jsp -> JDBC -> DELETE FROM readers using request parameters
```

#### Correct

```text
readers/form.jsp -> ReaderManagementServlet -> ReaderService -> ReaderDao -> readers.status = inactive
```

## Scenario: Borrowing Circulation Management Slice

### 1. Scope / Trigger

- Trigger: borrowing circulation now spans `borrow_records`, book inventory,
  reader eligibility, Servlet routes, service transactions, DAO locks, and JSP
  management/history screens.
- Schema path: `src/main/resources/db/schema.sql`.
- JSP paths: `WEB-INF/jsp/borrowing/manage.jsp`,
  `borrowing/form.jsp`, and `reader/loans.jsp`.

### 2. Signatures

- Entity signatures: `BorrowRecord` fields are `id`, `readerId`,
  `readerIdentifier`, `readerName`, `bookId`, `bookIdentifier`, `bookTitle`,
  `borrowedAt`, `dueAt`, nullable `returnedAt`, `renewalCount`, `status`,
  `createdAt`, and `updatedAt`.
- Status signature: `BorrowRecordStatus` enum codes are `active` and
  `returned`; overdue is derived from active, non-returned rows where
  `due_at < CURRENT_TIMESTAMP`.
- Search signature: `BorrowRecordSearchCriteria(readerIdentifier,
  bookIdentifier, statusCode)` where `statusCode` may be empty, `active`,
  `returned`, or the derived filter `overdue`.
- DAO signatures: `BorrowRecordDao.search(criteria)`,
  `findByReaderId(readerId)`, `findReaderByUserId(userId)`,
  `findReaderByIdentifierForUpdate(connection, identifier)`,
  `findBookByIdentifierForUpdate(connection, identifier)`,
  `findByIdForUpdate(connection, id)`, `countActiveByReaderId(connection,
  readerId)`, `create(connection, record)`, `decrementAvailableCopies(...)`,
  `incrementAvailableCopies(...)`, `markReturned(...)`, and `renew(...)`.
- Service signatures: `BorrowingService.searchRecords(actor, criteria)`,
  `borrowBook(actor, readerIdentifier, bookIdentifier)`,
  `returnBook(actor, recordId)`, `renewLoan(actor, recordId)`, and
  `listCurrentReaderHistory(actor)`, all returning `ServiceResult<T>`.
- Management routes: `GET /borrowing`, `GET /borrowing/new`,
  `POST /borrowing/create`, `POST /borrowing/return`, and
  `POST /borrowing/renew`.
- Reader route: `GET /reader/loans`.
- Protected permissions: `/borrowing*` requires `MANAGE_BORROWING`;
  `/reader/loans` requires `BORROW_BOOKS` and the `reader` role because it
  displays only the signed-in reader's own history.
- DB signature: `borrow_records(id, reader_id, book_id, borrowed_at, due_at,
  returned_at, renewal_count, status, created_at, updated_at)`, with foreign
  keys to `readers(id)` and `books(id)`, indexes on reader, book, status, and
  due date, and checks for non-negative renewal count and supported statuses.

### 3. Contracts

- Borrow operations require an active reader and a borrowable book with
  `BookStatus.AVAILABLE` and `available_copies > 0`.
- Reader active-loan count is rows with `status = active` and `returned_at IS
  NULL`; overdue rows still count toward `max_borrow_count`.
- Borrowing creates one `borrow_records` row and decrements
  `books.available_copies` in the same transaction.
- Returning an active loan sets `status = returned`, stores `returned_at`, and
  increments `books.available_copies` with a cap at `total_copies` in the same
  transaction.
- Renewing an active loan extends `due_at`, increments `renewal_count`, and is
  limited to one renewal per MVP loan.
- `JdbcUtil.executeInTransaction` is the local transaction helper for
  multi-table borrowing workflows. Services decide the workflow boundary; DAOs
  own SQL and row-lock statements.
- Demo `readers` and `books` seed rows must not overwrite existing rows during
  schema replay, because resetting reader eligibility or `available_copies` can
  corrupt live borrowing state.
- Servlet controllers set JSP attributes such as `criteria`, `statuses`,
  `overdueStatus`, `maxRenewals`, `borrowRecords`, `formValues`, `errors`,
  `errorMessage`, and `successMessage`.
- JSP pages render JavaBean properties only; they must not call DAOs or embed
  SQL.

### 4. Validation & Error Matrix

- Missing reader ID or book ID -> return to `borrowing/form.jsp` with field
  errors.
- Unknown reader -> field error on `readerIdentifier`.
- Inactive or suspended reader -> field error on `readerIdentifier`.
- Reader at or above `max_borrow_count` active loans -> field error on
  `readerIdentifier`.
- Unknown book -> field error on `bookIdentifier`.
- Unavailable, archived, or zero-copy book -> field error on `bookIdentifier`.
- Missing or non-positive record ID for return/renew -> flash error.
- Returning or renewing a returned loan -> validation failure on `status`.
- Renewing after the renewal limit -> validation failure on `renewalCount`.
- DAO/transaction failure -> log server-side details and return
  `Borrowing service is temporarily unavailable. Please try again later.`

### 5. Good/Base/Bad Cases

- Good: a librarian creates a loan for active reader `RD-1000` and available
  book `BK-1000`; the loan appears in `/borrowing` and book availability is
  decremented.
- Base: `/borrowing?status=overdue` lists active, non-returned loans past due
  without requiring a scheduler or stored overdue status.
- Bad: a Servlet updates `books.available_copies` outside the borrowing
  transaction or a JSP issues SQL to filter loan records.

### 6. Tests Required

- Run `BorrowingServiceCheck` assertions for permission denial, inactive
  readers, unavailable/no-copy books, max active loan count, successful borrow,
  return inventory restoration, one-renewal limit, overdue search, reader loan
  history, and DAO failure fallback.
- Run `PermissionPolicyCheck` to confirm readers have `BORROW_BOOKS`, readers
  lack `MANAGE_BORROWING`, and librarians lack reader self-borrow permission.
  Service or filter checks should also confirm staff use `/borrowing`, not the
  reader-only `/reader/loans` history route.
- Scan JSPs for scriptlets and SQL/JDBC references.
- When Maven/Tomcat dependencies are installed, run `mvn clean package` to
  compile Servlets and package JSP resources.

### 7. Wrong vs Correct

#### Wrong

```text
borrowing/manage.jsp -> JDBC -> UPDATE books SET available_copies = ...
```

#### Correct

```text
borrowing/form.jsp -> BorrowingManagementServlet -> BorrowingService -> BorrowRecordDao -> borrow_records + books in one transaction
```

## Scenario: Report Center Slice

### 1. Scope / Trigger

- Trigger: the report center adds staff-only operational reporting across
  `books`, `readers`, and `borrow_records` without adding report tables.
- Schema path: `src/main/resources/db/schema.sql`.
- JSP path: `WEB-INF/jsp/reports/dashboard.jsp`.

### 2. Signatures

- Entity signatures:
  - `InventorySummary(totalTitles, totalCopies, availableCopies,
    unavailableOrEmptyTitles)`.
  - `BorrowingSummary(activeLoans, returnedLoans, overdueLoans)`.
  - `OverdueReportRow(readerIdentifier, readerName, bookIdentifier,
    bookTitle, dueAt, overdueDays)`.
  - `PopularBookReportRow(bookIdentifier, title, author, borrowCount)`.
  - `ReportCenter(inventorySummary, borrowingSummary, overdueRows,
    popularBooks)`.
- DAO signatures: `ReportDao.loadInventorySummary()`,
  `loadBorrowingSummary()`, `findOverdueRows()`, and
  `findPopularBooks(int limit)`.
- Service signature: `ReportService.loadReportCenter(AuthenticatedUser actor)`
  returning `ServiceResult<ReportCenter>`.
- Servlet route: `GET /reports`.
- Protected permission: `/reports` requires `VIEW_REPORTS`.

### 3. Contracts

- Report data is read-only and derived from existing `books`, `readers`, and
  `borrow_records` rows; do not introduce aggregate/cache tables for the MVP.
- `unavailableOrEmptyTitles` counts book rows where `books.status` is not
  `available` or `books.available_copies <= 0`.
- `activeLoans` counts active borrow rows where `returned_at IS NULL`.
- `returnedLoans` counts rows with `status = returned`.
- `overdueLoans` and overdue rows use the derived rule
  `status = active AND returned_at IS NULL AND due_at < CURRENT_TIMESTAMP`.
- Popular book ranking groups by book and orders by borrow record count
  descending, with a service/DAO limit for the top rows.
- `ReportServlet` sets the `reportCenter` request attribute on success and
  `errorMessage` on safe service failure.
- JSP pages render JavaBean properties only; they must not call DAOs or embed
  SQL.
- Dashboard, role-home, and header navigation should expose reports only to
  administrator/librarian users.

### 4. Validation & Error Matrix

- Missing or unauthenticated actor -> `You do not have permission to view reports.`
- Reader actor -> `You do not have permission to view reports.`
- DAO failure while loading any report section -> log server-side details and
  return `Report service is temporarily unavailable. Please try again later.`
- Empty overdue list -> render a stable empty state, not an error.
- Empty popular ranking -> render a stable empty state, not an error.

### 5. Good/Base/Bad Cases

- Good: a librarian opens `/reports` and sees inventory totals, borrowing
  counts, active overdue rows, and top borrowed books.
- Base: no borrowing records exist; summaries show zero counts and tables show
  empty states.
- Bad: a reader reaches `/reports`, a JSP performs `SELECT` queries directly,
  or report queries update inventory/borrow state.

### 6. Tests Required

- Run `ReportServiceCheck` assertions for reader denial, librarian success,
  report section composition, and DAO failure fallback.
- Run `PermissionPolicyCheck` to confirm administrator/librarian roles allow
  `VIEW_REPORTS` and readers do not.
- Scan report JSPs for scriptlets and SQL/JDBC references.
- When Maven/Tomcat dependencies are installed, run `mvn clean package` to
  compile Servlets and package JSP resources.

### 7. Wrong vs Correct

#### Wrong

```text
reports/dashboard.jsp -> JDBC -> SELECT COUNT(*) FROM borrow_records
```

#### Correct

```text
reports/dashboard.jsp <- ReportServlet <- ReportService <- ReportDao <- books/readers/borrow_records
```

## Scenario: Login And Permission Scaffold Schema

### 1. Scope / Trigger

- Trigger: the initial Java Web scaffold introduced a concrete MySQL schema and
  login contract.
- Schema path: `src/main/resources/db/schema.sql`.
- Example configuration path: `src/main/resources/db.properties.example`.

### 2. Signatures

- DAO signature: `UserDao.findActiveByUsername(String username)`.
- Service signature: `AuthService.authenticate(String username, String password)`.
- Permission signature: `AuthService.hasPermission(AuthenticatedUser user, Permission permission)`.
- Servlet route: `POST /login` with `username`, `password`, and optional
  same-application `redirect`.
- Protected routes: `/dashboard`, `/admin/home`, `/librarian/home`, and
  `/reader/home`.
- Session keys: `authenticatedUser`, `userRole`, and `userPermissions`.
- DB config keys: `db.driver`, `db.url`, `db.username`, and `db.password`.
- Login tables: `roles`, `permissions`, `role_permissions`, `users`, and
  `system_logs`.

### 3. Contracts

- `users.username`: unique login identifier submitted by `LoginServlet`.
- `users.password_hash`: PBKDF2 hash in
  `pbkdf2_sha256$iterations$saltBase64$hashBase64` format.
- Local scaffold demo users must have documented, known initial passwords for
  new deployments: `admin/admin123`, `librarian/librarian123`, and
  `reader/reader123`. Their `schema.sql` hashes must verify through
  `PasswordHasher.verify` and must be treated as local/demo-only credentials,
  never production credentials.
- `schema.sql` uses `INSERT IGNORE` for demo `users` rows. Replaying the schema
  must not be assumed to reset existing account passwords; README reset
  guidance must call this out explicitly.
- `users.role_code`: foreign key to `roles.code`; supported scaffold values
  are `administrator`, `librarian`, and `reader`.
- `users.active`: only rows with `active = 1` can authenticate.
- `roles.code`, `permissions.code`, and `role_permissions` must match the Java
  `Role` and `Permission` enum codes exactly.
- `db.properties` must be local configuration. Commit
  `db.properties.example`, but do not commit real credentials.
- Session state stores an `AuthenticatedUser` snapshot, role code, and
  permission-code set. It must not store raw passwords or DAO result objects
  with password hashes.
- Login redirects must stay inside the current application context. Reject
  values that do not start with a single `/` or that contain CR/LF characters.

### 4. Validation & Error Matrix

- Missing username or password -> request returns to login JSP with
  `Username and password are required.`
- Unknown user, inactive user, or hash mismatch -> request returns to login JSP
  with `Invalid username or password.`
- Unsafe or blank redirect -> ignore and route to `/dashboard` after success.
- Missing `db.properties`, JDBC failure, or unsupported role code -> request
  returns a generic service-unavailable message and logs server-side details.
- Authenticated user missing a required permission -> HTTP 403 and
  `WEB-INF/jsp/auth/unauthorized.jsp`.

### 5. Good/Base/Bad Cases

- Good: `admin` resolves to `administrator`, receives all scaffold
  permissions, and can access `/admin/home`.
- Base: `reader` resolves to `reader`, can access `/reader/home`, and cannot
  access `/admin/home`.
- Bad: a JSP reads SQL or password hashes directly from the database. Keep that
  logic in DAO/service code.

### 6. Tests Required

- Compile service/DAO/entity/util classes with `javac` when Maven is
  unavailable.
- Run `PermissionPolicyCheck` or equivalent assertions for administrator,
  librarian, and reader permissions.
- Run `AuthServiceCheck` or equivalent assertions for required-field failures,
  invalid credentials, success, permission checks, and DAO failure fallback.
- When Maven/Tomcat dependencies are installed, run `mvn test` or
  `mvn clean package` to compile Servlet and JSP integration.

### 7. Wrong vs Correct

#### Wrong

```java
// JSP, Servlet, or session code opens JDBC and stores password_hash.
```

#### Correct

```text
login.jsp -> LoginServlet -> AuthService -> UserDao -> users/roles tables
session -> AuthenticatedUser snapshot + role/permission codes only
```
