# PRD — 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 prior PRD in the source repository or in Silicon Ark. Requirements below are recovered from `Handler.cs`, `serverless.yml`, `AwsDotnetCsharp.csproj`, the build scripts, `aws-lambda-tools.default.json`, and `README.md`. Provenance is labelled.

The database password in `Handler.cs` is omitted. Host, database name, and database user are included because they are the integration the handler calls.

`README.md` is a starter guide. Where it disagrees with `Handler.cs` or `serverless.yml`, the code is the implemented behaviour and the README is called out as a discrepancy.

## Product purpose

**Documented** in the README title and opening lines: a starter template that deploys a C# / .NET Core 3.1 solution to AWS Lambda and API Gateway, and that can be debugged in Visual Studio Code on any operating system the .NET SDK supports.

**Implemented** beyond that starter: `GET /hello` reads one value from MySQL table `sysUser` on database `qaunity`. `GET /getquerystring` echoes query-string parameters when `foo` is present.

**Inferred** from the service name `ozow-lambda-test`, AWS profile names `ozow` and `unity`, the commit "Update .net with Unity settings", and the database name `qaunity`: someone adapted the starter toward a Unity or Ozow environment. The code does not call an Ozow API and does not reference another Unity repository.

**Unknown / Requires Confirmation:** the product owner, the intended caller, and whether either route is still in use.

## Users and interactions

| Actor | Evidence | Status |
|---|---|---|
| HTTP caller | `serverless.yml` events are `http` GET with no `authorizer` and no `private` flag | **Implemented**. The caller application is not in this repo |
| Person debugging locally | README, `.vscode/launch.json`, `Properties/launchSettings.json` | **Documented**. The mock tool is expected on port 5050 |
| Person deploying | README "Silicon sequence": `build.cmd`, then `serverless deploy --aws-profile ozow` | **Documented**. Whether that sequence was completed in AWS is **Unknown** |
| Database user `qaunity` | Connection string in `Handler.HelloWorld` | **Implemented** as a compiled-in login. Password omitted here |

No role, tenant, or permission check exists in the handler.

## Systems the product talks to

1. AWS API Gateway and Lambda. Service name `ozow-lambda-test`, runtime `dotnetcore3.1`. Stage, region, memory, timeout, and profile are not set in `serverless.yml`. Serverless Framework's own defaults, when those fields are omitted, are stage `dev` and region `us-east-1`. That default is also what the README sample output shows. It is **Inferred** here from the README sample, not from a line in `serverless.yml`.
2. MySQL on host `qaunity.cehgxa1eerul.eu-west-1.rds.amazonaws.com`, database `qaunity`, user `qaunity`. **Implemented** as a literal connection string in `HelloWorld`. The password is in the same string and is omitted. There is no environment variable and no VPC block.
3. Amazon Lambda Test Tool 3.1 for local execution. **Documented** in the README, `setup.sh`, and `Properties/launchSettings.json`.
4. AWS CLI profile `ozow` on `serverless deploy`. **Documented** only in the README Silicon sequence.
5. AWS CLI profile `unity` and region `eu-west-1` for `dotnet lambda` / the Visual Studio wizard. **Implemented** as values in `aws-lambda-tools.default.json` since `ca18f8f`. Before that commit the file said profile `default` and region `eu-central-1`.

No queue, mailer, cache, object store, or monitoring vendor is configured. There is no Bitbucket Pipelines file.

A URL is written in the README Silicon sequence: `https://4row8qyxh1.execute-api.us-east-1.amazonaws.com/dev/hello`. That is **Documented**. This inspection did not call it. Whether it belongs to this service is **Unknown / Requires Confirmation**.

## Major workflows

Routes below are the path in `serverless.yml`. With Serverless default stage `dev` and no custom domain in the template, the deployed base path would be `/dev`. **Requires confirmation** of the public URL. The README's example URL uses `/dev/hello` in `us-east-1`.

### WF-1 — Read one `sysUser` scalar

**Implemented** in `Handler.HelloWorld`, `GET /hello`.

Trigger: API Gateway proxy request, or a local invoke of function `hello`.

