# Final pre-production QA audit

Audit date: **2026-10-22**. Project: `F:\office\hrms`. Scope: completed Phases 1–9.

## Remediation status — post-Phase-9 fixes

**All eight confirmed findings are fixed and verified by regression tests.** See [FINAL_QA_FIX_REPORT.md](FINAL_QA_FIX_REPORT.md) for root causes, implementation files, individual tests and remaining limitations.

| Finding | Current status |
| --- | --- |
| QA-01 | Fixed — owner sessions/reset tokens revoked, remember token rotated, sanitized reset audit and fresh login |
| QA-02 | Fixed — permission gates protect embedded audit queries and complete sections on all four central surfaces |
| QA-03 | Fixed — actual organization-time shift completion checked per employee; unfinished shifts skipped; MySQL concurrency verified |
| QA-04 | Fixed — actual neighboring intervals take precedence; ambiguous evidence retained unassigned for manual review |
| QA-05 | Fixed — visible shared validation summary, preserved input and no duplicate portal messages |
| QA-06 | Fixed — operational dependency checks prevent unsafe organization deletion with clear feedback |
| QA-07 | Fixed — saved history survives legacy metadata inconsistencies; contradictory joining-date edits are refused |
| QA-08 | Fixed — schedule card no longer claims attendance is missing |

Final verification: **519 passed, 2,804 assertions, 0 failed, 0 skipped**; Pint, NPM build and Composer audit passed; **169 routes**, **19 applied migrations**, none pending. One previewed, non-destructive biometric migration was applied. All 51 foreign keys remain and read-only hashes/counts show all 22 checked business tables unchanged in `hrms_db`.

Production approval is still **pending browser acceptance, product-owner decisions and production-environment review**. Half-day rules and published-roster history behavior were not changed. No browser checks, commit, push or deployment are claimed.

## Original audit — preserved historical findings and evidence

The audit below is retained as the original review snapshot. Its open-finding counts, release recommendation and statements about unchanged source describe that earlier audit, not the remediation status above. Original reproduction logs were not overwritten.

**Release recommendation: HOLD.** The existing suite passes, but additional isolated reproductions confirm four HIGH findings affecting security or attendance correctness. Browser acceptance testing is still outstanding. No fixes or deployment were performed.

## 1. Results and audit boundaries

| Classification | Confirmed findings |
| --- | ---: |
| CRITICAL — demonstrated severe security compromise or data loss | 0 |
| HIGH — material security boundary failure or incorrect core functionality | 4 |
| MEDIUM — workflow, validation feedback or historical reporting problems | 3 |
| LOW — misleading presentation | 1 |

No confirmed cross-organization employee/document access or database data loss was found in the reviewed paths. This is a bounded source and automated-test audit, not a guarantee that every vulnerability has been excluded.

Read `CLAUDE.md`, `README.md`, the UI design guidelines, and both Phase 8/9 plans and reports. Reviewed the current routes, middleware, policies, requests, services, models, migrations, Blade components, panel screens and relevant tests independently of the completion reports.

- Application source, configuration, migrations, dependency manifests and existing tests were not changed. An aggregate SHA-256 over 461 source/configuration/test/manifest files matched before and after review: `4CA2CC7750CD7ED6C7E640294AEC83D0132031198BAF35964B9DBB45A0418D31`.
- `hrms_db` received inspection queries only. The database inspection used an explicitly read-only MySQL transaction, rolled back after the queries. No accounts, memberships, employees, schedules or audit records were created there.
- Existing feature/unit tests used SQLite `:memory:`. MySQL tests used only the guarded `hrms_db_testing` connection. Additional defect probes used temporary SQLite in-memory fixtures.
- No production-like migration, destructive database command, reseeding, permission synchronization, dependency installation, commit, push or deployment was performed.
- Requested build/test execution generated normal build, cache and verification artifacts. `public/hot` was absent at inspection and at completion; it was not edited or deleted. No development server was started, stopped or interrupted.
- No browser automation capability was available. Laravel HTTP-kernel requests and rendered HTML checks were performed; these do not establish browser layout or JavaScript behavior.

## 2. Verification results

