# Business rules — DoctorX_lambda_api_Josh

Evidence date: 2026-10-16.

Source: `git@bitbucket.org:overdrive/doctorx_lambda_api_josh.git`, `master` at `40da799` (2021-04-30).

There is no business-rules document in the source repository. Every rule below is taken from code, SQL, or comments. 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**. Comments that the code does not follow are called out. Nothing here is an approved product policy until someone confirms it.

Unwired files are included only in the last section. They are not rules of the deployed API.

## Access

### BR-001 — HTTP calls must pass AWS IAM

**Implemented.** Each HTTP event sets `authorizer: aws_iam`. There is no API key, no JWT check, and no Cognito authorizer resource in `serverless.yml`.

### BR-002 — Comments require Cognito; the template requires IAM

**Discrepancy.** Comments on `getuseruuid` and `diagnose` say "cognito must be logged in". The authorizer is `aws_iam`. `getuseruuid.js` and `postxray.js` do not read `cognitoIdentityId`.

`logging.js` does read `cognitoIdentityId` and stores it as `userID`. If that field is missing, the put sends `userID` as `undefined`. There is no explicit "must be present" check, and the HTTP handler does not wait for the put.

**Unknown / Requires Confirmation:** whether API Gateway is wired to a Cognito identity pool outside this repository.

### BR-003 — Doctor rows are not limited to the caller

**Implemented.** Create writes the columns the caller sends. List returns every `crmDoctors` row. Get filters on the caller's `id`. Get-by-email filters on the caller's `strEmail`. Update writes the caller's fields and filters on the caller's email or `DoctorID`. None of these queries compare the row to `cognitoIdentityId`.

**Inferred:** any caller who can invoke the route can read and change the `crmDoctors` rows the SQL reaches, including every column `SELECT *` includes.

### BR-004 — `strRole` is stored text, not an authorisation decision

**Implemented.** Create writes `strRole` after replacing single quotes with double quotes. Update writes `strRole` in double quotes with no replacement. No handler compares `strRole` to a role table or branches on it.

### BR-005 — CORS is open

**Implemented.** Handler responses set `Access-Control-Allow-Origin` to `*` and `Access-Control-Allow-Credentials` to `true`. Gateway default 4XX and 5XX do the same for origin and allow every header.

## Doctors

### BR-006 — A doctor is a row in `crmDoctors`

**Implemented** as the table name in the routed queries since `d7aa39d` (before that commit the same statements used `crmdoctors`).

Columns the statements name:

| Column | Where it is written or read |
|---|---|
| `strName`, `strSurname`, `strEmail`, `strRole` | Insert always. Update when sent and not `""` |
| `dtCreated` | Insert only, set by the function, UTC `YYYY-MM-DD HH:MM:SS` |
| `DoctorID` | Optional insert. Update when sent, or copied from `id`. Also a `WHERE` key |
| `strUserCreatedName` | Optional insert only |
| `strCity` | Optional insert and update |
| `refUserID` | Optional insert and update |
| `dtLastLoggedIn` | Update only, `CAST(... AS DATETIME)` of the caller string |

Other columns returned by `SELECT *` are **Unknown**. Whether `DoctorID` is an auto-increment primary key is **Unknown / Requires Confirmation**. The insert omits the column when the caller does not send it.

### BR-007 — Empty string means "do not write this optional field"

**Implemented.** Optional insert and update columns are included only when the value is truthy and not `""`.

Create does not apply that test to `strName`, `strSurname`, `strEmail`, or `strRole`. Those four are always concatenated.

### BR-008 — Email and doctor id are different lookups

**Implemented.**

- `POST /josh/get` uses `DoctorID` and `parameters.id`, unquoted.
- `POST /josh/getuuid` uses `strEmail`, double-quoted.
- `POST /josh/getusers` uses neither.

### BR-009 — Listing ignores every parameter

**Implemented** in `getusers.js`. The SQL has no `WHERE`. The mock `id` has no effect.

### BR-010 — Update prefers email as the row key

**Implemented.** When both `strEmail` and `DoctorID` are non-empty, the `WHERE` clause is `strEmail=` plus the raw email. `DoctorID` is still assigned in the `SET` list if it was sent. `DoctorID` is the `WHERE` key only when email is missing or `""`.

If neither is non-empty and some other field was sent, the function returns `Must Supply a DoctorID!` and does not run SQL.

### BR-011 — An update that runs SQL still returns the failure payload

**Implemented.** Inside the branch that runs `mysql.query`, `let results` hides the outer `results`, which stays `false`. The return after that branch is `failure(false)` unless the query throws.

**Inferred:** this is a defect in the handler, not a rule that updates must fail. It is recorded because it is what the code does.

### BR-012 — There is no duplicate-email rule in the handler

**Implemented.** Create does not select an existing `strEmail` before insert. No unique index is declared in this repository. Whether the database rejects a duplicate is **Unknown**.

### BR-013 — Created time is the server clock

**Implemented.** `dtCreated` is produced in `adduser.js` from `new Date()`. The caller cannot set it through the optional-field list. The value is UTC with the ISO `T` and the fractional seconds removed.

### BR-014 — Last login is caller-supplied

**Implemented.** `dtLastLoggedIn` is written only when the caller sends it on update. The handler does not set it on create and does not set it from the current time.

### BR-015 — A missing get row is not an error

**Implemented.** `SELECT` that matches nothing yields an empty array. An empty array is truthy, so the handler returns the success payload. It does not return 404.

## Diagnose

### BR-016 — Diagnose does not depend on the request

**Implemented.** `postxray.js` assigns one object and returns it. `type`, `model`, and `xray` in the mock are not read. The MySQL block is commented out.

**Inferred:** the sentence is placeholder copy. It is not a clinical rule and it is not calculated from an image.

## Audit

### BR-017 — Every routed handler attempts an audit put

**Implemented.** The item stores the whole event, the query argument, and the result, plus a time-based `logID` and `createdAt` as epoch milliseconds.

The put is not awaited. A DynamoDB error does not change the HTTP status.

### BR-018 — The audit table name is `loging`

**Implemented** as the string in `serverless.yml` `environment.tableName`. The table resource is not in the template.

## Responses

### BR-019 — Application failure is nested inside HTTP 200

**Implemented.** `failure()` returns status 500 in an object. `handler-lib.js` wraps any returned object as status 200 and JSON-encodes it again. Only a thrown exception becomes HTTP 500 with `{error: message}` from the wrapper.

### BR-020 — Empty input on four routes is rejected

**Implemented** for create, update, get, and get-by-email: no keys after the body and path copy produces `Valid input not found!`.

List and diagnose do not use that check.

## Unwired code (not rules of the deployed API)

### BR-021 — Generic SQL action codes

**Implemented** in `global.js`, **not routed** at `40da799`.

| `data.a` | Result |
|---|---|
| `s` | `SELECT {c} FROM {t}` |
| `u` | `UPDATE {t} SET {c} ` |
| `i` | `INSERT INTO ` |
| anything else | `failure("No Action")` |

The file comment names Select, Update, Insert, and Delete, and nests the fields under `data`. The function does not implement that comment. Delete is not a code. Join and where both append `data.con` after calling `.length()` on `data.j` and `data.w`.

## Not a rule

The README steps for a `hello` function, `env.example`, and `serverless invoke local --function hello` describe the starter. `handler.js` is not in the tree. Those steps are not rules of `josh-lambda-api`.