Inputs: the method does not read the request body, path, or query string.

Steps:

1. Build a MySQL connection to host `qaunity.cehgxa1eerul.eu-west-1.rds.amazonaws.com`, database `qaunity`, user `qaunity`.
2. Open the connection.
3. Run `SELECT * FROM `sysUser``.
4. `ExecuteScalar()` and call `ToString()` on the result.
5. Put that string into a dictionary as the key, with value `""`.
6. Log the line `DB Setup DB`.
7. Return `CreateResponse` of that dictionary.

The previous body `{"hello":"world"}` is still in the file as a comment (`ca18f8f`).

Outputs on the success path: HTTP 200, `Content-Type: application/json`, `Access-Control-Allow-Origin: *`, body a JSON object with one key.

Validation: none.

Error conditions: there is no `try/catch`. A connection failure, a SQL error, or a null scalar (`ExecuteScalar()` returns null when there are no rows, and `ToString()` is then called) leaves the method. This repository does not define the HTTP status API Gateway returns for an unhandled Lambda exception.

The SQL is a constant. The caller does not supply the table name.

Provenance: **Implemented** since `f5b7278`. The README still says a local execute "should get hello world as a result" (**Documented**, stale relative to the handler).

### WF-2 — Echo query-string parameters

**Implemented** in `Handler.GetQuerystring`, `GET /getquerystring`.

Trigger: API Gateway proxy request, or a local invoke of function `getquerystring`.

Documented sample input in the README:

```json
{
  "httpMethod": "GET",
  "queryStringParameters": {
    "foo": "dfgdfg",
    "woot": "food"
  }
}
```

Steps:

1. Log `Processing request started` with the Lambda request id and function name.
2. If `request` is non-null and `request.QueryStringParameters.Count > 0`, enter the try block.
3. Build the response from the whole query-string dictionary (`CreateResponse`).
4. Log `First Parameter Value to read is: ` plus `request.QueryStringParameters["foo"]`.
5. Log `Processing request succeeded.`
6. If reading `foo` throws, log `Processing request failed` plus the exception message and return `CreateResponse(null)`.
7. If the request is null or the query-string count is not greater than 0, log that the caller should add queryStringParameter `foo`, and return `CreateResponse(null)`.

Outputs:

- `foo` present: HTTP 200 and the query-string map as JSON. Extra keys are included. `foo` is not filtered out.
- `CreateResponse(null)`: HTTP 500 and an empty body. The same CORS and content-type headers are set.

Validation: **Implemented** only as "the dictionary must contain key `foo`" inside the try block. Any other non-empty query string reaches that read and fails it. The README states the same rule: for a successful response, queryStringParameter `foo` must be inserted.

Error conditions:

- Missing `foo` while other keys exist: caught, HTTP 500, empty body.
- Null `request`, or a query-string dictionary with count 0: HTTP 500, empty body, not an exception.
- `request` non-null and `QueryStringParameters` null: the `Count` access is outside the `try`. **Implemented** as an unhandled exception. API Gateway often supplies null when the URL has no query string. This inspection did not execute that case.

This handler does not open MySQL.

### WF-3 — Build the deployment zip

**Documented** in the README and **Implemented** as scripts.

Windows: `build.cmd` runs `dotnet restore`, then `dotnet lambda package --configuration release --framework netcoreapp3.1 --output-package bin/release/netcoreapp3.1/hello.zip`.

Mac or Linux: `build.sh` installs `zip` on Debian if `/etc/debian_version` exists, then `dotnet restore`, then `dotnet lambda package --configuration Debug` with the same output path and framework.

`serverless.yml` points both functions at that zip. The zip is gitignored (`[Bb]in/`). Deploy without a prior package step has no artifact in git.

### WF-4 — Deploy

**Documented** in the README.

1. `serverless deploy` (starter section). The sample output uses service `myService`, stage `dev`, region `us-east-1`.
2. Silicon sequence, which is the later recipe in the same file:
   1. `dotnet add package MySql.Data`
   2. Edit `Handler.cs`
   3. Update functions in `serverless.yml`
   4. `.\build.cmd`
   5. `serverless deploy --aws-profile ozow`
   6. `serverless invoke -f hello -l`
   7. `curl` the `us-east-1` `/dev/hello` URL written in the README

