From 42884667f61ecd2646a614f6157e1d0a574f939d Mon Sep 17 00:00:00 2001 From: shad Date: Sun, 14 Jun 2026 14:13:44 +0400 Subject: [PATCH] fix(curation): exclude seed-process + chat-inbox + attendance contexts + startability QA Oracle round-6 watch-out: 'Employee Onboarding' (a CANVAS_SEED_PROCESS wizard-incomplete draft) was reaching the Assistant list because its display_name passed the dev-artefact regex. EA2 runtime then returned 500 transaction_creation_failed because it lacked view/data attachments. - flowCuration.NON_BUSINESS_SOURCE_CONTEXTS now drops CANVAS_SEED_PROCESS, CANVAS_SEED_STEP, CANVAS_CHAT_INBOX, CANVAS_ATTENDANCE_ROOT, CANVAS_ATTENDANCE_SITE - all internal canvas source_contexts. - qa/full_dogfood.mjs new assertion 'assistant_list_processes_all_startable' posts each Assistant-listed flow to /api/runtime/transactions and asserts non-4xx/5xx response. --- qa/full_dogfood.mjs | 40 +++++++++++++++++++++++++++++++++++++++- qa/probe_geo.mjs | 22 ++++++++++++++++++++++ src/lib/flowCuration.ts | 5 +++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 qa/probe_geo.mjs diff --git a/qa/full_dogfood.mjs b/qa/full_dogfood.mjs index c5090b5..20b0539 100644 --- a/qa/full_dogfood.mjs +++ b/qa/full_dogfood.mjs @@ -153,7 +153,45 @@ const visibleText = await pageBodyText(); const hexLeaks = visibleText.match(/[a-f0-9]{32}/g) || []; record("negative_no_raw_32hex_ids_visible", hexLeaks.length === 0, hexLeaks.length ? `LEAKED ${hexLeaks.length}: ${hexLeaks[0]}` : "clean"); -// 13. NEGATIVE: agent welcome must not claim to be an "AI agent". +// 13. STARTABILITY: every process the Assistant lists must be a real startable +// definition (not a wizard draft that lacks view/data attachments). +// Oracle round-6 watch-out: a non-startable flow in the user-visible list is +// a product-quality bug even if it's named like a business process. +await p.locator(".tab", { hasText: /Home/i }).first().click().catch(() => {}); +await p.waitForTimeout(400); +await p.locator(".hub-chip", { hasText: /Command Assistant/i }).click(); +await p.waitForTimeout(800); +{ + const before = await p.locator(".agent-turn-agent .agent-turn-body").count(); + await p.locator(".agent-composer textarea").fill("list processes"); + await p.locator(".agent-composer button", { hasText: /Send/i }).click(); + await p.waitForFunction((n) => document.querySelectorAll(".agent-turn-agent .agent-turn-body").length > n, before, { timeout: 8000 }).catch(() => {}); + await p.waitForTimeout(500); +} +const listReply = (await p.locator(".agent-turn-agent .agent-turn-body").last().textContent()) || ""; +const flowNames = listReply.split("\n").filter((l) => l.startsWith("•")).map((l) => l.replace(/^•\s*/, "").trim()); +const tokenResp = await p.request.post("https://canvas.flow-master.ai/api/v1/auth/dev-login", { + data: { email: "ceo-head@flow-master.ai" }, +}); +const token = (await tokenResp.json()).access_token; +const catalogue = await p.request.get("https://canvas.flow-master.ai/api/ea2/flow/processes?limit=500", { + headers: { Authorization: `Bearer ${token}` }, +}); +const items = ((await catalogue.json())?.items || []); +const byName = new Map(items.filter((it) => it.display_name).map((it) => [it.display_name, it._key])); +const unstartable = []; +for (const name of flowNames) { + const key = byName.get(name); + if (!key) continue; + const r = await p.request.post("https://canvas.flow-master.ai/api/runtime/transactions", { + headers: { Authorization: `Bearer ${token}` }, + data: { process_definition_id: key, business_subject: `qa-startability-${Date.now()}` }, + }); + if (r.status() >= 400) unstartable.push(`${name} (HTTP ${r.status()})`); +} +record("assistant_list_processes_all_startable", unstartable.length === 0, unstartable.length ? `unstartable: ${unstartable.join(", ")}` : `${flowNames.length} flows all startable`); + +// 14. NEGATIVE: agent welcome must not claim to be an "AI agent". await p.locator(".hub-chip", { hasText: /Command Assistant/i }).click(); await p.waitForTimeout(800); const welcome = (await p.locator(".agent-turn-agent .agent-turn-body").first().textContent()) || ""; diff --git a/qa/probe_geo.mjs b/qa/probe_geo.mjs new file mode 100644 index 0000000..ee5d4c1 --- /dev/null +++ b/qa/probe_geo.mjs @@ -0,0 +1,22 @@ +import { chromium } from "playwright"; +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 reqs = []; +p.on("request", (r) => { if (/\/api\/ea2/.test(r.url())) reqs.push({ m: r.method(), u: r.url() }); }); +p.on("response", async (r) => { + if (/\/api\/ea2/.test(r.url())) { + const t = await r.text().catch(() => ""); + console.log(`[${r.status()}] ${r.request().method()} ${r.url().replace("https://canvas.flow-master.ai", "")} → ${t.slice(0, 200)}`); + } +}); +await p.goto("https://canvas.flow-master.ai/", { waitUntil: "networkidle" }); +await p.waitForTimeout(800); +await p.locator(".persona-chip", { hasText: /Mariana/ }).click(); +await p.locator(".dev-login-btn").click(); +await p.waitForTimeout(2500); +await p.locator(".hub-chip", { hasText: /Attendance Map/ }).click(); +await p.waitForTimeout(4500); +const markers = await p.locator(".leaflet-marker-icon").count(); +console.log(`markers=${markers}`); +await b.close(); diff --git a/src/lib/flowCuration.ts b/src/lib/flowCuration.ts index e83b138..83dc216 100644 --- a/src/lib/flowCuration.ts +++ b/src/lib/flowCuration.ts @@ -33,6 +33,11 @@ const NON_BUSINESS_SOURCE_CONTEXTS = new Set([ "EA2_CHAT_MSG", "CANVAS_CHAT_THREAD", "CANVAS_CHAT_MSG", + "CANVAS_CHAT_INBOX", + "CANVAS_ATTENDANCE_ROOT", + "CANVAS_ATTENDANCE_SITE", + "CANVAS_SEED_PROCESS", + "CANVAS_SEED_STEP", "EA2_WIZARD_STEP", "EA2_DRAFT_PROCESS:process_creation", ]);