Files
huntingclubroster/TENANT-REPAIRS-2026-10-20.md
T

123 lines
6.7 KiB
Markdown
Raw Normal View History

# Tenant invitation and ticket repairs — October 20, 2026
Addresses the four confirmed defects in
[the multi-tenant production review](MULTI-TENANT-REVIEW-2026-10-20.md), in its
recommended order. No schema migration or development database change is needed.
All verification uses generated disposable local databases; no real email is sent.
## 1. Administrator invitation bypass
`ClubInvitations::accept()` now distinguishes existing **active** administrator
memberships from inactive historical roles. A member invitation can reactivate an
inactive membership only as a member; it cannot restore the historical
administrator role. A fresh administrator invitation still requires a current
Super System Administrator grant. Already active administrators keep their role
when accepting a member invitation, preventing accidental loss of the last club
administrator.
`RosterService::saveMember()` revokes outstanding invitations when the membership
role changes as well as when membership is made inactive. An earlier administrator
invitation therefore cannot undo a later explicit demotion. These changes are
scoped to the selected membership and do not change a shared account's other clubs.
Regression cases cover rejoining as a member, Super-approved restoration,
preserving active administrators, invitation invalidation on promotion/demotion
or deactivation, and unaffected access to another club.
## 2. Concurrent transactions
Invitation acceptance now locks the recipient user before the club, matching
membership updates and account operations that lock user records first.
New `classes/DatabaseTransaction.php` provides up to **three complete transaction
attempts** for database deadlocks, serialization failures and lock-wait timeouts.
Every failed attempt rolls back before a short randomized backoff and retry.
Other database errors and business/permission errors are propagated immediately.
Exhausted retries propagate the failure after rollback; retries are bounded.
Applied the helper to:
- Invitation creation, acceptance and revocation.
- Club creation/updates, membership updates and season creation.
- Ledger charges, payments, corrections and reversals.
- Global account edits, activation/deactivation and deletion.
- Ticket creation and replies.
Permission checks inside the transaction rerun on retry. Administrator invitation
issuance also rechecks Super privilege inside each attempt. Email delivery and
rate-limit accounting remain outside the retry closure, so retrying database work
does not repeat those external effects. Mandatory audits commit with their writes.
Tests inject retryable failures after database writes, verify exactly one
committed write, cover exhausted attempts and nonretryable permission failures,
and revoke authority between attempts to confirm the retry cannot commit.
Controlled concurrent real service calls cover acceptance versus activation and
acceptance versus deactivation. Existing financial and final-administrator races
remain part of the full suite.
## 3. Unicode ticket validation
- Ticket subjects and bodies use server-owned limits of **200** and **8,000**
Unicode characters, with specific required/invalid/over-limit errors.
- The dispatcher permits larger message transport only for POST ticket creation
and reply actions. That field retains a hard **65,536-byte** ceiling; all other
fields retain the existing 8,192-byte ceiling. The field validator then enforces
the actual 8,000-character limit before storage.
- Query and form values are checked separately. A valid POST value cannot hide an
array-shaped query value with the same name.
- Added `js/tickets.js`, which validates Unicode code-point length rather than
JavaScript UTF-16 units. HTML maxlength permits the required two UTF-16 units
per character; client validation and server validation enforce the character
limit. The same handling applies to subjects, replies and ticket search.
- HTTP tests preserve 3,000 CJK characters and 8,000 four-byte emoji without
truncation, reject 8,001-character messages/replies without partial writes,
and verify malformed/extreme requests still fail. Browser tests cover native
controls, meaningful validation messages and full Unicode submission.
## 4. Ticket pages and search
- Replaced the newest-100-only list with **25-ticket pages**, newest serial first,
and first/previous/next/last navigation.
- Added subject search with a 100-character limit. Search treats SQL wildcard
characters `%`, `_` and `!` literally and uses bound parameters.
- Both the count and row query apply the club and author/administrator scope.
Members see only their personal results/counts; club administrators see only
their managed club's tickets. Direct ticket access retains its own permission
check.
- Validates page numbers, clamps requests beyond the last page, escapes subjects
and search text, and provides clear no-match and page-count displays.
- Opening a ticket preserves list context. Reply redirects retain page/search
state; a newly created ticket returns to an unfiltered first page. Successful
submissions still redirect to GET, avoiding resubmission on refresh.
- Tests use 102 personal tickets, a private ticket from another member and a ticket
in another club. They verify complete nonduplicated traversal, old-ticket search,
literal wildcard handling, escaped HTML, private counts, malformed/tampered
requests, old-ticket replies and the browser navigation controls.
## Other changed files
`Base::VERSION` is now `1.03` for browser cache refresh. Updated `tests/run.py`
to include all new repair suites, test fixtures/workers to create owned data and
control concurrency, and release-package tests to require the new transaction
helper and browser script. Updated the tenancy documentation and linked the
review to this follow-up. New classes/scripts are automatically included by the
existing release builder.
## Verification results
The complete regression suite passed: **584 checks and nine concurrency
scenarios**, including the existing authentication, tenant isolation, financial,
backup and deployment checks. New coverage includes nine invitation checks,
eight transaction retry checks, 18 Unicode HTTP checks, 44 ticket paging HTTP
checks and eight ticket browser checks. The nine concurrency scenarios include
two new invitation/membership races.
Source validation passed for 53 PHP files, 57 application JavaScript files and
39 XSL files, along with bundled editor and jQuery integrity checks. Tests used
disposable databases on the isolated local server; no development database
migration or development data changes were needed.
The remaining MFA,
capacity and production-host acceptance work in the review is outside these four
repairs; these changes do not assert that production hosting has been validated.