import { coverageReviewSchema } from "./schemas";
import { z } from "zod";
import {
ENGINE_VERSION,
RULES_VERSION,
} from "../../../src/domain/checks/engine";
import { CHECK_SIGNED_OFF } from "../../../src/domain/checks/signoff";
import { baseSchema } from "./schemas";
import { CASES } from "./cases";
import { assess } from "./context";
import { extract, modelReady, modelLabel, modelVerification } from "./model";
import { ModelError } from "./model-runtime";
import { runEvaluation } from "./evaluation";
import { accessOwner } from "./access";
import { demoOnly, demoOwner } from "./demo-auth";
import { DurableSessions, type BlobStore } from "./durable-sessions";
/**
* CONTEXT_AUTH_MODE=demo serves Try a fictional patient to anyone (ACC-2): the fictional records
* only, the local reader only. Otherwise reviewers sign in through Cloudflare Access. There is no
* shared-password mode (ACC-11).
*/
export function createNetlifyHandler(
getStore: () => BlobStore,
authenticate?: (request: Request) => Promise<string> | string
) {
return async (request: Request): Promise<Response> => {
const headers: Record<string, string> = {
"Cache-Control": "no-store",
"X-Content-Type-Options": "nosniff",
"Referrer-Policy": "no-referrer",
};
const json = (status: number, body: unknown) =>
Response.json(body, { status, headers });
try {
const url = new URL(request.url);
const allowed = (
process.env.CONTEXT_ALLOWED_ORIGINS || "https://context.marmar.life"
)
.split(",")
.map((x) => x.trim());
if (
!allowed.includes(url.origin) ||
(request.headers.has("Origin") &&
!allowed.includes(request.headers.get("Origin")!))
)
throw new ModelError("Origin is not allowed.", 403);
const demo = !authenticate && process.env.CONTEXT_AUTH_MODE === "demo";
let owner: string;
if (authenticate) owner = await authenticate(request);
else if (demo) {
const visitor = demoOwner(request);
if (visitor.setCookie) headers["Set-Cookie"] = visitor.setCookie;
owner = visitor.owner;
} else owner = await accessOwner(request);
const sessions = new DurableSessions(getStore());
const path = url.pathname;
if (request.method === "GET" && path === "/api/status")
return json(200, {
engineVersion: ENGINE_VERSION,
rulesVersion: RULES_VERSION,
rulesReviewed: CHECK_SIGNED_OFF,
modelConfigured: !demo && modelReady(),
model: !demo ? modelLabel() : null,
modelVerifiedAt: demo ? null : modelVerification(),
hosted: true,
modelConnectionManaged: true,
sessionStorage: "durable",
demo,
});
if (request.method === "GET" && path === "/api/cases")
return json(200, CASES);
if (request.method === "GET" && path.startsWith("/api/reports/")) {
headers["Content-Disposition"] =
'attachment; filename="marmar-context-evidence.json"';
return json(
200,
await sessions.getReport(path.slice("/api/reports/".length), owner)
);
}
if (path === "/api/model")
throw new ModelError(
"Model connection is managed by the deployment administrator.",
403
);
if (request.method !== "POST")
throw new ModelError("Method not allowed.", 405);
if (!request.headers.get("Content-Type")?.startsWith("application/json"))
throw new ModelError("Use application/json.", 415);
if (Number(request.headers.get("Content-Length")) > 120000)
throw new ModelError("Request is too large.", 413);
const text = await request.text();
if (Buffer.byteLength(text) > 120000)
throw new ModelError("Request is too large.", 413);
let parsed: unknown;
try {
parsed = JSON.parse(text);
} catch {
throw new ModelError("Invalid JSON.", 400);
}
if (path === "/api/evaluate") return json(200, runEvaluation());
if (path === "/api/extract") {
const data = baseSchema
.extend({
useModel: z.boolean().default(false),
freshModel: z.boolean().default(false),
})
.strict()
.parse(parsed);
if (demo) demoOnly(data.sources, data.useModel);
await sessions.limit(owner);
const draft = await extract(
data.sources,
data.useModel,
data.freshModel,
request.signal
);
return json(200, await sessions.keepDraft(draft, data.sources, owner));
}
if (path === "/api/assess") {
const data = baseSchema
.extend({
draftId: z.string().uuid(),
review: z.record(z.string(), z.enum(["include", "exclude"])),
coverageReview: coverageReviewSchema,
fingerprint: z.string().length(64),
acknowledgeUnparsed: z.boolean(),
})
.strict()
.parse(parsed);
if (demo) demoOnly(data.sources);
const draft = await sessions.getDraft(
data.draftId,
data.sources,
owner
);
return json(
200,
await sessions.keepReport(
data.draftId,
assess(
data.sources,
data.review,
data.fingerprint,
data.acknowledgeUnparsed,
draft,
data.coverageReview
),
data.review,
owner
)
);
}
return json(404, { error: "Unknown endpoint." });
} catch (error) {
if (error instanceof ModelError) {
if (error.retryAfter) headers["Retry-After"] = String(error.retryAfter);
return json(error.status, { error: error.message });
}
if (error instanceof z.ZodError)
return json(400, {
error:
"Invalid request. Check field types, lengths and synthetic-data acknowledgement.",
});
// Domain/model errors are user-facing; storage transport details must never leak credentials.
if (
error instanceof Error &&
!/https?:|token|authorization|bearer|api.?key/i.test(error.message)
)
return json(400, { error: error.message });
return json(503, {
error:
"The preview could not complete this request. Your browser-saved work is unchanged. Retry shortly.",
});
}
};
}
export async function cleanup(getStore: () => BlobStore) {
return new DurableSessions(getStore()).purge();
}