`sls remove` is documented as the way to destroy the stack.

`aws-lambda-tools.default.json` is a second deploy path for `dotnet lambda` and Visual Studio. It sets profile `unity`, region `eu-west-1`, configuration `Release`, framework `netcoreapp3.1`, template `serverless.yml`. The README Silicon sequence does not use those values.

### WF-5 — Debug locally

**Documented.**

1. Install `Amazon.Lambda.TestTool-3.1` (`setup.sh` version 0.9.5, or the README's `dotnet tool install --global` without a version pin).
2. Point `.vscode/launch.json` `program` at the tool. The committed value is still the placeholder `/Users/{user}/.dotnet/tools/dotnet-lambda-test-tool-3.1`.
3. F5. The README says the mock tool listens on port 5050. `Properties/launchSettings.json` passes `--port 5050` and sets the working directory to `.\bin\$(Configuration)\netcoreapp3.1`.
4. Execute a function from the tool UI. The README says the result should be hello world. `HelloWorld` at `ca18f8f` queries MySQL instead.

`.vscode/tasks.json` pre-launch task runs `dotnet build`.

## Functional requirements

### FR-001 — Expose GET /hello

Requirement: The service exposes `GET /hello`, handled by `AwsDotnetCsharp::AwsDotnetCsharp.Handler::HelloWorld`.

Who: an HTTP caller. Deployed by a person running Serverless.

Trigger: HTTP GET on that path.

Inputs: none read by the method.

Expected behaviour: WF-1.

Outputs: JSON object, status 200, when the database call returns a non-null scalar.

Validation: none in the handler.

Error conditions: unhandled exception if the database call fails or the scalar is null.

Dependencies: MySQL at the host in `Handler.cs`; the zip `bin/release/netcoreapp3.1/hello.zip`.

Evidence: `serverless.yml` function `hello`; `Handler.HelloWorld`.

Implementation: Implemented.

Confidence: High.

### FR-002 — Expose GET /getquerystring

Requirement: The service exposes `GET /getquerystring`, handled by `AwsDotnetCsharp::AwsDotnetCsharp.Handler::GetQuerystring`.

Who: an HTTP caller.

Trigger: HTTP GET on that path.

Inputs: API Gateway `queryStringParameters`.

Expected behaviour: WF-2. Success requires key `foo`. The body is the full map, not only `foo`.

Outputs: 200 plus JSON, or 500 plus an empty body.

Validation: presence of `foo` when the map is non-empty.

Error conditions: see WF-2, including a null query-string map.

Dependencies: the same zip as FR-001.

Evidence: `serverless.yml` function `getquerystring`; `Handler.GetQuerystring`; README section "Test Another Example".

Implementation: Implemented.

Confidence: High.

### FR-003 — JSON responses allow any origin

Requirement: Responses built by `CreateResponse` set `Content-Type` to `application/json` and `Access-Control-Allow-Origin` to `*`.

Who: the handler, on every path that calls `CreateResponse`.

Trigger: return from `HelloWorld` or `GetQuerystring`.

Inputs: the dictionary, or null.

Expected behaviour: null dictionary → status 500 and empty body. Non-null dictionary → status 200 and `JsonConvert.SerializeObject` of the dictionary.

Outputs: `APIGatewayProxyResponse`.

Validation: none beyond the null check.

Error conditions: a null result is a 500, not a 4xx.

Dependencies: `Newtonsoft.Json`.

Evidence: `Handler.CreateResponse`.

Implementation: Implemented.

Confidence: High.

### FR-004 — Package both functions from one zip

Requirement: Functions `hello` and `getquerystring` both use artifact `bin/release/netcoreapp3.1/hello.zip`, built for `netcoreapp3.1`.

Who: the person running `build.cmd` or `build.sh`, then `serverless deploy`.

Trigger: package, then deploy.

Inputs: the project source.

Expected behaviour: one assembly contains both public methods. The handler string selects the method. `package.individually: true` is set, and each function names that same artifact.

Outputs: the zip path. The zip is not stored in git.

Validation: none in the repo. A missing zip would fail at deploy time. That failure text is not in the repo.

Error conditions: `build.sh` uses `--configuration Debug` and `build.cmd` uses `--configuration release`. Both write the same output path. Which configuration a deployed zip used depends on which script ran.

Dependencies: `dotnet lambda package` from `Amazon.Lambda.Tools`; a .NET Core 3.1 SDK.

Evidence: `serverless.yml` `package` blocks; `build.cmd`; `build.sh`.

Implementation: Implemented as configuration. A package was not built during this inspection.

Confidence: High for the file contents. The commands were not executed.

### FR-005 — Local mock execution

Requirement: A developer can run the handlers in the Amazon Lambda Test Tool.

Who: a developer with the .NET SDK and the global test tool.

Trigger: F5 in VS Code, or the `Mock Lambda Test Tool` profile.

Inputs: a JSON event such as the README sample. For `getquerystring`, `foo` is required for a 200.

Expected behaviour: tool on port 5050, working directory under `bin`. The committed VS Code `program` path is a `{user}` placeholder and must be edited before F5, as the README says.

Outputs: the handler response inside the tool.

Validation: the placeholder is still in `.vscode/launch.json`.

Error conditions: README "I deployed the solution but I get back a http 500 error" points at CloudWatch Logs, and repeats the `foo` rule.

Dependencies: `Amazon.Lambda.TestTool-3.1`. Version pin conflict: `setup.sh` says 0.9.5; README expected list says 0.10.0.

Evidence: `README.md`, `setup.sh`, `.vscode/launch.json`, `.vscode/tasks.json`, `Properties/launchSettings.json`.

Implementation: Documented. Not executed in this inspection.

Confidence: High for the files. Medium for whether the placeholder launch path works without the edit the README requires.

## Constraints

- Runtime in the template is `dotnetcore3.1`. The project target is `netcoreapp3.1`. .NET Core 3.1 left Microsoft support on 3 December 2022. That date is not written in the repository.
- Both routes are unauthenticated in `serverless.yml`.
- The MySQL password is in source control. It is not an environment variable.
- `HelloWorld` and the RDS host are in different configuration stories: the host name contains `eu-west-1`, while the README curl URL is `us-east-1`. Cross-region access is not configured in this repo (no VPC, no RDS proxy resource).
- There is no automated test project.
- Renovate is configured only as `config:base`. Whether a Renovate app is installed on the Bitbucket repository is **Unknown**.

## Discrepancies

| Topic | Documented | Implemented |
|---|---|---|
| `GET /hello` body | README: local execute returns hello world | `HelloWorld` queries `sysUser`. The hello/world line is commented out |
| Service name | README sample output: `myService` | `serverless.yml`: `ozow-lambda-test` |
| Region | README sample and curl URL: `us-east-1`. Tools file before `ca18f8f`: `eu-central-1` | Tools file now: `eu-west-1`. `serverless.yml` sets no region. RDS host name contains `eu-west-1` |
| AWS profile | README deploy: `ozow` | `aws-lambda-tools.default.json`: `unity`. `serverless.yml` sets no profile |
| Test tool version | README expected list: test tool 0.10.0, Lambda tools 4.0.0 | `setup.sh`: test tool 0.9.5 only |
| Build configuration | README treats `build.sh` and `build.cmd` as equivalents | `build.sh` passes `Debug`; `build.cmd` passes `release` |
| Sonar files | Onboarding scan on 2026-09-28 listed `sonar-project.properties` and `sonarqube_scan.bat` | Not in git at `ca18f8f` |

## Assumptions

None of the following is treated as a requirement:

- That the README URL is still live.
- That profile `ozow` and profile `unity` deploy the same stack.
- That `qaunity` is the Unity database used by `unity-lambda-api` or another Silicon Ark project. Those projects are not named in this repo. `unity-lambda-api` reads `sysUser` through environment variables, not through this connection string.
- That a null query string was intended to return 500 rather than throw.
