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

6.7 KiB

Tenant invitation and ticket repairs — October 20, 2026

Addresses the four confirmed defects in the multi-tenant production review, 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.
  • 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.