# Leave integrity foundation — 2026-09-06 This implements the existing-leave fixes and migration design. It does not enable statutory rules, accrual, a full entitlement ledger, or configurable approval stages. The policy-reference document is a future functional reference, not an instruction to apply laws or deploy this migration automatically. ## What changes - All HTTP leave writes enter `leave_guard.php` through `server/connection.php`. Old mutation endpoints are thin entry points; there is no second PHP balance writer. - Admin access requires the selected company/brand, a valid brand assignment (using the existing brand-access helper and its global-admin fallback), the module permission, and the action permission. Status-only decisions require `can_action`. Employee requests are forced to pending; employee reads are restricted to the signed-in employee. Notification reads also verify ownership. - Collections and exports filter by employee scope and, after migration, stored record ownership. Initial ownership is backfilled from the employee's current company/brand. Historical transfers cannot be reconstructed from this dump; review them before rollout. Old records keep their ownership after future transfers. - Existing global leave types are readable by users with relevant leave access. Only SuperAdmins with the action permission can mutate them while company policies are not yet available. A type referenced by leave history cannot be deleted. The configuration page uses the API capability flag to hide unavailable actions. - `employee_leaves.request_id` uniquely links a request to its current approved projection. Backfill requires an unambiguous business-field match in both directions. Unmatched/ambiguous approved requests are placed in `leave_link_review` and cannot be changed until reconciled. - SQL triggers alone debit/reverse usage. Repeated approvals and archives do not double-post. Changes in duration/type/year reverse the previous contribution before posting the replacement. Reversal uses the original year. A reversal without enough recorded usage fails for reconciliation rather than creating negative usage. - Archiving means cancelling the leave and reversing its usage, not merely hiding a row. Both request and approved record are updated. Deduction history is preserved. The audit table records source, actor, owning organization, before/after financial fields, and timestamp. - The dump's `leave_deduction_logs` has repeated `log_id=0` and no automatic key. The migration preserves these historical values and adds a unique auto-increment `entry_id`. Inspect the preflight column report if a target differs from the dump. - Active overlapping requests are rejected (including two half-day requests on the same date); this compatibility phase has no time-of-day segments to safely distinguish them. - Changes overlapping FINALIZED payroll are rejected with an adjustment instruction. No finalized payroll rows are rewritten. Voided batches do not block changes. - Live payroll caches are marked dirty. The PHP summary counts only the fraction falling in a cutoff. For old records, the total is distributed proportionally over calendar dates, with cumulative four-decimal rounding to conserve the total. Example: four days over September 14–17 contributes two days to each half-month cutoff. This is a documented compatibility fallback, not a statutory working-day rule. - The attendance leave-date map consumes the same active approved records, including direct HR entries. Precise partial-day attendance and scheduled-day allocation still require the future per-day model. - New uploads are restricted to PDF/PNG/JPEG up to 5 MB, stored transactionally in the database, and downloaded through `leave_request_employee/read_attachment?token=...` with authenticated scope checks. Legacy file uploads are not automatically migrated or reclassified as confidential. Before enabling sensitive statutory leaves, migrate legacy files, restrict static access, and introduce dedicated confidential permissions and UI download controls. ## Deployment sequence Do not deploy the PHP changes alone. Leave mutations intentionally fail closed until `leave_integrity_migrations` contains `2026_09_06`. 1. Back up schema, data, stored routines/triggers, and attachments. Save the matching application release. Use a restore-tested database clone first. 2. Run `backend/migrations/2026_09_06_leave_integrity_preflight.sql`. Resolve invalid/orphaned records. Review link ambiguity, unexplained usage, historical transfers, and fractional/calendar allocation. Do not automatically zero balances or manufacture missing history. 3. Stop leave writes and payroll recalculation/finalization workers during the maintenance window. Also suspend any external integration that writes leave directly. Check trigger definitions against the supplied September 4 dump; unexpected triggers must be reviewed. 4. Apply `backend/migrations/2026_09_06_leave_integrity.sql` to the explicitly selected clone/target database. MariaDB DDL auto-commits; the file is not transactional. The readiness marker is written last. If interrupted, keep writes disabled, inspect state, and rerun only after review. The integration suite verifies a clean rerun does not re-debit balances. 5. Deploy the PHP changes and rebuilt frontend together. Validate the selected brand header, role/module/action permissions, own-employee requests, legacy endpoint paths, and live cache recalculation. 6. Review `leave_link_review`. For each unresolved request, verify its real approved record from business evidence before assigning the unique `request_id`; record the reconciliation rationale separately. Never link by coincidentally equal IDs. Requests with no verified projection remain blocked. 7. Compare leave balances and live payroll totals before/after recalculation, especially cross-cutoff records. Confirm finalized snapshots are identical. Reopen writes and workers only after these checks. Rollback before new writes: restore the saved database/schema/triggers and matching application release. After new writes, do not blindly remove columns/triggers or restore old balances: preserve/export new audit and deduction entries and reconcile them first. A restore would otherwise discard legitimate transactions. ## Next schema phase NEW - September 6, 2026: The first company policy configuration and request-limit enforcement layer is implemented; see `company-leave-policies.md` for its separate additive migration, behavior and rollout. The entitlement/ledger work below remains future work. MODIFIED - September 6, 2026: The user deferred the historical employee-reference repair. Company policy development can proceed while these findings remain documented. Preserve existing records and require reconciliation before using the affected balances as verified opening entitlements. See the dated review for the confirmed query results and deferred follow-up. Keep existing type and request identifiers. Add company/brand-owned, effective-dated `leave_policies`, `employee_leave_entitlements`, and `employee_leave_ledger`, plus rule versions and `leave_request_days`. Give ledger effects a unique source/event key and retain reversal references. Reconcile the existing balances into explicit opening entries; never infer a complete ledger from deduction logs alone. The future day rows must record scheduled/calendar counting, duration, paid treatment, and the applied policy/rule version. Once verified day rows exist, replace the calendar-proportional fallback for those records. Migrate balance ownership from the interim triggers to the ledger service in one controlled release, rather than running both writers. Statutory legal values require independent validation before activation. ## Validation performed The synthetic integration fixture contains table definitions and original leave triggers from the supplied dump, with no employee or payroll data from that dump. Tests run in a disposable MariaDB 10.4.32 instance. They cover migration/backfill/rerun, repeated approval/archive, approved edits, rejection/reapproval, original-year reversals, unresolved links, overlap rejection, ownership after transfer, tenant-scoped payroll/date maps, finalized payroll, audit history, and allocation rounding. Guard cases cover admin/employee reads, foreign brand/employee, missing module/action/export permissions, and global-type restrictions. The agent has not migrated the live database. The subsequently supplied September 6 dump contains the migration completion marker. Its clone preflight found data requiring reconciliation; see `leave-integrity-review-2026-09-06.md`. Existing statutory/general-rule tabs remain previews.