- Shipped
- August 15, 2026 at 1:20 AM UTC
- Author
- kamo
- Commit
- 21a392c
Fourth cluster of the 48, and it split three ways rather than the two the design predicted. The design attributed the enforced MANAGE_MEET_SETTINGS check to ChatService. ChatService has no deployment — no kamowschat-deployment, no k8s directory, no manifest in KlusterServices — and still contains a full MeetController mapped to /api/meet, which is exactly why grepping for a gate finds one. The live Meet backend is MediaService, where the check really does live. A gate in undeployed code reads as a gate to the analyzer. VIEW_MEETINGS and START_MEETING are now enforced in MediaService and seeded by MeetRightsSeedMigration. MANAGE_MEETINGS stays on the list: there is no endpoint anywhere that manages a meeting — no list, end, cancel or reassign — and every MeetingStatus transition is the reminder sweep or a webinar cancelling its own record. EmailService's /api/calendar/meetings does update and cancel, but those are a member's own calendar invites and belong to the calendar family. That is a third outcome the earlier triages did not have a name for: not "no feature" like the chat channels, where the noun did not exist at all, and not "deployed and ungated" either. Meetings are live and actively developed; the administrative verb over them is simply unbuilt, and deleting id 80 is the one move that cannot be undone. The join redirect needed fixing or one of the gates would have been decorative. It treats an unresolvable join-info as a legacy room name and sends the clicker into meet.<domain>/<room> with a freshly minted one-time key, so a 403 collapsed into that path meant refused by the backend and admitted anyway. It now distinguishes refused from unresolvable. Worth checking first for every remaining cluster that goes through a proxy or a redirect. The client gate is inside MeetingDialog rather than at each of the six places that mount it, so the seventh cannot forget it. MediaService enforces regardless; this only spares members a form that was always going to come back 403. audit_v3.py wrote audit_v3.json while the parity test reads rights_audit_v3.json, so "regenerate the audit" produced a file nobody consumed and left the shipped list pinned to the previous run. It writes the committed artifact now. Seed migrations are excluded from the scan as well — naming a right in order to grant it is not gating it, and left in, a right that was only ever seeded would read as enforced.