OViq Hotel ERP — Phase 9: assigned multi-property access and switching Verification execution date: 2026-10-23 Environment: PHP 8.3.6, MySQL 8.0.46, Node.js 22.23.3, headless Chromium. 111 PHP files passed syntax checks; JavaScript files passed node --check. PHP >=8.2 is the source target; PHP8.2 and non-Chromium browsers were not locally executed. Optional GitHub CI is supplied, not activated or remotely executed. TOTAL: 600 automated assertions/checks across the layers below. These are not 600 distinct features or a production/security certification. IMPLEMENTED THIS BATCH - Implicit unchanged home access plus explicit additional-property membership records. - Property cards, global active-property badge, session ID/token rotation, old-form rejection and stale shared-AJAX context conflicts. - Central home-context system Super Admin property creation and branch-local staff provisioning; grant/revoke for active home-owned users; fresh password/enrolled-factor confirmation on sensitive administration routes. - Existing system role carries across assigned properties, with no per-property role override; custom property-owned roles stay local. Changing a role through ordinary account administration revokes additional memberships pending reapproval. - Membership optimistic versions and payload/user-bound provisioning request keys; grants/revocations invalidate the affected user's sessions globally. - Property policy 2FA gate for switching and any-assigned-policy gate for disabling account 2FA. Policy controls update the active property explicitly. - Account/role management remains home-only; staff/user/employee ownership is not rewritten on a switch. - Current session/membership revalidation after property mutex acquisition. Remaining legacy single-statement controller mutations and laundry movement now pass through the same guard. - Non-destructive migration creating membership/provisioning request tables, with no automatic grants or changes to existing home/role/password/session versions. NEW TARGETED CORRECTNESS / CONCURRENCY Property creation replay produces one branch; changed payloads cannot reuse request keys. Concurrent same-key property creation and membership grants resolve to one result. A revocation racing a tenant writer either follows an already committed write or rejects that writer; no tested writer commits after revocation with its stale session. Property creation, creator membership and replay record roll back together on audit failure. These are targeted two-process tests, not sustained load tests. NEW BROWSER ACCEPTANCE Create resort with currency/tax/timezone; provision correctly owned local staff; switch and inspect active badge; CRM write uses active branch; return home excludes branch collection records; old tab form fails 419; old shared AJAX request fails context check with 409 and refresh-required message; granted staff session revoked; reauthenticated receptionist can switch without acquiring accounting permissions; revoke terminates an already switched session; branch-home staff starts in its own branch; password-only session cannot enter a target requiring 2FA. Mobile page tested at 390px. No uncaught JS errors and no authenticated property pages cached by service worker. TRUST / ACCEPTANCE LIMITS One installation assumes one trusted hotel-group administration domain, not unrelated SaaS organizations. System Super Admins are centrally trusted provisioning authorities: they see active property names/codes and can provision users/grants, but operational data access still requires explicit membership. Membership edits are limited to accounts owned by the actor home. No home reassignment, per-property role matrix, branch cloning, profile editing, deactivation UI, consolidated reporting, cross-branch inventory/financial transfers, FX provider, OTA provider or other live integration was added. Existing tax, statutory payroll, payment and production-certification limits remain. Home access cannot be removed by an additional grant. Invalid explicit selection fails closed, never silently selects another property. A browser session has one active property across all its tabs; refresh other tabs after switching. Separate sessions may select separately. Personnel remain local and are not automatically shared by grants. DEPLOYMENT Back up and restore-drill; preserve configuration/key/uploads/installed.lock; public/ docroot; maintenance-mode php bin/migrate.php; php bin/preflight.php; keep DBA audit guards and least-privilege DB grants. An existing account receives only unchanged home access after upgrade. Read DEPLOYMENT.txt and RELEASE-STATUS.txt before acceptance. The two fault-injection suites use an isolated test DBA connection to create temporary failing audit triggers; the application does not need SUPER privileges. PHP suites refuse databases not ending in _test. Test preview images contain synthetic data. Browser harness output and actual execution records follow; portable PHP suites ship in tests/. ==== Integration/routes ==== PASS Authenticated property context resolves assigned property PASS Super-admin permission gate succeeds PASS Housekeeper denied CRM permission PASS Forged CSRF token rejected PASS Issued CSRF token accepted PASS Lead created at version zero PASS Lead stage persists PASS Stale lead update rejected PASS Unknown pipeline stage rejected PASS Cross-property lead update denied PASS Invalid calendar date rejected PASS Blank lead title rejected PASS Work assigned to technician PASS Asset retirement blocked while work outstanding PASS Work lifecycle reaches closed PASS Closed work cannot advance PASS Due maintenance produces one work order PASS Repeated due generation creates no duplicate PASS Retired assets cannot reactivate PASS Event quote equals two hours times hall rate PASS Overlapping event rejected PASS Back-to-back hall booking accepted PASS Hall capacity enforced PASS Completed event cannot reopen PASS Event cancellation persists PASS Cross-property hall booking denied PASS Pricing defaults disabled PASS Disabled pricing engine rejects generation PASS Pricing toggle persists PASS Pricing generates two daily recommendations PASS Regeneration uses unique date/type records PASS No duplicate recommendations PASS Approved recommendation stored as date override PASS Reapplying recommendation rejected PASS Base room-type rate unchanged PASS High-occupancy pricing formula verified PASS Low-occupancy pricing formula verified PASS Audit and mutation transaction rolls back together PASS Workflow audit records written PASS Controller renders crm PASS Controller renders maintenance PASS Controller renders events PASS Controller renders pricing PASS Controller renders reports PASS Date-rate API rejects missing inputs PASS Date-rate API returns approved override PASS Route handler exists /login PASS Route handler exists / PASS Route handler exists /dashboard PASS Route handler exists /pms/calendar PASS Route handler exists /pms/calendar-data PASS Route handler exists /pms/available-rooms PASS Route handler exists /pms/reservations PASS Route handler exists /pms/reservations/create PASS Route handler exists /pms/reservations/{id} PASS Route handler exists /pms/rooms PASS Route handler exists /pms/guests PASS Route handler exists /pms/guests/{id} PASS Route handler exists /pms/night-audit PASS Route handler exists /front-office PASS Route handler exists /pos PASS Route handler exists /pos/orders/{id} PASS Route handler exists /pos/kitchen PASS Route handler exists /housekeeping PASS Route handler exists /inventory PASS Route handler exists /inventory/purchase-orders PASS Route handler exists /inventory/purchase-orders/{id} PASS Route handler exists /accounting PASS Route handler exists /crm PASS Route handler exists /maintenance PASS Route handler exists /events PASS Route handler exists /pricing PASS Route handler exists /reports PASS Route handler exists /reports/export PASS Route handler exists /pms/date-rate PASS Route handler exists /login/verify PASS Route handler exists /security PASS Route handler exists /admin/access PASS Route handler exists /hr PASS Route handler exists /hr/schedule PASS Route handler exists /hr/attendance PASS Route handler exists /hr/payroll PASS Route handler exists /hr/payroll/{id} PASS Route handler exists /properties PASS Route handler exists /login PASS Route handler exists /logout PASS Route handler exists /pms/reservations PASS Route handler exists /pms/reservations/{id}/checkin PASS Route handler exists /pms/reservations/{id}/checkout PASS Route handler exists /pms/reservations/{id}/transition/{action} PASS Route handler exists /pms/reservations/{id}/charges PASS Route handler exists /pms/reservations/{id}/payments PASS Route handler exists /pms/room-types PASS Route handler exists /pms/rooms PASS Route handler exists /pms/rooms/{id}/status PASS Route handler exists /pms/guests PASS Route handler exists /pms/guests/{id} PASS Route handler exists /pms/night-audit/run PASS Route handler exists /front-office/wakeup PASS Route handler exists /front-office/wakeup/{id}/complete PASS Route handler exists /pos/outlets PASS Route handler exists /pos/tables PASS Route handler exists /pos/categories PASS Route handler exists /pos/items PASS Route handler exists /pos/orders PASS Route handler exists /pos/orders/{id}/items PASS Route handler exists /pos/orders/{id}/items/{item_id}/void PASS Route handler exists /pos/orders/{id}/items/{item_id}/split PASS Route handler exists /pos/orders/{id}/send-kitchen PASS Route handler exists /pos/orders/{id}/pay PASS Route handler exists /pos/orders/{id}/room-charge PASS Route handler exists /pos/kitchen/{item_id}/advance PASS Route handler exists /housekeeping/tasks PASS Route handler exists /housekeeping/tasks/{id}/assign PASS Route handler exists /housekeeping/tasks/{id}/advance PASS Route handler exists /housekeeping/laundry PASS Route handler exists /housekeeping/laundry/{id}/move PASS Route handler exists /housekeeping/lost-found PASS Route handler exists /housekeeping/lost-found/{id}/claim PASS Route handler exists /inventory/items PASS Route handler exists /inventory/stores PASS Route handler exists /inventory/adjust PASS Route handler exists /inventory/purchase-orders PASS Route handler exists /inventory/purchase-orders/{id}/approve PASS Route handler exists /inventory/purchase-orders/{id}/receive PASS Route handler exists /accounting/journal PASS Route handler exists /accounting/expenses PASS Route handler exists /ops/{module}/{action} PASS Route handler exists /login/verify PASS Route handler exists /security/{action} PASS Route handler exists /admin/access/{action} PASS Route handler exists /hr/{module}/{action} PASS Route handler exists /properties/switch PASS Route handler exists /properties/admin/{action} TOTAL 134 assertions passed; 88 registered routes. ==== Financial and tenant hardening ==== PASS Property context established PASS Decimal money addition uses exact minor units PASS Fractional line rounds half up PASS Negative monetary input rejected PASS Excess decimal places rejected PASS Cross-property guest rejected by reservation service PASS Room maximum occupancy enforced PASS Concurrent same-room booking: exactly one writer succeeds PASS Concurrent booking creates only one reservation PASS Unassigned reservations cannot oversell room-type inventory PASS Dirty/cleaning room cannot be checked in PASS Unpaid folio cannot check out PASS Folio includes room tax and extra charges exactly once PASS Identical folio payment replay is idempotent PASS Payment key reuse with changed amount rejected PASS Folio overpayment rejected PASS Paid checkout sends room to cleaning PASS Checkout generates one cleaning task PASS Closed folio rejects charges PASS Occupied restaurant table cannot be reopened PASS Split tax totals use exact minor units PASS POS partial-payment replay creates no duplicate PASS Split payment cannot exceed remaining balance PASS Cannot redistribute partially-paid lines PASS Fully-paid split rejects additional payment PASS All splits settled marks order paid PASS Settled order frees table PASS Settled order cannot be voided PASS Concurrent identical payment requests both resolve successfully PASS Concurrent payment replay persists only one receipt PASS Still-open current business date cannot be closed PASS Concurrent night audit closes date once PASS Night audit posts exactly one room charge per business date PASS Night audit does not double count quoted room amount PASS Opening stock adjustment persists PASS Negative inventory stock rejected PASS Cross-property stock adjustment rejected PASS Partial GRN advances PO state PASS Cumulative GRN over-receipt rejected PASS Weighted-average cost includes stock in both stores PASS Receipt leaves other store quantity unchanged PASS Receiving store gets exact received quantity PASS Fully-received PO closes PASS Double-entry journal balances exactly PASS Journal uses property currency, not hardcoded USD PASS Unbalanced journal rejected PASS Expense cannot post to an asset account PASS Rejected expense leaves no partial record PASS Expense journal preserves expense date PASS Database rejects audit update PASS Database rejects audit deletion PASS Login attempt bucket updated 1 PASS Login attempt bucket updated 2 PASS Login attempt bucket updated 3 PASS Login attempt bucket updated 4 PASS Login attempt bucket updated 5 PASS New session does not bypass database login throttle PASS Native DI resolves typed dependencies PASS Named routes encode path parameters PASS Dynamic SQL identifiers validated TOTAL 60 Phase 6 hardening assertions passed. ==== Identity/access/ledger ==== PASS RFC 6238 published vector 59 PASS RFC 6238 published vector 1111111109 PASS RFC 6238 published vector 1111111111 PASS RFC 6238 published vector 1234567890 PASS RFC 6238 published vector 2000000000 PASS RFC 6238 published vector 20000000000 PASS Generated TOTP secret has 160-bit base32 form PASS Current TOTP matches PASS Used counter cannot match again PASS Non-numeric TOTP rejected PASS Out-of-window TOTP rejected PASS TOTP secret round-trips through authenticated encryption PASS Tampered encrypted secret is rejected PASS Enrollment requires the current password PASS Enrollment issues a local authenticator URI PASS Enrollment issues ten unique recovery codes PASS Enrollment stores encrypted secret and removes plaintext slot PASS Enrollment revokes older sessions and upgrades current session PASS Database stores only hashes of recovery codes PASS Password alone grants no authenticated session for enrolled user PASS Fresh TOTP completes password sign-in PASS Verified sign-in TOTP cannot be replayed PASS Repeated password login returns challenge PASS Unused recovery code completes sign-in PASS Recovery code works once only PASS Enrolled settings administrator can enforce property 2FA policy PASS Required policy prevents disabling 2FA PASS Custom role belongs to current property PASS Staff account is assigned selected custom role PASS Permission update revokes sessions of assigned users PASS Stale role version rejected PASS System role templates are immutable through administration PASS Delegated role administrator cannot grant unheld financial permission PASS Non-super administrator cannot assign super-admin PASS Delegated administrator cannot alter super administrator PASS Administrator cannot edit own assigned role PASS Foreign-property role cannot be assigned PASS Administrator cannot disable own account PASS Cross-property user edits rejected PASS Authorized staff deactivation persists PASS Stale user version rejected PASS Property policy sends unenrolled password-valid user to enrollment PASS Recovery regeneration issues ten replacement codes PASS Regeneration invalidates every old recovery code PASS 2FA disable removes encrypted secret and recovery codes PASS Non-required unenrolled account can use password sign-in PASS Manual journal requires a replay key PASS Identical journal replay returns original entry PASS Changed journal payload cannot reuse a key PASS Identical expense replay returns original expense PASS Expense replay creates only one balanced journal PASS Changed expense payload cannot reuse a key PASS Expense request key cannot be reused for a journal PASS Security audit events written without secrets PASS Audit payloads do not contain authenticator secret TOTAL 55 identity/access/ledger assertions passed. ==== Identity concurrency ==== PASS Concurrent identical TOTP submissions produce one accepted verification PASS TOTP replay counter is advanced exactly once PASS Concurrent identical recovery codes produce one accepted verification PASS Recovery-code used marker persists once PASS Concurrent identical ledger submissions return the same entry PASS Concurrent ledger replay persists a single balanced entry PASS Concurrent role edits reject the stale version PASS Custom-role version advances once TOTAL 8 Phase 7 multi-process assertions passed. ==== HR/payroll ==== PASS Invalid payroll month rejected PASS Unsupported payroll year rejected PASS Unknown HR mutation rejected PASS Missing request key rejected PASS Employee creation retry returns the original employee PASS Changed employee payload cannot reuse request key PASS Employee number includes property and unique identity PASS Employee directory supports staff without login accounts PASS Invalid employee email rejected PASS Negative contract salary rejected PASS Cross-property linked account rejected PASS A login account cannot link to two employees PASS Stale employee write rejected PASS Overnight shift is derived from end-before-start PASS Zero-length shift rejected PASS Shift operational duration limit enforced PASS Invalid clock time rejected PASS Break cannot consume the entire shift PASS Roster stores explicit UTC boundaries PASS Duplicate employee/date assignment rejected PASS Overnight roster ends on the next calendar day PASS Overnight roster overlap rejected across work dates PASS Changing shift templates preserves existing roster snapshots PASS Stale shift edit rejected PASS Attendance subtracts unpaid breaks in integer minutes PASS Duplicate attendance rejected PASS Check-in must match the property-local work date PASS Reverse attendance interval rejected PASS Future/mismatched attendance rejected PASS Open attendance cannot be approved PASS Open attendance prevents overlapping subsequent entries PASS Stale attendance edit rejected PASS Draft displays pending-attendance and roster exceptions PASS Payroll approval blocks unresolved attendance PASS Approved attendance is protected from direct editing PASS Roster for approved attendance cannot change PASS Roster with attendance cannot be removed PASS Changed attendance invalidates the previous payroll source hash PASS Draft regeneration updates the same monthly slip PASS Resolved attendance yields an exception-free payroll draft PASS Overtime pays only actual minutes beyond snapshotted scheduled paid minutes PASS Integer payroll computes base, allowance, overtime, percentage and fixed deductions exactly PASS Stale payroll regeneration rejected PASS Payroll requires explicit local-review acknowledgement PASS Draft payroll cannot be marked paid PASS Payroll approval retry is idempotent PASS Approved payroll cannot be regenerated PASS Approved payroll freezes source attendance PASS Approved payroll freezes new roster records in its month PASS Approved payroll freezes new attendance in its month PASS Compensation changes do not rewrite approved historical payslips PASS External salary payment reference is mandatory PASS Repeated salary payment mark has one result PASS Manual payment marker follows draft/approved/paid lifecycle PASS Paid payroll cannot be marked paid again with another key PASS Partial-month base salary uses rounded integer calendar-day proration PASS Deductions exceeding gross are rejected PASS Invalid percentage deduction rejected PASS Current/incomplete month cannot be approved PASS Unused non-frozen roster can be removed PASS Attendance reopening requires an audit reason PASS Unfrozen approved attendance supports audited correction PASS Foreign employee lookup rejected PASS Foreign shift assignment rejected PASS Foreign employee payroll generation rejected PASS Foreign payslip lookup rejected PASS HR-only operator cannot generate payroll PASS HR-only operator cannot read salary slips PASS HR-only operator cannot set compensation PASS HR-only employee registration defaults compensation without exposing it PASS Non-payroll directory rendering omits salary controls PASS Employee names are HTML-escaped PASS Directory listing excludes foreign property employee data PASS HR controller renders index inside existing shell PASS HR controller renders schedule inside existing shell PASS HR controller renders attendance inside existing shell PASS HR controller renders payroll inside existing shell PASS Printable paid slip uses immutable payroll amounts and manual receipt reference PASS Nonexistent spring-forward wall time rejected PASS Ambiguous fall-back wall time rejected PASS Unambiguous DST property-local input converts to UTC PASS Audit insertion failure aborts HR mutation PASS HR row and request key roll back together with audit failure PASS Payroll manual payment markers do not silently post general-ledger entries PASS HR workflows emit transactional audit events TOTAL 85 HR/payroll assertions passed. ==== HR concurrency ==== PASS Concurrent duplicate roster writes produce one successful assignment PASS Concurrent roster requests persist one row PASS Simultaneous identical payroll-generation requests return one draft identity PASS Concurrent payroll generation persists one monthly payslip PASS Payroll approval and attendance creation serialize; exactly one wins PASS Approval never commits a stale snapshot alongside new pending attendance PASS Concurrent identical salary-payment markers resolve idempotently PASS Concurrent salary-payment replay creates one payment audit event PASS Session revocation is rechecked inside the HR mutation transaction TOTAL 9 HR concurrency assertions passed. ==== Assigned-property service and view assertions ==== PASS Existing user receives implicit home access without migration backfill PASS Super Admin does not automatically receive operational access to every property PASS Home system Super Admin has explicit central provisioning authority PASS Property creation retries return one property PASS Repeated property request inserts one row PASS Property request key cannot bind a different payload PASS Creating a property does not switch the active context PASS Creating a property grants explicit additional access to its creator PASS Property creation preserves home ownership PASS Invalid property input rejected: property_code PASS Invalid property input rejected: timezone PASS Invalid property input rejected: currency PASS Invalid property input rejected: tax_rate PASS Invalid property input rejected: tax_rate PASS Invalid property input rejected: type PASS Duplicate property code rejected PASS Unassigned Super Admin cannot switch into an unrelated property PASS Unknown property switch rejected PASS Switch selects the assigned property and its currency PASS Switch applies the target property timezone PASS Property switch regenerates session identifier PASS Property switch invalidates tokens from all previously open forms PASS Central provisioning requires returning to the home property PASS Switched Super Admin cannot provision from the wrong home context PASS Account creation through ordinary Users and Roles is home-context only PASS System role permissions apply in the explicitly assigned active property PASS Switched context cannot access home guest records PASS Switch back restores home context PASS Additional membership grants invalidate old target-user sessions PASS Repeated membership grant does not revoke sessions twice PASS Stale membership edits rejected PASS Implicit home access cannot be modified as an additional grant PASS Assigned non-admin can see its two accessible properties PASS Receptionist cannot invoke central provisioning PASS Branch switching retains the receptionist role without privilege escalation PASS Mutation rechecks user session version after obtaining the property mutex PASS Stale-session mutation leaves no row behind PASS Revoked explicit property selection fails closed without home fallback PASS Revoked context has no role permissions PASS Revoked membership cannot perform a tenant mutation PASS Regrant advances membership version instead of replacing provenance PASS Property-owned custom roles cannot receive cross-property grants PASS Even a raw invalid grant cannot expose another property to a custom role PASS Foreign custom-role context fails closed independently of stored membership PASS Central provisioning can create correctly owned branch-local staff PASS Branch-local initial password is hashed PASS Branch provisioning cannot mint another Super Admin PASS Membership administration cannot take over foreign-owned accounts PASS Self-revocation of additional access is blocked PASS Central staff provisioning preserves explicit operational access boundaries PASS Target requiring 2FA refuses password-only sign-in PASS Assigned-property 2FA policy applies even while the user is at home PASS Verified enrolled sign-in may switch into a 2FA-required property PASS Deactivated selected property fails closed PASS Explicit zero selection cannot silently fall back to home PASS Property names escape HTML in cards and administration forms PASS Application shell exposes active-property context for operators and AJAX PASS Property audit failure aborts provisioning PASS Property, creator membership and request record roll back with audit failure PASS Property switches are recorded in the transactional audit log PASS Changing a user role revokes additional memberships pending explicit reapproval TOTAL 61 property-access assertions passed. ==== Property/membership concurrency ==== PASS Concurrent same-key property creation returns one branch identity PASS Concurrent property creation persists one branch PASS Concurrent identical membership grants both resolve successfully PASS Concurrent membership retry revokes target sessions once PASS Concurrent membership grants persist one membership PASS Membership revocation succeeds against a competing property writer PASS Competing write either commits before revocation or is rejected; none commits afterward PASS Revocation leaves membership inactive after the race PASS Previously valid branch session cannot write after committed revocation TOTAL 9 property concurrency assertions passed. ==== Fresh installer and HTTP ==== PASS Uninstalled application routes to public installer PASS Installer rejects missing CSRF PASS Installer database configuration succeeds PASS Installer builds all tables and seeds identity catalog PASS Installer creates administrator and completes lock step PASS Installation lock written PASS Installed wizard cannot be reopened PASS Login rejects missing CSRF PASS Login authenticates and dashboard renders PASS Authenticated route /crm renders PASS Authenticated route /maintenance renders PASS Authenticated route /events renders PASS Authenticated route /pricing renders PASS Authenticated route /reports renders PASS Authenticated route /pms/calendar renders PASS Authenticated route /pms/rooms renders PASS Authenticated route /pos renders PASS Authenticated route /accounting renders PASS Nested-page base URL anchored at application root PASS CRM mutation rejects missing CSRF PASS Lead creation escapes stored HTML when rendered PASS AJAX lead movement persists PASS AJAX stale version returns conflict PASS Invalid report date returns 422 PASS Report CSV export responds PASS Unregistered route returns 404 without fatal error PASS Service worker excludes authenticated page persistence PASS CLI migration runner handles fresh install and rerun PASS Phase 4-style database receives Phase 5 additions PASS Non-CRM role denied CRM page PASS Housekeeper denied pricing page TOTAL 31 HTTP/deployment checks passed. ==== PMS/CRM/mobile/offline browser regression ==== PASS Desktop sidebar renders PASS Room type setup submits successfully PASS Room setup creates clean room tile PASS Guest registration reaches profile PASS Walk-in guest checked in through real browser form PASS Folio receipt records through idempotent payment form PASS Paid browser checkout succeeds PASS Tape chart JavaScript renders fourteen-day grid PASS Drag-and-drop CRM movement persists after reload PASS Accessible CRM selector persists stage change PASS Browser route renders /maintenance PASS Browser route renders /events PASS Browser route renders /pricing PASS Browser route renders /reports PASS Browser route renders /accounting PASS Browser route renders /housekeeping PASS Browser route renders /pos PASS Mobile sidebar toggle works at 390px PASS Mobile dashboard avoids document-level horizontal overflow PASS Browser cache contains no authenticated HTML pages PASS Offline navigation displays static fallback, not guest data PASS No uncaught browser JavaScript errors PASS No external asset requests needed for application rendering TOTAL 23 browser checks passed. ==== HR/payroll browser regression ==== PASS HR directory renders in the existing navy/gold application shell PASS Employee registration form writes a property-scoped staff record PASS Employee directory persistence verified independently in MySQL PASS Shift creation form saves local shift and unpaid break PASS Roster submission retains its historical schedule filter PASS Attendance submission retains the recorded payroll month PASS Attendance approval form commits its reviewed status PASS Payroll draft form generates exact monthly net pay with overtime PASS Generated payslip is explicitly a draft until reviewed PASS Payroll approval rejects incorrect reauthentication without changing its status PASS Reviewed payroll approval requires and accepts the current password PASS Manual salary-payment recording produces a frozen paid payslip PASS Payslip print stylesheet hides navigation and retains the document PASS Print/Save PDF control invokes browser printing PASS HR mutation rejects missing CSRF token PASS Invalid payroll-period query returns HTTP 422 without a fatal error PASS HTTP attendance correction cannot alter a paid payroll period PASS Cross-property payslip URL returns HTTP 404 PASS HR directory does not expose foreign-property employees PASS Role change revokes the browser session before HR access PASS HR-only account does not receive salary input fields PASS HR-only account is denied the payroll screen PASS HR-only account is denied printable payslips PASS HR-only account cannot invoke payroll-generation POST directly PASS Employee page fits a mobile viewport without horizontal overflow PASS HR workflows produce no uncaught browser JavaScript errors PASS Service worker does not persist authenticated HR or salary pages TOTAL 27 HR browser/HTTP checks passed. ==== Identity browser regression ==== PASS Browser creates property-owned role with selected permissions PASS Browser creates staff account assigned to custom role PASS Custom-role staff can open granted CRM module PASS Custom-role staff cannot open access administration PASS Role edit revokes the existing staff session PASS Reauthenticated staff loses revoked CRM permission PASS Enrollment renders local QR payload and manual setup key PASS Confirmed enrollment displays ten one-use recovery codes PASS Enabling two-factor revokes previously active browser session PASS Recovery codes disappear after their one-time display PASS Enrolled password sign-in requires a second-factor challenge PASS Incomplete challenge cannot reach dashboard PASS Current authenticator code completes browser sign-in PASS One-use recovery code completes browser sign-in PASS Used recovery code cannot be replayed in the browser PASS Settings administrator enforces property-wide enrollment policy PASS Required policy restricts unenrolled staff to enrollment page PASS Enrolled administrator retains access administration PASS Repeated journal form submission creates one journal entry PASS Repeated expense form submission creates one expense row PASS Authenticator and recovery pages are never cached by service worker PASS No uncaught JavaScript errors in identity workflows PASS QR and identity screens make no external asset requests TOTAL 23 Phase 7 browser checks passed. ==== Property switching browser/HTTP ==== PASS Property access page renders inside the existing shell PASS Property creation requires current-password confirmation PASS Property form creates an isolated resort with configured currency, tax and timezone PASS Central staff provisioning assigns a new login to its branch home property PASS Switch updates the global active-property badge PASS Central administration is hidden while the administrator is switched away from home PASS Ordinary user/role administration rejects switched-home context PASS Existing CRM mutations use the selected property rather than the user home PASS Old-tab native mutation is rejected after property-switch token rotation PASS Old-tab AJAX request returns a property-context conflict PASS Shared AJAX helper gives a refresh-required error for stale context PASS Home CRM collection excludes branch records after switching back PASS Returning home restores central provisioning controls PASS Unassigned receptionist cannot enumerate another operational property PASS Granting membership invalidates the target user previous session PASS Assigned receptionist can switch to its newly granted branch PASS Switching never grants accounting permissions to a receptionist PASS Revocation terminates an already switched branch session PASS Revoked additional membership is absent on the next sign-in PASS Branch-local staff signs in directly to its own home property PASS Target property 2FA requirement blocks password-only switching PASS Properties and membership controls fit the mobile viewport PASS Property switching and administration produce no uncaught browser JavaScript errors PASS Service worker does not persist authenticated property membership pages TOTAL 24 property browser/HTTP checks passed. ==== Tenant/security HTTP ==== PASS Foreign guest detail denied PASS Foreign reservation detail denied PASS Foreign POS order detail denied PASS Foreign outlet detail denied PASS Property-isolated collection /pms/guests PASS Property-isolated collection /pms/reservations PASS Property-isolated collection /pms/reservations/create PASS Property-isolated collection /inventory PASS Property-isolated collection /front-office PASS Property-isolated collection /dashboard PASS Cross-property checkout denied before mutation PASS Foreign inventory input cannot cross resource boundary PASS Unsupported JSON mutation fails closed PASS Disabled account loses existing session access PASS 2FA-marked account cannot bypass the second-factor challenge PASS HTTP login throttle survives six fresh cookie jars TOTAL 16 security HTTP checks passed. ==== SQL backup/restore and earlier upgrade ==== PASS Archived Phase6 database upgrades through Phase7 PASS Actual upgrade migration rerun is idempotent PASS Unknown legacy inventory ownership is preserved for manual reconciliation PASS Preflight blocks unresolved legacy inventory PASS Preflight reports missing DBA audit guards PASS DBA audit-guard installer succeeds and can be rerun PASS CLI backup produces a checksum-verified SQL dump PASS Backup tool refuses to overwrite an existing file PASS Backup restores matching table and fixture record counts into another database PASS Restore includes both append-only audit triggers TOTAL 10 upgrade/backup checks passed. ==== Application-key and account-recovery operator tools ==== PASS Key provisioning creates once and never silently rotates an existing key PASS Provisioned application key is owner-readable only PASS Emergency recovery refuses missing confirmation and replacement password PASS Confirmed operator recovery completes through shipped CLI PASS Recovery resets enrollment, revokes sessions and replaces password atomically PASS Emergency recovery records a DBA audit event TOTAL 6 key/recovery operator checks passed. ==== Archived Phase7 HR upgrade regression ==== PASS Actual archived Phase7 schema upgrades through Phase8 PASS Phase8 upgrade rerun is idempotent PASS Legacy linked employee names backfill only from matching-property accounts PASS Unknown legacy staff identity and end dates are preserved for reconciliation PASS Legacy shift ownership inferred from unambiguous single-property roster usage PASS Shared and unused legacy shifts remain unassigned rather than guessing ownership PASS Migration never silently approves historical attendance PASS Historical settled payroll is preserved without inventing calculation provenance PASS Readiness preflight blocks Legacy employee identity and employment-end dates reconciled PASS Readiness preflight blocks Legacy shift ownership reconciled PASS Readiness preflight blocks Legacy roster property and schedule snapshots reconciled PASS Readiness preflight blocks Legacy settled payroll has reviewed snapshot provenance PASS Interrupted-migration replay reuses metadata without duplicate columns or lost staff TOTAL 13 HR upgrade checks passed. ==== Archived Phase8 assigned-property upgrade ==== PASS Actual archived Phase8 schema upgrades through Phase9 PASS Phase9 migration rerun is idempotent PASS Upgrade preserves existing user homes, roles, hashes, session versions and guest ownership PASS Upgrade grants no additional property access implicitly PASS Membership and idempotent provisioning tables are created PASS Interrupted Phase9 metadata replay succeeds without destructive changes TOTAL 6 Phase9 upgrade checks passed.