OViq Hotel ERP — Phase 6 hardened development build

THIS IS NOT A PRODUCTION CERTIFICATION OR THE COMPLETE ORIGINAL MODULE MATRIX.
Read RELEASE-STATUS.txt for feature limitations and acceptance gates.

1. REQUIREMENTS
PHP >=8.2, PDO MySQL, mbstring, OpenSSL and Argon2id support.
MySQL 8.0+, transactional InnoDB, strict SQL mode. PHP-FPM and HTTPS for production.
The executed runtime was PHP 8.3.6 / MySQL 8.0.46 / Chromium.
PHP 8.2 itself, Safari, Firefox and Windows hosting were not exercised.
Bootstrap 5.3.3 is vendored locally, with its MIT license included.
No Composer install or external CDN is needed for the shipped UI.

2. FRESH INSTALL
Use a new EMPTY database. Set the web document root ONLY to public/.
Restrict /installer/ to your operator IP or HTTP authentication during setup.
Allow the PHP process to write storage/ and TEMPORARILY config/.
Open /installer/ and complete the wizard (admin password >=12 characters).
After success, remove config write permission and deny /installer/ at the server.
Keep storage/installed.lock. Do not expose app/, core/, tests/, config/ or bin/.
Do not use PHP's development server for live operations.

3. UPGRADE FROM PHASES 1–5
Back up the full database, config/, storage/uploads/ and installed.lock first.
Review deployment/preflight.sql for legacy-data inconsistencies.
Put the property application in maintenance mode and stop background writers.
Deploy code WITHOUT overwriting local config files, uploads or installed.lock.
From project root run: php bin/migrate.php
MySQL DDL auto-commits. The metadata-aware runner supports retry; reversal needs
restoring the pre-upgrade database AND matching code/config release together.
For old globally shared inventory items, automatic ownership is assigned only
when existing stock belongs to exactly one property. Remaining NULL owners are
quarantined from normal UI and require explicit operator reconciliation.
Do not assign ambiguous or shared inventory to an arbitrary property.
Historical room charges have no reliable business date in older builds.
Reconcile their business_date manually from verified source records; never infer
it from posted_at alone and never delete financial history to pass a preflight.
Reconcile any pre-upgrade POS group/receipt or inventory valuation differences.

4. DBA AUDIT GUARDS AND RUNTIME PRIVILEGES — REQUIRED BEFORE LIVE USE
Run as the DBA (separate from the web runtime):
  php bin/install-audit-guards.php
Optional setup connection overrides: OVIQ_SETUP_HOST, OVIQ_SETUP_USER,
OVIQ_SETUP_PASS. Use secret management; do not commit actual credentials.
Binary-logged MySQL may require DBA privileges to create triggers. Do not grant
SUPER to the runtime account or weaken server controls just to finish setup.
The two triggers reject UPDATE/DELETE on audit_logs. They do not defend against
a privileged operator dropping/truncating tables or disabling protections.
Use a separate runtime identity with table-specific DML grants and only
SELECT/INSERT on audit_logs. See deployment/runtime-grants.sql.example.
Use external append-only audit replication for privileged-operator resilience.
Then run: php bin/preflight.php
Any BLOCK output is a deployment stopper, not an informational warning.
A passing preflight checks only its listed invariants, not general security.

5. BACKUP AND RESTORE
Install the MySQL client tools. Use an independent backup database identity.
Run: php bin/backup.php /secure-backups/oviq-before-upgrade.sql
Optional overrides: OVIQ_BACKUP_HOST, OVIQ_BACKUP_USER, OVIQ_BACKUP_PASS.
The utility refuses overwrites, uses no shell string, creates owner-only files,
keeps passwords out of process arguments, removes incomplete files and prints
a SHA-256 digest. It does not encrypt backups: encrypt and restrict them outside
public/, apply a retention policy and protect guest/passport/payment metadata.
For a restore drill, have the DBA create a NEW disposable database and load the
SQL there with the MySQL client. Match table/record counts, journal balances,
audit triggers and application acceptance tests before a production restore.
The supplied release verification includes an isolated actual restore drill.
Do not restore into an occupied live database without a maintenance plan.

6. SERVER CONFIGURATION
Adapt deployment/nginx.conf.example and deployment/php.ini.example.
Test syntax before reloading. Configure real HTTPS, secure cookies, log rotation,
least-privilege file ownership and monitoring. No web-server configuration in
this package has been deployed or certified on your infrastructure.
The UI still has inline JavaScript; a strict nonce-based CSP migration is pending.
Mutation endpoints accept HTML form-encoded/multipart payloads only, not JSON.
CSRF tokens are checked centrally and in action handlers.
Authenticated HTML/API content is network-only. Offline fallback is static;
no offline guest records, queued writes or automatic resubmission are supported.

7. TESTS
The *_test suffix safety check does NOT guarantee a database is disposable.
Use a fresh dedicated database/user with no access to real hotel data.
Load installer/schema.sql, installer/seed.sql, then install DBA audit guards.
Set OVIQ_TEST_DB, OVIQ_TEST_HOST, OVIQ_TEST_USER, OVIQ_TEST_PASS.
Run: php tests/integration.php
Run: php tests/hardening.php
The latter starts independent PHP worker processes for race tests and requires
proc_open, filesystem access to a temporary directory and MySQL connections.
See tests/verification.txt for exact executed results and coverage limitations.

8. CUTOVER CHECKLIST
Complete all BLOCK gates; confirm tested backup and rollback; rotate setup
credentials; disable installer; verify HTTPS/session cookies; obtain legal/tax
and accounting sign-off; train staff; run a staged pilot with test payments;
complete external integration sandbox testing separately; obtain explicit
business acceptance. Concurrency control uses a coarse property-row mutex:
correctness was race-tested, but high-load throughput remains unbenchmarked.
