feat(report): birth-sky report cover bound to the report's birth fingerprint (F1–F3)
Product decision A (TASK-birth-sky-followup-20260928): - every generation path writes subject.birthFingerprint (stored-row fingerprint, same function as the chart page's profileFingerprint); never into the evidence bundle the model reads - GET /api/reports/:id compares on the server and returns only report.coverSubject (subject id or null); legacy reports never get one - report page shows the night cover above the body, paper cover alone on the first printed A4 page; silent on any failure, no waiting state - TS / JSON schema / Python contract accept the optional field - BLOCKED T5 struck through; DESIGN §17, CHANGELOG, checklist, PROGRESS Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
14ef18ac24
commit
a9a917d50c
@@ -18,6 +18,8 @@ import { createSupabasePersonalReportJobService } from "@/lib/personal-report-jo
|
||||
import { createPersonalReportSectionService } from "@/lib/personal-report-section-service";
|
||||
import { isSupabaseConfigurationError } from "@/lib/supabase/config";
|
||||
import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
import { subjectBirthFingerprint } from "@/lib/report-birth-binding";
|
||||
import { loadSubjectBirth } from "@/lib/subject-birth";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
@@ -49,6 +51,7 @@ async function resolvePersistenceForUser() {
|
||||
jobs: null,
|
||||
listSections: undefined,
|
||||
loadLongformAppendix: undefined,
|
||||
loadCurrentBirthFingerprint: undefined,
|
||||
};
|
||||
}
|
||||
const persistence = createSupabasePersonalReportService(supabase);
|
||||
@@ -84,12 +87,17 @@ async function resolvePersistenceForUser() {
|
||||
markdown: row.markdown,
|
||||
};
|
||||
},
|
||||
// Report cover binding: the same stored-row fingerprint the chart page uses.
|
||||
// A foreign, deleted or unreadable subject throws and resolves to no cover.
|
||||
loadCurrentBirthFingerprint: async (ownerId: string, subjectId: string) => (
|
||||
subjectBirthFingerprint((await loadSubjectBirth(supabase, ownerId, subjectId)).row)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export async function GET(request: Request, context: RouteContext) {
|
||||
try {
|
||||
const { userId, persistence, jobs, listSections, loadLongformAppendix } = await resolvePersistenceForUser();
|
||||
const { userId, persistence, jobs, listSections, loadLongformAppendix, loadCurrentBirthFingerprint } = await resolvePersistenceForUser();
|
||||
const { reportId } = await context.params;
|
||||
if (!uuidPattern.test(reportId)) {
|
||||
return NextResponse.json(
|
||||
@@ -116,6 +124,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
jobs: jobs ?? undefined,
|
||||
listSections,
|
||||
loadLongformAppendix,
|
||||
loadCurrentBirthFingerprint,
|
||||
validateReadyDocument: (document) => {
|
||||
const parsed = safeParseServerReportDocument(document);
|
||||
return parsed.ok
|
||||
|
||||
@@ -2,6 +2,7 @@ import { normalizeReportSubjectRequest } from "@/lib/report-subject-request";
|
||||
import { NextResponse } from "next/server";
|
||||
import { LONGFORM_SNAPSHOT_SECTION_ID } from "@/lib/personal-report-longform-snapshot";
|
||||
import { resolveMissingBirthTimezoneOffset } from "@/lib/birth-profile-timezone";
|
||||
import { stampReportBirthFingerprint } from "@/lib/report-birth-binding";
|
||||
import { runConsultationWorkflow } from "@/mastra";
|
||||
import { createPersonalReportAgent } from "@/mastra/personal-report";
|
||||
import { loadLanguageModelCatalog } from "@/lib/model-catalog";
|
||||
@@ -201,7 +202,11 @@ export async function POST(request: Request) {
|
||||
code: REPORT_STABLE_CODES.profileIncomplete,
|
||||
}, { status: 422 });
|
||||
}
|
||||
profile = await resolveMissingBirthTimezoneOffset(resolved.row, { useActiveDate: true });
|
||||
// The cover binding fingerprint is taken from the stored row, before the fill.
|
||||
profile = stampReportBirthFingerprint(
|
||||
resolved.row,
|
||||
await resolveMissingBirthTimezoneOffset(resolved.row, { useActiveDate: true }),
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof ConsultationSubjectError) {
|
||||
const failure = consultationSubjectFailureResponse(error);
|
||||
|
||||
Reference in New Issue
Block a user