| Check | Actual result |
| --- | --- |
| `php artisan test --compact` | **482 passed, 2,564 assertions, 0 failures, 0 skipped**, 45.65 seconds |
| `vendor/bin/pint --test` | Passed; no formatter changes applied |
| `npm run build` | Passed; 59 modules transformed |
| `php artisan route:list --json` | Passed; **169 routes** |
| `php artisan migrate:status` | **18 applied migrations; none pending** |
| `composer audit --format=json` | No reported advisories, abandoned packages or policy findings at audit time |
| Literal Blade named-route references | No missing complete literal route names; dynamic links and browser clicks require acceptance testing |
| Additional isolated defect checks | Reproduced QA-01 through QA-07; HIGH security cases also confirmed through the HTTP kernel |

The full suite includes the existing MySQL constraint, isolation and concurrency tests, with no skipped tests. These exercise composite ownership foreign keys, unique employee/day and punch identities, attendance shape/status checks, scheduling conflicts, last-administrator protection and ESS/report isolation. They do not cover every edge case found below.

Before executing the suite, test connection settings were explicitly set to `DB_CONNECTION=sqlite`, `DB_DATABASE=:memory:`, empty `DB_URL`, and `DB_TEST_MYSQL_DATABASE=hrms_db_testing`. Reviewed `tests/MySql/MySqlTestCase.php`: it refuses another database name, checks the connected name, applies forward migrations only to that test connection and rolls back fixtures. Concurrency fixtures use the isolated test database too.

Read-only live inspection found MySQL **8.4.9**, **51 foreign-key constraints**, 1 user, 0 organizations, 0 employees, 3 roles, 6 audit entries, 0 attendance records and 0 biometric punches. Consequently, realistic organization/employee workflows were exercised with isolated fixtures, not live business records.

Verification evidence is under `storage/logs/`:

- `final-qa-tests.txt` and `final-qa-routes.json`.
- `final-qa-probes.jsonl`: service/rendered-view reproductions and central dashboard/detail-page HTTP results.
- `final-qa-session.jsonl`: password reset followed by an authenticated request using an existing encrypted database session.
- `final-qa-probe-source.txt` and `final-qa-session-source.txt`: temporary reproduction harness sources retained as audit evidence. These are not application code or additions to the permanent test suite. Review their memory-database guard before any future reuse.

## 3. Confirmed defects

Reproduction steps below describe disposable test fixtures. Do not execute state-changing reproductions against `hrms_db` during a read-only audit.

### QA-01 — HIGH — Password reset leaves existing authenticated sessions valid

**Screen/module:** Authentication → Forgot password / Reset password; affects accounts using any panel.

**Problem:** The reset callback changes the password and remember token but does not revoke existing authenticated sessions. No password-hash session middleware or reset-event listener compensates for this path. A previously stolen session can remain usable after the owner resets the password.

**Reproduction:** Establish a database session for an active central account. Obtain a valid password-reset token and submit a different compliant password from a separate guest session. Request `/central/dashboard` using the original session cookie.

**Expected:** The password is reset and previously authenticated sessions lose access; a new login is required.

**Actual/evidence:** The isolated HTTP reproduction used the application's encrypted JSON database-session format. Password reset returned 302, the new password hash matched, the old database session remained, and the old cookie obtained **HTTP 200** from the protected dashboard. This was an HTTP-kernel check, not a browser test.

**Recommended fix:** Apply shared session revocation to successful broker resets, retain remember-token rotation, and add a regression that replays an existing session after reset. Also record a sanitized password-reset completion audit event: the callback currently emits Laravel's event without an application audit listener.

**Relevant files:** [ResetPasswordController.php](../app/Http/Controllers/Auth/ResetPasswordController.php), lines 27–40; [SessionRevoker.php](../app/Services/Access/SessionRevoker.php); [bootstrap/app.php](../bootstrap/app.php); [PasswordResetTest.php](../tests/Feature/Auth/PasswordResetTest.php).

### QA-02 — HIGH — Embedded central audit history bypasses audit-view permission

**Screen/module:** Central Dashboard; Central User, Role and Organization details.

