From 8f35f7b88b68861d7b5ce6db5c60eef80af27fd2 Mon Sep 17 00:00:00 2001 From: shad Date: Sun, 14 Jun 2026 13:46:19 +0400 Subject: [PATCH] feat(chat): migrate from flow.kind=definition hack to flow.kind=value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Oracle deferred caveat #2 — chat-on-flow-collection hack — CLOSED. Previous design used flow.kind=definition + source_context=EA2_CHAT_THREAD for chat threads and EA2_CHAT_MSG for messages, then filtered them out of hub/wizard surfaces via a NON_BUSINESS_SOURCE_CONTEXTS exclusion. That was containment, not a clean model. Verified EA2 contract: flow.kind='value' is a valid enum value (probed against ea2.baseline.svc — kind=value returns 200, kind=conversation/ thread/message/channel/note all return 400 schema violation). - Thread: flow.kind=value, source_context=CANVAS_CHAT_THREAD - Message: flow.kind=value, source_context=CANVAS_CHAT_MSG - Linked via defines edges (already validated in round 2) Because curatedPublishedFlows already requires kind='definition', chat docs are now invisible to business surfaces by SCHEMA, not by source_ context regex filter. The filter is kept as defence-in-depth (+ legacy docs created during round 2 still need to be filtered out). Tried data collection first; it requires relates(role=sourced_from) edge to an sdx_connections handle per the P19 invariant — wrong model for chat, so flow.kind=value is the right home. --- src/lib/chatApi.ts | 18 +++++++++--------- src/lib/flowCuration.ts | 2 ++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib/chatApi.ts b/src/lib/chatApi.ts index 031480a..232b41f 100644 --- a/src/lib/chatApi.ts +++ b/src/lib/chatApi.ts @@ -48,12 +48,12 @@ async function jsonFetch(path: string, init?: RequestInit) { } export const chatApi = { - /** List existing chat-thread flow docs for the current tenant. */ + /** List existing chat-thread flow docs (kind=value, source_context=CANVAS_CHAT_THREAD). */ async listThreads(signal?: AbortSignal): Promise { - const res = await jsonFetch(`/api/ea2/flow/processes?limit=200`, { signal }); + const res = await jsonFetch(`/api/ea2/flow/processes?limit=500&kind=value`, { signal }); const items = (res?.items || []) as any[]; return items - .filter((it) => it?.source_context === "EA2_CHAT_THREAD") + .filter((it) => it?.source_context === "CANVAS_CHAT_THREAD") .map((it) => ({ _key: it._key, display_name: it.display_name || "Chat", @@ -64,17 +64,17 @@ export const chatApi = { }, /** - * Create a thread between two persona emails. Returns the new thread key. - * Falls back to a deterministic key built from the participants when present. + * Create a thread as flow.kind=value so it is invisible to process-definition + * surfaces by EA2 schema rather than by source_context filtering. */ async createThread(displayName: string, participants: string[], signal?: AbortSignal): Promise { const payload = { - kind: "definition", + kind: "value", status: "published", name: `chat_${Date.now()}`, display_name: displayName, description: `Conversation between ${participants.join(" & ")}`, - source_context: "EA2_CHAT_THREAD", + source_context: "CANVAS_CHAT_THREAD", config: { chat: { participants } }, }; const res = await fetch(`${api.config.baseUrl}/api/ea2/flow`, { @@ -124,12 +124,12 @@ export const chatApi = { method: "POST", headers: authHeaders(), body: JSON.stringify({ - kind: "definition", + kind: "value", status: "published", name: `msg_${Date.now()}`, display_name: body.slice(0, 80), description: body, - source_context: "EA2_CHAT_MSG", + source_context: "CANVAS_CHAT_MSG", config: { chat: { author_email: authorEmail, thread_key: threadKey } }, }), signal, diff --git a/src/lib/flowCuration.ts b/src/lib/flowCuration.ts index 4268888..e83b138 100644 --- a/src/lib/flowCuration.ts +++ b/src/lib/flowCuration.ts @@ -31,6 +31,8 @@ const DEV_ARTEFACT_PATTERNS = [ const NON_BUSINESS_SOURCE_CONTEXTS = new Set([ "EA2_CHAT_THREAD", "EA2_CHAT_MSG", + "CANVAS_CHAT_THREAD", + "CANVAS_CHAT_MSG", "EA2_WIZARD_STEP", "EA2_DRAFT_PROCESS:process_creation", ]);