# Club tenancy and account access Implemented for the requested model. On October 20, 2026, the development database was backed up and migrated, and `james.richie@onesourceits.com` was promoted to **Super System Administrator**. Its existing password was preserved; old sessions were revoked. See the [development migration report](TENANT-MIGRATION-2026-10-20.md) for backup, restoration and live verification results. ## Access model | Capability | Super System Administrator | Club Administrator | User/member | | --- | --- | --- | --- | | View clubs | All | Assigned clubs | Active joined clubs | | View member records | All | All members of managed clubs | Own membership only | | Invite members | Any club | Managed clubs | No | | Appoint club administrators | Yes | No | No | | Create a new club | Yes | No | No | | Edit/archive/reactivate club | Any club | Managed clubs | No | | Seasons, dues, payments, corrections/reversals | Any club | Managed clubs | Read own dues/history only | | Export roster/financial data | Any club | Managed clubs | No | | Tickets | All clubs | All tickets in managed clubs | Submit/read/reply to own tickets | | Global accounts, credentials, notes, settings, journal | Yes | No | No | An administrator assigned to two clubs can manage both. An administrator in one club who is an ordinary member of another gets only member access in that other club. Administrators sharing a club see the same membership and invitation data, regardless of who issued the invitation. An account can belong to several clubs; deactivating membership in one club leaves its global account and other club memberships intact. Club administration is stored on `club_memberships.membership_role`, not inferred from the old global `Administrator` or `System Administrator` label. Those old labels do not confer cross-club access. The exact global `Super System Administrator` role grants platform access. Only a current Super System Administrator can assign global privileges or appoint club administrators. The last active Super System Administrator and the last active administrator membership in a club cannot be disabled/demoted through their respective controls. Members get club name/seasons and their own membership, dues, payment and correction history. Other members' contact information and private tickets are not shown to them. Site alerts, dropdowns, popovers and account notes remain platform-wide features, editable only by the Super System Administrator; they are not private club content editors. ## Invitations Club administrators use **Invite to this club** within their club workspace, instead of the global Users directory or a username/email lookup of existing accounts. This prevents searching another tenant's accounts or changing shared login credentials. Your Super System Administrator retains global user controls and can directly assign an existing verified account when provisioning a club. An invitation records the club, submitted email, inviter, role and seven-day expiration. It does not create an account, reset a password, or grant immediate access. The recipient signs up or signs in using that email, verifies mailbox ownership using the existing verification/reset process, opens **Invitations**, and explicitly accepts. A verified, signed-in matching email is required; an invitation ID alone is not a bearer credential. Consequently an invitation URL does not need to contain a secret token. Invitations cannot be accepted by another account, after expiry/revocation, or after the inviter loses authority. Acceptance and revocation serialize on the club record. Repeated acceptance does not duplicate membership. Only the Super System Administrator can issue administrator invitations. Deactivating an existing membership revokes its outstanding invitations for that club. Sending again requires a one-minute wait; per-actor/club limits also apply. Delivery uses the configured trusted HTTPS `ApplicationUrl`; the email explains signup, verification and acceptance. Failed delivery leaves a visible warning and a pending invitation that the recipient can find after signing in. An administrator can retry sending to the same email after a minute. Development email delivery is not claimed to work by these tests. ## Tickets Members submit a subject and message from **Tickets** or **My tickets** inside their club. Only the author, assigned club administrators and the Super System Administrator can see that ticket or its messages. Members may reply to open tickets; administrators can reply, close and reopen them. Archived clubs keep their history but do not accept new tickets/replies. Subject/message limits are 200/8,000 Unicode characters. Text is escaped on display, requests require CSRF protection, and submission/reply rates are limited. Tickets and replies commit with mandatory audit records. Successful submissions redirect to a read page, so refreshing does not resubmit the form. Ticket lists use 25-item pages with subject search; count queries and every page retain the same club/author restrictions. Older authorized tickets are discoverable through page navigation or search. This feature supports text discussions, not attachments or email notifications. ## Migration and rollout Use a protected maintenance window for the coordinated code/schema change: 1. Back up the target database and retain the matching application release. 2. Ensure migrations 001–003 are already applied and the designated existing login `james.richie@onesourceits.com` is active. This migration does not create an owner account or guess a replacement if that email is missing. 3. With migration credentials, set `HCR_SUPER_ADMIN_EMAIL` to `james.richie@onesourceits.com` and run `php bin/migrate-tenants.php`. Remove that environment variable afterward; it is a one-time explicit bootstrap selection, not a runtime setting. 4. Deploy this version with all new files and the updated runtime grants in `deploy/runtime-grants.sql.example`. Run `bin/check-health.php`. 5. Sign in again as the designated owner. Promotion revokes existing sessions and outstanding reset/verification tokens. Club memberships are **not** promoted automatically: open each club, assign its intended existing verified accounts if needed, and set their **Club role** to **Administrator**. Alternatively, invite an administrator and have them accept. 6. Verify each club administrator/member with separate sessions, then reopen service. Test production inbox delivery separately. The migration adds `membership_role` and three InnoDB tables: `club_invitations`, `club_tickets`, `club_ticket_messages`. Existing memberships default to `member`; clubs without an explicit administrator remain visible and manageable by the Super System Administrator. No historical financial records or memberships are removed. Club display names no longer need to be globally unique; club IDs remain the tenant boundary. Rerunning the migration is safe and does not re-promote an already promoted account or repeatedly revoke sessions. Do not deploy the new application by itself against the old schema: health checks will reject missing tenancy tables. If rollback is required after using the new roles, restore the matching backed-up database and release together; older code does not understand the new Super System Administrator role. ## Implementation and verification - `TenantAccess.php` centralizes current database-backed platform/club permissions. `Base.php` restricts global routes and passes the navigation capability to XSL. - `RosterService.php`, `LedgerService.php` and `RosterReports.php` enforce the selected club's access for reads, writes and downloads. Membership changes and permission checks participate in club transactions. Account services enforce Super System Administrator access and protect the final platform administrator. - `ClubInvitations.php`, `ClubTickets.php` and the new Roster screens implement the invitation and ticket flows. `xsl/content.xsl` shows scoped navigation. - `migrations/004_tenants.sql`, `bin/migrate-tenants.php`, runtime grants, release packaging, role options and application cache version (`1.02`) were updated. - Added separate migration, service, HTTP and browser tenancy suites; expanded concurrency coverage for invitation acceptance and the last club administrator. Test fixtures use generated scratch databases, capture invitation mail or force SMTP to a closed loopback port, and promote only a test account. Verification passed: **497 checks plus seven concurrency scenarios**, including the complete prior regression suite and the final owner-assignment browser check. | Suite | Passed checks | | --- | ---: | | Existing authentication, maintenance, security, ledger, browser, backup and release suites | 374 | | Tenant migration | 8 | | Tenant services | 32 | | Tenant HTTP access and isolation | 75 | | Tenant browser workflows, including owner assignment | 8 | Source checks passed for 49 PHP files, 56 application JavaScript files and 39 XSL stylesheets. Concurrency scenarios cover defaults, payment limits, financial retries, the final platform administrator, invitation acceptance and the final club administrator. The disposable local test databases were removed and their loopback server stopped. No development records, real recipients or live account privileges were changed. That regression run preceded the separately authorized development migration. The subsequent migration and 61 development acceptance checks are documented in the linked migration report; temporary verification data was removed afterward. See [the tenant repair follow-up](TENANT-REPAIRS-2026-10-20.md) for the invitation reactivation policy, bounded transaction retries and Unicode-aware ticket forms. Mandatory administrator MFA and capacity controls require the [security/capacity migration and deployment steps](SECURITY-CAPACITY-2026-10-20.md). Apply them before running this release.