feat(approvals): tenant-scoped queue (no cross-tenant work-items)

Plumbed me.tenant_id through api.ping + loginAs into the store as
tenantId. Approvals.loadQueue filters /api/ea2/work-items by
tenant_id matching the signed-in user so the queue only shows
actionable rows. Probe of EA2 work-items returned 73/80 in canvas's
tenant (a0000000-...), 6 in hms_dev, 1 in hub-cnt-f56ce0 -- those
7 are now hidden.
This commit is contained in:
2026-06-14 16:01:54 +04:00
parent 224c259bfd
commit 071166cae1
4 changed files with 65 additions and 3 deletions
+2 -2
View File
@@ -333,10 +333,10 @@ export const api = {
},
/** Probe whether the backend is reachable. Never throws. */
async ping(signal?: AbortSignal): Promise<{ ok: boolean; reason?: string; user?: string; user_id?: string }> {
async ping(signal?: AbortSignal): Promise<{ ok: boolean; reason?: string; user?: string; user_id?: string; tenant_id?: string }> {
try {
const me = await this.me(signal);
return { ok: true, user: me.email, user_id: me.user_id };
return { ok: true, user: me.email, user_id: me.user_id, tenant_id: me.tenant_id };
} catch (e) {
return { ok: false, reason: (e as Error).message };
}
+3 -1
View File
@@ -35,10 +35,12 @@ export default function Approvals() {
const [tx, setTx] = useState<RuntimeTransaction | null>(null);
const [busy, setBusy] = useState<string | null>(null);
const tenantId = useApp((s) => s.tenantId);
const loadQueue = async () => {
try {
const rows = await api.workItems();
setItems(rows.map((it) => ({ ...it, age_label: ageLabel(it) })));
const scoped = tenantId ? rows.filter((it) => (it as any).tenant_id === tenantId) : rows;
setItems(scoped.map((it) => ({ ...it, age_label: ageLabel(it) })));
} catch (err: any) {
pushToast("err", `Queue failed: ${err.message}`);
}
+4
View File
@@ -94,6 +94,7 @@ interface AppState {
actor: Actor | null;
userEmail: string;
userDisplayName: string | null;
tenantId: string | null;
isAuthed: boolean;
setUserEmail: (e: string) => void;
loginAs: (email: string, password?: string, options?: { method?: "password" | "dev" }) => Promise<void>;
@@ -141,6 +142,7 @@ async function runLiveFetch(
set({
actor: { mode: "direct_user", user_id: ping.user_id },
userEmail: ping.user ?? get().userEmail,
tenantId: ping.tenant_id ?? get().tenantId,
});
}
const { scenarios, workItems, distinctDefs } = await buildLiveScenariosFromApi();
@@ -256,6 +258,7 @@ export const useApp = create<AppState>((set, get) => {
actor: null,
userEmail: prefs.email ?? "dev@flow-master.ai",
userDisplayName: null,
tenantId: null,
isAuthed: typeof sessionStorage !== "undefined" && !!sessionStorage.getItem("fm.mc.token.v1"),
setUserEmail: (e) => { set({ userEmail: e }); persist(); },
loginAs: async (email, password, options) => {
@@ -274,6 +277,7 @@ export const useApp = create<AppState>((set, get) => {
set({
actor: { mode: "direct_user", user_id: me.user_id },
userDisplayName: me.display_name ?? null,
tenantId: me.tenant_id ?? null,
isAuthed: true,
});
get().pushToast("ok", `Signed in as ${me.email}`);