# Business rules — nodehtmltopdf

Evidence date: 2026-10-08. Revision: `master` `dc92bed`.

`README.md` states a few operating rules. Everything else is **Inferred** or **Implemented** from `index.js` and commit subjects. Inferred rules describe what the code does. They are not a confirmed business policy.

AWS access key values are not copied here.

## Who may call the service

**BR-001 — The HTTP routes do not check a caller identity.**
`index.js` does not read a header, token, query key, or allow-list before `POST /htmltopdf`, `POST /pdf`, or `GET /`. **Implemented.**

**BR-002 — Any browser origin is left to the `cors` package default.**
`app.use(cors())` passes no options. **Implemented.** The README describes a post to a server IP, not a browser. Whether a browser is allowed to call this host is **Unknown / Requires Confirmation.**

**BR-003 — The README's Lambda security-group sentence is not a rule this process enforces.**
Requirements text: "AWS AEC2 , S3 permissions , Lamda Access via same Security Groups if seperate". **Documented.** No security group id is in git. **Requires Confirmation** what "same security groups" was meant to constrain.

## What may be converted

**BR-004 — A missing `html` field does not produce a PDF and does not produce an HTTP error.**
If `req.body.html` is falsy, the handler logs `unable to get the data` and returns. **Implemented.**

**BR-005 — When `html` is present, that string is the document.**
Since `22e7404` ("update defualt text") the value passed to `pdf.create` is `req.body.html`. Before that commit the document was the literal `<b>hello world</b>` even though `html` was still required. **Implemented.**

**BR-006 — The service does not sanitize or size-limit the HTML in application code.**
The string is passed through. Body-parser is mounted with no custom `limit`. **Implemented.** What the library default rejects is the `body-parser` 1.19.0 default, not a rule written in this repo.

**BR-007 — `pagesize` does not change the page.**
The code sets `pageSize` to `req.body.pagesize` or `'a4'` and never reads `pageSize` again. `html-pdf` always receives `format: 'Letter'`. **Implemented.** **Requires Confirmation** whether Letter or A4 was the intended page for receipts.

**BR-008 — `data` on the body is ignored.**
`const data = req.body.data` is unused. The name `data` inside the file callback is the PDF buffer, not this field. **Implemented.**

## Naming and storage

**BR-009 — The object key is a random base-36 string plus `.pdf`.**
**Implemented** as `` Math.random().toString(36).slice(2) ``. No invoice number, date, or caller id is part of the key. **Inferred** that the key is not a business identifier.

**BR-010 — The object is stored in `tmt-pmf-prod-invoices` in `eu-west-1`.**
**Implemented** as the `Bucket` string and as the host in `path`. **Documented** only by those literals and by the prod-deployment commits, not by the README. Earlier buckets were `pmf-invoices` (`9025de3`) and `pmf-development-invoices` (`eacf1df`).

**BR-011 — The object is not given `public-read` by this code.**
`3e07698` ("update web accessable pdf") set `ACL: 'public-read'`. `b62094a` ("update acl to private") commented that line out. **Documented** by the commit subject. **Implemented** as a commented parameter. The bucket's own public-access setting is **Unknown / Requires Confirmation.**

**BR-012 — The URL returned to the caller is not the URL S3 returns.**
`path` is concatenated before upload. `data.Location` is only logged. **Implemented.** If the upload fails, the caller still has the URL. **Inferred** that a caller cannot treat `path` as proof the object exists.

**BR-013 — `config.js` is not the source of the bucket.**
It exports the same bucket name. `index.js` requires the module and does not read `bucket`. **Implemented.** A change to `config.js` alone does not move the upload.

## When the caller is told success

**BR-014 — HTTP success is "the request had `html`", not "the PDF is in S3".**
`res.send` runs before `toFile` finishes. **Implemented** since the first application commit. The README says the service will return JSON with filename and path. It does not say the return waits for S3. **Requires Confirmation** that callers are written to wait and then read the object. Silicon Ark's `pmf-backend-ah` documents a 1.5 second wait and then `GetObject` of `filename` on this bucket. That wait is not in this repository.

**BR-015 — Render failure does not cancel the upload attempt.**
The `toFile` error is logged and `uploadFile()` still runs. **Implemented.**

**BR-016 — Disk and S3 failures throw inside callbacks.**
`fs.readFile` and `S3.upload` use `throw` on error. **Implemented.** **Inferred** that those throws can take down the Node process, because they are not passed to Express. There is no retry.

**BR-017 — The local PDF is kept.**
No `fs.unlink` is present. **Implemented.** **Inferred** that the working directory accumulates PDF files for the life of the host.

## Credentials

**BR-018 — The running process authenticates to AWS with the keys in `cred.json`.**
**Implemented** at HEAD via `loadFromPath('./cred.json')`. The file is tracked. Values are omitted. Commits `16d9273` ("update accesskey") and `b781463` ("update cred") changed that file, so older key material remains in git history. **Requires Confirmation** that those keys are revoked.

**BR-019 — Secrets Manager is not the rule for the process `npm start` launches.**
`24d9561` switched `index.js` to secret `pmf-prod-s3-user` and removed `cred.json`. `dc92bed` ("revert previous changes") put `cred.json` back and left the secrets path in `index_wit SM.js`. **Implemented.** **Requires Confirmation** which file, if either, production actually starts.

**BR-020 — The secrets-manager variant expects three JSON fields.**
`Access_key_ID`, `Secret_access_key`, and `Invoice_Bucket` on the parsed secret string. **Implemented** in `index_wit SM.js`. The secret contents are not in git. **Unknown / Requires Confirmation.**

## Routes that are not the PDF contract

**BR-021 — `POST /pdf` returns the request body and does not create a file.**
**Implemented.** The README does not mention this route. **Requires Confirmation** whether any caller uses it.

**BR-022 — `GET /` is a log line plus an echo.**
It logs `PMF PDF Generator` and sends `req.body`. **Implemented.** It is not a health check with a fixed body.

## Logging

**BR-023 — The README says everything is logged in CloudWatch, and on the instance via `pm2 log`.**
**Documented.** Application code uses `console.log` and `console` of errors only. No log group, retention, or PM2 ecosystem file is in the repo. **Requires Confirmation** that CloudWatch is attached to the host.
