connect();$ids=json_decode(getenv('HCR_TENANT_IDS'),true,512,JSON_THROW_ON_ERROR);$checks=0; function capacityCheck(bool $ok,string $message):void{global $checks;if(!$ok)throw new RuntimeException($message);$checks++;echo "PASS: $message\n";} function capacityDenied(callable $work,int $status,string $message):void{try{$work();}catch(AppException $e){capacityCheck($e->status===$status,$message);return;}throw new RuntimeException($message.' was allowed');} function capacityActor(int $id):void{$_SESSION=[Base::USER_SERIAL=>$id];} $roster=new RosterService();$tickets=new ClubTickets();$resources=new ClubResources(); capacityActor(1); $hash=password_hash('Capacity test password!',PASSWORD_DEFAULT); $user=$db->prepare("INSERT INTO users(user_name,user_email,user_first_name,user_last_name,user_password_hash,user_email_verified_at,user_role) VALUES(?,?,?, ?,?,UTC_TIMESTAMP(),'User')"); $membership=$db->prepare("INSERT INTO club_memberships(club_serial,user_serial,joined_on) VALUES(?,?,'2026-01-01')"); $season=$db->prepare("INSERT INTO club_seasons(club_serial,season_name,starts_on,ends_on) VALUES(?,?,'2026-01-01','2026-12-31')"); $due=$db->prepare('INSERT INTO club_dues(club_serial,membership_serial,season_serial,amount_cents,recorded_by) VALUES(?,?,?,?,1)'); $payment=$db->prepare("INSERT INTO club_payments(due_serial,amount_cents,paid_on,request_key,recorded_by) VALUES(?,1,'2026-01-01',?,1)"); $adjustment=$db->prepare("INSERT INTO club_due_adjustments(due_serial,amount_cents,corrected_amount_cents,reason,recorded_by,request_key) VALUES(?,1,101,'test',1,?)"); $invite=$db->prepare("INSERT INTO club_invitations(club_serial,invite_email,invited_by,expires_at,sent_at) VALUES(?,?,1,DATE_ADD(UTC_TIMESTAMP(),INTERVAL 1 DAY),UTC_TIMESTAMP())"); for($i=1;$i<=76;$i++){ $user->execute(['capacity'.$i,'capacity'.$i.'@example.test','Capacity','Member '.sprintf('%03d',$i),$hash]);$uid=(int)$db->lastInsertId();$membership->execute([$ids['clubA'],$uid]);$mid=(int)$db->lastInsertId(); $season->execute([$ids['clubA'],'Capacity season '.sprintf('%03d',$i)]);$seasonid=(int)$db->lastInsertId(); $due->execute([$ids['clubA'],$mid,$seasonid,100]);$did=(int)$db->lastInsertId();$payment->execute([$did,bin2hex(random_bytes(32))]);$adjustment->execute([$did,bin2hex(random_bytes(32))]); $invite->execute([$ids['clubA'],'pending'.$i.'@example.test']); } $ticket=$db->prepare('INSERT INTO club_tickets(club_serial,user_serial,subject) VALUES(?,?,?)');$ticket->execute([$ids['clubA'],$ids['membera'],'Long conversation']);$tid=(int)$db->lastInsertId(); $message=$db->prepare('INSERT INTO club_ticket_messages(ticket_serial,author_serial,message_text) VALUES(?,?,?)');for($i=1;$i<=76;$i++)$message->execute([$tid,$ids['membera'],'Message '.$i]); capacityActor($ids['admina2']); foreach(['members','seasons','dues'] as $kind){ $rows=$roster->$kind($ids['clubA'],1);capacityCheck(count($rows)===25,"$kind page is bounded at 25 rows"); $last=$roster->$kind($ids['clubA'],9999);capacityCheck(count($last)>0&&count($last)<=25&&BoundedList::$pages[$kind]['page']===BoundedList::$pages[$kind]['pages'],"$kind clamps high page to reachable final records"); } $history=(new LedgerService())->history($ids['clubA'],1,1);capacityCheck(count($history['payments'])===25&&count($history['adjustments'])===25,'Payment and correction histories have independent bounded pages'); capacityCheck(count((new ClubInvitations())->forClub($ids['clubA'],1))===25,'Administrator invitation list is bounded'); capacityCheck(count($tickets->messages($ids['clubA'],$tid,1))===25&&count($tickets->messages($ids['clubA'],$tid,4))===1,'Long conversation exposes all replies across bounded pages'); capacityDenied(fn()=>$roster->members($ids['clubB'],1),403,'Paging cannot access another tenant'); capacityActor($ids['membera']);capacityCheck(count($roster->members($ids['clubA'],1))===1&&BoundedList::$pages['members']['count']===1,'Member roster rows and total remain personal'); capacityCheck(count($roster->dues($ids['clubA'],1))===0&&BoundedList::$pages['dues']['count']===0,'Member dues total excludes other members'); capacityActor($ids['adminb']);capacityDenied(fn()=>$tickets->messages($ids['clubA'],$tid,1),403,'Conversation paging cannot expose foreign replies'); capacityActor(1);putenv('HCR_EXPORT_MAX_ROWS=30'); capacityDenied(fn()=>BoundedList::exportGuard($db,'SELECT COUNT(*) FROM club_memberships WHERE club_serial=?',[$ids['clubA']]),422,'Oversized export is rejected before loading its rows'); BoundedList::$exporting=true;capacityDenied(fn()=>$roster->members($ids['clubA']),422,'Export query itself remains bounded if preflight count is bypassed');BoundedList::$exporting=false; $_GET=['Xaction'=>'Roster.workspace','club_serial'=>(string)$ids['clubA'],'dues_page'=>'2'];$roster->members($ids['clubA'],2);$nav=BoundedList::navigation('members');capacityCheck(str_contains($nav,'members_page=3')&&str_contains($nav,'dues_page=2'),'Page navigation preserves independent list context'); $_GET['members_page']='-1';capacityDenied(fn()=>BoundedList::page('members'),422,'Invalid list page is rejected');$_GET=[]; foreach(['email','ticket','reply','export'] as $resource){ putenv('HCR_CLUB_'.strtoupper($resource).'_HOURLY=2');capacityActor($ids['admina']);$resources->consume($ids['clubA'],$resource);capacityActor($ids['admina2']);$resources->consume($ids['clubA'],$resource); capacityDenied(fn()=>$resources->consume($ids['clubA'],$resource),429,"$resource budget is shared across administrators"); capacityActor($ids['adminb']);$resources->consume($ids['clubB'],$resource);capacityCheck(true,"$resource budget is independent for another tenant"); $stmt=$db->prepare('UPDATE club_resource_usage SET window_started=DATE_SUB(UTC_TIMESTAMP(),INTERVAL 2 HOUR) WHERE club_serial=? AND resource_name=?');$stmt->execute([$ids['clubA'],$resource]); capacityActor($ids['admina2']);$resources->consume($ids['clubA'],$resource);capacityCheck(true,"$resource budget resets after its window"); } capacityActor($ids['memberb']);capacityDenied(fn()=>$resources->consume($ids['clubA'],'email'),403,'Unauthorized resource request is denied before budget consumption'); putenv('HCR_CLUB_TICKET_HOURLY=invalid');capacityActor($ids['membera']);try{$resources->consume($ids['clubA'],'ticket');throw new LogicException('Invalid limit accepted');}catch(RuntimeException $e){capacityCheck(str_contains($e->getMessage(),'Invalid club'),'Invalid budget configuration fails closed');} putenv('HCR_CLUB_TICKET_HOURLY');putenv('HCR_EXPORT_MAX_ROWS'); // Idempotent migration must preserve both factors and counters. $before=$db->query('SELECT * FROM club_resource_usage ORDER BY club_serial,resource_name')->fetchAll();require __DIR__.'/../bin/migrate-security-capacity.php';capacityCheck($before===$db->query('SELECT * FROM club_resource_usage ORDER BY club_serial,resource_name')->fetchAll(),'Security-capacity migration is repeatable without resetting budgets'); // Exercise the real factor service, including rollback and password reset. capacityActor(1);$_SESSION['auth_version']=(int)$db->query('SELECT user_auth_version FROM users WHERE user_serial=1')->fetchColumn(); $mfa=new MfaService();$secret=OTPHP\TOTP::generate(new MfaClock())->getSecret(); $stmt=$db->prepare('INSERT INTO user_mfa(user_serial,secret_cipher) VALUES(1,?)');$stmt->execute([$mfa->sealSecret($secret,1)]); $code=OTPHP\TOTP::createFromSecret($secret,new MfaClock())->now(); capacityCheck($mfa->verify(1,$code,'Admin test password 123!'),'A fresh authenticator code verifies through the maintained library'); capacityCheck(!$mfa->verify(1,$code,'Admin test password 123!'),'The same authenticator time step cannot be reused'); $recovery=bin2hex(random_bytes(16));$stmt=$db->prepare('INSERT INTO mfa_recovery(user_serial,code_hash) VALUES(1,?)');$stmt->execute([hash('sha256',$recovery)]); $db->exec("CREATE TRIGGER reject_mfa_audit BEFORE INSERT ON journal FOR EACH ROW SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Test audit failure'"); try{$mfa->verify(1,$recovery,'Admin test password 123!');throw new LogicException('Audit failure accepted');}catch(PDOException $e){capacityCheck((int)$db->query('SELECT COUNT(*) FROM mfa_recovery WHERE user_serial=1')->fetchColumn()===1,'Failed mandatory audit rolls back recovery-code consumption');} $db->exec('DROP TRIGGER reject_mfa_audit');capacityCheck($mfa->verify(1,$recovery,'Admin test password 123!'),'Recovery code still works after a rolled-back attempt'); capacityCheck(!$mfa->verify(1,$recovery,'Admin test password 123!'),'Recovery code is single use after commit'); $cipher=$db->query('SELECT secret_cipher FROM user_mfa WHERE user_serial=1')->fetchColumn(); $stmt=$db->prepare('INSERT INTO user_mfa(user_serial,secret_cipher) VALUES(?,?)');$stmt->execute([$ids['memberb'],$cipher]); capacityActor($ids['memberb']);$stmt=$db->prepare('SELECT user_auth_version FROM users WHERE user_serial=?');$stmt->execute([$ids['memberb']]);$_SESSION['auth_version']=(int)$stmt->fetchColumn(); try{$mfa->verify($ids['memberb'],$code,'Tenant test password 123!');throw new LogicException('Copied ciphertext accepted');}catch(RuntimeException $e){capacityCheck(str_contains($e->getMessage(),'unlock'),'Encrypted secret is bound to its owning account');} class CapacityReset extends AuthService { public string $token=''; public function connect():PDO {return $GLOBALS['db'];} public function getSettings(){return new SimpleXMLElement('https://example.test');} protected function deliverResetEmail(array $user,string $url):bool{parse_str(parse_url($url,PHP_URL_QUERY),$query);$this->token=$query['token'];return true;} } $auth=new CapacityReset();$before=$db->query('SELECT * FROM user_mfa WHERE user_serial=1')->fetch();$auth->requestReset('admin@example.test'); capacityCheck($auth->resetPassword($auth->token,'Reset preserves MFA 123!','Reset preserves MFA 123!'),'Password reset works for an MFA-enrolled account'); capacityCheck($before===$db->query('SELECT * FROM user_mfa WHERE user_serial=1')->fetch(),'Password reset preserves the authenticator and replay counter'); $_SESSION['mfa_user']=1;capacityCheck($auth->login('admin@example.test','Reset preserves MFA 123!')&&!$mfa->verified(1),'A password login clears any earlier MFA verification'); echo "$checks capacity service checks passed.\n";ob_end_flush();