**Problem:** Controllers query audit data and templates render it without requiring `central.audit_logs.view`. The permission check protects only the “View all” link. Embedded detail tables expose change values, actor labels and IP addresses. User detail queries can also include organization-scoped events targeting that user.

**Reproduction:** Create an active global role with only `central.dashboard.view`; open the dashboard after a recent audit event. Separately add the appropriate `central.users.view`, `central.roles.view` or `central.organizations.view` permission, without audit-view permission, and open a corresponding record with audit history. Compare `/central/audit-logs`.

**Expected:** Audit history follows the explicit audit policy on every surface. Any intentionally narrower activity-summary capability must be defined and sanitized separately.

**Actual/evidence:** The dashboard returned **200** and contained a restricted target label while `/central/audit-logs` returned **403**. All three detail pages returned **200** and included test audit change values and actor IP `192.0.2.55`, despite `Gate::allows('viewAny', AuditLog::class)` being false.

**Recommended fix:** Gate both data retrieval and rendering consistently across all four surfaces. Add least-privilege role tests covering embedded activity, including organization-scoped events on user details.

**Relevant files:** [Central DashboardController.php](../app/Http/Controllers/Central/DashboardController.php), lines 18–19; [UserController.php](../app/Http/Controllers/Central/UserController.php), line 73; [RoleController.php](../app/Http/Controllers/Central/RoleController.php), line 84; [OrganizationController.php](../app/Http/Controllers/Central/OrganizationController.php); [dashboard.blade.php](../resources/views/central/dashboard.blade.php), lines 32–44; central `users/show.blade.php`, `roles/show.blade.php`, `organizations/show.blade.php`; [audit table](../resources/views/central/audit-logs/partials/table.blade.php); [AuditLogPolicy.php](../app/Policies/AuditLogPolicy.php).

### QA-03 — HIGH — Missing-day processing records absences before overnight shifts finish

**Screen/module:** Organization → Daily attendance → Process missing records for this day.

**Problem:** The completion guard compares calendar dates only. Yesterday's overnight shift can still be running today. The operation creates a manual absence and subsequent automatic biometric processing preserves that manual record.

**Reproduction:** Use UTC organization time. Assign a 22:00–06:00 overnight shift for October 21. At October 22, 00:30, with no saved attendance, process missing records for October 21.

**Expected:** Do not process that employee as absent until the resolved shift ends at 06:00. Skip unfinished shifts or reject the operation with an explanation.

**Actual/evidence:** The controller accepted the operation and saved `absent` while the snapshot's end was **2026-10-22 06:00:00**, 5.5 hours in the future. The read-only timeline otherwise correctly classifies such a shift as ongoing.

**Recommended fix:** Check the resolved end instant for each employee in organization time while holding the existing write lock. Preserve idempotency and explain skipped unfinished shifts. Cover overnight and month-boundary processing in regression tests.

**Relevant files:** [AttendanceController.php](../app/Http/Controllers/Organization/AttendanceController.php), lines 74–87; [AttendanceService.php](../app/Services/Attendance/AttendanceService.php), automatic-write preservation; [AttendanceTimeline.php](../app/Services/Attendance/AttendanceTimeline.php).

### QA-04 — HIGH — Unknown-direction biometric punches can attach to the wrong shift date

**Screen/module:** Organization → Import attendance → Raw device punches.

**Problem:** The prior overnight shift receives unknown/out punches for four hours after its end, without considering the current day's schedule. A valid night-to-day transition can therefore assign the next arrival to the previous attendance day. The four-hour rule is documented, but this resolvable schedule conflict produces incorrect daily records.

**Reproduction:** Assign October 21 night shift 22:00–06:00, then October 22 day shift 08:00–16:00, with no breaks. Import these mapped punches with `direction=unknown`:

```csv
device_employee_id,punched_at,direction
001,2026-10-21 22:00:00,unknown
001,2026-10-22 06:00:00,unknown
001,2026-10-22 08:00:00,unknown
001,2026-10-22 16:00:00,unknown
```

**Expected:** Resolve the two scheduled workdays as 22:00–06:00 and 08:00–16:00, or surface ambiguity for review instead of silently producing misleading endpoints.

