# Business rules — lambdaNodejsPdf

Evidence date: 2026-10-10.

Source: `git@bitbucket.org:overdrive/lambdanodejspdf.git`, `master` at `012edf2` (2021-09-30).

There is no business-rules document in the source repository. Every rule below is taken from code, the Serverless template, or the `htmltopdf.js` comment. Rules that the code enforces are **Implemented**. Rules that are a reasonable reading of that code, and are not written down as policy, are **Inferred**. Nothing here is an approved product policy until someone confirms it.

## Access

### BR-001 — Either route can be called without an authorizer

**Implemented.** `serverless.yml` does not set `authorizer` on `GET /api/pdf` or `POST /api/htmltopdf`. The route resources in `.serverless/cloudformation-template-update-stack.json` have no authorization type.

There is no API key, JWT check, or IAM authorizer in this service.

**Unknown / Requires Confirmation:** whether a resource policy or a later change outside git restricts the live API.

### BR-002 — API Gateway may invoke either function for any route on this API

**Implemented.** Each Lambda permission uses a `SourceArn` that ends in `/*` for this HTTP API id. The routes themselves still bind `GET /api/pdf` and `POST /api/htmltopdf` to one function each.

## Text PDF

### BR-003 — The PDF sentence is the query parameter `text`

**Implemented** in `handler.js`. The only content drawn is that string, via `doc.text`. It is not parsed as HTML.

### BR-004 — A missing or empty `text` value becomes `Hello world`

**Implemented.** The expression is `event.queryStringParameters.text || 'Hello world'`.

This default applies only when `queryStringParameters` is an object. A request with no query string does not get the default. The read throws.

Empty string is falsy, so `?text=` also becomes `Hello world`.

### BR-005 — The text PDF response is a base64 PDF body

**Implemented.** Status 200, content type `application/pdf`, `isBase64Encoded: true`. No other status is produced by this handler.

### BR-006 — The text PDF has no page-size input

**Implemented.** `new PDFKit()` is called with no options. Page size, font, and margins are whatever PDFKit 0.12.3 uses by default. This repository does not restate those library defaults.

## HTML PDF

### BR-007 — The HTML source is JSON field `data` on the HTTP body

**Implemented.** `JSON.parse(event.body)` then `body.data`.

**Discrepancy.** The comment shows `data`, `filename`, and `pagesize` as the event. It does not mention `body`. The code will not see those fields if they are placed on the Lambda event root instead of inside the JSON body.

The comment's HTML sample is not a stored document and is not a default. If `data` is missing, the sample is not substituted.

### BR-008 — `data` must be truthy or the handler takes the error branch

**Implemented.** `if (!body.data)` logs `unable to get the data` and then calls `callback`.

`callback` is not defined. The branch throws `ReferenceError` after the log. It does not return a status code.

Invalid JSON never reaches this check. `JSON.parse` throws first.

### BR-009 — The object name is `filename` or a random token, then `.pdf`

**Implemented.** `` `${(body.filename || Math.random().toString(36).slice(2))}.pdf` ``

**Inferred** from that expression:

- The random token is `Math.random().toString(36)` with the `0.` prefix removed by `slice(2)`. It is not a UUID.
- `.pdf` is always appended, so a caller-supplied `report.pdf` becomes `report.pdf.pdf`.
- The same string is the file name under `/tmp` and the S3 key.
- No character allow-list is applied. Path separators in `filename` are kept.

### BR-010 — Page size defaults to `a4`

**Implemented.** `const pageSize = body.pagesize || 'a4'`. The JSON name is `pagesize`. The variable passed into the wrapper is `pageSize`. The wrapper in the artifact dasherizes option names, so the binary argument is `--page-size`.

No check restricts the value to a known size. An unknown size fails inside `wkhtmltopdf`, which this handler does not map to an HTTP error.

### BR-011 — The HTML PDF is stored in bucket `pmf-invoices`

**Implemented** as the `Bucket` argument. `config.js` exports `bucket: 'pmf-invoices'`. Content type on the put is `application/pdf`.

The template does not create the bucket. The execution role's only actions are CloudWatch Logs actions. **Implemented:** this service, as templated, is not allowed to write the object.

**Inferred:** the bucket name is the Pay My Fines invoice bucket used by the earliest `nodehtmltopdf` commit. This repository does not say that. Confirmation belongs with the product owner. See `project-plan.md`.

### BR-012 — A finished HTML conversion is supposed to yield `{ filename }`

**Documented** by the success call `callback(null, { filename })` and by the error calls `callback('unable to get the data', {})` and `callback('Unable to send file to S3', {})`.

**Discrepancy.** `callback` is not in scope. The function is `async` and returns `undefined` as soon as the pipe is started. The success payload is not an API Gateway proxy response. A client of `POST /api/htmltopdf` is not given a filename by this code.

### BR-013 — HTML is not filtered before rendering

**Implemented.** `body.data` is passed to `wkhtmltopdf` as the document input.

**Inferred:** the spawned `wkhtmltopdf` binary receives that HTML as its page. This repository does not configure network blocking, local-file blocking, or a timeout of its own beyond the Lambda timeout of 6 seconds.

## Platform limits that behave as rules

### BR-014 — Both functions stop at 6 seconds and 1024 MB

**Implemented** in the update template: `Timeout: 6`, `MemorySize: 1024` on `GenerateDashpdfLambdaFunction` and `GenerateDashhtmltopdfLambdaFunction`. The HTTP API integration timeout is 6500 ms.

`serverless.yml` states memory only for `generate-htmltopdf`. It states no timeout. The compiled template still sets both values on both functions.

### BR-015 — The service runs as Node.js 12 in `eu-west-1`, stage `dev`

**Implemented** in `serverless.yml` and in the function properties: runtime `nodejs12.x`, region `eu-west-1`, stage `dev`, profile `unity`.

There is no second stage in git.

### BR-016 — Logs go to the two function log groups, with no retention set

**Implemented.** Log group names:

- `/aws/lambda/lambdanodepdf-dev-generate-pdf`
- `/aws/lambda/lambdanodepdf-dev-generate-htmltopdf`

The resources do not set `RetentionInDays`.

`htmltopdf.js` also calls `console.error(error)` on the S3 callback, including when `error` is null.

## What is not a rule

- No tenant, user, or invoice-number check exists. The bucket name is the only Pay My Fines-like token.
- No rule requires the text PDF to be stored. `handler.js` does not call S3.
- The string "hk" appears only in the commit subject of `012edf2`. It is not a code constant.
- CORS is not configured. That is an absence, not an allow-all rule.
