Roles stopped being a two-value admin/viewer flag and became a full capability matrix you can build custom roles from; the workspace-tab cache learned to stop resurrecting tabs you had closed; ingest grew a guard against the reserved e2e device UUID; and BusymateHelper 1.0 (build 164) went in for App Store review. Dashboard builds 271 → 278; Supabase migrations 190 → 191; iOS builds 159 → 164; docs builds 150 → 152.
What changed
Capability-based RBAC (dashboard 276 / supabase 190)
- A new
rolestable replaces the binary role: each role is a per-section × action capability matrix plus agrants_allescape hatch.profiles.roleis now an FK toroles.key, and ahas_capability(section, action)SQL function backs every gate. - RLS swap —
is_admin()is gone from the policies; fleet writes (settings_global,service_groups,tags) now require the matching capability, and reads ondevices/entries/settings_device/ push / JWTs / PAC / breakpoints are owner-or-capability. - Built-in roles are immutable:
admin(all) andviewer(own-only). A protection trigger blocks deleting or renamingadminand stops anyone stripping itsgrants_all. Changes fan out live over aroles:allRealtime broadcast. - Client —
useCapabilities()hook, aSECTION_GATESmap, and aRequireCapabilityguard (forbidden card) wrap every gated section; nav is filtered bycan(section, 'view'). A new Roles section (matrix editor) lets admins build custom roles, and the Users role picker is now populated from therolestable. - Edge Functions (admin user create/delete, password update, TestFlight admin, snapshot) gate on
has_capabilityvia a shared helper. MCP gained 5 role tools (count 61 → 66).
RBAC test coverage (dashboard / supabase)
@rbace2e specs cover the admin Roles UI (nav entry, role list, capability matrix per section, admin locked full-access, New-role dialog) and a@mcp @rbacsuite exerciseslist/create/update/delete_role+set_user_roleagainst livemcp.busymate.net, including the confirm-gate, a full CRUD round-trip, and the built-in-protection trigger.
Tab-persistence fixes (dashboard 277 → 278)
- Closed tabs reappearing after reload is fixed (277), and 278 adds full dead-mode cleanup and stops the warm-cache tab resurrection — a closed tab no longer comes back when the cache rehydrates.
Ingest hardening (supabase 191)
- Ingest to the reserved e2e device UUID is now blocked at the database, and the accumulated backlog from that fixture device was drained.
App Store 1.0 + TestFlight (ios 159 → 164 / dashboard 271 → 275 / docs 150 → 152)
- BusymateHelper 1.0 (build 164) was submitted for App Store review. Submission prep closed the last gates: the App Privacy nutrition label was settled, a
submitFastlane lane (submit-only deliver, manual release, export/IDFA pre-answered) was added, and the screenshot set was reordered app-flow-first (home, settings, network, detail, account, login) with a matched iPhone + iPad light/dark set. - Every TestFlight upload now always distributes to external testers — internal-only ships are no longer possible.
- iOS picked up Dynamic Type accessibility scaling (164), a redesigned Account screen with a live device list (163), an optional in-app cert setup video in the wizard (160 → 162), and Live Activity only while connected (159).
- The
/helperlisting preview was redeployed to reflect the reordered, theme-aware screenshot set, and the docs site gained a narrated certificate setup walkthrough page (docs 150 → 152).
Where it's documented
- Roles, capabilities, the matrix editor, and the capability-backed RLS: Roles & permissions — How-to.
- Capability-backed reads/writes and the ingest guard: Supabase backend — How-to.
- The certificate walkthrough: Certificate setup.