**Actual/evidence:** October 21 saved check-out **08:00**, **600 worked minutes**; October 22 saved check-in **16:00**, no check-out, `incomplete`. The 08:00 arrival was consumed by the prior-night window. Raw evidence remained intact.

**Recommended fix:** Resolve ambiguous punches against adjacent schedule intervals, considering the next start, and send unresolved evidence to review. Keep the adapter vendor-neutral. Test unknown-direction transitions, extended departures and cross-month cases.

**Relevant files:** [AttendanceImportService.php](../app/Services/Attendance/AttendanceImportService.php), lines 142–177; [ScheduleResolver.php](../app/Services/Scheduling/ScheduleResolver.php); [AttendanceTest.php](../tests/Feature/Attendance/AttendanceTest.php).

### QA-05 — MEDIUM — Finalized-month validation failures are invisible in attendance forms

**Screen/module:** Organization → Enter/correct attendance; related import and recalculation feedback paths.

**Problem:** `assertOpen()` raises an `attendance` validation error, but the attendance form has no field or error component for that key. The shared portal layout renders flash messages, not an error-bag summary.

**Reproduction:** Finalize a completed month. Open manual attendance for a date in that month and submit a valid entry/correction. Alternatively render the form with the resulting `attendance` error bag.

**Expected:** Explain that the month is finalized and must be reopened by an authorized user. Preserve entered values.

**Actual/evidence:** Backend writes remain blocked. Rendering the form with `This month is finalized. Reopen it before making changes.` in the error bag produces HTML without that message. Import/recalculation surfaces also lack a display for this non-field error; those additional interactions were source-reviewed, not browser-tested.

**Recommended fix:** Display non-field validation errors centrally or on each affected form. Ensure confirmation-dialog failures are visible after redirect and can be corrected without guessing.

**Relevant files:** [AttendanceService.php](../app/Services/Attendance/AttendanceService.php), lines 27–30; [portal.blade.php](../resources/views/layouts/portal.blade.php), lines 68–87; attendance [form](../resources/views/organization/attendance/form.blade.php), [import](../resources/views/organization/attendance/import.blade.php) and [show](../resources/views/organization/attendance/show.blade.php) views.

### QA-06 — MEDIUM — Organization deletion omits dependent-record blockers

**Screen/module:** Central → Organization details → Delete organization.

**Problem:** `deletionBlockers()` checks membership and attendance/device tables but omits earlier modules' branches, departments, designations, employees and shifts. An inactive organization with no remaining memberships can be reported as deletable while foreign keys still prohibit deletion.

**Reproduction:** In isolated fixtures, retain a branch in an inactive organization with no memberships or role assignments and no attendance/device records. Inspect deletion blockers, then request deletion with appropriate central authorization and typed confirmation.

**Expected:** Keep the organization and explain which retained data prevents deletion before attempting it.

**Actual/evidence:** The service returned an **empty blocker list**, then deletion raised `Illuminate\Database\QueryException` due to the foreign key. The transaction rolled back and the organization remained. The exception is not a handled business-rule refusal; the web path would fall through to a server error. The service failure was executed; a live-browser 500 was not exercised.

**Recommended fix:** Check all protected dependencies under the existing organization lock and return a useful preservation message. Do not weaken constraints or cascade-delete business history. Add a regression for a membership-free organization with retained structure data.

**Relevant files:** [OrganizationService.php](../app/Services/Organization/OrganizationService.php), lines 148–201; [OrganizationController.php](../app/Http/Controllers/Central/OrganizationController.php); [DestroyOrganizationRequest.php](../app/Http/Requests/Central/DestroyOrganizationRequest.php); [organization detail view](../resources/views/central/organizations/show.blade.php).

### QA-07 — MEDIUM — Joining-date edits hide already saved attendance history

**Screen/module:** Organization → Edit employee; expected attendance dates; attendance reports; ESS attendance when the employee is eligible.

**Problem:** The timeline skips dates before the employee's current joining date before consulting saved attendance. Joining-date changes are allowed without checking existing attendance. Moving that date forward makes retained records disappear from the projection and its totals.

