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.
This commit is contained in:
2026-06-14 14:13:44 +04:00
parent b5d1ea54b0
commit 42884667f6
3 changed files with 66 additions and 1 deletions
+39 -1
View File
@@ -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()) || "";
+22
View File
@@ -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();