import { withEditNotes } from "@/lib/report/guard"; import { StaleToken, manifestToken, updateClaim } from "@/lib/report/manifest.mjs"; import { resolveClaim } from "@/lib/report/serve.mjs"; import { readClaimDetail } from "@/lib/projects/report.mjs"; export const dynamic = "force-dynamic"; // One ledger claim, and the ruling on it. // // GET mirrors /api/report/clip: everything the bench needs in one request, plus // the manifest's mtime as a WRITE TOKEN, so a save can be refused when somebody // -- another tab, an agent, a `resolve-windows --write` -- has written in // between. Losing that write would be losing human judgement, which is the one // thing this whole surface exists to collect. export async function GET(request: Request) { const url = new URL(request.url); const projectId = url.searchParams.get("project") ?? ""; const claimId = url.searchParams.get("claim") ?? ""; const padArg = Number(url.searchParams.get("pad")); const r = await resolveClaim(projectId, claimId); if ("error" in r) return Response.json({ error: r.error }, { status: r.status }); const detail = await readClaimDetail(r.project.dir, claimId, { manifest: r.manifest, ...(Number.isFinite(padArg) && padArg > 0 ? { pad: Math.min(600, padArg) } : {}), }); if (!detail) return Response.json({ error: "no such claim" }, { status: 404 }); return Response.json( { project: r.project.id, ...detail, token: await manifestToken(r.project.dir) }, { headers: { "cache-control": "no-store" } }, ); } export async function PUT(request: Request) { const body = (await request.json().catch(() => ({}))) as Record; const projectId = String(body.project ?? ""); const claimId = String(body.claim ?? ""); const r = await resolveClaim(projectId, claimId); if ("error" in r) return Response.json({ error: r.error }, { status: r.status }); const patch: Record = {}; for (const k of ["scope", "scopeBasis", "scopeConfidence", "population", "valueKind", "flags", "roles", "note"]) { if (body[k] !== undefined) patch[k] = body[k]; } if (!Object.keys(patch).length) { return Response.json({ error: "nothing to write" }, { status: 400 }); } try { const { result: { entry, token }, editNotes, } = await withEditNotes(r.project, () => updateClaim(r.project.dir, claimId, patch, { token: body.token === undefined ? null : String(body.token), }), ); const detail = await readClaimDetail(r.project.dir, claimId); return Response.json({ claim: entry, gaps: detail?.gaps ?? [], token, editNotes }); } catch (err) { // A stale token is a 409 and never a silent overwrite. if (err instanceof StaleToken) { return Response.json({ error: err.message, stale: true }, { status: 409 }); } return Response.json({ error: (err as Error).message }, { status: 400 }); } }