**Reproduction:** Save attendance on October 5 for an employee who joined earlier. Read that date through the timeline, set the employee inactive, and read again. Through `EmployeeService::update`, move joining date to October 10, then read October 5 again.

**Expected:** Saved historical evidence remains discoverable, or the conflicting HR edit is explicitly refused/reviewed. Changing employment metadata should not silently hide recorded days.

**Actual/evidence:** Timeline row counts were **1 before**, **1 after inactivation**, and **0 after the joining-date change**. The attendance record still existed. The saved-record list and timeline-based reports can therefore disagree. No record was deleted. The same source ordering applies to finalized records, although that variant was not separately reproduced.

**Recommended fix:** Preserve saved rows independently of expected-date generation. Decide whether conflicting joining-date edits require refusal or an explicit reviewed correction; add active/inactive and finalized-history regressions.

**Relevant files:** [AttendanceTimeline.php](../app/Services/Attendance/AttendanceTimeline.php), lines 29–34; [SaveEmployeeRequest.php](../app/Http/Requests/Organization/SaveEmployeeRequest.php), line 77; [EmployeeService.php](../app/Services/Organization/EmployeeService.php), line 70; [ReportService.php](../app/Services/Reports/ReportService.php).

### QA-08 — LOW — Organization dashboard always says attendance is not recorded

**Screen/module:** Organization Dashboard → Today's schedule.

**Problem:** The schedule card contains the unconditional sentence `Planned schedule only. Attendance is not recorded yet.` Its controller computes schedules and never checks attendance records for that statement.

**Reproduction:** Save today's attendance, then open the dashboard as an administrator with roster-view permission.

**Expected:** Explain that the figures describe planned schedules, or derive any recording-status claim from actual attendance.

**Actual/evidence:** The template always renders the same sentence, including after attendance exists. Confirmed by source inspection; no browser assertion is claimed.

**Recommended fix:** Remove the unsupported status claim or calculate a truthful status. A general schedule-only explanation is sufficient.

**Relevant files:** [organization/dashboard.blade.php](../resources/views/organization/dashboard.blade.php), line 51; [Organization DashboardController.php](../app/Http/Controllers/Organization/DashboardController.php).

## 4. Three-panel coverage

The following combines source review with the existing passing feature/HTTP tests. “Reviewed” does not mean a human clicked every action in a browser.

| Panel / screens | Review and verification coverage |
| --- | --- |
| Central: Dashboard | Gate and widget review; isolated least-privilege HTTP reproduction confirms QA-02 |
| Central: Users and Account Types | Listing/search/status/sort, create/edit/show, account-type separation, role assignment and password/status/delete protections; `tests/Feature/Central/*` |
| Central: Roles and Permissions | Global/organization separation, sensitive permission confirmation, system-role protection, permission registry; `tests/Feature/Rbac/*`; embedded activity affected by QA-02 |
| Central: Organizations | Create/update/status/logo and deletion guards; `tests/Feature/Organizations/*`; QA-02 and QA-06 |
| Central: Organization Enrollment | Central-only enrollment, membership versus role distinction, scoped references, suspended access and last-admin protection; `tests/Feature/Enrollment/*` |
| Central: Audit Logs | Read authorization, target links and sanitized values; `AuditLogTest`; embedded surfaces require QA-02 |
| Organization: Dashboard, Users and Roles | Permission-aware statistics/actions, current-organization membership binding, no self-escalation or global role grants; enrollment/RBAC tests; QA-08 |
| Organization: Branches, Departments, Designations | Scoped validation/policies, active references, dependent-record handling, shared table/form/status components; `StructureManagementTest` |
| Organization: Employees and Documents | Scoped CRUD/import/export, manager-cycle rules, separate account linking, private photo/document streams and file validation; `tests/Feature/Employees/*`; QA-07 |
| Organization: Shifts and Assignments | Valid timing, date-effective assignments, inactive/foreign-reference rejection, overlap/history rules and transaction handling; `ShiftManagementTest`, `ShiftAssignmentTest` and MySQL scheduling tests |
| Organization: Weekly/Monthly Rosters | Single-day changes, preview/fingerprint confirmation, bulk atomicity, copy, off-day precedence, publish/draft behavior and scoped CSV exports; `RosterTest` |
| Organization: Daily/Monthly Attendance | Calculation snapshots, UTC/local conversion, corrections/reasons, finalization/reopening, missing-day processing and expected-date projection; `AttendanceTest`, report/ESS tests; QA-03, QA-05, QA-07 |
| Organization: Devices and Imports | Device activation, permanent mapping, CSV validation/rollback, raw evidence, duplicate identity and correction preservation; attendance feature/MySQL tests; QA-04 |
| Organization: Reports | Category/module/export permission intersection, scoped filters, date/row/PDF limits and CSV/PDF/XLSX generation; `ReportingTest`, MySQL ESS isolation tests |
| Employee: Dashboard and Profile | Real linked active identity, own fields/photo, masked/private-field exclusions, permission-dependent widgets; `EmployeePortalTest` |
| Employee: Attendance | Self-only date selection, overlapping flags, ongoing/future/off/unprocessed distinctions and saved finalized reads; ESS/report tests; QA-07 is a shared projection issue |
| Employee: Shifts and Roster | Own assignments, published-only overrides, off days, overnight timing and default fallback; ESS/scheduling tests and documented limitation below |
| Employee: Reports | Own-record restriction, prohibited foreign selectors and explicit own view/export rights for each format; reporting/MySQL tests |
| Employee: Account Settings | Current-password/confirmation/strength checks, other-session and reset-token revocation, sanitized audit; ESS tests. Broker-based forgot-password reset remains separately affected by QA-01 |

