feat(chat): migrate from flow.kind=definition hack to flow.kind=value
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.
This commit is contained in:
+9
-9
@@ -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<ChatThread[]> {
|
||||
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<string> {
|
||||
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,
|
||||
|
||||
@@ -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",
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user