# Business rules — serverless-net-core

Evidence date: 2026-10-04.

Source: `git@bitbucket.org:overdrive/serverless-net-core.git`, `master` at `ca18f8f` (2021-06-01).

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

The database password is not restated.

## Access

### BR-001 — Neither HTTP route requires a caller identity

**Implemented.** The `http` events for `GET /hello` and `GET /getquerystring` set no `authorizer`, no `private` flag, and no API key. `serverless.yml` has no `apiKeys` block. The README FAQ explains how to add an API key and does not say one is configured.

**Inferred:** any client that can reach the deployed URL can call both routes. Whether API Gateway was locked down outside this repository is **Unknown / Requires Confirmation**.

### BR-002 — The database login is compiled into the function

**Implemented.** `HelloWorld` uses a literal MySQL connection string. Host `qaunity.cehgxa1eerul.eu-west-1.rds.amazonaws.com`, user `qaunity`, database `qaunity`. The password is in the same literal and in git history from `f5b7278`. It is not read from the environment. `serverless.yml` has no `environment` block.

**Inferred:** anyone with read access to the repository has the database password. Rotation status is **Unknown / Requires Confirmation**.

### BR-003 — CORS allows any origin on handler responses

**Implemented.** `CreateResponse` sets `Access-Control-Allow-Origin` to `*`. It does not set `Access-Control-Allow-Credentials` or allowed methods. Gateway-generated errors are not given CORS headers in this repo. There is no `resources/api-gateway-errors.yml`.

## Query string

### BR-004 — getquerystring succeeds only when `foo` can be read

**Implemented** in `GetQuerystring`, and **Documented** in the README ("For a successful response querystringParameter **foo** must be inserted").

- A non-empty query-string map that contains `foo` returns HTTP 200 and the whole map.
- A non-empty map that does not contain `foo` is caught as an exception and returns HTTP 500 with an empty body.
- A null request, or a map whose count is not greater than 0, returns HTTP 500 with an empty body and a log line that tells the caller to add `foo`.

The success body is not limited to `foo`. Other keys are returned with it.

### BR-005 — A missing query-string object is outside the catch

**Implemented.** The `Count` check is in the `if` condition. The `try` starts only after that check passes. If `request` is non-null and `QueryStringParameters` is null, the exception is not turned into `CreateResponse(null)`.

**Inferred:** a normal API Gateway GET with no query string can fail as an unhandled Lambda error rather than the 500 empty body used for an empty map. This was not executed in this inspection.

### BR-006 — Query-string values are not used as SQL

**Implemented.** `GetQuerystring` does not open a connection. `HelloWorld` does not read `request`. The only SQL string is a constant.

## sysUser

### BR-007 — hello returns one scalar, not the row set

**Implemented.** The statement is `SELECT * FROM `sysUser``. The method calls `ExecuteScalar()`, which is the first column of the first row, then `ToString()`, then stores that string as a dictionary key with value `""`.

**Inferred:** callers do not receive a list of users, column names, or the scalar as a JSON value. They receive a JSON object whose single key is that scalar. If the scalar is empty, the key is empty. If there are no rows, `ExecuteScalar()` is null and `ToString()` throws. The handler does not convert that into HTTP 500 itself.

Which column is first, and whether an order is guaranteed, is **Unknown**. There is no `ORDER BY` and no schema in the repo.

### BR-008 — hello does not filter sysUser

**Implemented.** The SQL has no `WHERE`. Combined with BR-001, the first scalar of that table is returned to any caller who can invoke the route, when the call succeeds.

**Inferred:** this is not a "current user" lookup. Nothing in the handler compares the row to a caller id.

### BR-009 — The hello/world pair is not the contract anymore

**Implemented** as a comment in `HelloWorld` (`//dict.Add("hello", "world");`). **Documented** and stale: the README still says the local tool should return hello world.

The response contract at `ca18f8f` is BR-007, not a greeting.

## Errors and logging

### BR-010 — A null result dictionary is HTTP 500

**Implemented** in `CreateResponse`. Status is 200 only when the dictionary is non-null. The body is empty when it is null. `GetQuerystring` uses that for the failure paths. `HelloWorld` calls `CreateResponse` only after the dictionary has been filled, so database failures do not use this branch.

### BR-011 — Logs include the Lambda request id and function name

**Implemented** in `LogMessage`, used only by `GetQuerystring`. The line format is `{AwsRequestId}:{FunctionName} - {message}`.

`HelloWorld` logs `DB Setup DB` through `context.Logger.LogLine` and does not use `LogMessage`. It does not log the scalar.

## Packaging and deploy names

### BR-012 — Both routes ship in hello.zip

**Implemented.** Changing either method requires rebuilding `bin/release/netcoreapp3.1/hello.zip`. `serverless.yml` does not point the two functions at different artifacts.

**Inferred:** a deploy of one function's artifact path still uploads a package that contains both methods. Serverless function configuration (path and handler name) is what selects the entry point.

### BR-013 — Deploy settings in the repo do not name one environment

**Discrepancy.** Three different settings are written down, and `serverless.yml` sets none of stage, region, or profile.

| Source | Profile | Region | Stage |
|---|---|---|---|
| `serverless.yml` | not set | not set | not set |
| README sample output and Silicon sequence URL | sample says none; deploy command uses `ozow` | `us-east-1` | `dev` in the sample URL path |
| `aws-lambda-tools.default.json` since `ca18f8f` | `unity` | `eu-west-1` | not set |
| MySQL host in `Handler.cs` | n/a | host name contains `eu-west-1` | n/a |

**Unknown / Requires Confirmation:** which of these was used for a real deploy. No rule in the code chooses among them.

## What is not a rule

- The commented event examples in `serverless.yml` (S3, schedule, Cognito, and the rest) are template comments. They do not constrain the API.
- `renovate.json` extending `config:base` does not state a product policy. No Renovate pull request is in this git history.
- Table `sysUser` existing, and the RDS instance being reachable from a Lambda outside a VPC, are assumptions the handler makes. They are not confirmed by a schema or a network resource in this repository.
