- Shipped
- August 28, 2026 at 8:29 PM UTC
- Author
- Kamo
- Commit
- e88dce8
The lead Communications tab reported "We couldn't load this lead's communications" for org 1168485648209608710 on 2026-08-28. The endpoint returned 500 five times between 20:02 and 20:03, and the body was Spring's default error JSON rather than LeadCommunicationsController's own — proof the controller never ran. **************** reads child_org_security_models to answer "does this org have CRM" on every /api/security/leads/** request. Yugabyte raised a retryable 40001 on that table ("schema version mismatch for table ...fad1: expected 6, got 3") while a catalog change propagated, the CannotAcquireLockException escaped preHandle, and the whole request became a 500 before any handler existed to report it. Every other reader of those rows already survives such a burst — OrganizationController and MemberRightsAppliedService log and carry on — so the gates were the last place a blip could still fail a request outright. EntitlementGateReads now runs both gates' reads: three attempts with a short backoff, which turns nearly every burst back into a real answer, and then falls open rather than closed. An unanswerable read means "not known right now", and neither alternative says that — a 403 would tell the member their organization has not bought an app it has bought, and a 500 says the feature is broken. Falling open hands the decision to the handler's own authorisation, which is separate and stricter: LeadCommunicationsController still requires a valid org session, the lead to be in that org, and VIEW_UNASSIGNED_LEADS / VIEW_LEAD_CONTACT_INFO_OTHERS; PatientCommsController still requires VIEW_CHART. What is briefly skipped is the billing question, not the access one. Non-transient faults still propagate, so a genuine break in enforcement stays loud rather than silently disabling the gate. **************** carried the identical defect on the same request path family and gets the same treatment.