Security controls supported by source and passing tests include active-user enforcement, POST logout and session invalidation, login throttling, hashed passwords, CSRF middleware, fail-closed organization scopes/bindings, composite ownership constraints, and own identity resolution using both user and organization. Private documents use authorized attachment streams with no-sniff/private-cache headers. Reports use fixed columns; CSV/XLSX cells are formula-safe, and PDF remote/PHP/JavaScript execution is disabled. These findings do not remove the confirmed exceptions above.

Audit recording is present across reviewed enrollment, role, employee, schedule, attendance, device and report-download services, with secret-key filtering and sensitive-field redaction. Model hooks prevent ordinary audit updates/deletes; this is application-level append-only behavior, not proof that privileged direct SQL cannot alter logs. Download audit events are created when a response is prepared, so they do not prove the client received the entire streamed file.

## 5. Required business-rule reviews and known limitations

These behaviors are confirmed in source/documentation. They are separated from unambiguous defects because a product/HR decision is required.

| Topic | Observed behavior / implication | Required decision or follow-up |
| --- | --- | --- |
| Published roster history | Editing a published row overwrites that row and clears `published_at`. ESS excludes the draft and falls back to its default assignment or Not scheduled. There is no separately retained last-published override. Draft contents are not exposed. Bulk audit summaries are not a complete per-day publication archive. | HR must explicitly accept this loss of prior published-plan visibility before release. If staff must retain their last communicated plan until a replacement is published, versioned publication/history is required. See `RosterService::write`, `ScheduleResolver::load`, Phase 9 documentation. |
| Half-day convention | Complete endpoints, positive work, and worked minutes at most 50% of the saved net planned duration. Break is subtracted from planned duration; missing punches do not qualify. Present/late/early/short/half-day flags overlap. | Obtain HR approval of the threshold, rounding and fixed-break convention. These figures are not leave entitlement, overtime or payroll rules. No defect is asserted solely because another office might use a different policy. |
| Unprocessed visibility | The original list/CSV contains saved records only. Review expected dates, ESS and newer reports project unsaved scheduled dates as Unprocessed only after shift end; ongoing/future/off/unscheduled dates differ. Reading does not manufacture absences. | Explain the two views during acceptance testing. QA-03 is a separate writer defect that can undermine this otherwise correct read behavior. |
| Inactive employee history | Saved attendance remains visible when an employee is merely made inactive, verified by existing tests and an additional probe. Unsaved expected history is not inferred for inactive employees because no effective inactivity date exists. Inactive employees cannot use ESS. | Accept the limitation or define an effective-dated history requirement separately. QA-07 is an additional confirmed issue when joining date changes. |
| Attendance finalization | Finalizes completed months and locks writes/imports; does not require every expected day to be processed or certify completeness. Reopening is permissioned and reasoned. | HR must understand that Finalized means locked, not fully reconciled. Review expected dates before finalizing. Backend locking tests pass; QA-05 affects feedback. |
| Biometric foundation | CSV normalization only; first/last eligible endpoints and one fixed break. Four-hour previous-night rule, exact duplicate detection, permanent mappings. No split-shift pairing or safe identity reuse on an existing device registration. | Validate the intended file workflow and ambiguous-punch review process; fix QA-04. No vendor protocol, hardware transport, real-time connectivity or device clock reliability was tested or claimed. |
| Report limits | At most 62 days/5,000 generated rows; PDF at most 500. Employee selection is also capped before report-specific aggregation, even for shift definitions or employment-type totals. | Validate expected organization sizes. Large organizations may need narrower filters; aggregate/definition reports may hit a limit despite small final output. This scalability behavior was source-reviewed, not load-tested. |

