Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions packages/app-shell/src/providers/ExpressionProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,23 @@ interface ExpressionProviderProps {

export function ExpressionProvider({ children, user = {}, app = {}, data = {}, features = {} }: ExpressionProviderProps) {
const value = useMemo(() => {
const context = { user, app, data, features };
// ADR-0068: expose the SAME user object under the canonical `current_user`
// plus the back-compat `user` alias and the server-RLS-parity `ctx.user`
// alias, so a predicate authored against any one form evaluates identically
// on client, server-formula, and server-RLS.
const context = { current_user: user, user, ctx: { user }, app, data, features };
const evaluator = new ExpressionEvaluator(context);
return { user, app, data, features, evaluator };
}, [user, app, data, features]);

// Also feed the predicate scope used by useCondition/useExpression in
// @object-ui/react so action visibility predicates (e.g. on toolbar
// buttons) can see deployment-level flags like features.multiOrgEnabled.
const scope = useMemo(() => ({ user, app, data, features }), [user, app, data, features]);
// Mirror the canonical `current_user`/`user`/`ctx.user` aliases here too.
const scope = useMemo(
() => ({ current_user: user, user, ctx: { user }, app, data, features }),
[user, app, data, features],
);

return (
<ExprCtx.Provider value={value}>
Expand All @@ -76,7 +84,8 @@ export function useExpressionContext(): ExpressionContextValue {
if (!ctx) {
// Return a safe default so components can be used outside the provider
const fallback = { user: {}, app: {}, data: {}, features: {} };
return { ...fallback, evaluator: new ExpressionEvaluator(fallback) };
const evalContext = { current_user: {}, ctx: { user: {} }, ...fallback };
return { ...fallback, evaluator: new ExpressionEvaluator(evalContext) };
}
return ctx;
}
Expand Down
6 changes: 6 additions & 0 deletions packages/auth/src/useIsWorkspaceAdmin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ const ADMIN_ROLES = new Set([
'superadmin',
'platform_admin',
'system_admin',
// ADR-0068 canonical role names emitted into `user.roles[]` by the server
// customSession (raw better-auth owner/admin are normalized to org_owner/
// org_admin). Accept both raw and canonical so admin detection survives the
// removal of the `user.role = 'admin'` overwrite footgun.
'org_owner',
'org_admin',
]);

function isAdminRole(role: unknown): boolean {
Expand Down
Loading