Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions apps/presentation/dashboard/smoke/bound-conversation-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,20 @@ try {
assert.deepEqual(calls, [{path: `/api/chat/sessions?conversation_binding_id=${bindingId}`, method: "GET"}],
"A fresh check of the same unchanged binding reuses the complete verified history");

// The final source check can keep the same address while reporting a
// different canonical Session scope. Previously read pages cannot publish.
sourceChecks = 0;
globalThis.fetch = async (input, init) => {
const response = await routedRead(input, init);
if (!isBoundDiscovery(input) || ++sourceChecks !== 2) return response;
const changedSource = await response.json();
changedSource.sessions[0].channel_id = "unrelated-original-channel";
return new Response(JSON.stringify(changedSource), {status: response.status});
};
await assert.rejects(() => fetchChatHistory(options), /original Session scope changed during history read/);
assert.equal(sourceChecks, 2);
globalThis.fetch = routedRead;

// Current source revocation blocks recovery before another history page.
child.stdin.write("deny\n"); assert.equal((await next()).source_verified, false);
calls.length = 0;
Expand Down
7 changes: 5 additions & 2 deletions apps/presentation/dashboard/src/data/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1375,10 +1375,13 @@ export async function fetchChatHistory(options: {
// while it was in flight. Reuse the original source owner, never a fallback.
if (missing.length && results.some(result => result.status === "fulfilled")) {
if (options.bound) {
const read = results[0];
try {
await resolveBoundChatSession(options.bound, AbortSignal.timeout(CHAT_HISTORY_READ_TIMEOUT_MS));
const confirmed = await resolveBoundChatSession(options.bound, AbortSignal.timeout(CHAT_HISTORY_READ_TIMEOUT_MS));
if (read?.status === "fulfilled" && !sameHistorySource(confirmed, read.value.session)) {
throw new Error("The original Session scope changed during history read.");
}
} catch (error) {
const read = results[0];
if (options.managed || read?.status !== "fulfilled" || !historyTransportUnavailable(error)) throw error;
return {
messages: [],
Expand Down
Loading