## 6. UI findings, suspected risks and untested behavior

Source review found consistent shared page/table/filter/form components, visible action labels, permission-aware navigation, empty states, pagination and responsive utility classes. The Central Users list retains Account type and displays assigned global roles with “No role assigned”; organization-specific roles are not used in that column. System/Sensitive presentation badges remain removed while backend rules remain present.

Confirmed UI defects are QA-05 and QA-08. QA-06 also degrades workflow feedback. The following are **suspected risks or coverage gaps**, not browser-confirmed failures:

- **MEDIUM candidate — dialog keyboard accessibility:** `<x-confirm>` sets initial Cancel focus, but source contains no focus trap, background inert handling or explicit focus restoration. The Alpine initialization imports no focus plugin. Verify Tab/Shift+Tab containment and focus after closing with an actual browser/screen reader. Relevant: `resources/views/components/confirm.blade.php`, `resources/js/app.js`.
- **MEDIUM candidate — validation within closed dialogs/filter panels:** dialogs start closed after redirect, and the portal lacks a global error summary. Some field errors may be hidden until the control is reopened. QA-05 proves one concrete failure; check the remaining confirmation/filter forms before extending that finding.
- **Untested layout/interaction:** mobile/tablet overflow, roster scrolling/list alternatives, sticky/fixed More menus near viewport edges, browser-native date inputs, outside click/Escape, touch behavior, dropdown keyboard navigation, long names and translated text.
- **Untested operational behavior:** realistic concurrent import/finalization load beyond existing automated scenarios, slow/failed streamed downloads, PDF font coverage for required languages, external mail delivery and real reset-email URLs, physical devices/time drift, backups/restoration and production web-server/private-storage configuration.
- **Environment observation, not a deployed exposure finding:** this installation is `APP_ENV=local`, `APP_DEBUG=true`, with database sessions and secure-cookie configuration unset. It is not a verified production configuration. The eventual release environment must be reviewed separately; no configuration was changed or deployment initiated.

## 7. Manual browser acceptance checklist

Use a separately authorized staging/disposable dataset with two organizations, central roles of different privilege, an organization administrator, a view-only member, linked employees, and inactive/suspended/unlinked examples. This audit did not create such data in `hrms_db`.

