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
+56
View File
@@ -0,0 +1,56 @@
// Audit: Approvals scene loads, queue populates, hub filter works,
// selecting a row shows the active step, and clicking an action posts
// to /api/runtime/transactions/<id>/actions/<id> (real EA2 write).
import { chromium } from "playwright";
const URL = "https://canvas.flow-master.ai/";
const args = ["--host-resolver-rules=MAP canvas.flow-master.ai 65.21.71.186", "--ignore-certificate-errors"];
const b = await chromium.launch({ headless: true, args });
const p = await (await b.newContext({ viewport: { width: 1440, height: 900 } })).newPage();
const writes = [];
p.on("request", (req) => {
const u = req.url();
const m = req.method();
if ((m === "POST" || m === "PUT") && /\/api\/runtime\/transactions\//.test(u)) {
writes.push({ method: m, path: u.replace("https://canvas.flow-master.ai", "") });
}
});
p.on("response", async (res) => {
if (/\/api\/runtime\/transactions\//.test(res.url()) && res.request().method() === "POST") {
const t = await res.text().catch(() => "");
console.log(`[resp ${res.status()}] ${res.request().method()} ${res.url().replace("https://canvas.flow-master.ai","")}${t.slice(0,200)}`);
}
});
await p.goto(URL, { waitUntil: "networkidle" });
await p.waitForTimeout(800);
await p.locator(".persona-chip", { hasText: /Mariana/ }).click();
await p.locator(".dev-login-btn").click();
await p.waitForSelector(".hero-actions", { timeout: 15000 });
await p.waitForFunction(() => document.querySelectorAll(".hub-chip").length >= 8, undefined, { timeout: 15000 }).catch(() => {});
await p.locator(".hub-chip", { hasText: /Approvals queue/ }).click();
await p.waitForSelector(".approvals-row", { timeout: 15000 }).catch(() => {});
await p.waitForTimeout(1500);
const rowCount = await p.locator(".approvals-row").count();
const hubChips = await p.locator(".approvals-filter-row .hub-chip").count();
console.log(`[queue] rows=${rowCount} hubFilters=${hubChips}`);
if (rowCount > 0) {
await p.locator(".approvals-row").first().click();
await p.waitForSelector(".approvals-card", { timeout: 8000 }).catch(() => {});
const stepTitle = (await p.locator(".approvals-card-title").first().textContent()) || "";
const actionCount = await p.locator(".approvals-actions .btn").count();
console.log(`[detail] step="${stepTitle.trim().slice(0,60)}" actions=${actionCount}`);
if (actionCount > 0) {
await p.locator(".approvals-actions .btn").first().click();
await p.waitForTimeout(2500);
}
}
console.log(`\n=== runtime writes during run ===`);
writes.forEach((w) => console.log(` ${w.method} ${w.path}`));
console.log(`\ntotal runtime writes: ${writes.length}`);
await b.close();
process.exit(0);