fix(curation): kill dev-artefact leak + rename agent honestly

Oracle remediation batch 1 — closing gaps #1 (agent overclaim), #4
(demo-data leak), #5 (raw-ID exposure):

- src/lib/flowCuration.ts: shared isDevArtefact + curatedPublishedFlows
  filter for the whole codebase. Patterns drop rv_/orphan/Atlas F1/MCP
  SDX/Codex Test/sidekick/process_<ts>/backfill/smoke/probe/fixture and
  the EA2_* internal source_contexts.
- src/scenes/Hub.tsx: loadPublishedFlows uses the shared curator
- src/lib/agentTools.ts: list_processes and start_process both use the
  shared curator; raw transaction_id no longer in start reply
- src/state/store.ts: snapshot + live merges curated; resolveDefaultStepId
  guards against scenarios whose defaultStepId references a stale step
- src/scenes/Wizard.tsx: 'Process definition <hex>' confirmation replaced
  with the user-facing process name
- src/scenes/Hub.tsx: start toast no longer shows transaction_id prefix
- src/scenes/Agent.tsx + App.tsx + Landing.tsx: rename Pi -> FlowMaster
  Command Assistant. Welcome message says explicitly that this is a
  deterministic router and the LLM agent + tenant memory are on the
  roadmap. Tab label, hub chip, turn author all rebranded.
- src/scenes/GeoAttendance.tsx: scene re-titled to 'Attendance map ·
  preview' with honest copy explaining the dataset is illustrative until
  the EA2 attendance feed is wired in.
- src/data/synthetic.ts: orphaned from runtime (kept in tree for tests).
  Snapshot now sources only the live-cached EA2 read (scenarios.json),
  filtered through the same curator.

29/29 tests green. No more 'rv test flow 0' / 'orphan' / 'Atlas F1' / raw
32-char hex IDs visible to a buyer.
This commit is contained in:
2026-06-14 13:11:54 +04:00
parent 7ceb5c05bb
commit 8933148719
9 changed files with 109 additions and 48 deletions
+11 -10
View File
@@ -1,6 +1,7 @@
import { api } from "./api";
import { wizardApi } from "./wizardApi";
import { chatApi } from "./chatApi";
import { curatedPublishedFlows } from "./flowCuration";
export interface ToolResult {
ok: boolean;
@@ -72,19 +73,17 @@ const TOOLS: ToolDef[] = [
const procs = await fetch(`${api.config.baseUrl}/api/ea2/flow/processes?limit=200`, {
headers: { Authorization: `Bearer ${sessionStorage.getItem("fm.mc.token.v1")}` },
}).then((r) => r.json());
const items: any[] = procs?.items || [];
const items = curatedPublishedFlows((procs?.items || []) as any[]);
const norm = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
const target = norm(processName);
const match = items
.filter((it) => it.status === "published" && it.kind === "definition")
.find((it) => norm(it.display_name || "").includes(target) || norm(it.name || "").includes(target));
const match = items.find((it) => norm(it.display_name || "").includes(target) || norm(it.name || "").includes(target));
if (!match) return { ok: false, display: `Couldn't find a published process matching "${processName}". Try the Studio to create one.` };
try {
const res = await wizardApi.startInstance(match._key, subject);
return {
ok: true,
display: `Started ${match.display_name}${subject ? ` for ${subject}` : ""}. Transaction ${res.transaction_id?.slice(0, 8) || "ok"}.`,
display: `Started ${match.display_name}${subject ? ` for ${subject}` : ""}.`,
data: res,
};
} catch (e: any) {
@@ -129,12 +128,14 @@ const TOOLS: ToolDef[] = [
description: "List published processes for this tenant",
matcher: /^(list|show|what)\s+(processes|workflows|flows)/i,
async run() {
const procs = await fetch(`${api.config.baseUrl}/api/ea2/flow/processes?limit=20`, {
const procs = await fetch(`${api.config.baseUrl}/api/ea2/flow/processes?limit=200`, {
headers: { Authorization: `Bearer ${sessionStorage.getItem("fm.mc.token.v1")}` },
}).then((r) => r.json());
const published = (procs?.items || []).filter((it: any) => it.status === "published" && it.kind === "definition").slice(0, 10);
if (!published.length) return { ok: true, display: "No published processes yet. Build one in the Studio." };
const lines = published.map((it: any) => `${it.display_name || it.name}`).join("\n");
})
.then((r) => r.json())
.catch(() => ({ items: [] }));
const published = curatedPublishedFlows((procs?.items || []) as any[]).slice(0, 10);
if (!published.length) return { ok: true, display: "No published processes yet. Open the Studio to design one." };
const lines = published.map((it) => `${it.display_name}`).join("\n");
return { ok: true, display: `Here's what's published:\n${lines}` };
},
},
+58
View File
@@ -0,0 +1,58 @@
export interface RawFlow {
_key: string;
display_name?: string;
name?: string;
description?: string;
source_context?: string;
status?: string;
kind?: string;
}
const DEV_ARTEFACT_PATTERNS = [
/^rv[_\s]/i,
/^orphan/i,
/^atlas[_\s]?f1/i,
/\batlas-f1-fresh\b/i,
/mcp[_\s]?sdx[_\s]?smoke/i,
/\bcodex[_\s]?test\b/i,
/\bsidekick\b/i,
/^process_\d{10,}$/i,
/\bbackfill\b/i,
/\bsmoke[_\s]?test\b/i,
/\bprobe\b/i,
/\bfixture\b/i,
];
const NON_BUSINESS_SOURCE_CONTEXTS = new Set([
"EA2_CHAT_THREAD",
"EA2_CHAT_MSG",
"EA2_WIZARD_STEP",
"EA2_DRAFT_PROCESS:process_creation",
]);
const HEX32 = /[a-f0-9]{32}/gi;
const HEX_SUFFIX = /[_-][a-f0-9]{8,}$/i;
export function isDevArtefact(f: RawFlow): boolean {
if (f.source_context && NON_BUSINESS_SOURCE_CONTEXTS.has(f.source_context)) return true;
const haystack = `${f.display_name || ""} ${f.name || ""} ${f.description || ""}`;
return DEV_ARTEFACT_PATTERNS.some((p) => p.test(haystack));
}
export function curatedPublishedFlows<T extends RawFlow>(items: T[]): T[] {
return items.filter(
(it) => it.status === "published" && it.kind === "definition" && !!it.display_name && !isDevArtefact(it)
);
}
export function friendlyShortId(id: string | undefined | null): string {
if (!id) return "";
const trimmed = id.replace(HEX_SUFFIX, "");
if (trimmed.length < id.length && trimmed.length > 0) return trimmed;
if (HEX32.test(id)) return `ref-${id.slice(0, 4)}`;
return id;
}
export function scrubIdsFromText(text: string): string {
return text.replace(HEX32, "");
}