- [ ] **Central navigation:** Dashboard, Users, Account Types on user details, Roles, Permissions, Organizations, organization enrollment and Audit Logs. Verify page titles, breadcrumbs, back navigation and authorized landing pages.
- [ ] **Central least privilege:** Recheck QA-02 on all four surfaces after a fix; direct restricted URLs must fail. Confirm the User Role column, Account type, inactive assigned roles, empty role text and absence of System/Sensitive badges.
- [ ] **Organization navigation:** Dashboard, Users/Roles, Branches, Departments, Designations, Employees/Documents, Shifts/Assignments, weekly/monthly Rosters, daily/monthly Attendance, expected dates, Devices/Imports and Reports.
- [ ] **Employee navigation:** Dashboard, Profile, Attendance/day details, Shifts, weekly/monthly Roster, Reports and Account Settings. Confirm own data and selected organization on every page/export.
- [ ] **Isolation:** Switch organization/panel, refresh, open a saved URL and use another tab. Try another organization's record IDs and employee selectors. Suspended memberships, inactive accounts and unlinked employees must lose access appropriately.
- [ ] **Tables:** Useful identifying columns, long text, zero rows, filtered-zero rows, several pages, sorting and preserved filters. Verify private details are not added to compact tables.
- [ ] **Forms:** Required/invalid/duplicate/cross-organization values, old input after rejection, date/time guidance and clear next-step messages. Recheck finalized-month feedback and dialog-contained errors.
- [ ] **Confirmations:** Cancel, Escape, outside click where supported, typed permanent-deletion confirmation, required reason and accidental double submission. Keyboard focus must remain usable and return to the trigger.
- [ ] **Menus:** More/user/organization dropdowns with mouse, keyboard and touch; open near each viewport edge, scroll a wide table and resize while open. No clipping or inaccessible actions.
- [ ] **Responsive:** At approximately 360/390, 768 and 1280 pixels, verify sidebar, tables, forms, dialog buttons and roster alternatives. No page-level horizontal scroll; necessary grid scrolling stays inside its card.
- [ ] **Authentication:** Login throttling/messages, inactive login refusal, logout/back/reload, valid/expired reset links and two independent sessions. Confirm QA-01 is resolved before release.
- [ ] **Employees/documents:** Separate employment and login identities; photo and attachment uploads/downloads, unsafe files, unauthorized/cross-employee URLs and deletion blockers.
- [ ] **Scheduling:** Overlapping assignments, neighboring overnight days, bulk preview/stale confirmation, copy week, publish, edit published rows and republish. HR signs off on the documented fallback.
- [ ] **Attendance:** Complete/short/late/early/missing/off/unscheduled days, no-record states, positive half days and overlapping counts. Compare saved list, expected dates, ESS and exports. Include QA-03 and QA-07.
- [ ] **Corrections/finalization:** Required reason, saved snapshot preservation, completed-month finalization, blocked manual/import/recalculation writes and authorized reopening. Review missing expected dates before locking.
- [ ] **Devices/imports:** Active/inactive device, mapping errors, malformed/formula/wrong-month files, duplicate reimport, all-or-nothing row failure, manual-correction preservation, out-only/unknown punches, QA-04 and month-boundary overnight punches.
- [ ] **Reports:** Every permitted category/filter; view versus export permissions; CSV/PDF/XLSX row counts and own-data scope; formula-like/non-ASCII values; date/row/PDF limits and readable rejection messages. Open exported files in their intended reader.
- [ ] **Audit:** Successful state changes, corrections, imports, publication and downloads have correct actor/organization/time and sanitized values. Verify rejected/rolled-back changes leave no misleading successful-change entries.
- [ ] **Accessibility:** Keyboard-only completion, visible focus, labels/errors, status text in addition to color, 200% zoom and screen-reader dialog/menu behavior.

Record browser/version, viewport, role, fixture, result and screenshot for failures. No checkbox above is represented as completed by this audit.

## 8. Production blockers and recommended fix order

1. **Security blockers:** Resolve QA-01 and QA-02; add regression coverage for replayed sessions and embedded audit visibility.
2. **Attendance blockers:** Resolve QA-03 and QA-04 before relying on automated missing-day processing or unknown-direction imports.
3. **Workflow/history:** Resolve QA-05 and QA-07, then QA-06. Retain database constraints and history protections.
4. **Presentation:** Address QA-08 and confirm the suspected keyboard/validation issues in a browser.
5. **Acceptance decisions:** Obtain HR approval for published-roster history/fallback, half-day/fixed-break calculations, inactive expected history and the meaning of finalization. An unacceptable published-history limitation becomes a business release blocker.
6. **Release evidence:** Complete the browser checklist and an independently authorized production-environment review. Rerun affected regressions and the full required verification after fixes.

All eight confirmed findings remain open. No application fixes, new modules or deployment work were started. Stop here for report review.
