fix(report): persist longform appendix on self-hosted upsert (BUG-576)

Engine calls already returned markdown; the worker failed because local postgres upsert required onConflict and the appendix write omitted it.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-07 14:19:21 +08:00
co-authored by Cursor
parent 4716fe46b3
commit e87c58d6e4
19 changed files with 382 additions and 70 deletions
+21 -8
View File
@@ -26,7 +26,7 @@ function envCheck(names: string[]): Check {
: { status: "ok" };
}
async function jyotishApiCheck(): Promise<Check> {
async function jyotishApiCheck(): Promise<{ check: Check; gitCommit: string }> {
const started = Date.now();
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 3000);
@@ -35,16 +35,28 @@ async function jyotishApiCheck(): Promise<Check> {
cache: "no-store",
signal: controller.signal,
});
const body = response.ok
? await response.json().catch(() => null) as { git_commit?: unknown } | null
: null;
const gitCommit = typeof body?.git_commit === "string" && body.git_commit.trim()
? body.git_commit.trim()
: "unknown";
return {
status: response.ok ? "ok" : "blocked",
message: response.ok ? undefined : `http:${response.status}`,
latencyMs: Date.now() - started,
check: {
status: response.ok ? "ok" : "blocked",
message: response.ok ? undefined : `http:${response.status}`,
latencyMs: Date.now() - started,
},
gitCommit,
};
} catch (error) {
return {
status: "blocked",
message: error instanceof Error ? error.name : "jyotish_api_unavailable",
latencyMs: Date.now() - started,
check: {
status: "blocked",
message: error instanceof Error ? error.name : "jyotish_api_unavailable",
latencyMs: Date.now() - started,
},
gitCommit: "unknown",
};
} finally {
clearTimeout(timeout);
@@ -148,7 +160,7 @@ export async function GET() {
...databaseChecks,
modelProviderEncryption: envCheck(["MODEL_PROVIDER_CONFIG_ENCRYPTION_KEY"]),
modelCatalog,
jyotishApi,
jyotishApi: jyotishApi.check,
rectificationMigrations: migrations.check,
researchTruthSource: {
status: truthSourceIdentity.status,
@@ -162,6 +174,7 @@ export async function GET() {
timestamp: new Date().toISOString(),
deployment: {
gitCommit,
apiGitCommit: jyotishApi.gitCommit,
},
database: {
latestMigration: migrations.database.latestMigration,
@@ -48,7 +48,7 @@ async function resolvePersistenceForUser() {
persistence: null as PersonalReportService | null,
jobs: null,
listSections: undefined,
loadLongformMarkdown: undefined,
loadLongformAppendix: undefined,
};
}
const persistence = createSupabasePersonalReportService(supabase);
@@ -57,11 +57,11 @@ async function resolvePersistenceForUser() {
userId: user.id,
persistence,
jobs: createSupabasePersonalReportJobService(supabase),
listSections: async (ownerId: string, requestId: string) => {
listSections: async (ownerId: string, requestId: string) => {
const rows = await sections.list(ownerId, requestId);
return rows.map((row) => ({ status: row.status, lastErrorCode: row.lastErrorCode }));
},
loadLongformMarkdown: async (input: Readonly<{ userId: string; reportId: string }>) => {
loadLongformAppendix: async (input: Readonly<{ userId: string; reportId: string }>) => {
const appendixRead = await supabase
.from(LONGFORM_APPENDIX_TABLE)
.select("report_id,user_id,request_id,status,markdown,content_sha256,attempt_count,last_error_code")
@@ -70,14 +70,19 @@ async function resolvePersistenceForUser() {
.maybeSingle();
if (appendixRead.error) return null;
const row = parseLongformAppendixRow(appendixRead.data);
return row?.status === "ready" && row.markdown ? row.markdown : null;
if (!row) return null;
return {
status: row.status,
lastErrorCode: row.lastErrorCode,
markdown: row.markdown,
};
},
};
}
export async function GET(request: Request, context: RouteContext) {
try {
const { userId, persistence, jobs, listSections, loadLongformMarkdown } = await resolvePersistenceForUser();
const { userId, persistence, jobs, listSections, loadLongformAppendix } = await resolvePersistenceForUser();
const { reportId } = await context.params;
if (!uuidPattern.test(reportId)) {
return NextResponse.json(
@@ -103,7 +108,7 @@ export async function GET(request: Request, context: RouteContext) {
// a substitute.
jobs: jobs ?? undefined,
listSections,
loadLongformMarkdown,
loadLongformAppendix,
validateReadyDocument: (document) => {
const parsed = safeParseServerReportDocument(document);
return parsed.ok