@formancy/server 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completers-C-E2b7Jm.mjs","names":["answer","argon2Hash","argon2Verify","isRecord","unavailable","read","APIError"],"sources":["../src/header-values.ts","../src/rate-limits.ts","../src/routes/files.ts","../src/headers.ts","../src/routes/publish.ts","../src/routes/access.ts","../src/routes/examples.ts","../src/routes/deliveries.ts","../src/routes/drafts.ts","../src/routes/model.ts","../src/routes/submissions.ts","../src/auth-runtime.ts","../src/upload-settings.ts","../src/server-log.ts","../src/error-replies.ts","../src/nul-characters.ts","../src/audit-trail.ts","../src/app.ts","../src/db.ts","../src/postgres-examples.ts","../src/postgres-identities.ts","../src/postgres-storage.ts","../src/postgres-rate-limits.ts","../src/file-store.ts","../src/file-collector.ts","../src/model-settings.ts","../src/anthropic-completer.ts","../src/openai-completer.ts","../src/completers.ts"],"sourcesContent":["/**\n * A response header's value made from data: a name somebody else chose\n * ([0175](../../../docs/decisions/0175-a-name-goes-into-a-header-encoded.md)).\n *\n * Node writes a header only when every character of its value is a tab, printable ASCII or one\n * of the bytes 0x80 to 0xFF, and throws `ERR_INVALID_CHAR` for anything else — when the reply is\n * written, after the route has answered. Nothing about a file's name or a form's path keeps it\n * inside that: a respondent names a file `日本 €.pdf`, an author publishes a path with a line\n * break in it. And the bytes 0x80 to 0xFF it does accept are no better: the server sent `é` as one\n * byte before a stream and as two, its UTF-8, before a string body, so a client read `café` on a\n * download and `café` on an export.\n *\n * So a value is made here, from the data, in printable ASCII only, and a route writes what this\n * returns rather than a template of its own. `header-values.test.ts` holds every export to\n * every UTF-16 code unit, and reads the server's sources for a header written any other way.\n */\n\n/**\n * `content-disposition` for a download, by RFC 6266: `attachment`, and the name twice when\n * ASCII cannot carry it — once readable, once exact.\n *\n * `filename` is a quoted string of printable ASCII, with every character it cannot hold as\n * itself made `_`: anything outside it, the quote that would end it, the backslash a reader may\n * or may not take as an escape, and the percent sign RFC 6266 (appendix D) says some readers\n * decode. `filename*` is RFC 8187's extended value, the name's UTF-8 percent-encoded, which\n * RFC 6266 has a reader prefer when both are there. It is added only when the readable one\n * lost something, so an ordinary name keeps the header it always had.\n *\n * The name is not cleaned of anything: a control character or a path separator goes to the\n * reader's browser encoded, and making a file name its system can hold is the browser's, as it\n * is for every download.\n */\nexport function attachment(filename: string): string {\n // `u`, so a character beyond the BMP is one `_` rather than one for each half of its pair.\n const readable = filename.replace(/[^\\x20-\\x7e]|[\"\\\\%]/gu, '_')\n if (readable === filename) return `attachment; filename=\"${readable}\"`\n return `attachment; filename=\"${readable}\"; filename*=UTF-8''${percentEncoded(filename)}`\n}\n\n/** RFC 8187's attr-char: what an extended value may hold as itself. */\nconst ATTR_CHAR = /^[A-Za-z0-9!#$&+\\-.^_`|~]$/\n\n/**\n * `text` as UTF-8, each byte that is not an attr-char written `%XX`.\n *\n * Through a `Buffer` rather than `encodeURIComponent`, which leaves `'`, `(`, `)` and `*` as they\n * are — and a `'` ends an extended value's charset — and throws on a lone surrogate, which a\n * JSON body can carry. A `Buffer` writes one as U+FFFD.\n */\nfunction percentEncoded(text: string): string {\n let encoded = ''\n for (const byte of Buffer.from(text, 'utf8')) {\n const character = String.fromCharCode(byte)\n encoded += ATTR_CHAR.test(character) ? character : `%${byte.toString(16).toUpperCase().padStart(2, '0')}`\n }\n return encoded\n}\n","import type { RateLimitOptions } from '@fastify/rate-limit'\n\n/** So many requests in so many milliseconds: a limit as `AppOptions` takes it. */\nexport interface Budget {\n readonly max: number\n readonly timeWindowMs: number\n}\n\n/**\n * What a limit does when its count cannot be had — the shared store failing, or slower than\n * its bound (0170).\n *\n * - `admit`: the request goes on, uncounted. For a route that needs the database for its own\n * work anyway, where the person refused would be a respondent: refusing adds nothing when\n * the database is down, and turns a fault in the counter into an outage of every form when\n * it is not.\n * - `refuse`: a 503. For a limit that is what stands between somebody and a thing worth\n * having — guesses at a password, or the operator's money at a model's provider.\n */\nexport type WhenUncounted = 'admit' | 'refuse'\n\n/**\n * A route's `config.rateLimit`.\n *\n * The failure mode is an argument, so no limit can be written without saying it; left to the\n * plugin, it is an error, which a route answers as a 500 nobody chose. And the window is\n * translated here, once: `timeWindowMs` is this app's name for it and the plugin does not\n * read it, which one route found out by silently taking the global window instead.\n */\nexport function limited(budget: Budget, whenUncounted: WhenUncounted): RateLimitOptions {\n return { max: budget.max, timeWindow: budget.timeWindowMs, skipOnError: whenUncounted === 'admit' }\n}\n","import { randomUUID } from 'node:crypto'\nimport type { FastifyBaseLogger, FastifyInstance, FastifyRequest } from 'fastify'\nimport { offerUpload, resolveForm, screenUpload } from '@formancy/server-core'\nimport type { Actor, FileRecord, ServerDeps } from '@formancy/server-core'\nimport type { FileStore } from '../file-store.js'\nimport { attachment } from '../header-values.js'\nimport { limited } from '../rate-limits.js'\nimport type { Budget } from '../rate-limits.js'\n\n/**\n * How long one request holds a file while its bytes are scanned and written (0153).\n *\n * A judgement, not a bound the adapters guarantee. The object store this package supplies\n * abandons a PUT after 30 seconds, as a whole; its clamd adapter's 30 seconds are of silence\n * — any traffic on the connection restarts them — so nothing here limits how long a scan\n * takes. Longer makes an overrun rarer, and keeps a PUT of the same file busy for longer\n * after a request that died holding it. A request that does overrun it can still replace a\n * claimed file's bytes, and `docs/architecture/11-risks-and-debt.md` says what that costs.\n */\nconst RECEIVE_LEASE_MS = 2 * 60_000\n\n/** What a receiving request answers, decided before it is sent. */\ninterface Answer {\n status: 204 | 409 | 422 | 503\n body?: { error: string; message: string }\n}\n\n/** A request whose lease ran out is not accepted, whichever check found it. */\nconst OUTLASTED: Answer = {\n status: 409,\n body: {\n error: 'busy',\n message:\n 'This upload took longer than the server holds a file for one request, so it was not accepted. Try again.',\n },\n}\n\n/**\n * A form's files: offering one, receiving its bytes, and serving it back.\n *\n * One route family per plugin, the seam `app.ts`'s size budget names. It moved when\n * scanning an upload's bytes needed lines that file had no room for.\n */\nexport async function fileRoutes(\n app: FastifyInstance,\n {\n deps,\n fileStore,\n maxFileBytes,\n limit,\n actorOf,\n }: {\n deps: ServerDeps\n fileStore: FileStore | undefined\n maxFileBytes: number\n limit: Budget\n actorOf: (request: FastifyRequest) => Promise<Actor | undefined>\n },\n): Promise<void> {\n const { storage } = deps\n const submissionLimit = limit\n\n /**\n * Ask where to put a file.\n *\n * Refusing here, before a byte is sent, is the difference between rejecting\n * a 2 GB upload and receiving one first. The reply carries the id the\n * submission will reference and the URL to PUT the bytes to.\n */\n app.post(\n '/f/:path/files',\n {\n config: { rateLimit: limited(submissionLimit, 'admit') },\n },\n async (request, reply) => {\n if (fileStore === undefined) {\n return reply.code(501).send({\n error: 'uploads_unavailable',\n message: 'This deployment has no file store configured, so it cannot accept uploads.',\n })\n }\n\n const { path } = request.params as { path: string }\n const resolved = await resolveForm(deps, path)\n if (resolved === undefined) return reply.code(404).send({ error: 'unknown_form' })\n\n const form = await storage.getFormByPath(path)\n if (form === undefined) return reply.code(404).send({ error: 'unknown_form' })\n\n const actor = await actorOf(request)\n const origin = request.headers.origin\n\n const body = (request.body ?? {}) as {\n field?: unknown\n name?: unknown\n size?: unknown\n contentType?: unknown\n }\n const field = typeof body.field === 'string' ? body.field : undefined\n const name = typeof body.name === 'string' ? body.name : undefined\n const size = typeof body.size === 'number' ? body.size : undefined\n const contentType =\n typeof body.contentType === 'string' ? body.contentType : 'application/octet-stream'\n\n if (field === undefined || name === undefined || size === undefined) {\n return reply.code(400).send({\n error: 'invalid_request',\n message: 'Send the field, the name and the size of the file you want to upload.',\n })\n }\n if (!Number.isSafeInteger(size) || size < 0) {\n // A size counts bytes. A fraction reached the `integer` column and failed there as a\n // 500; a negative one was stored, and no upload could ever match it.\n return reply.code(400).send({\n error: 'invalid_request',\n message: 'The size of a file is a whole number of bytes, zero or more.',\n })\n }\n\n if (size > maxFileBytes) {\n return reply.code(413).send({\n error: 'too_large',\n message: `This deployment accepts files up to ${String(maxFileBytes)} bytes.`,\n })\n }\n\n const outcome = await offerUpload(\n { storage, now: () => new Date(), newId: () => randomUUID() },\n {\n schema: resolved.schema,\n form,\n actor: actor === undefined ? 'anonymous' : 'authenticated',\n origin: typeof origin === 'string' ? origin : undefined,\n field,\n name,\n size,\n contentType,\n },\n )\n\n if (!outcome.ok) {\n const status =\n outcome.reason === 'not_allowed' ? 403 : outcome.reason === 'too_large' ? 413 : 400\n return reply.code(status).send({ error: outcome.reason })\n }\n\n return reply.code(201).send({\n id: outcome.file.id,\n name: outcome.file.name,\n size: outcome.file.size,\n contentType: outcome.file.contentType,\n storageKey: outcome.file.storageKey,\n // Where to PUT the bytes. A URL rather than a key so an S3 store can\n // hand back a presigned one here and the client never learns the\n // difference.\n uploadUrl: `/f/${encodeURIComponent(path)}/files/${outcome.file.id}`,\n })\n },\n )\n\n /**\n * Receive the bytes for a file that was offered.\n *\n * In a context of its own, with every body parser removed and one put back that hands\n * any body over as bytes. A file is whatever the reader had — the form's `accept` list\n * decides whether it is allowed, at the offer — and Fastify parses `text/plain` and\n * `application/json` itself: a catch-all added beside those only ever saw the types with\n * no parser of their own, so a `.txt` or `.json` file arrived here as a string or an\n * object and was refused as no body. Encapsulated, so that no other route is handed raw\n * bytes, nor a body as large as a file past the request body cap.\n */\n await app.register(async (byteRoutes) => {\n byteRoutes.removeAllContentTypeParsers()\n byteRoutes.addContentTypeParser(\n '*',\n { parseAs: 'buffer', bodyLimit: maxFileBytes },\n (_request, body, done) => {\n done(null, body)\n },\n )\n\n byteRoutes.put(\n '/f/:path/files/:id',\n {\n // Counted before the body is read, which is the point: an offer makes a place, not one\n // attempt at it, and every attempt is read whole — up to the deployment's ceiling on a\n // file — before the route can refuse it, and one that matches its offer is scanned. The\n // offer's budget, counted apart from the offers, so a respondent may send each file they\n // offered. Admitted uncounted, as the offer is: refusing the bytes of a file whose offer\n // went through would fail every file field while the counter does, and the route needs\n // the database for the file's row anyway.\n config: { rateLimit: limited(submissionLimit, 'admit') },\n },\n async (request, reply) => {\n if (fileStore === undefined) return reply.code(501).send({ error: 'uploads_unavailable' })\n\n const { path, id } = request.params as { path: string; id: string }\n const form = await storage.getFormByPath(path)\n if (form === undefined) return reply.code(404).send({ error: 'unknown_form' })\n\n const file = await storage.getFile(id)\n // Checked against the form in the URL: an id alone must not be enough to\n // write bytes into a form somebody cannot submit to.\n if (file === undefined || file.formId !== form.id) {\n return reply.code(404).send({ error: 'unknown_file' })\n }\n if (file.state !== 'offered') {\n // An upload is once. Re-writing a claimed file would change what a\n // stored submission says was attached to it.\n return reply.code(409).send({ error: 'already_uploaded' })\n }\n\n const bytes = request.body\n if (!Buffer.isBuffer(bytes)) return reply.code(400).send({ error: 'no_body' })\n if (bytes.byteLength > maxFileBytes) return reply.code(413).send({ error: 'too_large' })\n if (bytes.byteLength !== file.size) {\n // The offer named a size and the offer is what was checked against the\n // field's limit. Accepting a different number of bytes would make that\n // check a suggestion.\n return reply.code(400).send({\n error: 'size_mismatch',\n message: `This upload was offered as ${String(file.size)} bytes and ${String(bytes.byteLength)} arrived.`,\n })\n }\n\n // One request receives a file's bytes at a time (0153). Taken after the checks above,\n // so a request that was never going to be kept does not hold the file against one that\n // is.\n const now = deps.nowIso()\n const lease = new Date(Date.parse(now) + RECEIVE_LEASE_MS).toISOString()\n if (!(await storage.leaseFile(file.id, now, lease))) {\n return reply.code(409).send({\n error: 'busy',\n message: \"Another request is sending this file's bytes, so these were not kept. Try again in a moment.\",\n })\n }\n\n // Every way out that does not keep the bytes gives the file back, and before the reply:\n // a client that sends this file again, as it is told to, must not find it still held.\n const answer = await receive(fileStore, file, bytes, lease, request.log).catch(\n async (error: unknown) => {\n await giveBack(file.id, lease, request.log)\n throw error\n },\n )\n if (answer.status !== 204) await giveBack(file.id, lease, request.log)\n return reply.code(answer.status).send(answer.body)\n },\n )\n })\n\n /**\n * Give a file back after a request that kept nothing, without letting the release decide\n * the reply.\n *\n * A release that fails is most likely the database the request has already failed on, and\n * its error would stand in for the one that says why the bytes were not kept — or turn a\n * scanner's refusal into a 500. The lease runs out on its own, so all a failed release\n * costs is a `PUT` of this same file being busy until it does.\n */\n async function giveBack(id: string, lease: string, log: FastifyBaseLogger): Promise<void> {\n await storage.releaseFile(id, lease).catch((cause: unknown) => {\n // Its lease will run out on its own.\n log.error({ event: 'upload.unreleased', err: cause })\n })\n }\n\n /**\n * Scan a file's bytes, write them and settle its row, for a request holding its lease.\n *\n * Returns the answer rather than sending it, so the route can give the lease back first.\n */\n async function receive(\n store: FileStore,\n file: FileRecord,\n bytes: Buffer,\n lease: string,\n log: FastifyBaseLogger,\n ): Promise<Answer> {\n // Asked before the bytes are kept, so a file the deployment's scanner refuses is never\n // in its store, not even until the collector runs. The row stays offered: a retry may\n // send the bytes again (0131).\n const screened = await screenUpload(deps.scanner, file, bytes)\n if (!screened.ok) {\n if (screened.reason === 'refused') {\n // The finding is in the answer; the log says only that there was one.\n log.warn({ event: 'upload.refused' })\n return {\n status: 422,\n body: {\n error: 'refused_by_scanner',\n message: `This file was refused by the deployment's virus scanner (${screened.finding}).`,\n },\n }\n }\n // The cause is a sentence, and can name an address: the log says the scanner could not\n // be asked, and its words go nowhere (0168).\n log.error({ event: 'scanner.unreachable' })\n return {\n status: 503,\n body: {\n error: 'scanner_unavailable',\n message: 'This file could not be scanned just now, so it was not kept. Try again.',\n },\n }\n }\n\n // Asked again before the write, because the scan is where the time goes: a lease that\n // ran out meanwhile may be another request's now, whose bytes may already be kept and\n // claimed by a submission.\n const held = await storage.getFile(file.id)\n if (held?.receivingUntil !== lease || Date.parse(deps.nowIso()) >= Date.parse(lease)) {\n return OUTLASTED\n }\n\n await store.put(file.storageKey, bytes)\n if (await storage.settleFile(file.id, lease)) return { status: 204 }\n\n // The write itself outlasted the lease. The row is untouched, but these bytes are under a\n // key another request may also have written — the residual 0153 accepts. Nothing but this\n // answer records it: the request log has a 409, as it has for every other 409 here.\n return OUTLASTED\n }\n\n /**\n * Serve a file back.\n *\n * Always as an attachment, never inline, and `nosniff` alongside it. Stored\n * cross-site scripting through an uploaded HTML or SVG file is the most\n * commonly exploited vulnerability in this product category, and the real\n * answer is a separate hostname — which a single-container deployment does\n * not have. Forcing a download is the accommodation, and it is the one place\n * this deployment model genuinely costs something.\n */\n app.get('/f/:path/files/:id', async (request, reply) => {\n if (fileStore === undefined) return reply.code(501).send({ error: 'uploads_unavailable' })\n\n const { path, id } = request.params as { path: string; id: string }\n const actor = await actorOf(request)\n if (actor === undefined) return reply.code(401).send({ error: 'unauthenticated' })\n\n const form = await storage.getFormByPath(path)\n const file = await storage.getFile(id)\n if (form === undefined || file === undefined || file.formId !== form.id) {\n return reply.code(404).send({ error: 'unknown_file' })\n }\n\n const stream = await fileStore.open(file.storageKey)\n if (stream === undefined) return reply.code(404).send({ error: 'unknown_file' })\n\n return (\n reply\n .header('content-type', 'application/octet-stream')\n .header('x-content-type-options', 'nosniff')\n .header('content-security-policy', \"default-src 'none'; sandbox\")\n // The respondent's own name for it, which can be anything: encoded, since Node\n // refuses a header holding most of what a name can (0175).\n .header('content-disposition', attachment(file.name))\n .send(stream)\n )\n })\n}\n","/**\n * The header a client declares what it was looking at with.\n *\n * One name, read by the submission route and by the publish plugin: a\n * submission says which version it rendered, and a publish says which version it\n * opened. Two spellings of one header is a bug nobody sees until a client sends\n * the other one.\n */\nexport const SCHEMA_HASH_HEADER = 'x-formancy-schema-hash'\n\n/** A solved proof-of-work challenge, as base64 JSON: the shape an ALTCHA client already produces. */\nexport const CHALLENGE_HEADER = 'x-formancy-challenge'\n\n/**\n * The token a response is sent with: handed out with the form, and with a draft, naming the id\n * the response is stored under (0169). Required of an anonymous submission.\n */\nexport const SUBMISSION_TOKEN_HEADER = 'x-formancy-submission-token'\n","import type { FastifyInstance, FastifyRequest, preHandlerHookHandler } from 'fastify'\nimport { publishForm } from '@formancy/server-core'\nimport type { Actor, ServerDeps } from '@formancy/server-core'\nimport { SCHEMA_HASH_HEADER } from '../headers.js'\n\n/**\n * Publishing a form, as a plugin of its own.\n *\n * One route family per plugin, which is Fastify's own unit and what `app.ts`'s\n * size budget named as the seam. It moved when the budget refused the next thing\n * added to it rather than letting the number drift.\n */\nexport async function publishRoutes(\n app: FastifyInstance,\n { deps, requires }: { deps: ServerDeps; requires: (action: 'form.publish') => preHandlerHookHandler },\n): Promise<void> {\napp.post('/forms', { preHandler: requires('form.publish') }, async (request, reply) => {\n const body = request.body as { path?: unknown; schema?: unknown } | null\n if (body === null || typeof body.path !== 'string' || body.path === '' || body.schema === undefined) {\n return reply.code(400).send({ error: 'bad_request', message: 'Body needs { path, schema }.' })\n }\n\n // The same header the submission route reads, and for the same reason: the\n // client declares what it was looking at. Absent is a real answer — a script\n // or an agent composes a document rather than opening one — so this is not\n // required the way it is on a submission.\n const basedOn = request.headers[SCHEMA_HASH_HEADER]\n\n const outcome = await publishForm(deps, {\n path: body.path,\n schema: body.schema,\n // Passed in so the audit row can be written INSIDE the publish's\n // transaction. Appended here afterwards, it would record a publish that\n // half-applied as having happened. The request's id goes with it, so that\n // row names the request log's line for this request (0168).\n actor: (request as FastifyRequest & { actor: Actor }).actor,\n requestId: request.id,\n ...(typeof basedOn === 'string' && basedOn !== '' ? { basedOnSchemaHash: basedOn } : {}),\n })\n if (!outcome.ok) {\n // A switch rather than a ternary chain, so adding a refusal kind to\n // PublishOutcome fails to compile here until it is given a shape.\n switch (outcome.kind) {\n case 'invalid_schema':\n return reply.code(422).send({ error: outcome.kind, errors: outcome.errors })\n case 'unsafe_pattern':\n // The pattern and its complexity, so the author can see which field\n // and why rather than being told no.\n return reply.code(422).send({ error: outcome.kind, patterns: outcome.patterns })\n case 'invalid_logic':\n return reply.code(422).send({ error: outcome.kind, message: outcome.message })\n case 'unknown_options_source':\n // The names, so an author can see which list this deployment has never\n // heard of rather than being told no. A published version is frozen\n // forever, so a form naming an unresolvable list would render a message\n // instead of a chooser with nothing ever having said so.\n return reply.code(422).send({ error: outcome.kind, sources: outcome.sources })\n case 'already_published':\n // 409 as well, because it is the same kind of answer: the state on the\n // server is not what this request assumed. Named differently, and\n // carrying the version, because the fix is different — there is\n // nothing to merge, the document is already there.\n return reply.code(409).send({ error: outcome.kind, version: outcome.version })\n case 'version_changed':\n // The same status, the same error name and the same body the\n // submission route sends, so a client that already handles one stale\n // version handles both — and an editor can show what changed rather\n // than fetching and diffing to find out why it was refused.\n return reply.code(409).send({\n error: 'FORM_VERSION_CHANGED',\n current:\n outcome.current === undefined\n ? undefined\n : {\n version: outcome.current.version,\n schemaHash: outcome.current.schemaHash,\n schema: outcome.current.schema,\n },\n })\n }\n }\n // Warnings ride on the 201 rather than changing the status, because the\n // publish succeeded and a 2xx that means \"partly\" is a status nobody can\n // handle. Omitted when there are none, so a client that has never heard of\n // them sees the body it always saw.\n return reply.code(201).send({\n version: outcome.version,\n schemaHash: outcome.schemaHash,\n ...(outcome.warnings.length > 0 ? { warnings: outcome.warnings } : {}),\n })\n})\n}\n","import type { FastifyInstance, FastifyRequest, preHandlerHookHandler } from 'fastify'\nimport { setFormAccess } from '@formancy/server-core'\nimport type { AuditDraft, ServerDeps } from '@formancy/server-core'\n\n/**\n * Who may submit a form, and from where: a property of the deployment, not of the document,\n * so changing it mints no version and leaves every client's schema hash standing\n * ([0044](../../../../docs/decisions/0044-access-outside-the-document.md)).\n *\n * One route family per plugin, the seam `app.ts`'s size budget names. It left `app.ts` when\n * the examples kept beside a form needed a plugin registered there (0166).\n */\nexport async function accessRoutes(\n app: FastifyInstance,\n {\n deps,\n requires,\n audit,\n }: {\n deps: ServerDeps\n requires: (action: 'form.publish') => preHandlerHookHandler\n audit: (request: FastifyRequest, draft: AuditDraft) => Promise<void>\n },\n): Promise<void> {\n app.put('/f/:path/access', { preHandler: requires('form.publish') }, async (request, reply) => {\n const { path } = request.params as { path: string }\n const body = request.body as { submit?: unknown; allowedOrigins?: unknown } | null\n\n const submit = body?.submit\n if (submit !== 'authenticated' && submit !== 'public') {\n return reply.code(400).send({\n error: 'invalid_access',\n message: 'submit must be \"authenticated\" or \"public\".',\n })\n }\n\n const origins = body?.allowedOrigins\n if (origins !== undefined && !(Array.isArray(origins) && origins.every((o) => typeof o === 'string'))) {\n return reply.code(400).send({\n error: 'invalid_access',\n message: 'allowedOrigins must be an array of strings, or absent for no allowlist.',\n })\n }\n\n const outcome = await setFormAccess(deps, {\n path,\n submit,\n ...(origins === undefined ? {} : { allowedOrigins: origins as string[] }),\n })\n if (!outcome.ok) return reply.code(404).send({ error: 'unknown_form' })\n // A permission change is the event a security review looks for first.\n await audit(request, {\n action: 'form.access.changed',\n subject: path,\n detail: { submit, allowedOrigins: origins === undefined ? 'unchanged' : (origins as string[]).join(' ') },\n })\n return reply.code(204).send()\n })\n}\n","import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'\nimport { keepExamples, readExamples } from '@formancy/server-core'\nimport type { Actor, KeepExamplesOutcome, ServerDeps } from '@formancy/server-core'\n\n/**\n * A form's examples, and the sample they start from, kept beside the form (0166).\n *\n * `GET /f/:path/examples` answers `{ scenarios, sample? }`, with `unreadable` beside them when\n * something kept is not an example, and `PUT` replaces both with `{ scenarios, sample? }`. The routes authenticate and carry: who may read and change them, what an\n * example is, and what a publish says about them are `server-core`'s, so the 403 here is the\n * use-case's answer and not a `requires` of the route's own. Publishing needs nothing from\n * here — `publishForm` reads the examples itself, and its warnings ride on the `201`.\n *\n * One route family per plugin, the seam `app.ts`'s size budget names.\n */\nexport async function exampleRoutes(\n app: FastifyInstance,\n { deps, actorOf }: { deps: ServerDeps; actorOf: (request: FastifyRequest) => Promise<Actor | undefined> },\n): Promise<void> {\n app.get('/f/:path/examples', async (request, reply) => {\n const actor = await actorOf(request)\n if (actor === undefined) return reply.code(401).send({ error: 'unauthenticated' })\n const { path } = request.params as { path: string }\n return answer(reply, await readExamples(deps, { path, actor }))\n })\n\n app.put('/f/:path/examples', async (request, reply) => {\n const actor = await actorOf(request)\n if (actor === undefined) return reply.code(401).send({ error: 'unauthenticated' })\n const { path } = request.params as { path: string }\n // The request's id, so the audit row written with the change names this request's line (0168).\n return answer(reply, await keepExamples(deps, { path, actor, examples: request.body, requestId: request.id }))\n })\n}\n\n/** An outcome as a response: the examples as kept, or the refusal, in the plane's shapes. */\nfunction answer(reply: FastifyReply, outcome: KeepExamplesOutcome): FastifyReply {\n if (outcome.ok) return reply.send(outcome.examples)\n switch (outcome.kind) {\n case 'forbidden':\n // The body `requires` sends, so a client that handles one 403 handles this one.\n return reply.code(403).send({ error: 'forbidden', action: 'form.publish' })\n case 'unknown_form':\n return reply.code(404).send({ error: 'unknown_form' })\n case 'invalid_examples':\n // Each sentence names the example by position and name, and says what is wrong with it.\n return reply.code(422).send({ error: outcome.kind, problems: outcome.problems })\n }\n}\n","import type { FastifyInstance, FastifyRequest, preHandlerHookHandler } from 'fastify'\nimport { healthOf, replayDelivery } from '@formancy/server-core'\nimport type { AuditDraft, ServerDeps } from '@formancy/server-core'\n\n/**\n * Webhooks as an operator sees them: their health, the deliveries that died, and\n * sending one of those again.\n *\n * One route family per plugin, the seam `app.ts`'s size budget names. It moved\n * when that file needed room to say which proxy it believes about a client.\n */\nexport async function deliveryRoutes(\n app: FastifyInstance,\n {\n deps,\n requires,\n audit,\n }: {\n deps: ServerDeps\n requires: (action: 'form.publish') => preHandlerHookHandler\n audit: (request: FastifyRequest, draft: AuditDraft) => Promise<void>\n },\n): Promise<void> {\n /**\n * Webhook health, and the deliveries that died.\n *\n * The reason the breaker keeps its counters on the row rather than in the\n * worker's memory. A self-hoster has no operations team watching a\n * dashboard, so a destination refusing deliveries since Tuesday has to be\n * answerable from the product — otherwise it is found when somebody asks\n * why the CRM has no leads this week.\n */\n app.get('/webhooks', { preHandler: requires('form.publish') }, async (_request, reply) => {\n const now = new Date(deps.nowIso())\n const hooks = await deps.storage.listWebhooks()\n return reply.send({ webhooks: hooks.map((hook) => healthOf(hook, now)) })\n })\n\n app.get('/deliveries/dead', { preHandler: requires('form.publish') }, async (request, reply) => {\n const raw = (request.query as { limit?: unknown } | undefined)?.limit\n const asked = typeof raw === 'string' ? Number.parseInt(raw, 10) : 50\n const limit = Number.isFinite(asked) ? Math.min(Math.max(asked, 1), 200) : 50\n const dead = await deps.storage.deadDeliveries(limit)\n // Not the body. That is the submission's data in another coat, and this\n // endpoint answers \"what failed\", not \"what was in it\".\n return reply.send({\n deliveries: dead.map((delivery) => ({\n id: delivery.id,\n webhookId: delivery.webhookId,\n submissionId: delivery.submissionId,\n eventId: delivery.eventId,\n attempt: delivery.attempt,\n lastError: delivery.lastError,\n })),\n })\n })\n\n app.post(\n '/deliveries/:id/replay',\n { preHandler: requires('form.publish') },\n async (request, reply) => {\n const { id } = request.params as { id: string }\n const outcome = await replayDelivery(\n { storage: deps.storage, now: () => new Date(deps.nowIso()) },\n id,\n )\n\n if (!outcome.ok) {\n return outcome.kind === 'unknown'\n ? reply.code(404).send({ error: 'unknown_delivery' })\n : reply.code(409).send({\n error: 'not_dead',\n state: outcome.state,\n message:\n 'Only a dead delivery can be replayed. A pending one is already queued, and a delivered one would be sent twice.',\n })\n }\n\n // Recorded: it sends data to a third party on a person's say-so.\n await audit(request, {\n action: 'delivery.replayed',\n subject: id,\n detail: { webhookId: outcome.delivery.webhookId },\n })\n return reply.code(202).send({ id, state: outcome.delivery.state })\n },\n )\n}\n","import type { FastifyInstance } from 'fastify'\nimport { resumeDraft, saveDraft, startDraft } from '@formancy/server-core'\nimport type { ServerDeps } from '@formancy/server-core'\nimport { limited } from '../rate-limits.js'\nimport type { Budget } from '../rate-limits.js'\n\n/** Proves the bearer started this draft. Lower-case: Fastify normalises. */\nconst DRAFT_TOKEN_HEADER = 'x-formancy-draft-token'\n\n/**\n * A respondent's draft: started, written and read back, each with the key only its starter\n * holds ([0062](../../../../docs/decisions/0062-a-draft-carries-its-own-key.md)).\n *\n * One route family per plugin, the seam `app.ts`'s size budget names. It moved when the\n * deployment's model needed lines that file had no room for.\n */\nexport async function draftRoutes(\n app: FastifyInstance,\n { deps, limit }: { deps: ServerDeps; limit: Budget },\n): Promise<void> {\n /**\n * Start a draft, and get the only key to it.\n *\n * The public plane is anonymous, so a draft has no account behind it. It was\n * previously addressed by an id the CALLER chose, with no check on either\n * route: anybody who knew or guessed an id could read a stranger's part-filled\n * form, and overwrite it — after which that person submits the substituted\n * content under their own name and nothing says otherwise\n * ([0062](../../../../docs/decisions/0062-a-draft-carries-its-own-key.md)).\n *\n * So the server picks the id and signs it, and the token comes back exactly\n * once. Losing it means losing the draft, which is the correct trade for the\n * alternative.\n */\n app.post(\n '/f/:path/drafts',\n {\n // Unauthenticated, like the submission route, and every call hands out a\n // key. Cheap per call, but nothing should be free on the public plane.\n config: { rateLimit: limited(limit, 'admit') },\n },\n async (request, reply) => {\n const { path } = request.params as { path: string }\n const started = await startDraft(deps, { path })\n if (started === undefined) return reply.code(404).send({ error: 'unknown_form' })\n return reply.code(201).send(started)\n },\n )\n\n app.put(\n '/f/:path/drafts/:draftId',\n {\n // A database row per request, reachable without an account. The submission\n // route used to be described as \"the one unauthenticated write in the\n // product\"; that stopped being true when drafts were exposed here, and\n // nobody moved the limit across.\n config: { rateLimit: limited(limit, 'admit') },\n },\n async (request, reply) => {\n const { path, draftId } = request.params as { path: string; draftId: string }\n const token = request.headers[DRAFT_TOKEN_HEADER]\n if (typeof token !== 'string') {\n return reply.code(401).send({ error: 'draft_token_required' })\n }\n\n const saved = await saveDraft(deps, { path, draftId, token, data: request.body ?? {} })\n if (saved === undefined) return reply.code(404).send({ error: 'unknown_form' })\n if (!saved.saved) return reply.code(403).send({ error: 'draft_token_invalid' })\n return reply.send({ version: saved.version })\n },\n )\n\n app.get(\n '/f/:path/drafts/:draftId',\n {\n // Limited because this is where a token would be tried one after another.\n // An HMAC is not realistically guessable, but limiting the write and\n // leaving the guess surface open is not a position worth defending.\n config: { rateLimit: limited(limit, 'admit') },\n },\n async (request, reply) => {\n const { path, draftId } = request.params as { path: string; draftId: string }\n const token = request.headers[DRAFT_TOKEN_HEADER]\n if (typeof token !== 'string') {\n return reply.code(401).send({ error: 'draft_token_required' })\n }\n\n const resumed = await resumeDraft(deps, { path, draftId, token })\n // 404 for a wrong token as well as a missing draft. Answering differently\n // would confirm which ids exist, which is the enumeration this closed.\n if (resumed === undefined) return reply.code(404).send({ error: 'unknown_draft' })\n return reply.send(resumed)\n },\n )\n}\n","import type { FastifyInstance, FastifyReply, FastifyRequest, preHandlerHookHandler } from 'fastify'\nimport { MODEL_REQUEST_KINDS } from '@formancy/builder-core'\nimport { completeBuilderRequest } from '@formancy/server-core'\nimport type {\n Actor,\n AuditDraft,\n BuilderRequest,\n BuilderRequestOutcome,\n Cancellation,\n Completer,\n} from '@formancy/server-core'\nimport type { ModelProvider } from '../model-settings.js'\nimport { limited } from '../rate-limits.js'\nimport type { Budget } from '../rate-limits.js'\n\n/** The deployment's model: which provider, which model, and the adapter that asks it. */\nexport interface DeploymentModel {\n readonly provider: ModelProvider\n readonly model: string\n readonly completer: Completer\n}\n\n/**\n * The largest request the route reads, in bytes, set on the route rather than inherited.\n *\n * Every byte is an input token the operator pays for, so the cap is the route's own:\n * raising the server's limit for something else does not raise what an editor can send a\n * model. It has to hold what the builders send about a form the server publishes, and the\n * largest of that is a translation, not the form: each answer's row repeats the question\n * it answers as its context, pretty-printed and escaped again as a string.\n * `model-route.test.ts` sends all three requests about the largest questionnaire the server's\n * default limit publishes, its questions a sentence long, and they are taken. Longer\n * questions over many answers make a translation past this, refused before it is read (0165).\n */\nexport const MODEL_BODY_LIMIT_BYTES = 1024 * 1024\n\n/**\n * The builders' requests to the deployment's model (0165).\n *\n * `GET /model` says whether there is one, and which, to somebody who may edit a form —\n * `{ provider, model }`, both `null` when there is none (0183) — and the admin draws the\n * prompt pane only when there is. `POST /model/complete` takes\n * `{ kind, user }` and answers `{ text }` — the model's answer, for the browser to check\n * — or `{ declined }` when the provider refused, which the admin's `AskModel` hands back as\n * a decline (0158). Everything else is an error with a sentence for the person.\n *\n * One route family per plugin, the seam `app.ts`'s size budget names.\n */\nexport async function modelRoutes(\n app: FastifyInstance,\n {\n model,\n requires,\n audit,\n limit,\n }: {\n model: DeploymentModel | undefined\n requires: (action: 'form.publish') => preHandlerHookHandler\n audit: (request: FastifyRequest, draft: AuditDraft) => Promise<void>\n limit: Budget\n },\n): Promise<void> {\n // Asking a model is part of editing a form, so it takes the permission editing does:\n // `form.publish`, which an editor and an admin hold and a viewer does not.\n // None is how a deployment ships, so it is an answer and not a missing resource (0183): a\n // 404 here was a failed request in the console of every editor's sign-in.\n app.get('/model', { preHandler: requires('form.publish') }, async (_request, reply) =>\n reply.send(model === undefined ? { provider: null, model: null } : { provider: model.provider, model: model.model }),\n )\n\n app.post(\n '/model/complete',\n {\n preHandler: requires('form.publish'),\n bodyLimit: MODEL_BODY_LIMIT_BYTES,\n config: {\n rateLimit: {\n // Refused when it cannot be counted: this route needs no database, so admitting it\n // would make an outage of one an unlimited spend on the operator's key (0170).\n ...limited(limit, 'refuse'),\n // After `requires`, so the count is per session: an office behind one address is\n // not one budget, and one editor cannot spend another's. A request refused for\n // its session spends nothing.\n hook: 'preHandler',\n keyGenerator: (request) => {\n const actor = (request as FastifyRequest & { actor?: Actor }).actor\n return actor === undefined ? `ip:${request.ip}` : `${actor.kind}:${actor.id}`\n },\n },\n },\n },\n async (request, reply) => {\n // Asking a model that is not there is a request nothing can serve, unlike asking whether\n // there is one, so it stays an error; the admin never sends it once `/model` said none.\n if (model === undefined) return reply.code(404).send({ error: 'model_not_configured' })\n const body = (request.body ?? {}) as BuilderRequest\n\n const outcome = await completeBuilderRequest(model.completer, body, cancelledWith(reply))\n\n if (outcome.ok || (outcome.failure !== 'unknown_kind' && outcome.failure !== 'no_user')) {\n // Asked: the form went to the provider, at the operator's cost. The kind and the\n // size, never the text (0057).\n await audit(request, {\n action: 'model.asked',\n subject: String(body.kind),\n detail: {\n provider: model.provider,\n model: model.model,\n characters: String(body.user).length,\n outcome: outcome.ok ? 'answered' : outcome.failure,\n // The provider's status, which is what an operator has to go on, here and in\n // the log; its words go nowhere (SAFETY-ANALYSIS C3).\n ...(!outcome.ok && outcome.failure === 'unavailable' && outcome.status !== undefined\n ? { status: outcome.status }\n : {}),\n },\n })\n }\n return answer(reply, request, outcome)\n },\n )\n}\n\n/**\n * The browser going away, as a cancellation: the response closing before it was written.\n *\n * Every response closes, so one that finished is not a browser that left; only a close\n * with nothing written yet tells the adapter to abandon the request at the provider.\n *\n * A browser can also have gone before the handler ran, while the session or an API key was\n * still being checked. That response has closed already and will not close again, so it\n * is read as gone from the start, and the adapter is told at once — before it sends.\n */\nfunction cancelledWith(reply: FastifyReply): Cancellation {\n let gone = reply.raw.destroyed && !reply.raw.writableFinished\n const listeners: Array<() => void> = []\n reply.raw.once('close', () => {\n if (reply.raw.writableFinished) return\n gone = true\n for (const listener of listeners.splice(0)) listener()\n })\n return {\n onCancel(listener) {\n if (gone) listener()\n else listeners.push(listener)\n },\n }\n}\n\n/** An outcome as a response, every failure with a sentence for the person reading it. */\nfunction answer(reply: FastifyReply, request: FastifyRequest, outcome: BuilderRequestOutcome): FastifyReply {\n if (outcome.ok) return reply.send({ text: outcome.text })\n switch (outcome.failure) {\n case 'unknown_kind':\n return reply.code(400).send({\n error: 'unknown_kind',\n message: `kind must be one of ${MODEL_REQUEST_KINDS.join(', ')}: the requests formancy makes of a model.`,\n })\n case 'no_user':\n return reply\n .code(400)\n .send({ error: 'bad_request', message: 'Body needs { kind, user }, user being the text to ask.' })\n case 'refused':\n return reply.send({ declined: outcome.reason })\n case 'truncated':\n return reply.code(502).send({\n error: 'model_truncated',\n message: 'The model’s answer reached its length limit and was cut off, so it was not used.',\n })\n case 'cancelled':\n // Nobody is there to read it.\n return reply.code(499).send({ error: 'cancelled' })\n case 'unavailable':\n // The provider's own words can name the account, so they go nowhere: the log has the\n // status it answered with (C3).\n request.log.error({ event: 'model.unreachable', upstream: outcome.status })\n return reply.code(502).send({ error: 'model_unavailable', message: unavailableSentence(outcome.status) })\n }\n}\n\n/** What a provider's status means for the person whose turn it ended. */\nfunction unavailableSentence(status: number | undefined): string {\n if (status === 401 || status === 403) return 'The model service refused this server’s key.'\n if (status === 429) return 'The model service is limiting this server’s requests. Try again in a while.'\n if (status === 400 || status === 404) {\n return 'The model service refused the request: the model this server names may not exist, or may not take it.'\n }\n return 'The model service could not be reached, or answered with an error.'\n}\n","import type { FastifyInstance, FastifyRequest } from 'fastify'\nimport { createSubmission, decodeSolution, formToFill, mintChallenge, verifySolution } from '@formancy/server-core'\nimport type { Actor, ServerDeps } from '@formancy/server-core'\nimport { CHALLENGE_HEADER, SCHEMA_HASH_HEADER, SUBMISSION_TOKEN_HEADER } from '../headers.js'\nimport { limited } from '../rate-limits.js'\nimport type { Budget } from '../rate-limits.js'\n\n/**\n * What a respondent's browser does with a form: read it, ask for a challenge, and submit it.\n *\n * One route family per plugin, the seam `app.ts`'s size budget names. It moved when the\n * submission token needed lines that file had no room for.\n */\nexport async function submissionRoutes(\n app: FastifyInstance,\n {\n deps,\n challengeSecret,\n limit,\n actorOf,\n }: {\n deps: ServerDeps\n /** Absent, the challenge is off: its route answers 404 and no submission is asked for one. */\n challengeSecret: string | undefined\n limit: Budget\n actorOf: (request: FastifyRequest) => Promise<Actor | undefined>\n },\n): Promise<void> {\n const submissionLimit = limit\n\n /**\n * Fetch a published form. The one public route deliberately NOT rate-limited.\n *\n * Every other unauthenticated route is: submitting, starting a draft, writing\n * one, reading one, minting a challenge, asking for an upload target. Those are\n * writes, or guesses, or work somebody can demand of the server. This is the\n * read every visitor has to make before they can do anything at all.\n *\n * Limiting it by IP would refuse the form to real people sharing an address —\n * an office, a school, a phone network behind CGNAT — and the failure would\n * look like the form being broken rather than like a limit. That is a worse\n * outcome than the cheap read it would prevent.\n *\n * Written down because the asymmetry is deliberate and looks like an omission.\n *\n * It also hands out the token the response is sent with, and that changes neither half of\n * the argument: minting one is an HMAC and writes nothing, and it is not a defence against\n * anybody who can read the form, so limiting it would buy nothing (0169). What it does change\n * is that the reply is per reading: `no-store`, because a cache that kept it would hand one\n * token to everybody behind it, and only the first of them could send a response.\n */\n app.get('/f/:path', async (request, reply) => {\n const { path } = request.params as { path: string }\n const form = await formToFill(deps, path)\n if (form === undefined) return reply.code(404).send({ error: 'unknown_form' })\n return reply.header('cache-control', 'no-store').send({\n version: form.version,\n schemaHash: form.schemaHash,\n schema: form.schema,\n submissionToken: form.submissionToken,\n })\n })\n\n /**\n * A puzzle for an anonymous visitor to solve before submitting.\n *\n * Public because the form is: requiring a session to obtain a challenge for\n * an unauthenticated submission would be a circle. It is cheap to mint and\n * stateless, so handing one out costs this server a hash.\n */\n app.get(\n '/f/:path/challenge',\n {\n // One per submission attempt is the legitimate rate, so the submission\n // limit is the right one. The point of a proof of work is that the\n // ATTACKER pays; handing out unlimited puzzles for free is the one part\n // of it that costs us instead.\n config: { rateLimit: limited(submissionLimit, 'admit') },\n },\n async (request, reply) => {\n if (challengeSecret === undefined) {\n // Not configured is not an error: a deployment may decide its forms are\n // not public enough to need one, and the field should say so rather\n // than fail.\n return reply.code(404).send({ error: 'challenge_not_enabled' })\n }\n const { path } = request.params as { path: string }\n const form = await deps.storage.getFormByPath(path)\n if (form === undefined) return reply.code(404).send({ error: 'unknown_form' })\n\n return reply.send(\n await mintChallenge({\n secret: challengeSecret,\n nowSeconds: Math.floor(new Date(deps.nowIso()).getTime() / 1000),\n }),\n )\n },\n )\n\n app.post(\n '/f/:path/submissions',\n {\n // The unauthenticated write that matters most, and no longer the only\n // one: drafts write too, and are limited the same way. Keyed by IP, which\n // is the only identity an anonymous submitter has — behind a proxy, only\n // once `trustProxy` names it; until then it is the proxy's.\n config: { rateLimit: limited(submissionLimit, 'admit') },\n },\n async (request, reply) => {\n const { path } = request.params as { path: string }\n const declaredSchemaHash = request.headers[SCHEMA_HASH_HEADER]\n if (typeof declaredSchemaHash !== 'string' || declaredSchemaHash === '') {\n return reply.code(400).send({\n error: 'missing_schema_hash',\n message: `Send the schema hash you rendered in the ${SCHEMA_HASH_HEADER} header.`,\n })\n }\n\n // The public plane is public, so an identity is optional here — but if one\n // is presented and valid, the access policy does not apply to it. An\n // invalid credential is treated as no credential rather than as an error:\n // this route's job is to accept submissions, not to adjudicate logins.\n const actor = await actorOf(request)\n const origin = request.headers.origin\n const token = request.headers[SUBMISSION_TOKEN_HEADER]\n\n // Before the engine runs, and only for a visitor who is not signed in.\n // Somebody with a session has already paid a cost the challenge is a\n // substitute for, and making them solve a puzzle as well would be\n // ceremony — while an anonymous submission is the surface this exists\n // to protect.\n if (challengeSecret !== undefined && actor === undefined) {\n const refusal = await checkChallenge(deps, challengeSecret, request)\n if (refusal !== undefined) return reply.code(400).send(refusal)\n }\n\n const outcome = await createSubmission(deps, {\n path,\n declaredSchemaHash,\n requestId: request.id,\n data: request.body ?? {},\n actor: actor === undefined ? 'anonymous' : 'authenticated',\n ...(typeof origin === 'string' ? { origin } : {}),\n // An empty header is a token, and not one this server signed: refused, never ignored.\n ...(typeof token === 'string' ? { token } : {}),\n })\n\n if (outcome.ok) return reply.code(201).send({ id: outcome.id, data: outcome.canonicalData })\n\n switch (outcome.kind) {\n case 'unknown_form':\n return reply.code(404).send({ error: 'unknown_form' })\n case 'version_changed':\n // 409 carries the CURRENT schema so the client can re-render and\n // preserve what it can, instead of guessing why it was refused.\n return reply.code(409).send({\n error: 'FORM_VERSION_CHANGED',\n current:\n outcome.current === undefined\n ? undefined\n : {\n version: outcome.current.version,\n schemaHash: outcome.current.schemaHash,\n schema: outcome.current.schema,\n },\n })\n case 'invalid':\n // The identical error shape the client's engine produces, so server\n // errors render through the same code path as local ones.\n return reply.code(422).send({ error: 'invalid', errors: outcome.errors })\n case 'forbidden':\n // Deliberately says nothing about WHICH rule refused. \"Not public\" and\n // \"not from your origin\" are the same answer to someone probing.\n return reply.code(403).send({ error: 'forbidden' })\n case 'source_unavailable':\n // 503 and not 422: nothing about the submission is wrong, a list this\n // deployment owns could not vouch for it. Retryable, and the draft still\n // holds the answers — where accepting the value unchecked would store\n // something nobody can detect afterwards.\n return reply.code(503).send({ error: 'source_unavailable', source: outcome.source })\n // The three answers about the token (0169). Each says what to do next, in words a host\n // can show: none of them is the respondent's doing, and none of them loses an answer —\n // the page still holds them, and a refused attempt spends nothing.\n case 'token_required':\n return reply.code(400).send({\n error: 'submission_token_required',\n message: `This form was sent without the token it was handed out with. Read the form again and send its submissionToken in ${SUBMISSION_TOKEN_HEADER}.`,\n })\n case 'token_invalid':\n return reply.code(400).send({\n error: 'submission_token_invalid',\n message: 'This form was sent with a token the server does not recognise. Read the form again for a new one, and send the answers with it.',\n })\n case 'token_spent':\n // 409, as the resource the token names exists: the response is stored, as `id`.\n return reply.code(409).send({\n error: 'submission_token_spent',\n id: outcome.id,\n // About the response, never \"these answers\": a second send may carry different ones,\n // and it is the first that is kept.\n message: 'This response has already been sent, and is stored. It was not stored again.',\n })\n }\n },\n )\n}\n\n/**\n * Check a submitted solution, and spend it.\n *\n * Returns the refusal to send, or undefined when it passed. The spend is\n * separate from the verification and comes last: the stateless checks say\n * the solution is correct, and only the database can say it has not been\n * used — a correct solution stays correct, so without this one puzzle\n * would buy a thousand submissions.\n */\nasync function checkChallenge(\n deps: ServerDeps,\n challengeSecret: string,\n request: FastifyRequest,\n): Promise<{ error: string; message: string } | undefined> {\n const header = request.headers[CHALLENGE_HEADER]\n if (typeof header !== 'string' || header === '') {\n return {\n error: 'challenge_required',\n message: `This form needs a solved challenge. Ask GET /f/:path/challenge for one and return it in ${CHALLENGE_HEADER}.`,\n }\n }\n\n const solution = decodeSolution(header)\n if (solution === undefined) {\n return { error: 'challenge_malformed', message: 'The challenge header is not base64 JSON.' }\n }\n\n const nowSeconds = Math.floor(new Date(deps.nowIso()).getTime() / 1000)\n const verified = await verifySolution(challengeSecret, solution, nowSeconds)\n if (!verified.ok) {\n return {\n error: `challenge_${verified.reason}`,\n message: 'The challenge was not solved. Ask for a new one and try again.',\n }\n }\n\n // The expiry comes back from verification rather than being parsed out of\n // the salt a second time — one place reads that format.\n const spent = await deps.storage.spendChallenge(\n verified.challenge,\n new Date(verified.expiresAtSeconds * 1000).toISOString(),\n )\n if (!spent) {\n return {\n error: 'challenge_spent',\n message: 'That challenge has already been used. Each one is good for one submission.',\n }\n }\n return undefined\n}\n","import { randomBytes } from 'node:crypto'\nimport { hash as argon2Hash, verify as argon2Verify } from '@node-rs/argon2'\nimport { SignJWT, jwtVerify } from 'jose'\nimport type { Actor, Role } from '@formancy/server-core'\n\n/**\n * The real cryptography, confined to the composition root: argon2id for\n * secrets at rest, an HMAC-signed short-lived access token for sessions, and\n * a CSPRNG for API keys. server-core never sees any of this — it is handed\n * hash/verify/random functions and stays deterministic under test.\n *\n * HS256 rather than EdDSA for the session token, deliberately for this phase:\n * one secret in one env var is the self-hosting story a single-container\n * deployment can operate, and the signing scheme is an implementation detail\n * of a token that lives fifteen minutes. Revisit with key rotation.\n */\nexport function realSecretHashing(): {\n hashSecret(secret: string): Promise<string>\n verifySecret(secret: string, hash: string): Promise<boolean>\n} {\n return {\n hashSecret: (secret) => argon2Hash(secret),\n verifySecret: async (secret, digest) => {\n try {\n return await argon2Verify(digest, secret)\n } catch {\n // A malformed digest is a failed verification, not a crash: this runs\n // on the login path with attacker-influenced timing.\n return false\n }\n },\n }\n}\n\nexport function realRandomToken(): string {\n return randomBytes(30).toString('base64url')\n}\n\nconst TOKEN_LIFETIME = '15m'\n\nexport interface SessionTokens {\n issue(actor: Actor): Promise<string>\n verify(token: string): Promise<Actor | undefined>\n}\n\nexport function createSessionTokens(secret: string): SessionTokens {\n if (secret.length < 32) {\n throw new Error(\n 'FORMANCY_AUTH_SECRET must be at least 32 characters. Generate one: openssl rand -base64 33',\n )\n }\n const key = new TextEncoder().encode(secret)\n\n return {\n async issue(actor) {\n return new SignJWT({ role: actor.role, kind: actor.kind })\n .setProtectedHeader({ alg: 'HS256' })\n .setSubject(actor.id)\n .setIssuedAt()\n .setExpirationTime(TOKEN_LIFETIME)\n .sign(key)\n },\n\n async verify(token) {\n try {\n const { payload } = await jwtVerify(token, key, { algorithms: ['HS256'] })\n const role = payload['role']\n const kind = payload['kind']\n if (\n typeof payload.sub !== 'string' ||\n (role !== 'admin' && role !== 'editor' && role !== 'viewer') ||\n (kind !== 'user' && kind !== 'apiKey')\n ) {\n return undefined\n }\n return { id: payload.sub, role: role as Role, kind }\n } catch {\n return undefined\n }\n },\n }\n}\n","/**\n * How large a file this deployment accepts.\n *\n * Its own module because `main.ts` is a composition root that connects to a database\n * when it is imported, so nothing in it can be tested — and this is a setting whose wrong\n * values all look plausible.\n */\n\n/** Ten megabytes: what the documentation and `.env.example` give as the default. */\nexport const DEFAULT_MAX_FILE_BYTES = 10 * 1024 * 1024\n\n/**\n * The largest ceiling a deployment may set: the largest number `files.size` can hold.\n *\n * That column is a PostgreSQL `integer` (`db.ts`), a signed 32-bit number. Every size an\n * offer accepts is written to it, so a ceiling above this would let an offer through the\n * route and fail it at the insert. Raising it means widening the column first;\n * `server.integration.test.ts` reads the column's precision from the database and fails\n * when this number and the column disagree.\n */\nexport const LARGEST_MAX_FILE_BYTES = 2_147_483_647\n\n/**\n * `value`, once it is a ceiling the server can honour: a whole number of bytes, at least\n * one, and no more than `files.size` holds. Throws naming `setting`, because the message\n * is all an operator has to go on.\n */\nexport function checkedMaxFileBytes(value: number, setting: string): number {\n if (!Number.isInteger(value) || value < 1 || value > LARGEST_MAX_FILE_BYTES) {\n throw new Error(\n `${setting} must be a whole number of bytes from 1 to ${String(LARGEST_MAX_FILE_BYTES)}, ` +\n 'the largest size the files table holds.',\n )\n }\n return value\n}\n\n/**\n * The ceiling `FORMANCY_MAX_FILE_BYTES` sets, or the default when it is unset.\n *\n * Empty is refused rather than read as unset: the compose files already substitute the\n * default for an empty value, so an empty one here was set by hand, to something.\n */\nexport function maxFileBytesFrom(env: Readonly<Record<string, string | undefined>>): number {\n const raw = env['FORMANCY_MAX_FILE_BYTES']\n if (raw === undefined) return DEFAULT_MAX_FILE_BYTES\n // Number('') is 0 and Number('10MB') is NaN, and the range refuses both.\n return checkedMaxFileBytes(Number(raw), 'FORMANCY_MAX_FILE_BYTES')\n}\n","import { METHODS } from 'node:http'\nimport { LogController } from 'fastify'\nimport type { FastifyBaseLogger, FastifyReply, FastifyRequest, FastifyServerOptions } from 'fastify'\nimport { AUDIT_ACTIONS } from '@formancy/server-core'\n\n/**\n * The server's log: a JSON line for every request, one for every error that answered one, and\n * one for each thing a route or a background worker has to say\n * ([0168](../../../docs/decisions/0168-the-log-is-built-from-a-list-of-fields.md)).\n *\n * **What keeps everything else out is construction, not a filter over text.** A line is made\n * from the fields `LOG_FIELDS` names, each kept only when its value is of that field's kind,\n * and from nothing else. A key a call hands over that is not on the list is never read, so a\n * body, a header, a query string, an answer, a password, a draft's key or a file's name has no\n * field to go in. The words of a call are never written at all — pino's message argument,\n * Fastify's `error.message`, its `Route GET:/the/raw/url not found` — so neither are the\n * parameters a database error quotes: a line says what happened with an `event` from\n * `LOG_EVENTS`, and a thrown error is written as its kind and its code.\n *\n * Its own logger rather than pino's options, because pino's rule for what to keep would be\n * three hooks — a log formatter, a bindings formatter and a method hook to drop the message —\n * whose order is pino's internals; here it is one function, `written`, and a list.\n */\n\n/** Most severe first: `fatal` writes the least, `trace` the most. The names are pino's. */\nexport const LOG_LEVELS = ['fatal', 'error', 'warn', 'info', 'debug', 'trace'] as const\nexport type LogLevel = (typeof LOG_LEVELS)[number]\n\nconst SEVERITY: Readonly<Record<LogLevel, number>> = { fatal: 60, error: 50, warn: 40, info: 30, debug: 20, trace: 10 }\n\n/** Where lines go: standard output in the server, an array in a test. */\nexport interface LogSink {\n write(line: string): unknown\n}\n\n/** What `createApp` is told to log: where, and from which level up. */\nexport interface ServerLogSettings {\n readonly sink: LogSink\n readonly level: LogLevel\n}\n\n/** What a line can say happened. Anything else is `unlisted`, or not written. */\nexport const LOG_EVENTS = [\n // Every request, once, when it has been answered — at `error` when the answer is a 5xx.\n 'request',\n // A request whose client left before its answer was sent: no status, since none arrived.\n 'request.abandoned',\n // An error that answered a request: one the client caused (4xx), one the server did (5xx).\n 'request.refused',\n 'request.failed',\n // What a route says about something that went wrong beside its answer.\n 'audit.unwritten',\n 'upload.refused',\n 'upload.unreleased',\n 'scanner.unreachable',\n 'model.unreachable',\n // A background worker's pass that threw. It comes back on its next tick.\n 'outbox.failed',\n 'collector.failed',\n 'sweeper.failed',\n // The rate limits' counter (0170): it stopped answering, so each limit admits uncounted or\n // refuses with a 503 as it declares, and it answers again — each said when it changes, not\n // once a request — and its sweep of ended windows threw.\n 'ratelimit.unanswered',\n 'ratelimit.answering',\n 'ratelimit.sweep.failed',\n // What the database said beside an answer, by its code.\n 'database.notice',\n // At warn and above, something that named no event on this list — Fastify's own\n // warnings — written without its words. Below warn it is not written.\n 'unlisted',\n] as const\nexport type LogEvent = (typeof LOG_EVENTS)[number]\n\n/**\n * Every field a line can have, in the order it is written, each with the kind of value it\n * holds. A field is kept only when its value is of its kind, so a string in a number's place,\n * or a sentence in an identifier's, is dropped rather than written.\n */\nconst KINDS = {\n time: (value: unknown) => typeof value === 'string',\n level: (value: unknown) => (LOG_LEVELS as readonly unknown[]).includes(value),\n event: (value: unknown) => (LOG_EVENTS as readonly unknown[]).includes(value),\n // Fastify's own id, `req-` and a counter; never taken from a header, which Fastify 5 does\n // only when told to. Every audit row a request writes carries it too, the rows written in\n // a publish's, an examples change's and a submission's transaction included.\n reqId: (value: unknown) => typeof value === 'string' && /^[\\w-]{1,64}$/.test(value),\n method: (value: unknown) => typeof value === 'string' && METHODS.includes(value),\n // A route as the route table writes it, `/f/:path/drafts/:draftId`: never the path asked\n // for, and so never a form's path, a draft's id or a query string. Only `RequestLines`\n // sets it, from `request.routeOptions.url`.\n route: (value: unknown) => typeof value === 'string' && /^\\/[\\w\\-./:*]*$/.test(value),\n status: isStatus,\n ms: (value: unknown) => typeof value === 'number' && Number.isFinite(value) && value >= 0,\n // What a thrown error calls itself, and the first code along its causes.\n kind: isIdentifier,\n code: isCode,\n // The audit row that could not be written, by its action.\n action: (value: unknown) => (AUDIT_ACTIONS as readonly unknown[]).includes(value),\n // The status a model's provider answered with.\n upstream: isStatus,\n} as const satisfies Record<string, (value: unknown) => boolean>\n\ntype Field = keyof typeof KINDS\nexport const LOG_FIELDS = Object.keys(KINDS) as readonly Field[]\n\n/** A line before it is checked: anything in its fields, which `written` decides about. */\ntype Line = { readonly level: LogLevel; readonly event: LogEvent } & { readonly [F in Field]?: unknown }\n\n/**\n * The one place a line becomes text: dropped below `level`, and otherwise each listed field\n * whose value is of its kind, in the listed order.\n */\nfunction written(sink: LogSink, level: LogLevel): (line: Line) => void {\n const threshold = SEVERITY[level]\n return (line) => {\n if (SEVERITY[line.level] < threshold) return\n const kept: Partial<Record<Field, unknown>> = {}\n const given: { readonly [F in Field]?: unknown } = { ...line, time: new Date().toISOString() }\n for (const field of LOG_FIELDS) {\n if (KINDS[field](given[field])) kept[field] = given[field]\n }\n sink.write(`${JSON.stringify(kept)}\\n`)\n }\n}\n\n/** A log whose every line is built by the rule above. What `createApp` hands Fastify. */\nexport function createServerLog(sink: LogSink, level: LogLevel): FastifyBaseLogger {\n return logger(written(sink, level), level, undefined)\n}\n\n/**\n * Fastify's logger interface over the rule: each call contributes its event, its error, and\n * the status, code, action and upstream status a caller may name, and nothing else it was\n * handed. A\n * child keeps the request id it is bound to, which is how Fastify gives a request's lines its\n * id, and nothing else bound beside it.\n */\nfunction logger(write: (line: Line) => void, level: LogLevel, reqId: unknown): FastifyBaseLogger {\n const at =\n (lineLevel: LogLevel) =>\n (first?: unknown): void => {\n const given = (first instanceof Error ? { err: first } : isRecord(first) ? first : {}) as Record<string, unknown>\n const event = given['event'] !== 'unlisted' && KINDS.event(given['event']) ? (given['event'] as LogEvent) : undefined\n // Below warn, a line nobody named is Fastify saying it is listening or that a route was\n // not found: nothing an operator reads, and the words are a raw URL.\n if (event === undefined && SEVERITY[lineLevel] < SEVERITY.warn) return\n write({\n level: lineLevel,\n event: event ?? 'unlisted',\n reqId,\n status: given['status'],\n code: given['code'],\n ...described(given['err']),\n action: given['action'],\n upstream: given['upstream'],\n })\n }\n return {\n level,\n fatal: at('fatal'),\n error: at('error'),\n warn: at('warn'),\n info: at('info'),\n debug: at('debug'),\n trace: at('trace'),\n silent: () => undefined,\n child: (bindings) => logger(write, level, KINDS.reqId(bindings['reqId']) ? bindings['reqId'] : reqId),\n }\n}\n\n/**\n * When Fastify writes about a request, written as this log's lines: Fastify's own extension\n * point for its internal lines, rather than hooks beside a logger that also writes them.\n *\n * One line when a request is answered, not one when it arrives as well — half the lines,\n * and the one that has the status. A route nobody registered gets no line of its own: its\n * request line says 404 and names no route.\n *\n * **Every request, including the ones Fastify answers past its own request line.** Fastify\n * writes that line when an answer has been sent, so three kinds left none: a request whose\n * client went away first, whose answer is never sent; one refused for an unreadable URL or an\n * over-long parameter, answered by hand before routing unless `frameworkErrors` is set; and\n * one that arrives while the server closes, refused with a `503` before routing and said at\n * `info` in words, which this log drops. Each is written here as well.\n */\nclass RequestLines extends LogController {\n readonly #write: (line: Line) => void\n\n constructor(write: (line: Line) => void) {\n super()\n this.#write = write\n }\n\n /**\n * Nothing written on arrival; but a response that closes before it has finished sending is\n * a client that left. Read from the response's own `finish`, which a socket and an injected\n * request both emit, rather than `writableFinished`, which an injected one never sets.\n */\n override incomingRequest(request: FastifyRequest, reply: FastifyReply): void {\n let sent = false\n reply.raw.once('finish', () => (sent = true))\n reply.raw.once('close', () => {\n if (sent) return\n this.#write({ level: 'info', event: 'request.abandoned', ...where(request), ms: Math.round(reply.elapsedTime) })\n })\n }\n\n override routeNotFound(): void {}\n\n /** At `error` for a 5xx, thrown or answered by a route, so a level kept for failures keeps it. */\n override requestCompleted(error: Error | null | undefined, request: FastifyRequest, reply: FastifyReply): void {\n this.#write({\n level: error != null || reply.statusCode >= 500 ? 'error' : 'info',\n event: 'request',\n ...where(request),\n status: reply.statusCode,\n ms: Math.round(reply.elapsedTime),\n ...described(error),\n })\n }\n\n /**\n * A request refused before it was routed, because its URL cannot be decoded or a parameter\n * is longer than the router takes: answered through the reply, so the error handler names it\n * as it names any refusal, and its request line written once the answer has gone. Fastify\n * calls this `frameworkErrors` and starts no clock for it, so `ms` is 0.\n */\n readonly refusedBeforeRouting = (error: Error, request: FastifyRequest, reply: FastifyReply): void => {\n reply.raw.once('finish', () => this.requestCompleted(null, request, reply))\n void reply.send(error)\n }\n\n /** Words and no event from Fastify, so written here: the `503` and the request's id. */\n override serviceUnavailable(logger: FastifyBaseLogger): void {\n logger.error({ event: 'request', status: 503 })\n }\n\n override defaultErrorLog(error: Error, request: FastifyRequest, reply: FastifyReply): void {\n const failed = reply.statusCode >= 500\n this.#write({\n level: failed ? 'error' : 'info',\n event: failed ? 'request.failed' : 'request.refused',\n ...where(request),\n status: reply.statusCode,\n ...described(error),\n })\n }\n}\n\nfunction where(request: FastifyRequest): { reqId: string; method: string; route: string | undefined } {\n return { reqId: request.id, method: request.method, route: request.routeOptions.url }\n}\n\n/**\n * Fastify's options for a server that keeps this log, or none for one that keeps no log —\n * `createApp` given no `log`, which is how a host embedding it keeps its own output (0115).\n * Without one, Fastify answers what it refuses before routing by hand, as it always has; with\n * one, through the reply so the refusal can be written — the same status and the same fields.\n */\nexport function serverLogOptions(\n log: ServerLogSettings | undefined,\n): Pick<FastifyServerOptions, 'loggerInstance' | 'logController' | 'frameworkErrors'> {\n if (log === undefined) return {}\n const write = written(log.sink, log.level)\n const lines = new RequestLines(write)\n return { loggerInstance: logger(write, log.level, undefined), logController: lines, frameworkErrors: lines.refusedBeforeRouting }\n}\n\n/**\n * What a thrown thing is, from what it calls itself: its class, and the first code along its\n * causes — Drizzle wraps PostgreSQL's error, whose SQLSTATE is the useful part. Never its\n * message or its stack, whose first line is the message.\n */\nfunction described(error: unknown): { kind?: string; code?: string } {\n if (!(error instanceof Error)) return {}\n let code: string | undefined\n let at: unknown = error\n for (let depth = 0; code === undefined && at instanceof Error && depth < 4; depth += 1) {\n const own = (at as { code?: unknown }).code\n if (isCode(own)) code = own\n at = at.cause\n }\n return { kind: kindOf(error), ...(code === undefined ? {} : { code }) }\n}\n\n/** `name`, which most errors set; a class that leaves it as `Error` by its constructor. */\nfunction kindOf(error: Error): string {\n if (error.name !== 'Error' && isIdentifier(error.name)) return error.name\n const constructed: unknown = error.constructor.name\n return isIdentifier(constructed) ? constructed : 'Error'\n}\n\nfunction isIdentifier(value: unknown): value is string {\n return typeof value === 'string' && /^[A-Za-z_$][\\w$]{0,63}$/.test(value)\n}\n\n/**\n * `ECONNREFUSED`, `22P02`, `FST_ERR_CTP_BODY_TOO_LARGE`: capitals, digits and underscores. The\n * one rule for what a code looks like, which the reply to a 5xx keeps a code by as well (0172).\n */\nexport function isCode(value: unknown): value is string {\n return typeof value === 'string' && /^[A-Z0-9_]{1,64}$/.test(value)\n}\n\nfunction isStatus(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 100 && value <= 599\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\n/** What a background worker complains through. */\nexport type WorkerLog = Pick<FastifyBaseLogger, 'error'>\n\n/**\n * The log a worker was given, or one writing its failures to standard error by the same rule:\n * where they went before there was a log, and so where a host that starts a worker without\n * one still finds them.\n */\nexport function workerLog(given: WorkerLog | undefined): WorkerLog {\n return given ?? createServerLog(process.stderr, 'error')\n}\n\n/**\n * What the database says beside its answers, as lines by the same rule: its SQLSTATE, never\n * its words. postgres.js prints each notice whole to standard output unless it is handed\n * somewhere else to put it, and the bootstrap draws one on every start for each table, column\n * and trigger that already exists. Those are `debug`; a `WARNING` is `warn`. With no log,\n * nowhere.\n */\nexport function databaseNotices(\n log: FastifyBaseLogger | undefined,\n): (notice: Readonly<Record<string, unknown>>) => void {\n return (notice) => {\n if (log === undefined) return\n const line = { event: 'database.notice', code: notice['code'] }\n if (notice['severity'] === 'WARNING') log.warn(line)\n else log.debug(line)\n }\n}\n","import { STATUS_CODES, validateHeaderName, validateHeaderValue } from 'node:http'\nimport type { FastifyInstance, FastifyReply } from 'fastify'\nimport { isCode } from './server-log.js'\n\n/**\n * What an error answers its client with\n * ([0172](../../../docs/decisions/0172-a-5xx-says-nothing-of-what-was-thrown.md)).\n *\n * Fastify answers an error with `{ statusCode, code, error, message }`, the message being the\n * error's own. For an error that names its own 4xx that is a sentence written for the client —\n * the body did not parse, the file is too large — and it stays. For a 5xx it is whatever was\n * thrown: a database error's message is the failing query and its parameters, a disk's names\n * the path it could not write. So a 5xx keeps the shape and says only its status: the status's\n * own name as `error` and as `message`, and a `code` only when the error named its own status —\n * made to be answered, as `RATE_LIMIT_UNAVAILABLE` is (0170) — and only in the shape the log\n * keeps a code in. A fault's code, `ENOSPC` or an SQLSTATE, is the operator's, in the log. And a\n * fault answered under a 4xx — Fastify keeps a status of 400 or more that the reply already had\n * — says only that status too (0175): its words were no more written for the client there.\n *\n * **Two hooks rather than an error handler**, because Fastify writes its line about an error —\n * `request.failed` and `request.refused`, with the error's kind and code (`server-log.ts`) —\n * only from its own default handler. One set with `setErrorHandler` replaces it, and the line\n * would go with it. So the default handler still sets the status and writes the line, and the\n * body is replaced on its way out, in `onSend`, where the status is final. `onError` is how\n * `onSend` knows the body is an error's: a route that answers a 5xx itself — no file store, a\n * scanner that could not be asked — chose its words for the person reading them.\n *\n * **An error's reply is written without a header Node cannot write**\n * ([0175](../../../docs/decisions/0175-a-name-goes-into-a-header-encoded.md)). Node checks a\n * header's value when the reply is written, after the route has answered, and throws. Fastify\n * answers that error with the headers the reply already had, so its own reply failed the same\n * way, and Fastify's last resort — which runs no hook — wrote it with Node's code and sentence,\n * under whatever status the reply had. Dropping the header here is what lets this reply be the\n * one that goes out, because no `onSend` hook runs after this one but Fastify's own on a `HEAD`\n * route, which sets only a length — `error-replies.test.ts` puts such a header on a request to\n * every route and reads each answer. A route's own value is built so it cannot be refused\n * (`header-values.ts`); this is for the one that was not.\n *\n * On the root instance, before any route is registered, so every route and every plugin's\n * context has both. Not tied to the log: an app given none withholds the same words.\n */\nexport function withholdServerErrors(app: FastifyInstance): void {\n const thrown = new WeakMap<FastifyReply, unknown>()\n\n app.addHook('onError', async (_request, reply, error) => {\n thrown.set(reply, error)\n })\n\n app.addHook('onSend', async (_request, reply, payload) => {\n if (!thrown.has(reply)) return payload\n dropUnwritable(reply)\n const error = thrown.get(reply)\n if (reply.statusCode < 500 && answerable(error)) return payload\n return JSON.stringify(withheld(reply.statusCode, error))\n })\n}\n\n/** Remove every header Node would refuse to write, by its own checks rather than a copy of them. */\nfunction dropUnwritable(reply: FastifyReply): void {\n for (const [name, value] of Object.entries(reply.getHeaders())) {\n if (!writable(name, value)) reply.removeHeader(name)\n }\n}\n\n/**\n * A header as Node writes it: a number as its digits, and a list one value at a time. Fastify\n * keeps no header without a value — `reply.header` defaults one to `''` — though `getHeaders`\n * is typed as if it might.\n */\nfunction writable(name: string, value: string | number | readonly string[] | undefined): boolean {\n try {\n validateHeaderName(name)\n for (const each of [value ?? []].flat()) validateHeaderValue(name, String(each))\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Whether an error names its own status: made to be answered, by this server or by Fastify, so\n * its code is part of the answer and, below 500, its sentence too. One that names none is a\n * fault from below, whatever status the reply already had when it was thrown.\n */\nfunction answerable(error: unknown): boolean {\n return isRecord(error) && (typeof error['statusCode'] === 'number' || typeof error['status'] === 'number')\n}\n\n/** Fastify's shape for an error's reply, with nothing in it the thrown error said. */\nfunction withheld(status: number, error: unknown): { statusCode: number; code?: string; error: string; message: string } {\n const name = STATUS_CODES[status] ?? 'Server Error'\n const own = isRecord(error) ? error : {}\n const code = answerable(own) && isCode(own['code']) ? own['code'] : undefined\n return { statusCode: status, ...(code === undefined ? {} : { code }), error: name, message: name }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n","import type { FastifyInstance } from 'fastify'\n\n/**\n * A body with a NUL character in it is refused as malformed\n * ([0173](../../../docs/decisions/0173-a-nul-character-names-nothing-and-a-body-with-one-is-refused.md)).\n *\n * PostgreSQL holds no U+0000 in `text`, `22021`, nor in `jsonb`, `22P05`, so a string with one\n * can be kept nowhere this server keeps anything: an answer, a draft, a schema, an example, a\n * file's name or type, an origin, an email, a key's name. Each was a `500` where its route\n * wrote it, and a line at `error`. Refused here, once, on the root instance before any route,\n * so every route and every plugin's context has it and a route added later needs nothing of\n * its own.\n *\n * In `preValidation`, which is after the body is parsed — by whichever parser the request's\n * type chose, so a `text/plain` string is looked at as well as JSON — and before any route's\n * own `preHandler`. That makes it what a body that does not parse is: answered by the body\n * alone, whoever sent it and whatever accounts exist, before a session is asked for. A file's\n * bytes arrive as a `Buffer` and are not looked at; a NUL byte is ordinary in a file.\n *\n * The answer is the route plane's shape for a refusal, with the JSON Pointer (RFC 6901) to the\n * string or the member's name the NUL is in, so a client can say which answer to correct.\n */\nexport function refuseNulCharacters(app: FastifyInstance): void {\n app.addHook('preValidation', async (request, reply) => {\n const pointer = nulAt(request.body)\n if (pointer === undefined) return\n return reply.code(400).send({\n error: 'nul_character',\n pointer,\n message: `The NUL character (U+0000) is in no string this server accepts, and this request's body has one at ${\n pointer === '' ? 'the body itself' : pointer\n }.`,\n })\n })\n}\n\n/** Where a container sits: the member or index of the one above it that holds it. */\ninterface Step {\n readonly up: Step | undefined\n readonly key: string | number\n}\n\n/**\n * The JSON Pointer to the first string or member name in `body` with a NUL in it, in the order\n * the body was written, or `undefined` when there is none.\n *\n * A loop over a stack of its own rather than a recursion: `JSON.parse` takes a body nested\n * deeper than the call stack goes, and a walk that threw on it would make that body a `500` on\n * every route. And every body is walked, so nothing is built for a value on the way — a step\n * for each container, and the pointer only for the string it finds. Building a pointer for\n * every value made the walk of a body at the cap take longer than parsing it.\n */\nfunction nulAt(body: unknown): string | undefined {\n // Three stacks in step: a value, the container it is in, and its key there.\n const values: unknown[] = [body]\n const containers: Array<Step | undefined> = [undefined]\n const keys: Array<string | number | undefined> = [undefined]\n while (values.length > 0) {\n const value = values.pop()\n const container = containers.pop()\n const key = keys.pop()\n if (typeof value === 'string') {\n if (value.includes('\\u0000')) return pointer(container, key)\n continue\n }\n const here = key === undefined ? container : { up: container, key }\n if (Array.isArray(value)) {\n for (let index = value.length - 1; index >= 0; index -= 1) {\n values.push(value[index])\n containers.push(here)\n keys.push(index)\n }\n } else if (isPlainObject(value)) {\n const names = Object.keys(value)\n for (let index = names.length - 1; index >= 0; index -= 1) {\n const name = names[index]!\n values.push(value[name])\n containers.push(here)\n keys.push(name)\n // The name before what it names, which the stack pops first; both point at the member.\n if (name.includes('\\u0000')) {\n values.push(name)\n containers.push(here)\n keys.push(name)\n }\n }\n }\n }\n return undefined\n}\n\n/** RFC 6901's spelling of the place `key` names in `container`: '' for the body itself. */\nfunction pointer(container: Step | undefined, key: string | number | undefined): string {\n const steps: Array<string | number> = []\n for (let at = key === undefined ? container : { up: container, key }; at !== undefined; at = at.up) steps.push(at.key)\n return steps.reverse().map((step) => `/${typeof step === 'number' ? String(step) : token(step)}`).join('')\n}\n\n/** A member's name as one step of a JSON Pointer: `~` and `/` escaped, as RFC 6901 has them. */\nfunction token(name: string): string {\n return name.replaceAll('~', '~0').replaceAll('/', '~1')\n}\n\n/** What a parser makes of a JSON object, and nothing else — not a `Buffer`, not a stream. */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null) return false\n const prototype: unknown = Object.getPrototypeOf(value)\n return prototype === Object.prototype || prototype === null\n}\n","import type { FastifyRequest } from 'fastify'\nimport { auditedBy } from '@formancy/server-core'\nimport type { Actor, AuditDraft, ServerDeps } from '@formancy/server-core'\n\n/** What a route calls to record something it has done. */\nexport type Audit = (request: FastifyRequest, draft: AuditDraft) => Promise<void>\n\n/**\n * Append an audit row for something that has already happened.\n *\n * Never throws. An export that succeeded and an audit row that did not is\n * bad; an export that 500s because the audit row failed, after the rows have\n * already been written to the response, is worse and helps nobody. So the\n * failure is written to the log at error level, as `audit.unwritten` with the\n * row's action and the error's kind — never its subject, which for a failed\n * login is the email somebody tried — and the request stands.\n *\n * For a mutation that HAS a transaction, do not use this — pass the entry\n * into that call so the two commit together. A publish, a change to a form's\n * examples and a submission are those: their use-cases build the row, and\n * the route hands them `request.id` so it names the request as this one does.\n *\n * Its own module because what happens when a row cannot be written is the log's\n * business (0168), and `app.ts` had no room for it.\n */\nexport function auditTrail(deps: Pick<ServerDeps, 'storage' | 'newId' | 'nowIso'>): Audit {\n return async (request, draft) => {\n const actor = (request as FastifyRequest & { actor?: Actor }).actor\n try {\n await deps.storage.recordAudit({\n id: deps.newId(),\n at: deps.nowIso(),\n requestId: request.id,\n ...auditedBy(actor, draft),\n })\n } catch (error) {\n request.log.error({ event: 'audit.unwritten', action: draft.action, err: error })\n }\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport { fileRoutes } from './routes/files.js'\nimport { publishRoutes } from './routes/publish.js'\nimport { accessRoutes } from './routes/access.js'\nimport { exampleRoutes } from './routes/examples.js'\nimport { deliveryRoutes } from './routes/deliveries.js'\nimport { draftRoutes } from './routes/drafts.js'\nimport { modelRoutes } from './routes/model.js'\nimport type { DeploymentModel } from './routes/model.js'\nimport { submissionRoutes } from './routes/submissions.js'\nimport Fastify from 'fastify'\nimport type { FileStore } from './file-store.js'\nimport rateLimit, { type FastifyRateLimitStoreCtor } from '@fastify/rate-limit'\nimport { limited } from './rate-limits.js'\nimport type { FastifyInstance, FastifyRequest, preHandlerHookHandler } from 'fastify'\nimport {\n authenticateApiKey,\n authenticateLocal,\n can,\n createApiKey,\n createLocalUser,\n exportCsv,\n listForms,\n listSubmissions,\n listVersions,\n} from '@formancy/server-core'\nimport type {\n Action,\n Actor,\n AuthDeps,\n Role,\n Scanner,\n ServerDeps,\n ServerOptionsSources,\n Storage,\n} from '@formancy/server-core'\nimport { createSessionTokens, realRandomToken, realSecretHashing } from './auth-runtime.js'\nimport { checkedMaxFileBytes, DEFAULT_MAX_FILE_BYTES } from './upload-settings.js'\nimport { serverLogOptions } from './server-log.js'\nimport type { ServerLogSettings } from './server-log.js'\nimport { withholdServerErrors } from './error-replies.js'\nimport { refuseNulCharacters } from './nul-characters.js'\nimport { auditTrail } from './audit-trail.js'\nimport { attachment } from './header-values.js'\n\nexport { CHALLENGE_HEADER, SCHEMA_HASH_HEADER, SUBMISSION_TOKEN_HEADER } from './headers.js'\n\nexport const API_KEY_HEADER = 'x-formancy-api-key'\n\nexport interface AppOptions {\n /** Signs the short-lived session tokens. At least 32 characters. */\n authSecret: string\n /**\n * Created at startup IF no user exists yet — the docker-compose path to a\n * first login. Ignored once any user exists, so it cannot re-seed an admin\n * into a running installation.\n */\n bootstrapAdmin?: { email: string; password: string }\n /**\n * Turns the proof-of-work challenge on, and signs the challenges it mints.\n *\n * Absent means anonymous submissions are defended by the rate limits, the\n * origin allowlist and the body cap alone. That is a supported state rather\n * than a broken one — a deployment whose forms all require a session has\n * no anonymous surface to protect — and the challenge route says so with a\n * 404 rather than failing.\n */\n challengeSecret?: string\n /**\n * The lists a form document may name with `optionsSource`, and how to check a value\n * against one.\n *\n * Without this the membership check in `server-core` is unreachable, which is how it\n * shipped: the port existed, the use-case called it, and the HTTP server never passed\n * anything — so `SAFETY-ANALYSIS.md`'s A7 stated a constraint that could not run.\n * Found by review before the beta.\n *\n * Absent is still a supported state, and it means what A7 says it means: the server\n * has no vocabulary, so it cannot refuse a publish for naming an unknown list and\n * cannot check a submitted value. A deployment with no sourced field never needs one.\n */\n optionsSources?: ServerOptionsSources\n /**\n * Anonymous submissions allowed per IP per minute. Off in tests by setting\n * it high; a real deployment should leave the default.\n */\n submissionRateLimit?: { max: number; timeWindowMs: number }\n /** Where every limit counts; absent, each process counts its own. `main.ts` passes the\n * database every replica shares, through `createPostgresRateLimitStore` (0170). */\n rateLimitStore?: FastifyRateLimitStoreCtor\n /** Login attempts per IP per minute. Defaults to 10. */\n loginRateLimit?: { max: number; timeWindowMs: number }\n /**\n * The reverse proxies, as addresses and CIDR ranges, whose X-Forwarded-For\n * names the client every limit above counts. Absent trusts none, so behind a\n * proxy all respondents share its one budget; `trust-proxy.ts` says why there\n * is no hop count and no `true`.\n */\n trustProxy?: string[]\n /**\n * Largest accepted request body, in bytes. A structural cap BEFORE parsing:\n * the JSON parser should never be handed something enormous in the first\n * place, whatever the schema says afterwards.\n */\n bodyLimitBytes?: number\n /**\n * Where uploaded bytes go. Absent means this deployment accepts no files:\n * a form with a file field still renders and still submits, and the field\n * says plainly that there is nowhere to put one\n * ([0055](../../../docs/decisions/0055-files-are-claimed.md)).\n */\n fileStore?: FileStore\n /**\n * Largest file this deployment will accept, whatever a form says. A form\n * author sets the per-field limit; this is the operator's ceiling over all\n * of them, because the disk is theirs. Defaults to 10 MB; a whole number no\n * larger than `files.size` holds, or `createApp` throws (`upload-settings.ts`).\n */\n maxFileBytes?: number\n /** Asked about every upload's bytes before they are kept; absent, none is asked (0131). */\n scanner?: Scanner\n /**\n * The deployment's model, which the builders ask through `/model/complete` under the\n * briefing this server writes. Absent, there is none: `/model` names no provider and no\n * model, and the admin draws no prompt pane (0165, 0183).\n */\n model?: DeploymentModel\n /** Model requests per session per minute. Defaults to 10: a run of three turns, three times. */\n modelRateLimit?: { max: number; timeWindowMs: number }\n /**\n * Where the server's log goes and from which level: a line per request and per error, each\n * built from a list of fields (`server-log.ts`, 0168). Absent, there is none — `main.ts`\n * passes one unless `FORMANCY_LOG_LEVEL` is `off`; a host embedding the app chooses (0115).\n */\n log?: ServerLogSettings\n}\n\n/**\n * The HTTP surface, split into two planes.\n *\n * PUBLIC — what a person filling a form needs: resolve, submit, drafts. These\n * stay unauthenticated by design; anonymous-submission hardening is its own\n * upcoming step and is opt-in per form.\n *\n * MANAGEMENT — everything an operator does: publish, catalog, submissions,\n * export, users, keys. Authenticated via a session token (login) or an API\n * key header, authorized through server-core's one-table can().\n *\n * This file is the composition root, so THIS is where ambient reality enters:\n * randomUUID, the real clock, argon2 and the CSPRNG. Everything below stays\n * injected and replayable.\n */\nexport async function createApp(storage: Storage, options: AppOptions): Promise<FastifyInstance> {\n const app = Fastify({\n ...serverLogOptions(options.log),\n // 256 kB by default. Large enough for a long form with a repeater, small\n // enough that a request cannot cost meaningful memory before it is rejected.\n bodyLimit: options.bodyLimitBytes ?? 256 * 1024,\n // Which hop's word request.ip takes, and so whose budget a rate limit spends.\n trustProxy: options.trustProxy ?? false,\n })\n // Before any route: a 5xx says its status and nothing of what was thrown (0172), and a body\n // with a NUL character in it, which nothing here can keep, is refused as malformed (0173).\n withholdServerErrors(app)\n refuseNulCharacters(app)\n\n const maxFileBytes = checkedMaxFileBytes(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES, 'maxFileBytes')\n const fileStore = options.fileStore\n\n const submissionLimit = options.submissionRateLimit ?? { max: 30, timeWindowMs: 60_000 }\n const loginLimit = options.loginRateLimit ?? { max: 10, timeWindowMs: 60_000 }\n const challengeSecret = options.challengeSecret\n await app.register(rateLimit, {\n global: false, // opted into per route: the management plane is authenticated\n max: submissionLimit.max,\n timeWindow: submissionLimit.timeWindowMs,\n ...(options.rateLimitStore === undefined ? {} : { store: options.rateLimitStore }),\n })\n\n const deps: ServerDeps = {\n storage,\n newId: () => randomUUID(),\n nowIso: () => new Date().toISOString(),\n // The host's own signing key. A draft token is a server-signed bearer\n // token, which is what that key is already for, and a separate optional\n // secret would mean drafts are unprotected whenever nobody set one.\n draftSecret: options.authSecret,\n ...(options.optionsSources === undefined ? {} : { optionsSources: options.optionsSources }),\n ...(options.scanner === undefined ? {} : { scanner: options.scanner }),\n capabilities: {\n now: () => Date.now(),\n today: () => new Date().toISOString().slice(0, 10),\n random: () => Math.random(),\n },\n }\n\n const authDeps: AuthDeps = {\n storage,\n newId: deps.newId,\n nowIso: deps.nowIso,\n randomToken: realRandomToken,\n ...realSecretHashing(),\n }\n\n const sessions = createSessionTokens(options.authSecret)\n\n if (options.bootstrapAdmin !== undefined) {\n const alreadyThere = await storage.getUserByEmail(options.bootstrapAdmin.email)\n if (alreadyThere === undefined) {\n const outcome = await createLocalUser(authDeps, { ...options.bootstrapAdmin, role: 'admin' })\n if (!outcome.ok) throw new Error(`Bootstrap admin rejected: ${outcome.message}`)\n }\n }\n\n async function actorOf(request: FastifyRequest): Promise<Actor | undefined> {\n const bearer = request.headers.authorization\n if (typeof bearer === 'string' && bearer.startsWith('Bearer ')) {\n return sessions.verify(bearer.slice('Bearer '.length))\n }\n const apiKey = request.headers[API_KEY_HEADER]\n if (typeof apiKey === 'string' && apiKey !== '') {\n const outcome = await authenticateApiKey(authDeps, apiKey)\n return outcome.ok ? outcome.actor : undefined\n }\n return undefined\n }\n\n /** 401 without an identity, 403 with one that lacks the permission — the\n * difference tells a client whether to log in or to give up. */\n function requires(action: Action): preHandlerHookHandler {\n return async (request, reply) => {\n const actor = await actorOf(request)\n if (actor === undefined) {\n return reply.code(401).send({ error: 'unauthenticated' })\n }\n if (!can(actor, action)) {\n return reply.code(403).send({ error: 'forbidden', action })\n }\n ;(request as FastifyRequest & { actor: Actor }).actor = actor\n }\n }\n\n // Never throws: a row that cannot be written is said in the log, and the request stands.\n const audit = auditTrail(deps)\n\n // -------------------------------------------------------------------- auth\n\n app.post(\n '/auth/login',\n {\n // Enumeration resistance makes a wrong guess cost a full argon2 verification, which is\n // deliberate — but it also means an unlimited login endpoint is an unlimited invitation\n // to spend the server's CPU. Tighter than submission: nobody logs in ten times a minute\n // honestly. Refused when it cannot be counted, since guessing is what it is for (0170).\n config: { rateLimit: limited(loginLimit, 'refuse') },\n },\n async (request, reply) => {\n const body = request.body as { email?: unknown; password?: unknown } | null\n if (body === null || typeof body.email !== 'string' || typeof body.password !== 'string') {\n return reply\n .code(400)\n .send({ error: 'bad_request', message: 'Body needs { email, password }.' })\n }\n const outcome = await authenticateLocal(authDeps, {\n email: body.email,\n password: body.password,\n })\n if (!outcome.ok) {\n // Failures too, and this is the pair that matters: a hundred failures\n // then one success is the shape of an attack, and recording only the\n // success hides it. The email is the subject because that is what was\n // tried — there may be no such user.\n await audit(request, { action: 'auth.login.failed', subject: body.email })\n return reply.code(401).send({ error: 'invalid_credentials' })\n }\n await audit(request, {\n action: 'auth.login',\n subject: body.email,\n actorKind: 'user',\n actorId: outcome.actor.id,\n })\n return reply.send({ token: await sessions.issue(outcome.actor), role: outcome.actor.role })\n },\n )\n\n app.post('/users', { preHandler: requires('user.create') }, async (request, reply) => {\n const body = request.body as { email?: unknown; password?: unknown; role?: unknown } | null\n if (\n body === null ||\n typeof body.email !== 'string' ||\n typeof body.password !== 'string' ||\n (body.role !== 'admin' && body.role !== 'editor' && body.role !== 'viewer')\n ) {\n return reply\n .code(400)\n .send({ error: 'bad_request', message: 'Body needs { email, password, role }.' })\n }\n const outcome = await createLocalUser(authDeps, {\n email: body.email,\n password: body.password,\n role: body.role as Role,\n })\n if (!outcome.ok) return reply.code(422).send({ error: 'rejected', message: outcome.message })\n return reply.code(201).send({ id: outcome.id })\n })\n\n app.post('/api-keys', { preHandler: requires('apiKey.create') }, async (request, reply) => {\n const body = request.body as { name?: unknown; role?: unknown } | null\n if (\n body === null ||\n typeof body.name !== 'string' ||\n (body.role !== 'admin' && body.role !== 'editor' && body.role !== 'viewer')\n ) {\n return reply.code(400).send({ error: 'bad_request', message: 'Body needs { name, role }.' })\n }\n const outcome = await createApiKey(authDeps, { name: body.name, role: body.role as Role })\n if (!outcome.ok) return reply.code(422).send({ error: 'rejected', message: outcome.message })\n // The one and only time the secret exists in full outside a hash.\n return reply.code(201).send({ id: outcome.id, secret: outcome.secret })\n })\n\n // -------------------------------------------------------------- management\n\n // Publishing is its own plugin: one route family per plugin, which is\n // Fastify's own unit and what this file's size budget named as the seam.\n await app.register(publishRoutes, { deps, requires })\n\n // Who may submit a form, and what it is checked against: kept beside it rather than in\n // a version, each a route family of its own (0044, 0166).\n await app.register(accessRoutes, { deps, requires, audit })\n await app.register(exampleRoutes, { deps, actorOf })\n\n /**\n * The log itself.\n *\n * Admin only, and reading it is NOT audited. A log that records its own\n * reads grows without bound from a dashboard that polls it, and the entry\n * would say nothing an access log does not — the events worth recording\n * are the ones that touch somebody else's data.\n */\n app.get('/audit', { preHandler: requires('user.create') }, async (request, reply) => {\n const raw = (request.query as { limit?: unknown } | undefined)?.limit\n const asked = typeof raw === 'string' ? Number.parseInt(raw, 10) : 100\n const limit = Number.isFinite(asked) ? Math.min(Math.max(asked, 1), 500) : 100\n return reply.send({ entries: await deps.storage.listAudit(limit) })\n })\n\n // Webhook health, dead deliveries and replay: a route family of their own.\n await app.register(deliveryRoutes, { deps, requires, audit })\n\n // The deployment's model, asked on a builder's behalf with the key kept here.\n const modelLimit = options.modelRateLimit ?? { max: 10, timeWindowMs: 60_000 }\n await app.register(modelRoutes, { model: options.model, requires, audit, limit: modelLimit })\n\n app.get('/forms', { preHandler: requires('form.read') }, async (_request, reply) => {\n return reply.send({ forms: await listForms(deps) })\n })\n\n app.get('/f/:path/versions', { preHandler: requires('form.read') }, async (request, reply) => {\n const { path } = request.params as { path: string }\n const versions = await listVersions(deps, path)\n if (versions === undefined) return reply.code(404).send({ error: 'unknown_form' })\n return reply.send({ versions })\n })\n\n app.get(\n '/f/:path/submissions',\n { preHandler: requires('submission.read') },\n async (request, reply) => {\n const { path } = request.params as { path: string }\n const listed = await listSubmissions(deps, path)\n if (listed === undefined) return reply.code(404).send({ error: 'unknown_form' })\n // The event a data protection officer asks about, and the one an audit\n // log that covers only mutations cannot answer: who read other people's\n // answers. The count, never the answers.\n await audit(request, {\n action: 'submission.read',\n subject: path,\n detail: { count: listed.length },\n })\n return reply.send({ submissions: listed })\n },\n )\n\n app.get(\n '/f/:path/submissions/export.csv',\n { preHandler: requires('submission.export') },\n async (request, reply) => {\n const { path } = request.params as { path: string }\n const csv = await exportCsv(deps, path)\n if (csv === undefined) return reply.code(404).send({ error: 'unknown_form' })\n // An export leaves the building. Bytes rather than rows, because that is\n // what was actually handed over.\n await audit(request, {\n action: 'submission.exported',\n subject: path,\n detail: { bytes: Buffer.byteLength(csv, 'utf8') },\n })\n return reply\n .header('content-type', 'text/csv; charset=utf-8')\n // The path is the author's, and a path can hold what a header cannot (0175).\n .header('content-disposition', attachment(`${path}-submissions.csv`))\n .send(csv)\n },\n )\n\n // ------------------------------------------------------------------ public\n\n // Reading a form, its challenge and submitting it: what a respondent's browser does.\n await app.register(submissionRoutes, { deps, challengeSecret, limit: submissionLimit, actorOf })\n\n // A respondent's drafts, each behind the key only its starter holds: a family of their own.\n await app.register(draftRoutes, { deps, limit: submissionLimit })\n\n // Offering a file, receiving its bytes and serving it back: a route family of\n // their own, which is the seam this file's size budget names.\n await app.register(fileRoutes, { deps, fileStore, maxFileBytes, limit: submissionLimit, actorOf })\n\n return app\n}\n","import { jsonb, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'\nimport type postgres from 'postgres'\nimport type { AuditEntry } from '@formancy/server-core'\n\n/**\n * The thin slice's tables. Three rules from the design spec are enforced HERE,\n * in the database, because application code can be bypassed and these must not\n * be:\n *\n * - published versions are immutable (a trigger refuses UPDATE),\n * - a submission binds to its exact version by real foreign key,\n * - that key is ON DELETE RESTRICT, so orphaning a submission from the schema\n * that produced it is structurally impossible.\n */\nexport const forms = pgTable('forms', {\n id: uuid('id').primaryKey(),\n path: text('path').notNull().unique(),\n currentVersionId: uuid('current_version_id'),\n // Defaults to the safe value in the DATABASE as well as in the code, so a\n // row inserted by a migration or a fixture is private unless it says\n // otherwise. A default of 'public' one refactor away from being forgotten is\n // how a private form becomes a public one.\n accessSubmit: text('access_submit').notNull().default('authenticated'),\n // NULL means no allowlist is in force. An empty array means nothing is\n // allowed — a different thing, and the reason this is nullable.\n allowedOrigins: text('allowed_origins').array(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n})\n\nexport const formVersions = pgTable('form_versions', {\n id: uuid('id').primaryKey(),\n formId: uuid('form_id')\n .notNull()\n .references(() => forms.id),\n version: integer('version').notNull(),\n schema: jsonb('schema').notNull(),\n schemaHash: text('schema_hash').notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n})\n\nexport const users = pgTable('users', {\n id: uuid('id').primaryKey(),\n email: text('email').notNull().unique(),\n passwordHash: text('password_hash').notNull(),\n role: text('role').notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull(),\n})\n\nexport const apiKeys = pgTable('api_keys', {\n id: uuid('id').primaryKey(),\n name: text('name').notNull(),\n prefix: text('prefix').notNull(),\n secretHash: text('secret_hash').notNull(),\n role: text('role').notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull(),\n revokedAt: timestamp('revoked_at', { withTimezone: true }),\n})\n\nexport const drafts = pgTable('drafts', {\n id: text('id').notNull(),\n formId: uuid('form_id')\n .notNull()\n .references(() => forms.id),\n formVersionId: uuid('form_version_id')\n .notNull()\n .references(() => formVersions.id),\n data: jsonb('data').notNull(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull(),\n})\n\nexport const webhooks = pgTable('webhooks', {\n id: uuid('id').primaryKey(),\n formId: uuid('form_id')\n .notNull()\n .references(() => forms.id),\n url: text('url').notNull(),\n secret: text('secret').notNull(),\n consecutiveFailures: integer('consecutive_failures').notNull().default(0),\n openedAt: timestamp('opened_at', { withTimezone: true }),\n})\n\n/**\n * The outbox. A row here and the submission that caused it are written by the\n * same transaction, which is what makes \"accepted\" and \"will be delivered\" one\n * decision rather than two.\n */\nexport const deliveries = pgTable('deliveries', {\n id: uuid('id').primaryKey(),\n webhookId: uuid('webhook_id').notNull(),\n submissionId: uuid('submission_id').notNull(),\n eventId: uuid('event_id').notNull(),\n body: text('body').notNull(),\n attempt: integer('attempt').notNull().default(0),\n nextAttemptAt: timestamp('next_attempt_at', { withTimezone: true }).notNull(),\n state: text('state').notNull().default('pending'),\n lastError: text('last_error'),\n})\n\nexport const spentChallenges = pgTable('spent_challenges', {\n challenge: text('challenge').primaryKey(),\n expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),\n})\n\nexport const auditLog = pgTable('audit_log', {\n id: uuid('id').primaryKey(),\n at: timestamp('at', { withTimezone: true }).notNull(),\n action: text('action').notNull(),\n actorKind: text('actor_kind'),\n actorId: text('actor_id'),\n subject: text('subject'),\n requestId: text('request_id'),\n detail: jsonb('detail'),\n})\n\n/**\n * A form's examples and the sample they start from, one row per form, beside it and never in\n * a version (0166). The examples change while the form does not, and a version cannot.\n */\nexport const formExamples = pgTable('form_examples', {\n formId: uuid('form_id')\n .primaryKey()\n .references(() => forms.id),\n scenarios: jsonb('scenarios').notNull(),\n sample: jsonb('sample'),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull(),\n})\n\n/**\n * An audit entry as the table holds it.\n *\n * Absent and null are the same thing here, and the conversion is in one place, beside the\n * table, so the storage's call sites — a publish, a submission, a form's examples — cannot\n * disagree about which they write.\n */\nexport function auditRow(entry: AuditEntry): typeof auditLog.$inferInsert {\n return {\n id: entry.id,\n at: new Date(entry.at),\n action: entry.action,\n actorKind: entry.actorKind ?? null,\n actorId: entry.actorId ?? null,\n subject: entry.subject ?? null,\n requestId: entry.requestId ?? null,\n detail: entry.detail ?? null,\n }\n}\n\nexport const files = pgTable('files', {\n id: uuid('id').primaryKey(),\n formId: uuid('form_id').notNull(),\n name: text('name').notNull(),\n size: integer('size').notNull(),\n contentType: text('content_type').notNull(),\n storageKey: text('storage_key').notNull(),\n state: text('state').notNull().default('offered'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull(),\n submissionId: uuid('submission_id'),\n receivingUntil: timestamp('receiving_until', { withTimezone: true }),\n})\n\nexport const submissions = pgTable('submissions', {\n id: uuid('id').primaryKey(),\n formId: uuid('form_id')\n .notNull()\n .references(() => forms.id),\n formVersionId: uuid('form_version_id')\n .notNull()\n .references(() => formVersions.id, { onDelete: 'restrict' }),\n data: jsonb('data').notNull(),\n submittedAt: timestamp('submitted_at', { withTimezone: true }).notNull(),\n})\n\n/**\n * Whether `id` can be compared with a `uuid` column: the hyphenated form every id this server\n * mints has. PostgreSQL refuses anything it cannot read as a uuid with an error, `22P02`, where\n * a `text` column — or the in-memory storage — finds no row; so a lookup by an id from a URL\n * that is not one answers no row without asking (0172). A spelling PostgreSQL would also read,\n * braced or without hyphens, names no row either: no id was handed out in one.\n */\nexport function isUuid(id: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)\n}\n\n/**\n * Whether `value` can be compared with a `text` column: PostgreSQL holds no NUL character in\n * text and refuses one with an error, `22021`, where the in-memory storage finds no row. So a\n * lookup by a value with one — a form's path from a URL, an API key's prefix from a header —\n * answers no row without asking, as `isUuid` does for its column (0173). No row was ever\n * written with one, since no write of one ever succeeded.\n */\nexport function fitsText(value: string): boolean {\n return !value.includes('\\u0000')\n}\n\n/**\n * Bootstrap DDL for the thin slice — drizzle-kit migrations arrive with the\n * lifecycle work; a self-hosted skeleton that creates its own tables on first\n * start is worth more right now than a migration pipeline.\n */\nexport async function bootstrapSchema(sql: postgres.Sql): Promise<void> {\n await sql`\n CREATE TABLE IF NOT EXISTS forms (\n id uuid PRIMARY KEY,\n path text NOT NULL UNIQUE,\n current_version_id uuid,\n -- Private by default in the DATABASE, not only in the code, so a row\n -- inserted by a migration or a fixture is not accidentally open.\n access_submit text NOT NULL DEFAULT 'authenticated'\n CHECK (access_submit IN ('authenticated', 'public')),\n -- NULL: no allowlist in force. Empty array: nothing allowed. Different\n -- things, which is why this is nullable rather than defaulting to '{}'.\n allowed_origins text[],\n created_at timestamptz NOT NULL DEFAULT now()\n )`\n // Existing databases predate the two columns above. Adding them here rather\n // than in a separate migration keeps bootstrap idempotent for both a fresh\n // database and one created before access control existed.\n await sql`\n ALTER TABLE forms\n ADD COLUMN IF NOT EXISTS access_submit text NOT NULL DEFAULT 'authenticated'`\n await sql`ALTER TABLE forms ADD COLUMN IF NOT EXISTS allowed_origins text[]`\n await sql`\n CREATE TABLE IF NOT EXISTS form_versions (\n id uuid PRIMARY KEY,\n form_id uuid NOT NULL REFERENCES forms(id),\n version integer NOT NULL,\n schema jsonb NOT NULL,\n schema_hash text NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n UNIQUE (form_id, version),\n UNIQUE (form_id, schema_hash)\n )`\n await sql`\n CREATE TABLE IF NOT EXISTS submissions (\n id uuid PRIMARY KEY,\n form_id uuid NOT NULL REFERENCES forms(id),\n form_version_id uuid NOT NULL REFERENCES form_versions(id) ON DELETE RESTRICT,\n data jsonb NOT NULL,\n submitted_at timestamptz NOT NULL\n )`\n await sql`\n CREATE TABLE IF NOT EXISTS webhooks (\n id uuid PRIMARY KEY,\n form_id uuid NOT NULL REFERENCES forms(id) ON DELETE CASCADE,\n url text NOT NULL,\n secret text NOT NULL\n )`\n await sql`\n ALTER TABLE webhooks\n ADD COLUMN IF NOT EXISTS consecutive_failures integer NOT NULL DEFAULT 0,\n -- When the breaker opened, null while the destination is answering. On\n -- the row rather than in the worker's memory: memory does not survive a\n -- restart, and a self-hoster with no operations team needs a failing\n -- webhook to be visible on a screen rather than in a log nobody tails.\n ADD COLUMN IF NOT EXISTS opened_at timestamptz`\n await sql`\n CREATE TABLE IF NOT EXISTS deliveries (\n id uuid PRIMARY KEY,\n webhook_id uuid NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,\n -- RESTRICT, not CASCADE: a delivery is the record that something was\n -- sent about this submission, and deleting the submission must not\n -- quietly erase it.\n submission_id uuid NOT NULL REFERENCES submissions(id) ON DELETE RESTRICT,\n event_id uuid NOT NULL,\n body text NOT NULL,\n attempt integer NOT NULL DEFAULT 0,\n next_attempt_at timestamptz NOT NULL,\n state text NOT NULL DEFAULT 'pending'\n CHECK (state IN ('pending', 'delivered', 'dead')),\n last_error text\n )`\n await sql`\n CREATE INDEX IF NOT EXISTS deliveries_due\n ON deliveries (next_attempt_at) WHERE state = 'pending'`\n await sql`\n CREATE TABLE IF NOT EXISTS files (\n id uuid PRIMARY KEY,\n form_id uuid NOT NULL REFERENCES forms(id) ON DELETE RESTRICT,\n -- What the reader called it. Display only: the storage key is minted by\n -- the server, because a key built from a submitted filename is a path\n -- traversal waiting for somebody to try it.\n name text NOT NULL,\n size integer NOT NULL,\n content_type text NOT NULL,\n storage_key text NOT NULL,\n state text NOT NULL DEFAULT 'offered'\n CHECK (state IN ('offered', 'stored', 'claimed')),\n created_at timestamptz NOT NULL,\n -- RESTRICT for the same reason a delivery uses it: the row is the record\n -- that something was attached, and deleting the submission must not\n -- quietly erase what it carried.\n submission_id uuid REFERENCES submissions(id) ON DELETE RESTRICT,\n -- One file, one submission. Enforced here rather than in application\n -- code, because two concurrent submissions claiming the same file is\n -- exactly the race application code loses — and the loser would be a\n -- submission referencing bytes another submission owns.\n CONSTRAINT files_one_submission UNIQUE (id, submission_id)\n )`\n // One request receives a file's bytes at a time (0153): until this instant, or nobody\n // while it is null. A lease rather than a fourth state, so the CHECK above stands and a\n // request that died holding it holds it only until it runs out. Added here rather than\n // in the CREATE so a database from before it gains the column on start.\n await sql`ALTER TABLE files ADD COLUMN IF NOT EXISTS receiving_until timestamptz`\n await sql`\n CREATE INDEX IF NOT EXISTS files_abandoned\n ON files (created_at) WHERE state <> 'claimed'`\n await sql`\n CREATE TABLE IF NOT EXISTS users (\n id uuid PRIMARY KEY,\n email text NOT NULL UNIQUE,\n password_hash text NOT NULL,\n role text NOT NULL,\n created_at timestamptz NOT NULL\n )`\n await sql`\n CREATE TABLE IF NOT EXISTS api_keys (\n id uuid PRIMARY KEY,\n name text NOT NULL,\n prefix text NOT NULL,\n secret_hash text NOT NULL,\n role text NOT NULL,\n created_at timestamptz NOT NULL,\n revoked_at timestamptz\n )`\n await sql`CREATE INDEX IF NOT EXISTS api_keys_prefix ON api_keys (prefix)`\n await sql`\n CREATE TABLE IF NOT EXISTS spent_challenges (\n -- The primary key IS the mechanism. Two requests arriving with one\n -- solution both pass every stateless check — the signature is ours\n -- and the hash is right — and only the database can decide which of\n -- them spends it. A read-then-write would race exactly where somebody\n -- is looking.\n challenge text PRIMARY KEY,\n expires_at timestamptz NOT NULL\n )`\n await sql`CREATE INDEX IF NOT EXISTS spent_challenges_expiry ON spent_challenges (expires_at)`\n // Every rate limit's count, shared by every replica (0170): one row per route and client,\n // written by every limited request. UNLOGGED, because a count is worth less than the\n // write-ahead log entry: a crash empties the table, which costs one window of budget, and\n // nothing waits for the log to reach the disk or a standby. No index on `resets_at`: the\n // sweep reads the table once in ten minutes, and every write would keep the index.\n await sql`\n CREATE UNLOGGED TABLE IF NOT EXISTS rate_limit_counters (\n key text PRIMARY KEY,\n hits integer NOT NULL,\n resets_at timestamptz NOT NULL\n )`\n\n await sql`\n CREATE TABLE IF NOT EXISTS audit_log (\n id uuid PRIMARY KEY,\n at timestamptz NOT NULL,\n action text NOT NULL,\n -- Null for an anonymous public submission, which is an answer rather\n -- than a gap: the form was open and nobody was signed in.\n actor_kind text CHECK (actor_kind IN ('user', 'apiKey')),\n actor_id text,\n subject text,\n request_id text,\n -- Identifiers and counts. NEVER a submission's answers: an audit log is\n -- read by more people and kept longer than the data it describes, so\n -- data inside it is a second copy of the thing being protected.\n detail jsonb\n )`\n // Reading the log is the common query and it is always \"recently, and\n // usually about one thing\".\n await sql`CREATE INDEX IF NOT EXISTS audit_log_at ON audit_log (at DESC)`\n await sql`CREATE INDEX IF NOT EXISTS audit_log_subject ON audit_log (subject, at DESC)`\n\n await sql`\n CREATE TABLE IF NOT EXISTS drafts (\n id text NOT NULL,\n form_id uuid NOT NULL REFERENCES forms(id),\n form_version_id uuid NOT NULL REFERENCES form_versions(id),\n data jsonb NOT NULL,\n updated_at timestamptz NOT NULL,\n PRIMARY KEY (form_id, id)\n )`\n // A form's examples, beside it (0166). Added on start like every table here, so a\n // database from before them gains the table and has none kept: every form starts with\n // no examples, which is what it had. The primary key is the form, so there is one list\n // per form and a write replaces it; the foreign key refuses a list for no form.\n await sql`\n CREATE TABLE IF NOT EXISTS form_examples (\n form_id uuid PRIMARY KEY REFERENCES forms(id),\n scenarios jsonb NOT NULL,\n sample jsonb,\n updated_at timestamptz NOT NULL\n )`\n // Immutability lives in the database, not in application discipline: a\n // published version row can never change, because every submission's audit\n // story depends on it staying exactly what the user saw.\n await sql`\n CREATE OR REPLACE FUNCTION refuse_version_update() RETURNS trigger AS $$\n BEGIN\n RAISE EXCEPTION 'form_versions rows are immutable; publish a new version instead';\n END\n $$ LANGUAGE plpgsql`\n await sql`DROP TRIGGER IF EXISTS form_versions_immutable ON form_versions`\n await sql`\n CREATE TRIGGER form_versions_immutable\n BEFORE UPDATE ON form_versions\n FOR EACH ROW EXECUTE FUNCTION refuse_version_update()`\n\n // Append-only, in the database rather than by application discipline — for\n // the same reason the version rows are immutable there. An audit log the\n // application can rewrite is a log that says whatever the person who got\n // into the application wants it to say, and the one moment it matters is\n // exactly the moment somebody has a reason to edit it.\n //\n // A deployment should ALSO give the application's role INSERT and SELECT and\n // nothing else. This trigger is the belt: the grant is the braces, and it is\n // the one a self-hoster has to do themselves.\n await sql`\n CREATE OR REPLACE FUNCTION refuse_audit_change() RETURNS trigger AS $$\n BEGIN\n RAISE EXCEPTION 'audit_log is append-only';\n END\n $$ LANGUAGE plpgsql`\n await sql`DROP TRIGGER IF EXISTS audit_log_append_only ON audit_log`\n await sql`\n CREATE TRIGGER audit_log_append_only\n BEFORE UPDATE OR DELETE ON audit_log\n FOR EACH ROW EXECUTE FUNCTION refuse_audit_change()`\n}\n","import { eq } from 'drizzle-orm'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport type { ExamplesRecord, Storage } from '@formancy/server-core'\nimport { auditLog, auditRow, formExamples } from './db.js'\n\n/**\n * A form's examples over PostgreSQL: one row per form in `form_examples`, replaced whole\n * (0166).\n *\n * Its own file because it changes for its own reason, and because `postgres-storage.ts`\n * would otherwise have crossed the size budget with it.\n */\nexport function examplesStorage(db: PostgresJsDatabase): Pick<Storage, 'getExamples' | 'keepExamples'> {\n return {\n async getExamples(formId) {\n const rows = await db.select().from(formExamples).where(eq(formExamples.formId, formId)).limit(1)\n const row = rows[0]\n if (row === undefined) return undefined\n return {\n formId: row.formId,\n scenarios: row.scenarios as ExamplesRecord['scenarios'],\n sample: row.sample as ExamplesRecord['sample'],\n updatedAt: row.updatedAt.toISOString(),\n }\n },\n\n async keepExamples(record, audit) {\n // ONE transaction, as a publish's: a change that rolled back leaves no row saying it\n // happened. The insert is the write whether or not a list was kept before, and the\n // foreign key refuses one for a form that is not there.\n await db.transaction(async (tx) => {\n const row = {\n formId: record.formId,\n scenarios: record.scenarios,\n sample: record.sample,\n updatedAt: new Date(record.updatedAt),\n }\n await tx\n .insert(formExamples)\n .values(row)\n .onConflictDoUpdate({\n target: formExamples.formId,\n set: { scenarios: row.scenarios, sample: row.sample, updatedAt: row.updatedAt },\n })\n if (audit !== undefined) await tx.insert(auditLog).values(auditRow(audit))\n })\n },\n }\n}\n","import { eq } from 'drizzle-orm'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport type { Role, Storage } from '@formancy/server-core'\nimport { apiKeys, fitsText, users } from './db.js'\n\n/**\n * Who may use the management plane, over PostgreSQL: users with a password and API keys, each\n * stored as a hash and looked up by what a request presents — an email, a key's prefix.\n *\n * Its own file because it changes for its own reason — the way somebody is let in, not the\n * forms and responses everything else here stores — and because `postgres-storage.ts` would\n * otherwise have crossed the size budget when its lookups learned to find nothing for a NUL\n * (0173).\n */\nexport function identitiesStorage(\n db: PostgresJsDatabase,\n): Pick<Storage, 'insertUser' | 'getUserByEmail' | 'insertApiKey' | 'listApiKeys' | 'findApiKeysByPrefix' | 'revokeApiKey'> {\n return {\n async insertUser(record) {\n await db.insert(users).values({\n id: record.id,\n email: record.email,\n passwordHash: record.passwordHash,\n role: record.role,\n createdAt: new Date(record.createdAt),\n })\n },\n\n async getUserByEmail(email) {\n // An address a text column cannot hold has no account, and asking is a 22021 (0173).\n if (!fitsText(email)) return undefined\n const rows = await db.select().from(users).where(eq(users.email, email)).limit(1)\n const row = rows[0]\n if (row === undefined) return undefined\n return {\n id: row.id,\n email: row.email,\n passwordHash: row.passwordHash,\n role: row.role as Role,\n createdAt: row.createdAt.toISOString(),\n }\n },\n\n async insertApiKey(record) {\n await db.insert(apiKeys).values({\n id: record.id,\n name: record.name,\n prefix: record.prefix,\n secretHash: record.secretHash,\n role: record.role,\n createdAt: new Date(record.createdAt),\n revokedAt: record.revokedAt === null ? null : new Date(record.revokedAt),\n })\n },\n\n async listApiKeys() {\n const rows = await db.select().from(apiKeys)\n return rows.map(toApiKeyRecord)\n },\n\n async findApiKeysByPrefix(prefix) {\n // From a header, as anybody wrote it.\n if (!fitsText(prefix)) return []\n const rows = await db.select().from(apiKeys).where(eq(apiKeys.prefix, prefix))\n return rows.map(toApiKeyRecord)\n },\n\n async revokeApiKey(id, atIso) {\n await db.update(apiKeys).set({ revokedAt: new Date(atIso) }).where(eq(apiKeys.id, id))\n },\n }\n}\n\n/** An API key's row as the port describes it: timestamps as ISO strings. */\nfunction toApiKeyRecord(row: typeof apiKeys.$inferSelect) {\n return {\n id: row.id,\n name: row.name,\n prefix: row.prefix,\n secretHash: row.secretHash,\n role: row.role as Role,\n createdAt: row.createdAt.toISOString(),\n revokedAt: row.revokedAt === null ? null : row.revokedAt.toISOString(),\n }\n}\n","import { and, desc, eq, inArray, isNull, lt, lte, max, ne, or } from 'drizzle-orm'\nimport { drizzle } from 'drizzle-orm/postgres-js'\nimport type postgres from 'postgres'\nimport type { FormSchema } from '@formancy/spec'\nimport type { AuditEntry, FileRecord, Storage } from '@formancy/server-core'\nimport { auditLog, auditRow, spentChallenges, deliveries, drafts, files, fitsText, forms, formVersions, isUuid, submissions, webhooks } from './db.js'\nimport { examplesStorage } from './postgres-examples.js'\nimport { identitiesStorage } from './postgres-identities.js'\n\n/** The Storage port over Postgres — the mirror of server-core's in-memory one. */\nexport function createPostgresStorage(sql: postgres.Sql): Storage {\n const db = drizzle(sql)\n\n return {\n // A form's examples, beside it: their own file, by the reason they change (0166).\n ...examplesStorage(db),\n // Who may sign in, and with what: users and API keys, their own file by theirs.\n ...identitiesStorage(db),\n\n async getFormByPath(path) {\n // A path from a URL: one a text column cannot hold is no form, not a 22021 (0173).\n if (!fitsText(path)) return undefined\n const rows = await db.select().from(forms).where(eq(forms.path, path)).limit(1)\n const row = rows[0]\n if (row === undefined) return undefined\n return {\n id: row.id,\n path: row.path,\n currentVersionId: row.currentVersionId,\n accessSubmit: row.accessSubmit === 'public' ? 'public' : 'authenticated',\n allowedOrigins: row.allowedOrigins,\n }\n },\n\n async listForms() {\n const rows = await db.select().from(forms)\n return rows.map((row) => ({\n id: row.id,\n path: row.path,\n currentVersionId: row.currentVersionId,\n accessSubmit: row.accessSubmit === 'public' ? ('public' as const) : ('authenticated' as const),\n allowedOrigins: row.allowedOrigins,\n }))\n },\n\n async createForm(record) {\n await db.insert(forms).values({\n id: record.id,\n path: record.path,\n currentVersionId: record.currentVersionId,\n accessSubmit: record.accessSubmit,\n allowedOrigins: record.allowedOrigins,\n })\n },\n\n async updateFormAccess(formId, access) {\n await db\n .update(forms)\n .set({ accessSubmit: access.accessSubmit, allowedOrigins: access.allowedOrigins })\n .where(eq(forms.id, formId))\n },\n\n async setCurrentVersion(formId, versionId) {\n await db.update(forms).set({ currentVersionId: versionId }).where(eq(forms.id, formId))\n },\n\n async insertVersion(record) {\n await db.insert(formVersions).values({\n id: record.id,\n formId: record.formId,\n version: record.version,\n schema: record.schema,\n schemaHash: record.schemaHash,\n })\n },\n\n async publishVersion({ form, version, audit }) {\n // ONE transaction. Three calls left a window where the form row existed\n // with a null pointer, which is a form that answers its URL and has no\n // schema to render — present in the list, 404 when opened.\n await db.transaction(async (tx) => {\n if (form !== undefined) {\n await tx.insert(forms).values({\n id: form.id,\n path: form.path,\n currentVersionId: form.currentVersionId,\n accessSubmit: form.accessSubmit,\n allowedOrigins: form.allowedOrigins,\n })\n }\n\n await tx.insert(formVersions).values({\n id: version.id,\n formId: version.formId,\n version: version.version,\n schema: version.schema,\n schemaHash: version.schemaHash,\n })\n\n const pointed = await tx\n .update(forms)\n .set({ currentVersionId: version.id })\n .where(eq(forms.id, version.formId))\n .returning({ id: forms.id })\n\n // An UPDATE that matched nothing is not an error in SQL, so it has to\n // be looked at: without this, publishing to a form that has been\n // deleted underneath would insert an orphan version and report success.\n if (pointed.length !== 1) {\n throw new Error(\n `Publishing to form ${version.formId} matched no form row. Rolling back, so there is no version pointing at a form that is not there.`,\n )\n }\n\n if (audit !== undefined) await tx.insert(auditLog).values(auditRow(audit))\n })\n },\n\n async getVersionById(id) {\n const rows = await db.select().from(formVersions).where(eq(formVersions.id, id)).limit(1)\n const row = rows[0]\n if (row === undefined) return undefined\n return {\n id: row.id,\n formId: row.formId,\n version: row.version,\n schema: row.schema as FormSchema,\n schemaHash: row.schemaHash,\n }\n },\n\n async findVersionByHash(formId, schemaHash) {\n const rows = await db\n .select()\n .from(formVersions)\n .where(eq(formVersions.formId, formId))\n const row = rows.find((candidate) => candidate.schemaHash === schemaHash)\n if (row === undefined) return undefined\n return {\n id: row.id,\n formId: row.formId,\n version: row.version,\n schema: row.schema as FormSchema,\n schemaHash: row.schemaHash,\n }\n },\n\n async latestVersionNumber(formId) {\n const rows = await db\n .select({ latest: max(formVersions.version) })\n .from(formVersions)\n .where(eq(formVersions.formId, formId))\n return rows[0]?.latest ?? 0\n },\n\n async insertSubmission(record, queued, claimFileIds, audit) {\n // ONE transaction. The submission and the deliveries it triggers commit\n // together or not at all — the requirement that chose this database\n // (docs/decisions/0024-postgres-over-mongodb.md).\n return db.transaction(async (tx) => {\n // First, and the answer to whether anything else is written. The id is the one the\n // response's token names, so a conflict on the primary key is that response sent\n // again (0169). `ON CONFLICT DO NOTHING` and look at what came back, as\n // `spendChallenge` does: a second send that arrives while the first is uncommitted\n // waits on the key, then finds it taken.\n const stored = await tx\n .insert(submissions)\n .values({\n id: record.id,\n formId: record.formId,\n formVersionId: record.formVersionId,\n data: record.data,\n submittedAt: new Date(record.submittedAt),\n })\n .onConflictDoNothing({ target: submissions.id })\n .returning({ id: submissions.id })\n if (stored.length === 0) return false\n\n if (queued !== undefined && queued.length > 0) {\n await tx.insert(deliveries).values(\n queued.map((delivery) => ({\n id: delivery.id,\n webhookId: delivery.webhookId,\n submissionId: delivery.submissionId,\n eventId: delivery.eventId,\n body: delivery.body,\n attempt: delivery.attempt,\n nextAttemptAt: new Date(delivery.nextAttemptAt),\n state: delivery.state,\n lastError: delivery.lastError,\n })),\n )\n }\n\n if (claimFileIds !== undefined && claimFileIds.length > 0) {\n // In the same transaction, and only from `stored`: two concurrent\n // submissions naming the same file both pass the check made outside\n // the transaction, and this is where the second one loses. The row\n // count is what says it lost — an UPDATE that matched nothing is not\n // an error in SQL, so it has to be looked at.\n const claimed = await tx\n .update(files)\n .set({ state: 'claimed', submissionId: record.id })\n .where(and(inArray(files.id, [...claimFileIds]), eq(files.state, 'stored')))\n .returning({ id: files.id })\n\n if (claimed.length !== claimFileIds.length) {\n throw new Error(\n 'A file this submission names was claimed by another one first. Rolling back, so the submission does not reference bytes it does not own.',\n )\n }\n }\n\n // Last, and inside: a submission that rolled back must leave nothing\n // behind saying it happened.\n if (audit !== undefined) await tx.insert(auditLog).values(auditRow(audit))\n return true\n })\n },\n\n async hasSubmission(id) {\n const found = await db.select({ id: submissions.id }).from(submissions).where(eq(submissions.id, id)).limit(1)\n return found.length === 1\n },\n\n async spendChallenge(challenge, expiresAtIso) {\n // `ON CONFLICT DO NOTHING` and look at what came back: the insert is the\n // claim, and the row count is the answer. Checking first and inserting\n // second would leave a window between the two.\n const claimed = await db\n .insert(spentChallenges)\n .values({ challenge, expiresAt: new Date(expiresAtIso) })\n .onConflictDoNothing()\n .returning({ challenge: spentChallenges.challenge })\n return claimed.length === 1\n },\n\n async forgetExpiredChallenges(beforeIso) {\n const gone = await db\n .delete(spentChallenges)\n .where(lt(spentChallenges.expiresAt, new Date(beforeIso)))\n .returning({ challenge: spentChallenges.challenge })\n return gone.length\n },\n\n async recordAudit(entry) {\n await db.insert(auditLog).values(auditRow(entry))\n },\n\n async listAudit(limit) {\n const rows = await db.select().from(auditLog).orderBy(desc(auditLog.at)).limit(limit)\n return rows.map((row) => ({\n id: row.id,\n at: row.at.toISOString(),\n action: row.action as AuditEntry['action'],\n ...(row.actorKind === null ? {} : { actorKind: row.actorKind as 'user' | 'apiKey' }),\n ...(row.actorId === null ? {} : { actorId: row.actorId }),\n ...(row.subject === null ? {} : { subject: row.subject }),\n ...(row.requestId === null ? {} : { requestId: row.requestId }),\n ...(row.detail === null\n ? {}\n : { detail: row.detail as NonNullable<AuditEntry['detail']> }),\n }))\n },\n\n async insertFile(record) {\n await db.insert(files).values({\n ...record,\n createdAt: new Date(record.createdAt),\n receivingUntil: record.receivingUntil === null ? null : new Date(record.receivingUntil),\n })\n },\n\n async getFile(id) {\n // An id from a URL or a response: one the column cannot hold is no file, not a 22P02 (0172).\n if (!isUuid(id)) return undefined\n const [row] = await db.select().from(files).where(eq(files.id, id)).limit(1)\n return row === undefined ? undefined : toFileRecord(row)\n },\n\n async leaseFile(id, nowIso, untilIso) {\n // One conditional UPDATE, and the row count is the answer, as in `spendChallenge`.\n // Two requests for one file both match it; the second waits on the row lock the\n // first holds, re-reads the row once that commits, finds it held and matches nothing.\n const free = or(isNull(files.receivingUntil), lte(files.receivingUntil, new Date(nowIso)))\n const leased = await db\n .update(files)\n .set({ receivingUntil: new Date(untilIso) })\n .where(and(eq(files.id, id), eq(files.state, 'offered'), free))\n .returning({ id: files.id })\n return leased.length === 1\n },\n\n async settleFile(id, untilIso) {\n // The state and the lease, never the submission: this is the write that used to put\n // a claimed file back to `stored`. Both conditions are in the UPDATE itself, so a\n // claim committed a moment ago is seen, not overwritten.\n const held = eq(files.receivingUntil, new Date(untilIso))\n const settled = await db\n .update(files)\n .set({ state: 'stored', receivingUntil: null })\n .where(and(eq(files.id, id), eq(files.state, 'offered'), held))\n .returning({ id: files.id })\n return settled.length === 1\n },\n\n async releaseFile(id, untilIso) {\n const released = await db\n .update(files)\n .set({ receivingUntil: null })\n .where(and(eq(files.id, id), eq(files.receivingUntil, new Date(untilIso))))\n .returning({ id: files.id })\n return released.length === 1\n },\n\n async abandonedFiles(beforeIso) {\n // A claimed file belongs to a submission and is never rubbish, however\n // old. Age is a reason to collect an unclaimed one, never a claimed one.\n const rows = await db\n .select()\n .from(files)\n .where(and(ne(files.state, 'claimed'), lt(files.createdAt, new Date(beforeIso))))\n return rows.map(toFileRecord)\n },\n\n async deleteFiles(ids) {\n if (ids.length === 0) return\n await db.delete(files).where(inArray(files.id, [...ids]))\n },\n\n async webhooksForForm(formId) {\n const rows = await db.select().from(webhooks).where(eq(webhooks.formId, formId))\n return rows.map(toWebhookRecord)\n },\n\n async insertWebhook(record) {\n // Spelled out rather than spread: `openedAt` is an ISO string on the\n // record and a timestamp in the column, and a spread would have passed\n // the string straight through.\n await db.insert(webhooks).values({\n id: record.id,\n formId: record.formId,\n url: record.url,\n secret: record.secret,\n consecutiveFailures: record.consecutiveFailures,\n openedAt: record.openedAt === null ? null : new Date(record.openedAt),\n })\n },\n\n async updateWebhook(record) {\n // Health only. The url and the secret are not the worker's to change,\n // and a full row update here would let a failed delivery quietly\n // rewrite where the next one goes.\n await db\n .update(webhooks)\n .set({\n consecutiveFailures: record.consecutiveFailures,\n openedAt: record.openedAt === null ? null : new Date(record.openedAt),\n })\n .where(eq(webhooks.id, record.id))\n },\n\n async listWebhooks() {\n const rows = await db.select().from(webhooks)\n return rows.map(toWebhookRecord)\n },\n\n async deadDeliveries(limit) {\n const rows = await db\n .select()\n .from(deliveries)\n .where(eq(deliveries.state, 'dead'))\n .orderBy(desc(deliveries.nextAttemptAt))\n .limit(limit)\n return rows.map(toDeliveryRecord)\n },\n\n async getDelivery(id) {\n if (!isUuid(id)) return undefined\n const [row] = await db.select().from(deliveries).where(eq(deliveries.id, id)).limit(1)\n return row === undefined ? undefined : toDeliveryRecord(row)\n },\n\n async claimDueDeliveries(nowIso, limit) {\n const rows = await db\n .select()\n .from(deliveries)\n .where(and(eq(deliveries.state, 'pending'), lte(deliveries.nextAttemptAt, new Date(nowIso))))\n .orderBy(deliveries.nextAttemptAt)\n .limit(limit)\n return rows.map(toDeliveryRecord)\n },\n\n async updateDelivery(record) {\n await db\n .update(deliveries)\n .set({\n attempt: record.attempt,\n nextAttemptAt: new Date(record.nextAttemptAt),\n state: record.state,\n lastError: record.lastError,\n })\n .where(eq(deliveries.id, record.id))\n },\n\n async listSubmissions() {\n const rows = await db.select().from(submissions)\n return rows.map(toSubmissionRecord)\n },\n\n async listSubmissionsByForm(formId) {\n const rows = await db\n .select()\n .from(submissions)\n .where(eq(submissions.formId, formId))\n .orderBy(desc(submissions.submittedAt), desc(submissions.id))\n return rows.map(toSubmissionRecord)\n },\n\n async listVersionsByForm(formId) {\n const rows = await db\n .select()\n .from(formVersions)\n .where(eq(formVersions.formId, formId))\n .orderBy(desc(formVersions.version))\n return rows.map((row) => ({\n id: row.id,\n formId: row.formId,\n version: row.version,\n schema: row.schema as FormSchema,\n schemaHash: row.schemaHash,\n }))\n },\n\n async upsertDraft(record) {\n await db\n .insert(drafts)\n .values({\n id: record.id,\n formId: record.formId,\n formVersionId: record.formVersionId,\n data: record.data,\n updatedAt: new Date(record.updatedAt),\n })\n .onConflictDoUpdate({\n target: [drafts.formId, drafts.id],\n set: {\n formVersionId: record.formVersionId,\n data: record.data,\n updatedAt: new Date(record.updatedAt),\n },\n })\n },\n\n async getDraft(formId, draftId) {\n if (!fitsText(draftId)) return undefined\n const rows = await db\n .select()\n .from(drafts)\n .where(and(eq(drafts.formId, formId), eq(drafts.id, draftId)))\n .limit(1)\n const row = rows[0]\n if (row === undefined) return undefined\n return {\n id: row.id,\n formId: row.formId,\n formVersionId: row.formVersionId,\n data: row.data,\n updatedAt: row.updatedAt.toISOString(),\n }\n },\n }\n}\n\nfunction toSubmissionRecord(row: {\n id: string\n formId: string\n formVersionId: string\n data: unknown\n submittedAt: Date\n}) {\n return {\n id: row.id,\n formId: row.formId,\n formVersionId: row.formVersionId,\n data: row.data,\n submittedAt: row.submittedAt.toISOString(),\n }\n}\n\n/** A delivery row as the port describes it. */\nfunction toDeliveryRecord(row: typeof deliveries.$inferSelect) {\n return {\n id: row.id,\n webhookId: row.webhookId,\n submissionId: row.submissionId,\n eventId: row.eventId,\n body: row.body,\n attempt: row.attempt,\n nextAttemptAt: row.nextAttemptAt.toISOString(),\n state: row.state as 'pending' | 'delivered' | 'dead',\n lastError: row.lastError,\n }\n}\n\n/** A files row as the port describes it: timestamps as ISO strings. */\nfunction toFileRecord(row: typeof files.$inferSelect): FileRecord {\n return {\n id: row.id,\n formId: row.formId,\n name: row.name,\n size: row.size,\n contentType: row.contentType,\n storageKey: row.storageKey,\n state: row.state as FileRecord['state'],\n createdAt: row.createdAt.toISOString(),\n submissionId: row.submissionId,\n receivingUntil: row.receivingUntil === null ? null : row.receivingUntil.toISOString(),\n }\n}\n\n/** A webhook row as the port describes it, health included. */\nfunction toWebhookRecord(row: typeof webhooks.$inferSelect) {\n return {\n id: row.id,\n formId: row.formId,\n url: row.url,\n secret: row.secret,\n consecutiveFailures: row.consecutiveFailures,\n openedAt: row.openedAt === null ? null : row.openedAt.toISOString(),\n }\n}\n","import postgres from 'postgres'\nimport type { FastifyBaseLogger } from 'fastify'\nimport type { FastifyRateLimitStore, FastifyRateLimitStoreCtor } from '@fastify/rate-limit'\nimport { databaseNotices } from './server-log.js'\n\n/**\n * How long a request waits for its count before its limit decides without one (0170).\n *\n * Far above what a count takes when the database is well — the record has the measurement —\n * and short enough that a database which has stopped answering costs each limited request a\n * second rather than the thirty the driver waits to connect. The database ends a count at the\n * same bound, so one abandoned there does not go on holding its connection.\n */\nexport const COUNT_TIMEOUT_MS = 1_000\n\n/**\n * The connections the counter has of its own (0170).\n *\n * Its own, because a count waiting for a lock held a connection that storage queried on, and\n * the queries sent after it on that connection waited for the lock too: with the table locked,\n * a few more limited requests than storage had connections stopped every route, unlimited ones\n * included. A few, because each is one more against the database's own limit on connections.\n */\nexport const COUNTER_CONNECTIONS = 4\n\n/**\n * The most counts sent and not yet answered: eight pipelined on each connection.\n *\n * Measured (0170): one a connection counted a third to a half as many a second as storage's ten\n * connections had, and eight a connection more. A count past this waits in the process, where\n * one abandoned at the bound is dropped unsent — so no more than this can land late.\n */\nexport const COUNTS_AT_ONCE = 8 * COUNTER_CONNECTIONS\n\n/** How often, at most, a count also deletes the counters whose window has ended. */\nexport const SWEEP_EVERY_MS = 10 * 60_000\n\nexport interface PostgresRateLimitOptions {\n /** {@link COUNT_TIMEOUT_MS} unless a test needs a slow database to be slow sooner. */\n readonly timeoutMs?: number\n /** {@link SWEEP_EVERY_MS} unless a test needs to see a sweep. */\n readonly sweepEveryMs?: number\n /**\n * The server's log (0168), where the counter says that it stopped answering, that it answers\n * again and that a sweep failed — each an event from `LOG_EVENTS`, by what was thrown and never\n * its words, which name the database's address — and where its connections' notices go.\n * Without one it says nothing, as `createApp` without one keeps no log: a host embedding both\n * decides.\n */\n readonly log?: FastifyBaseLogger\n}\n\n/** The counter: a store for `createApp`, and its connections, for whoever made it to end. */\nexport interface PostgresRateLimits {\n /** `@fastify/rate-limit`'s store, which `createApp` takes as `rateLimitStore`. */\n readonly store: FastifyRateLimitStoreCtor\n /** Closes the counter's connections, once the app that counts in them is closed. */\n readonly end: () => Promise<void>\n}\n\n/**\n * `@fastify/rate-limit`'s store over the PostgreSQL every replica already shares (0170).\n *\n * The plugin's own store keeps its counts in the process, so behind N replicas each limit\n * allowed N times what it said. This one keeps them in `rate_limit_counters`, one row per\n * route and client, and counts with one statement: the upsert takes the row's lock, so two\n * replicas counting the same client at once are counted one after the other and neither\n * increment is lost.\n *\n * A window starts at a key's first request and lasts the route's `timeWindow`, as the plugin's\n * own stores do it, on the database's clock — one clock for every replica, where each\n * process's own would end the window at a different moment on each.\n *\n * It opens its own connections from the database's address rather than taking a pool, because\n * which connections it counts on is what keeps a counter that cannot answer from stopping\n * everything else: handed the pool storage queries on, it would count correctly and bring that\n * outage back. A count that does not come back within the bound is abandoned: one still waiting\n * here to be sent is dropped unsent, and one already sent is ended by the database once it runs,\n * by the `statement_timeout` of these connections, with no cancel request — a connection more —\n * needed for it. The error the plugin is handed says nothing about the database, because a route\n * that refuses without a count sends it to whoever asked; what each route does then is its own\n * declaration (`rate-limits.ts`).\n *\n * The plugin's in-memory store is the second implementation of the same interface, and what\n * `createApp` counts in when it is given none.\n */\nexport function createPostgresRateLimitStore(\n databaseUrl: string,\n options: PostgresRateLimitOptions = {},\n): PostgresRateLimits {\n const timeoutMs = options.timeoutMs ?? COUNT_TIMEOUT_MS\n const sweepEveryMs = options.sweepEveryMs ?? SWEEP_EVERY_MS\n const log = options.log\n const sql = postgres(databaseUrl, {\n max: COUNTER_CONNECTIONS,\n connection: { statement_timeout: timeoutMs },\n // By their code, as storage's are, rather than printed whole by the driver.\n onnotice: databaseNotices(log),\n })\n\n // Whether the last count was answered: said when it changes, not once a request.\n let answering = true\n let sweptAt = Date.now()\n let sweeping = false\n\n // Statements sent and not yet answered, never more than COUNTS_AT_ONCE: past that the driver\n // would queue them itself, and send each in turn whether anybody still waited for it or not.\n // The rest wait here, in order.\n let running = 0\n const waiting: Array<() => void> = []\n\n function done(): void {\n running--\n waiting.shift()?.()\n }\n\n function count(key: string, windowMs: number): Promise<{ current: number; ttl: number }> {\n return new Promise((resolve, reject) => {\n const send = (): void => {\n running++\n sql<Array<{ current: number; ttl: number }>>`\n INSERT INTO rate_limit_counters AS counter (key, hits, resets_at)\n VALUES (${key}, 1, now() + ${windowMs}::double precision * interval '1 millisecond')\n ON CONFLICT (key) DO UPDATE SET\n hits = CASE WHEN counter.resets_at <= now() THEN 1 ELSE counter.hits + 1 END,\n resets_at = CASE WHEN counter.resets_at <= now() THEN excluded.resets_at ELSE counter.resets_at END\n RETURNING hits AS current, ceil(extract(epoch FROM resets_at - now()) * 1000)::integer AS ttl`\n .then(\n ([row]) => {\n clearTimeout(timer)\n if (row === undefined) reject(new Error('the count returned no row'))\n else resolve(row)\n },\n (error: unknown) => {\n clearTimeout(timer)\n reject(error)\n },\n )\n .finally(done)\n }\n const timer = setTimeout(() => {\n const queued = waiting.indexOf(send)\n if (queued !== -1) waiting.splice(queued, 1)\n reject(late())\n }, timeoutMs)\n if (running < COUNTS_AT_ONCE) send()\n else waiting.push(send)\n })\n }\n\n /** On the back of a count, at most once per interval, one at a time, and never waited for. */\n function sweep(): void {\n const now = Date.now()\n if (sweeping || running >= COUNTS_AT_ONCE || now - sweptAt < sweepEveryMs) return\n sweptAt = now\n sweeping = true\n running++\n sql\n .begin(async (tx) => {\n // However long it takes: a table grown past what one bound deletes — a flood from many\n // addresses — would otherwise fail every sweep and never shrink.\n await tx`SELECT set_config('statement_timeout', '0', true)`\n await tx`DELETE FROM rate_limit_counters WHERE resets_at <= now()`\n })\n .then(\n () => {},\n (error: unknown) => log?.error({ event: 'ratelimit.sweep.failed', err: error }),\n )\n .finally(() => {\n sweeping = false\n done()\n })\n }\n\n class PostgresRateLimitStore implements FastifyRateLimitStore {\n readonly #route: string\n\n constructor(_options?: unknown, route = '') {\n this.#route = route\n }\n\n incr(\n key: string,\n callback: (error: Error | null, result?: { current: number; ttl: number }) => void,\n timeWindow: number,\n ): void {\n count(`${this.#route}${key}`, timeWindow).then(\n (result) => {\n if (!answering) {\n answering = true\n // At the level of the line it ends: below it, a log kept for errors would show a\n // counter that stopped and never one that came back.\n log?.error({ event: 'ratelimit.answering' })\n }\n callback(null, result)\n },\n (error: unknown) => {\n if (answering) {\n answering = false\n // Until it answers, each limit admits uncounted or refuses with a 503, as it declares.\n log?.error({ event: 'ratelimit.unanswered', err: error })\n }\n callback(uncounted())\n },\n )\n sweep()\n }\n\n child(routeOptions: Parameters<FastifyRateLimitStore['child']>[0]): FastifyRateLimitStore {\n // The plugin hands a route over as `routeInfo` (its index.js, and its own Redis store\n // reads it there); its type definitions say otherwise, so it is read as it arrives. The\n // route is part of the key, or every limit an address has would be one budget.\n const { method, url } = (routeOptions as unknown as { routeInfo: { method: string | string[]; url: string } })\n .routeInfo\n return new PostgresRateLimitStore(undefined, `${[method].flat().join(',')} ${url} `)\n }\n }\n\n // Whatever is still on the database by then is a count nobody waits for, or a sweep the next\n // start does again; neither is worth holding a shutdown past the bound.\n return { store: PostgresRateLimitStore, end: () => sql.end({ timeout: timeoutMs / 1_000 }) }\n}\n\n/** What a route that refuses without a count answers: a 503, and nothing about the database. */\nfunction uncounted(): Error {\n return Object.assign(\n new Error('This request could not be counted against its limit, so it was not taken. Try again in a moment.'),\n { statusCode: 503, code: 'RATE_LIMIT_UNAVAILABLE' },\n )\n}\n\n/**\n * A count not answered within the bound. Its own code, because the log writes an error's kind\n * and code and nothing else: without one, a database too slow to count — a lock on the table, a\n * flood — reads the same as any other failure, and the operator cannot tell which to look for.\n */\nfunction late(): Error {\n return Object.assign(new Error('The count was not answered within its bound.'), { code: 'RATE_LIMIT_TIMEOUT' })\n}\n","import { createHash } from 'node:crypto'\nimport { createReadStream } from 'node:fs'\nimport { mkdir, rm, stat, writeFile } from 'node:fs/promises'\nimport { dirname, join, resolve, sep } from 'node:path'\nimport type { Readable } from 'node:stream'\n\n/**\n * Where uploaded bytes live.\n *\n * An interface with one implementation, because the second one is the point:\n * S3 or a customer's own service replaces this file and nothing else. The\n * use-cases in `@formancy/server-core` never see it at all — they decide what\n * is rubbish, and the host deletes it.\n *\n * Local disk is the default because `docker compose up` has to produce\n * something that works, and a self-hoster with one container and one volume is\n * the deployment this product is for. It is genuinely the weakest option: it\n * does not survive more than one replica, and the section below says so rather\n * than leaving somebody to find out.\n */\nexport interface FileStore {\n /** Write the bytes. The key was minted by the server, never by a client. */\n put(key: string, bytes: Buffer): Promise<void>\n /** Read them back, as a stream, so a large file is not held in memory. */\n open(key: string): Promise<Readable | undefined>\n /** Remove them. Missing is success: a collector must be safe to re-run. */\n remove(key: string): Promise<void>\n /** The size on disk, for checking the bytes match what was promised. */\n sizeOf(key: string): Promise<number | undefined>\n}\n\n/**\n * Bytes under one directory, one file per key.\n *\n * **Keys are checked, not trusted.** A key is minted by the server from a form\n * id and a file id, so it cannot contain a traversal — but this resolves it\n * and refuses anything landing outside the root regardless. A path check that\n * only runs when the input is untrusted is a path check that stops running the\n * day somebody adds a caller.\n */\nexport function createLocalFileStore(root: string): FileStore {\n const base = resolve(root)\n\n const pathFor = (key: string): string => {\n // Keys use forward slashes on every platform. Reject Windows separators\n // before native path resolution: POSIX treats them as filename characters.\n if (key.includes('\\\\')) {\n throw new Error(`Refusing a storage key that could resolve outside the store: ${key}`)\n }\n const full = resolve(join(base, key))\n // `startsWith(base)` alone matches `/data/formancy-evil` against\n // `/data/formancy`, so the separator is part of the test.\n if (full !== base && !full.startsWith(base + sep)) {\n throw new Error(`Refusing a storage key that resolves outside the store: ${key}`)\n }\n return full\n }\n\n return {\n async put(key, bytes) {\n const full = pathFor(key)\n await mkdir(dirname(full), { recursive: true })\n await writeFile(full, bytes)\n },\n\n async open(key) {\n const full = pathFor(key)\n try {\n await stat(full)\n } catch {\n return undefined\n }\n return createReadStream(full)\n },\n\n async remove(key) {\n // `force` so a collector that ran twice, or crashed after deleting the\n // bytes and before deleting the row, is not an error the second time.\n await rm(pathFor(key), { force: true })\n },\n\n async sizeOf(key) {\n try {\n return (await stat(pathFor(key))).size\n } catch {\n return undefined\n }\n },\n }\n}\n\n/**\n * The checksum a receiver can compare against.\n *\n * Returned when the bytes are stored and never asked of the client: a checksum\n * the uploader computed is a checksum the uploader can lie about, so this one\n * is only useful for telling a *later* reader that the bytes have not changed.\n */\nexport function sha256(bytes: Buffer): string {\n return createHash('sha256').update(bytes).digest('hex')\n}\n","import { collectAbandonedFiles } from '@formancy/server-core'\nimport type { Storage } from '@formancy/server-core'\nimport type { FileStore } from './file-store.js'\nimport { workerLog } from './server-log.js'\nimport type { WorkerLog } from './server-log.js'\n\n/**\n * Deleting files nobody claimed.\n *\n * Somebody who attaches a file and then closes the tab leaves bytes behind.\n * Without this they stay forever, and a disk that fills up for a reason nobody\n * is watching is the most tedious kind of outage.\n *\n * **Bytes first, then the row.** A row with no bytes is a broken reference and\n * shows up the moment anybody tries to download it. Bytes with no row are\n * invisible: nothing will ever look for them again, and the disk fills up\n * silently. Of the two ways to be interrupted halfway, the first is the one\n * you find out about.\n *\n * The same shape as the outbox worker, for the same reasons: a timer in the\n * server process rather than a queue library, one pass at a time, and a\n * thrown pass complains and comes back rather than stopping the timer. It is\n * likewise not a distributed job — `abandonedFiles` takes no lock, so two\n * replicas would both try to delete the same file. That is harmless here,\n * unlike a webhook sent twice, because deleting a file twice is deleting it\n * once.\n */\nexport interface CollectorHandle {\n stop: () => void\n}\n\nexport function startFileCollector(\n storage: Storage,\n store: FileStore,\n options: { intervalMs?: number; afterHours?: number; log?: WorkerLog } = {},\n): CollectorHandle {\n const interval = options.intervalMs ?? 15 * 60_000\n const log = workerLog(options.log)\n const afterHours = options.afterHours ?? 24\n let stopped = false\n let running = false\n\n const tick = async (): Promise<void> => {\n if (running || stopped) return\n running = true\n try {\n const abandoned = await collectAbandonedFiles(storage, new Date(), afterHours)\n if (abandoned.length === 0) return\n\n const removed: string[] = []\n for (const file of abandoned) {\n // One at a time, and the row only once its bytes are gone. A failure\n // partway leaves the rest for the next pass rather than losing track\n // of what was already done.\n await store.remove(file.storageKey)\n removed.push(file.id)\n }\n await storage.deleteFiles(removed)\n } catch (error) {\n // A collector that throws is a collector that stops, and a disk that\n // fills up. Complain and come back, without what was thrown (C3).\n log.error({ event: 'collector.failed', err: error })\n } finally {\n running = false\n }\n }\n\n const timer = setInterval(() => void tick(), interval)\n timer.unref?.()\n\n return {\n stop: () => {\n stopped = true\n clearInterval(timer)\n },\n }\n}\n","/**\n * Which model the environment names for the builders: none, or a provider, its key and\n * the model.\n *\n * A function of the environment rather than lines in `main.ts`, as `fileStoreSettings` is:\n * the rules are where a deployment goes wrong, and `main.ts` is a composition root no test\n * imports. Constructing the adapter stays in `main.ts`; this only says which\n * ([0165](../../../docs/decisions/0165-a-deployments-model-is-asked-through-its-server.md)).\n */\nexport const MODEL_PROVIDERS = ['anthropic', 'openai', 'xai'] as const\n\n/** Anthropic's Claude, OpenAI's models, or xAI's Grok — each through its own key. */\nexport type ModelProvider = (typeof MODEL_PROVIDERS)[number]\n\nexport type ModelSettings =\n | { readonly kind: 'none' }\n | {\n readonly kind: 'configured'\n readonly provider: ModelProvider\n readonly apiKey: string\n readonly model: string\n }\n\ntype Environment = Readonly<Record<string, string | undefined>>\n\n/**\n * The variable each provider's SDK reads for headers of its own to add to every request.\n *\n * The adapters name the base URL, the organisation, the project, the credential and the log\n * level rather than leave them to the environment; these headers no option undoes. So a\n * server with a model refuses to start while the variable its provider's SDK reads is set.\n * xAI is asked through OpenAI's client, so it is OpenAI's variable.\n */\nconst SDK_HEADERS_VARIABLE: Readonly<Record<ModelProvider, string>> = {\n anthropic: 'ANTHROPIC_CUSTOM_HEADERS',\n openai: 'OPENAI_CUSTOM_HEADERS',\n xai: 'OPENAI_CUSTOM_HEADERS',\n}\n\n/**\n * Absent — all three unset or blank — is a supported state, and the default: this\n * deployment has no model, the server says so to the admin, and no prompt pane is drawn.\n *\n * Throws on anything in between, so the server refuses to start rather than starting\n * without the model the operator thinks they configured: a key or a model with no\n * provider, a provider formancy has no adapter for, or a provider without its key or\n * its model. Throws too when the environment would add headers to every request the\n * provider's SDK sends. **No provider has a default model.** A model id goes stale when\n * the provider retires it, and then every turn fails as a model that could not be\n * reached, after an upgrade nobody made; and which model runs is what the operator pays\n * for. Neither is this file's to decide. The message never repeats a value, because the\n * key is one.\n */\nexport function modelSettings(env: Environment): ModelSettings {\n const provider = setting(env, 'FORMANCY_MODEL_PROVIDER')\n const apiKey = setting(env, 'FORMANCY_MODEL_API_KEY')\n const model = setting(env, 'FORMANCY_MODEL')\n\n if (provider === undefined) {\n if (apiKey === undefined && model === undefined) return { kind: 'none' }\n throw new Error(\n 'FORMANCY_MODEL_API_KEY or FORMANCY_MODEL is set without FORMANCY_MODEL_PROVIDER: name the ' +\n 'provider (anthropic, openai or xai), or unset both to run without a model.',\n )\n }\n\n if (!isProvider(provider)) {\n throw new Error('FORMANCY_MODEL_PROVIDER must be anthropic, openai or xai.')\n }\n if (apiKey === undefined) {\n throw new Error('FORMANCY_MODEL_API_KEY is required when FORMANCY_MODEL_PROVIDER is set.')\n }\n if (model === undefined) {\n throw new Error(\n 'FORMANCY_MODEL is required when FORMANCY_MODEL_PROVIDER is set: name the model as the ' +\n \"provider's documentation does. There is no default.\",\n )\n }\n const headers = SDK_HEADERS_VARIABLE[provider]\n if (setting(env, headers) !== undefined) {\n throw new Error(\n `${headers} is set, and the SDK formancy asks ${provider} through would send each line of it ` +\n 'as a header with every form. Unset it to run with a model.',\n )\n }\n return { kind: 'configured', provider, apiKey, model }\n}\n\nfunction isProvider(value: string): value is ModelProvider {\n return (MODEL_PROVIDERS as readonly string[]).includes(value)\n}\n\n/** A setting, with the empty string read as unset — compose blanks a variable, it cannot drop one. */\nfunction setting(env: Environment, name: string): string | undefined {\n const value = env[name]\n return value === undefined || value.trim() === '' ? undefined : value\n}\n","import Anthropic, { APIError } from '@anthropic-ai/sdk'\nimport type { Message } from '@anthropic-ai/sdk/resources/messages'\nimport type { Cancellation, Completer, Completion, CompletionPrompt } from '@formancy/server-core'\n\n/**\n * Where the request goes. Named rather than left to the SDK, which would otherwise take\n * `ANTHROPIC_BASE_URL` from the environment: the deployment view says which host a form is\n * sent to, and an ambient variable could change it without anybody reading this file.\n */\nexport const ANTHROPIC_BASE_URL = 'https://api.anthropic.com'\n\n/**\n * The most a turn may write, thinking included.\n *\n * Generous on purpose: an answer is a whole form document, and one cut off at the limit is\n * not an answer at all (`truncated`), so a limit too low wastes the turn it stopped.\n * Streamed, so a long answer is not cut off by the HTTP timeout either. It is the ceiling\n * on what one turn can cost — the operator pays per token written, not per token allowed.\n */\nexport const ANTHROPIC_MAX_OUTPUT_TOKENS = 64_000\n\n/** What a refused turn says when Anthropic gave no explanation. */\nconst DECLINED = 'The model service declined this request.'\n\nexport interface AnthropicCompleterOptions {\n readonly apiKey: string\n /** As Anthropic names it — `claude-opus-5`. Never defaulted (`model-settings.ts`). */\n readonly model: string\n /** The transport, for the tests; the platform's own `fetch` otherwise. */\n readonly fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>\n}\n\n/**\n * Claude, through Anthropic's official SDK (0165).\n *\n * One system prompt and one user message, with adaptive thinking — the model decides how\n * much to think — streamed and read with the SDK's final-message helper. **The stop reason\n * is read before the content.** A refusal ends the person's run as a decline, with\n * Anthropic's explanation; an answer stopped at the output limit, or by the context\n * window, is `truncated` and never handed over as the first half of a document. Thinking\n * blocks are not part of the answer.\n *\n * Adaptive thinking needs a model that has it — Claude Opus 4.6, Sonnet 4.6 and later. A\n * model without it is refused by Anthropic with a 400, and every turn ends `unavailable`.\n *\n * The SDK retries what it calls temporary failures (a 429, a 5xx, a dropped connection)\n * twice, by its own default.\n */\nexport function createAnthropicCompleter(options: AnthropicCompleterOptions): Completer {\n const client = new Anthropic({\n apiKey: options.apiKey,\n // The SDK would also send ANTHROPIC_AUTH_TOKEN, as a second credential beside the key,\n // and with ANTHROPIC_LOG=debug write every request — the whole form — to the console:\n // a second request log, outside the rule the server's own is built by\n // (SAFETY-ANALYSIS C3). Neither is the\n // environment's to switch on. ANTHROPIC_CUSTOM_HEADERS cannot be undone here, so\n // `modelSettings` refuses to start with it.\n authToken: null,\n logLevel: 'off',\n baseURL: ANTHROPIC_BASE_URL,\n ...(options.fetch === undefined ? {} : { fetch: options.fetch }),\n })\n\n return {\n async complete(prompt: CompletionPrompt, cancellation: Cancellation): Promise<Completion> {\n const controller = new AbortController()\n cancellation.onCancel(() => controller.abort())\n if (controller.signal.aborted) return { ok: false, failure: 'cancelled' }\n\n let message: Message\n try {\n message = await client.messages\n .stream(\n {\n model: options.model,\n max_tokens: ANTHROPIC_MAX_OUTPUT_TOKENS,\n thinking: { type: 'adaptive' },\n system: prompt.system,\n messages: [{ role: 'user', content: prompt.user }],\n },\n { signal: controller.signal },\n )\n .finalMessage()\n } catch (error) {\n if (controller.signal.aborted) return { ok: false, failure: 'cancelled' }\n return unavailable(error)\n }\n return read(message)\n },\n }\n}\n\n/** A finished message, by its stop reason first. */\nfunction read(message: Message): Completion {\n switch (message.stop_reason) {\n case 'refusal': {\n const explanation = message.stop_details?.explanation\n return {\n ok: false,\n failure: 'refused',\n reason: typeof explanation === 'string' && explanation.trim() !== '' ? explanation : DECLINED,\n }\n }\n case 'max_tokens':\n case 'model_context_window_exceeded':\n return { ok: false, failure: 'truncated' }\n case 'end_turn':\n case 'stop_sequence': {\n const text = message.content.flatMap((block) => (block.type === 'text' ? [block.text] : [])).join('')\n return { ok: true, text }\n }\n default:\n // `tool_use` and `pause_turn` need tools this request does not offer: whatever it\n // is, it is not a finished answer.\n return { ok: false, failure: 'unavailable', cause: `Anthropic stopped for ${String(message.stop_reason)}.` }\n }\n}\n\n/** An error from the SDK, as a failure: the status when Anthropic answered, the message for the log. */\nfunction unavailable(error: unknown): Completion {\n const status = error instanceof APIError && typeof error.status === 'number' ? error.status : undefined\n const cause = error instanceof Error ? error.message : String(error)\n return { ok: false, failure: 'unavailable', ...(status === undefined ? {} : { status }), cause }\n}\n","import OpenAI, { APIError } from 'openai'\nimport type { Response as ModelResponse } from 'openai/resources/responses/responses'\nimport type { Cancellation, Completer, Completion, CompletionPrompt } from '@formancy/server-core'\n\n/**\n * Where each provider's requests go. Named rather than left to the SDK, which would take\n * `OPENAI_BASE_URL` from the environment and send every form wherever it pointed.\n */\nexport const OPENAI_BASE_URL = 'https://api.openai.com/v1'\n\n/**\n * xAI's API, which xAI documents as compatible with OpenAI's SDKs and their Responses API at\n * this base URL. Taken from xAI's documentation as a web search summarised it on\n * 2026-10-10; the pages themselves refused the fetch, and no test here reaches xAI (0165).\n */\nexport const XAI_BASE_URL = 'https://api.x.ai/v1'\n\n/**\n * The most a turn may write, reasoning included — as generous as Anthropic's, and for the\n * same reason: an answer cut off at the limit is not an answer (`truncated`).\n */\nexport const OPENAI_MAX_OUTPUT_TOKENS = 64_000\n\n/** What a withheld answer says: the provider gives no words of its own for a filter. */\nconst FILTERED = 'The model service withheld its answer to this request.'\n\nexport interface OpenAiCompleterOptions {\n readonly apiKey: string\n /** As the provider names it. Never defaulted (`model-settings.ts`). */\n readonly model: string\n /** `OPENAI_BASE_URL` or `XAI_BASE_URL`: the provider, as far as this adapter is concerned. */\n readonly baseURL: string\n /** The transport, for the tests; the platform's own `fetch` otherwise. */\n readonly fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>\n}\n\n/**\n * OpenAI's models, or xAI's Grok, through OpenAI's official SDK and the Responses API (0165).\n *\n * The briefing as a system message and the request as a user message, streamed and read\n * with the SDK's final-response helper. `store: false`, so the provider keeps no copy of\n * the form for later retrieval; what it keeps under its own terms is not this adapter's\n * to say. **The response's status is read before its text.** Incomplete at the output\n * limit is `truncated`, never half a document; withheld by the provider's filter, or a\n * refusal in the output, ends the person's run as a decline.\n *\n * The SDK would take an organisation and a project from the environment and send them as\n * headers — to xAI as well — so both are set to nothing here. It would take a log level\n * too, and with OPENAI_LOG=debug write a line for every request and response to the\n * console — a second request log, outside the rule the server's own is built by\n * (SAFETY-ANALYSIS C3) — so the level is set. OPENAI_CUSTOM_HEADERS cannot be undone here, so `modelSettings` refuses to start\n * with it.\n */\nexport function createOpenAiCompleter(options: OpenAiCompleterOptions): Completer {\n const client = new OpenAI({\n apiKey: options.apiKey,\n baseURL: options.baseURL,\n organization: null,\n project: null,\n logLevel: 'off',\n ...(options.fetch === undefined ? {} : { fetch: options.fetch }),\n })\n\n return {\n async complete(prompt: CompletionPrompt, cancellation: Cancellation): Promise<Completion> {\n const controller = new AbortController()\n cancellation.onCancel(() => controller.abort())\n if (controller.signal.aborted) return { ok: false, failure: 'cancelled' }\n\n let response: ModelResponse\n try {\n response = await client.responses\n .stream(\n {\n model: options.model,\n input: [\n { role: 'system', content: prompt.system },\n { role: 'user', content: prompt.user },\n ],\n max_output_tokens: OPENAI_MAX_OUTPUT_TOKENS,\n store: false,\n },\n { signal: controller.signal },\n )\n .finalResponse()\n } catch (error) {\n if (controller.signal.aborted) return { ok: false, failure: 'cancelled' }\n return unavailable(error)\n }\n return read(response)\n },\n }\n}\n\n/** A finished response, by its status first. */\nfunction read(response: ModelResponse): Completion {\n if (response.status === 'incomplete') {\n const reason = response.incomplete_details?.reason\n if (reason === 'max_output_tokens') return { ok: false, failure: 'truncated' }\n if (reason === 'content_filter') return { ok: false, failure: 'refused', reason: FILTERED }\n return { ok: false, failure: 'unavailable', cause: `The response was incomplete: ${String(reason)}.` }\n }\n if (response.status !== 'completed') {\n const said = response.error?.message\n const cause = `The response ended ${String(response.status)}${said === undefined ? '' : `: ${said}`}.`\n return { ok: false, failure: 'unavailable', cause }\n }\n\n const parts = response.output.flatMap((item) => (item.type === 'message' ? item.content : []))\n const refusal = parts.find((part) => part.type === 'refusal')\n if (refusal !== undefined) {\n return { ok: false, failure: 'refused', reason: refusal.refusal.trim() === '' ? FILTERED : refusal.refusal }\n }\n return { ok: true, text: parts.flatMap((part) => (part.type === 'output_text' ? [part.text] : [])).join('') }\n}\n\n/** An error from the SDK, as a failure: the status when the provider answered, the message for the log. */\nfunction unavailable(error: unknown): Completion {\n const status = error instanceof APIError && typeof error.status === 'number' ? error.status : undefined\n const cause = error instanceof Error ? error.message : String(error)\n return { ok: false, failure: 'unavailable', ...(status === undefined ? {} : { status }), cause }\n}\n","import type { Completer } from '@formancy/server-core'\nimport { createAnthropicCompleter } from './anthropic-completer.js'\nimport { OPENAI_BASE_URL, XAI_BASE_URL, createOpenAiCompleter } from './openai-completer.js'\nimport type { ModelSettings } from './model-settings.js'\n\n/**\n * The adapter for the provider the operator named (0165): Anthropic through its own SDK,\n * OpenAI and xAI through OpenAI's, at each one's base URL.\n *\n * `fetch` is for the tests, which check where each provider's requests go.\n */\nexport function createCompleter(\n settings: Extract<ModelSettings, { kind: 'configured' }>,\n fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>,\n): Completer {\n const { apiKey, model } = settings\n const transport = fetch === undefined ? {} : { fetch }\n switch (settings.provider) {\n case 'anthropic':\n return createAnthropicCompleter({ apiKey, model, ...transport })\n case 'openai':\n return createOpenAiCompleter({ apiKey, model, baseURL: OPENAI_BASE_URL, ...transport })\n case 'xai':\n return createOpenAiCompleter({ apiKey, model, baseURL: XAI_BASE_URL, ...transport })\n }\n}\n"],"mappings":"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"}