jobcard_feature_assert(str_contains($schema,"('jobcards.close', 'Close jobcards')"),'Fresh schema must seed jobcards.close.');
jobcard_feature_assert(str_contains($upgrade,"('jobcards.close', 'Close jobcards')"),'Upgrade must seed jobcards.close.');
jobcard_feature_assert(str_contains($front,"if (\$to === 'closed') require_permission('jobcards.close');"),'Closing a jobcard must require jobcards.close.');
jobcard_feature_assert(str_contains($front,"name=\"q\"")&&str_contains($front,"name=\"status_filter\""),'Jobcards page must expose search and status filters.');
jobcard_feature_assert(str_contains($front,"j.status <> 'closed'"),'Default Jobcards filter must exclude closed records.');
jobcard_feature_assert(str_contains($front,"j.status = 'closed'"),'Closed filter must explicitly select closed records.');
jobcard_feature_assert(str_contains($front,'All clients')&&str_contains($front,'$reportClient'),'Reports must use a client-name dropdown.');
jobcard_feature_assert(str_contains($front,'format=xls&detail=1'),'Reports must expose detailed XLS export.');
jobcard_feature_assert(str_contains($front,"if (\$user['role_name'] === 'Technician') {")&&str_contains($front,'$reportClientsStmt->execute'),'Report client dropdown must bind parameters only on the technician prepared-query branch.');
jobcard_feature_assert(str_contains($front,'$reportClientsStmt->execute([(int)$user[\'id\']])'),'Technician report client lookup must use a positional bound parameter.');
foreach(['Work requested','Technician notes / Work performed','Hours notes']as$heading)jobcard_feature_assert(str_contains($front,$heading),"Detailed report must include {$heading}.");