From f3a3794e54ff5fddaa761aa004ffd6116ce3812f Mon Sep 17 00:00:00 2001 From: canvas-bot Date: Tue, 16 Jun 2026 21:16:31 +0400 Subject: [PATCH] fix: eliminate all 4xx noise from scene walks - buildScenarios: drop the /api/runtime/transactions/{id} headline GET entirely. Synthesize the RuntimeTransaction shape from the work-item data. Backend's per-id endpoint is broken (500 on valid IDs, 404 on work-item IDs); we already have all the display fields we need. - store.ts pollLiveTick: don't refetch headline transaction; reuse the existing scenario.raw.headlineRt synthesized at build time. - useBackendHealth: pass the bearer token if present so the probe returns 200 instead of 401 for authenticated users. The probe still treats 401 as 'up' for unauthenticated landing page. - agentMemory.remember: track ensureVault result. Skip the edge attach apply-batch entirely if the vault didn't materialize, so we don't fire a guaranteed-422 create_edge against a missing from-doc. --- src/lib/agentMemory.ts | 6 +++++- src/lib/buildScenarios.ts | 4 +--- src/lib/useBackendHealth.ts | 5 ++++- src/state/store.ts | 9 ++------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/agentMemory.ts b/src/lib/agentMemory.ts index 5f20160..3a9a8ca 100644 --- a/src/lib/agentMemory.ts +++ b/src/lib/agentMemory.ts @@ -66,7 +66,7 @@ export const agentMemory = { async remember(email: string, content: string, tags: string[] = [], signal?: AbortSignal): Promise { if (!content.trim()) return null; const vaultKey = vaultKeyFor(email); - await ensureVault(vaultKey, email, signal); + const vaultReady = await ensureVault(vaultKey, email, signal); const doc = await fetch(`${api.config.baseUrl}/api/ea2/flow`, { method: "POST", headers: authHeaders(), @@ -83,6 +83,10 @@ export const agentMemory = { }); if (!doc.ok) return null; const mem = await doc.json(); + if (!vaultReady) { + console.warn(`[memory] vault not ready for ${email}, skipping edge attach`); + return mem._key; + } try { const edgeRes = await fetch(`${api.config.baseUrl}/api/ea2/apply-batch`, { method: "POST", diff --git a/src/lib/buildScenarios.ts b/src/lib/buildScenarios.ts index 40e4400..e14da1a 100644 --- a/src/lib/buildScenarios.ts +++ b/src/lib/buildScenarios.ts @@ -272,9 +272,7 @@ export async function buildLiveScenariosFromApi(signal?: AbortSignal): Promise<{ const recentCandidates = c.cases .filter((w) => w.transaction_id && w.transaction_id !== headlineCase?.transaction_id) .slice(0, 3); - const headlineRt = headlineCase?.transaction_id - ? (await api.transaction(headlineCase.transaction_id, signal)) ?? workItemToRt(headlineCase) - : null; + const headlineRt = workItemToRt(headlineCase); const recent: RuntimeTransaction[] = recentCandidates .map((w) => workItemToRt(w)) .filter((r): r is RuntimeTransaction => r != null); diff --git a/src/lib/useBackendHealth.ts b/src/lib/useBackendHealth.ts index 620e2e2..cc403a3 100644 --- a/src/lib/useBackendHealth.ts +++ b/src/lib/useBackendHealth.ts @@ -12,7 +12,10 @@ const POLL_MS = 30_000; async function probeOnce(signal: AbortSignal): Promise { try { - const r = await fetch("/api/v1/auth/me", { method: "GET", signal }); + const token = typeof sessionStorage !== "undefined" ? sessionStorage.getItem("fm.mc.token.v1") || "" : ""; + const headers: Record = { "Accept": "application/json" }; + if (token) headers.Authorization = `Bearer ${token}`; + const r = await fetch("/api/v1/auth/me", { method: "GET", headers, signal }); if (r.status === 200 || r.status === 401 || r.status === 403) return "up"; if (r.status === 502 || r.status === 504) return "proxy-down"; if (r.status === 503) return "auth-down"; diff --git a/src/state/store.ts b/src/state/store.ts index e56c9cc..94171ea 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -311,13 +311,8 @@ export const useApp = create((set, get) => { try { const workItems = await api.workItems(); const sc = s.scenarios.find((x) => x.id === s.scenarioId); - let headlineRt = null; - if (sc?.headlineTx) { - // Best-effort: the per-transaction endpoint can 500/404 when the - // runtime DB is unreachable. Don't surface that as a polling error — - // existing headlineRt in scenario.raw is still valid for display. - headlineRt = await api.transaction(sc.headlineTx).catch(() => null); - } + const existingHeadline = (sc?.raw as { headlineRt?: unknown } | undefined)?.headlineRt ?? null; + const headlineRt = existingHeadline; const updatedScenarios = s.scenarios.map((scen) => { if (!scen.live) return scen; const myCases = workItems.filter((w) => w.definition_key === scen.defKey);