# Technical documentation — DigitalBio

Evidence date: 2026-10-17. Revision: `master` `2739231` (2023-03-07, author `Dirk`, subject "Name changes and changed tile to show on ML Results page"). 54 tracked files. Default branch `master`. One other remote branch, `origin/ui-update`, is an ancestor of `master` (merged as pull request #1).

AWS account numbers, IAM role ARNs, the Amplify app id, stack ids, and secret values are omitted. Region, resource names, the secret id, and the API host from `README.md` are included because they are in the tree and are not themselves the secret.

## What this repository is

A Create React App client plus an AWS Amplify backend definition. `package.json` name is `digitalbio`, version `0.1.0`, `private: true`. The Amplify project name is `digitalbio` (`amplify/.config/project-config.json`, `version` `3.1`, frontend `javascript`, framework `react`).

The browser title is Digical Digital. The signed-in UI is a single page with a sidebar. There is no React Router dependency and no server-side rendered app.

The only backend compute in the tree is Lambda `SearchTiles`: an Express app wrapped by `aws-serverless-express`, querying PostgreSQL with the `pg` client. Credentials are loaded from Secrets Manager. The React app never opens a database connection.

`aws-exports.js` is listed in `.gitignore` and is not in the clone. `src/App.js` imports it. The README says to create that file before `npm start`.

## Repository layout

```text
digitalbio/
  package.json
  package-lock.json
  README.md                     four local steps and one API host
  .gitignore                    Amplify local files, build, node_modules, aws-exports.js
  public/
    index.html                  title "Digical Digital"; apple icon digicalBio-icon.png
    manifest.json               name "Digical Digital"; icons digicalBio-icon.png
    digitalBio-icon.png         tracked icon; not the name the HTML requests
    favicon.ico
    robots.txt                  allow all
  src/
    index.js                    React 18 root, reportWebVitals() with no callback
    App.js                      Amplify.configure, withAuthenticator, page switch
    App.css                     CRA sample plus authenticator background
    App.test.js                 expects the text "learn react"
    setupTests.js               jest-dom
    reportWebVitals.js
    logo.svg                    CRA logo, unused by the header
    Components/
      AppHeader.jsx             username and sign out
      AppSearchField.jsx        shared search box
      Hearder.jsx               logo (filename spelling)
      SignIn.jsx                unused
      SignUp.jsx                unused
      FormElement.jsx           unused except by SignIn and SignUp
    Pages/
      Dashboard.jsx             ML Results
      SlideExplorer.jsx
      ResultTileViewer.jsx
      Jobs.jsx
      CanvasSearch.jsx
      CanvasResultViewer.jsx
    util/
      getDataFromAPI.js
      getDataWithSignedURL.js
      s3Utils.js
    Images/
      digitalBio.png
      digitalBio-icon.png
      No_Image_Available.jpg
  amplify/
    cli.json
    team-provider-info.json     environment key "dev" only
    backend/
      backend-config.json       auth, api, function
      tags.json                 user:Stack, user:Application
      auth/digitalbio5fd52944/
      api/apia19f7356/
      function/SearchTiles/
      types/amplify-dependent-resources-ref.d.ts
```

No `Dockerfile`, `bitbucket-pipelines.yml`, `serverless.yml`, `.env`, `.nvmrc`, `engines` field, or `sonar-project.properties` is tracked.

The onboarding issue (captured 2026-09-28 from the scan host) listed `sonar-project.properties` and `sonarqube_scan.bat` beside `package.json`. They are not in `git ls-files` at `2739231`. **Unknown / Requires Confirmation** whether the scan host has untracked copies.

## Runtime and toolchain

| Item | Value | Evidence |
|---|---|---|
| UI | React 18.2, React DOM 18.2 | `package.json` |
| Build | `react-scripts` 5 (`start`, `build`, `test`, `eject`) | `package.json` |
| Amplify JS | `aws-amplify` ^5.0.15 | `package.json`. This is Amplify JS v5, not v6 |
| Amplify UI | `@aws-amplify/ui-react` ^4.3.8 | `package.json` |
| AWS SDK in the browser | `aws-sdk` ^2.1318.0, used by `s3Utils.js` | `src/util/s3Utils.js` |
| Secrets SDK in the browser package | `@aws-sdk/client-secrets-manager` ^3.272.0 | Listed. No import under `src/` |
| Other UI libraries | `react-icons` ^4.7.1, `react-jss` ^10.10.0, `react-zoom-pan-pinch` ^2.6.1 | Used by the viewers and the search tables |
| Tests | Testing Library React 13, jest-dom 5, user-event 13 | `package.json` |
| Lambda runtime | `nodejs14.x` | `SearchTiles-cloudformation-template.json` |
| Lambda handler | `index.handler` | same template |
| Lambda timeout | 25 seconds | same template |
| Lambda memory | not set in the template | same template |
| Lambda dependencies | express ^4.15.2, body-parser ^1.17.1, aws-serverless-express ^3.3.5, pg ^8.9.0, `@aws-sdk/client-secrets-manager` ^3.272.0 | `SearchTiles/src/package.json` |
| Amplify env | `dev` | `amplify/team-provider-info.json` |
| Region | `eu-west-1` | that file, the presign client, and the Secrets Manager client |
| CloudFormation stack name | `amplify-digitalbio-dev-83808` | `team-provider-info.json` |
| Frontend build command | `npm.cmd run-script build` | `amplify/.config/project-config.json` |
| Frontend start command | `npm.cmd run-script start` | same file |
| Distribution directory | `build` | same file |

`npm.cmd` is the Windows npm shim. **Inferred:** the Amplify project was configured on Windows. The operating system of any hosted build is **Unknown / Requires Confirmation**.

There is no lockfile pin for a Node version for the CRA app. `package-lock.json` is tracked.

## Application structure

`src/index.js` creates a React 18 root on `#root` and renders `<App />` under `StrictMode`.

`App.js` calls `Amplify.configure(awsconfig)` at module load. State:

| State | Initial | Who sets it |
|---|---|---|
| `pageName` | unset, then `Dashboard` in an effect | Sidebar, search submit, tile click, canvas click, canvas viewer's Dashboard button |
| `searchValue` | `''` | ML Results search field only |
| `selectedSlide` | `[]` | `selectTile` |
| `selectedCanvas` | `[]` | `selectCanvas` |

Jobs and Canvasses keep their own search strings inside the page components. Slide Explorer reads the App-level `searchValue`.

The content switch is a chain of equality checks on `pageName`. An unrecognised value renders an empty fragment. There is no 404 page.

`withAuthenticator` supplies `signOut` and `user`. The header component for the authenticator is `Hearder` (the logo).

## Authentication

Resource name `digitalbio5fd52944`. Service Cognito. `authSelections` is `identityPoolAndUserPool`.

From `amplify/backend/auth/digitalbio5fd52944/cli-inputs.json`:

| Setting | Value |
|---|---|
| Identity pool allows unauthenticated | false |
| MFA | OFF |
| MFA types listed | SMS Text Message |
| Sign-up attribute | email |
| Verification | email |
| Username attributes | none (not email-as-username) |
| Alias attributes | none |
| Username case sensitive | false |
| Password min length | 8 |
| Password character rules | none (`passwordPolicyCharacters` empty, `defaultPasswordPolicy` false) |
| App client secret | false |
| Refresh token validity | 30 |
| Read/write attributes | email |
| User pool groups | none |
| Social providers | none |

`backend-config.json` repeats MFA off, minimum length 8, signup attribute EMAIL, verification EMAIL.

The React gate is `withAuthenticator`. `SignIn.jsx` and `SignUp.jsx` are not imported.

No application code stores tokens in `localStorage` itself. Amplify v5 does that inside the library. This repo does not configure a custom cookie storage. A comment in `App.js` says "add cookie config to amplify config" and does not add one.

## API

Amplify category `api`, resource `apia19f7356`, service API Gateway, provider `awscloudformation`. It depends on function `SearchTiles` attributes `Name` and `Arn`.

`amplify/backend/api/apia19f7356/cli-inputs.json` maps every path to Lambda `SearchTiles` with `permissions.setting` `open`.

Client call (`src/util/getDataFromAPI.js`):

```text
API.get(apiName, path, { response: true })
return res.data.data.rows
```

`{ response: true }` makes Amplify return the full HTTP response. The Lambda sends `{ data: <node-pg result> }`. The rows are `result.rows`, so the client path is `res.data.data.rows`. A body with a different shape throws before the screen's `catch`.

### Routes

All methods are GET. SQL is in `amplify/backend/function/SearchTiles/src/app.js`. "Contains" means a case-insensitive match. The path value is concatenated into the SQL text (see Security).

| Path | Table | Filter | Order / limit | Called from |
|---|---|---|---|---|
| `/getlastslides` | `image` | none | `slide`, limit 10 | `Dashboard.jsx` |
| `/searchslides/{searchValue}` | `image` | `slide` contains | `slide`, limit 10 | `Dashboard.jsx` when length ≥ 3 |
| `/getslides/{slideNumber}` | `image` | `slide` contains | `slide`, no limit | `SlideExplorer.jsx` |
| `/getslidebyid/{slideid}` | `image` | `id` equals | none | `ResultTileViewer.jsx` |
| `/maximageid` | `image` | `max(id)` | one row | `ResultTileViewer.jsx` |
| `/imageresult/{imageID}` | `bbox` | `image_id` equals | none | `ResultTileViewer.jsx` |
| `/getlastjobs` | `batch_transform_jobs` | none | `name`, limit 10 | `Jobs.jsx` |
| `/getjobbyname/{jobname}` | `batch_transform_jobs` | `name` contains | `name`, no limit | `Jobs.jsx` |
| `/getlastcanvas` | `canvas` | none | `name`, limit 10 | `CanvasSearch.jsx` |
| `/getCanvas/{searchValue}` | `canvas` | `slide` contains | `slide`, no limit | `CanvasSearch.jsx` |
| `/getcanvasbyid/{canvasid}` | `canvas` | `id` equals | none | `CanvasResultViewer.jsx` |
| `/maxcanvasid` | `canvas` | `max(id)` | one row | `CanvasResultViewer.jsx` |
| `/canvastiles/{canvasid}` | `tiles` | `canvas_id` equals | none | `CanvasResultViewer.jsx` |

`/getCanvas/` uses a capital C. The other paths are lowercase. The client uses that capital C.

Express listens on port 3000 at the bottom of `app.js` for a local process. Lambda uses `index.js`, which wraps the same app with `awsServerlessExpress.createServer` and returns `proxy(..., 'PROMISE').promise`. The handler logs the whole API Gateway event.

`event.json` is the Amplify sample (`POST /item`). No route implements it.

### Lambda response and errors

Success is `res.status(200).json({ data: data })` where `data` is the `pg` result object (`rows`, `rowCount`, and the driver's other fields).

Each `catch` is `console.log(error)` with no `res.status` and no `next(err)`. The HTTP request stays open until the Lambda times out. The React `catch` then runs. List screens that set `isLoading` true do not set it false in `catch`.

The `pg` client is created inside the handler and `connect()` is called. `end()` is not called.

### Secrets Manager

Middleware on every request:

- Region `eu-west-1`.
- `GetSecretValue` for secret id `webapp_admin_user`.
- `SecretString` parsed as JSON.
- Fields read: `username`, `host`, `dbname`, `password`, `port`.
- Stored on a module-level `credentials` variable.
- On failure, the error is logged and `next()` runs without assigning `credentials`. A previous successful value remains. The first failure leaves `credentials` undefined.

The secret value is not in the repository. The database host is not in the repository.

### IAM in the template

`SearchTiles-cloudformation-template.json`:

- Function name `SearchTiles` plus the env name, unless `ShouldNotCreateEnvResources`.
- Environment variables `ENV` and `REGION` only. The secret id is hardcoded in `app.js`, not an environment variable.
- Execution role name pattern `digitalbioLambdaRole60b46f9d` plus the env.
- Inline policy `lambda-execution-policy` allows `logs:CreateLogGroup`, `logs:CreateLogStream`, and `logs:PutLogEvents` on that function's log group.
- No `secretsmanager:*`, no VPC config, no security group.

`amplify/backend/function/SearchTiles/custom-policies.json` is one object with empty `Action` and empty `Resource`.

If this template is what was deployed, `GetSecretValue` is denied. The app may have been given a policy outside the repo. **Unknown / Requires Confirmation.**

## Data

No `CREATE TABLE`, migration, or ORM model is in the repo. Columns below are the ones the SQL filters or the React components read. `select *` means other columns can exist and are ignored by the UI.

### `image`

Used as a tile. Evidence: `Dashboard.jsx` shows `name` after searching `slide`; the viewer labels `slide` as Slide Number and `name` as Tile Number.

| Column | Use |
|---|---|
| `id` | Row key, viewer selection, previous/next, `max(id)`, `bbox.image_id` |
| `slide` | Search column and "Slide Number" |
| `name` | ML Results cell, card label, "Tile Number" |
| `uri` | Presign input. Absent `uri` skips presign |

### `bbox`

| Column | Use |
|---|---|
| `id` | Result table |
| `image_id` | Filter |
| `results` | Result column |
| `score` | `Math.round(score * 100)` shown as Confidence |

### `batch_transform_jobs`

The UI calls these jobs. The table name is the only hint of a batch-transform system. No SageMaker SDK is in the repo. Whether the rows are SageMaker jobs is **Unknown / Requires Confirmation**.

| Column | Use |
|---|---|
| `id` | ID column |
| `name` | "Image Name" column and the search filter |
| `perc` | `Done` when strictly equal to 1, otherwise a percentage |
| `created_date` | Started column |
| `title` | Referenced only in commented JSX. Column existence is **Unknown** |

### `canvas`

| Column | Use |
|---|---|
| `id` | Selection, previous/next, `max(id)`, `tiles.canvas_id` |
| `name` | "Canvas Number". Also the order column of the default list |
| `slide` | Search filter |
| `uri` | Presign input |

### `tiles`

| Column | Use |
|---|---|
| `id` | React key |
| `canvas_id` | Filter |
| `row` | Shown as `row/col` |
| `col` | Shown as `row/col` |
| `name` | Image column |

Relationships **inferred** from the filters, not from a foreign-key declaration: `bbox.image_id` points at `image.id`, `tiles.canvas_id` points at `canvas.id`. A canvas row has its own `slide` column. Nothing in the SQL joins `canvas.slide` to `image.slide`.

## Object storage

`generatePresignedUrl(bucketName, objectKey, expirationTime)`:

1. `Auth.currentCredentials()`.
2. `new AWS.S3({ credentials, region: 'eu-west-1' })` (SDK v2).
3. `getSignedUrl('getObject', { Bucket, Key, Expires })`.

`getUrl` in `getDataWithSignedURL.js`:

```text
split uri on "//"
drop the scheme segment
first remaining segment = bucket
the rest, joined with "/", = key
```

Callers pass `3600`. `getDataWithSignedURL` maps the rows with `Promise.all`. One rejected presign rejects the whole list.

There is no Amplify Storage category in `backend-config.json`. Bucket names are not configured in the repo. They arrive inside `uri`.

## Deployment and environments

Checked-in Amplify categories: auth, api, function. There is no hosting category, no custom domain file, and no pipeline file.

`team-provider-info.json` has a single environment, `dev`, in `eu-west-1`, stack name `amplify-digitalbio-dev-83808`. Auth and API category blocks under that environment are empty objects. The function block records a deployment bucket key `amplify-builds/SearchTiles-576f74696e6f30475145-build.zip` (the build artifact name, not a secret).

`README.md` **documents**:

1. Clone
2. `npm install`
3. Create `aws-exports.js` in `src`
4. `npm start`

And the host `https://z24ayjk4pg.execute-api.eu-west-1.amazonaws.com/dev`.

The README does not say how to produce `aws-exports.js` (`amplify pull` is not mentioned). It does not mention the Lambda or the database. Whether that host still exists is **Unknown / Requires Confirmation**.

Local Lambda listen port 3000 is for running `app.js` directly. The CRA dev server is the react-scripts default (the README does not set `PORT`).

`.gitignore` also ignores `amplify/#current-cloud-backend`, `amplify/.config/local-*`, `amplify/logs`, `amplify/mock-data`, `amplify/backend/amplify-meta.json`, `build/`, `node_modules/`, `awsconfiguration.json`, `amplifyconfiguration.json`.

## Security observations

These are properties of the checked-in code. They are not an approved design.

- API Gateway paths are `open` while the UI is behind Cognito. See BR-005.
- Path parameters are concatenated into SQL. See BR-018.
- Express CORS sets `Access-Control-Allow-Origin` to `*` and `Access-Control-Allow-Headers` to `*`.
- The database password is fetched by a hardcoded secret id. The value is not in git. The Lambda template does not grant `secretsmanager:GetSecretValue`.
- The handler logs `JSON.stringify(event)` for every invocation.
- MFA is off. The password policy in git has a minimum length and no character classes.
- The identity pool does not allow unauthenticated identities. Presigns use the signed-in identity's credentials. The S3 bucket policy that must allow those identities is not in the repo.
- `robots.txt` allows all user agents. The app is a private tool only if Cognito and the API are locked down in the deployed account.
- No rate limit, WAF, or authorizer appears in this repo.
- `custom-policies.json` is empty. It does not add a dangerous allow, and it does not add the secret allow the code needs.

## Development and testing

There is no contributor guide beyond the four README lines.

`src/App.test.js` renders `<App />` and expects `/learn react/i`. The current `App` does not render that text. The import of `./aws-exports` fails on a clean clone before the assertion. The test was not executed for this document.

`src/setupTests.js` imports `@testing-library/jest-dom`. No other test file is tracked. The Lambda has no tests.

`reportWebVitals()` is called with no argument, so no metric is collected. No analytics script is in `index.html`. The meta description is still "Web site created using create-react-app".

`public/index.html` and `manifest.json` point at `digicalBio-icon.png`. The tracked file is `public/digitalBio-icon.png`. The apple-touch icon and the manifest icons do not match a tracked filename.

## Monitoring

The Lambda role can write CloudWatch Logs for itself. The handler logs the event and logs caught errors. There is no structured metric, no X-Ray configuration, no alarm, and no frontend error reporter. Screen failures go to `console.log`.

## Background work

No queue, no worker, no cron, and no Amplify function trigger other than API Gateway. `SearchTiles` is the only function. It runs when a route is called. It does not publish messages.

The table `batch_transform_jobs` implies work that happens somewhere else. That producer is not in this repository.

## Historical API

On 2023-02-17, `6842c2a` added Amplify API `SearchSlide` and Lambda `SearchSlides`. `e5dd795` deleted them the same day, including `APIGatewayAuthStack.json`. `SearchTiles` and `apia19f7356` enter history at `4cc2894` (2023-02-24) and are the API at tip.

## Related Silicon Ark projects

Nothing in this repo imports or links another Overdrive repository. `silicon_ark` has many separate Unity and Amplify projects. None of those documents was used as a source for DigitalBio behaviour. Shared Amplify patterns are coincidence until someone confirms a shared platform. **Unknown / Requires Confirmation** whether DigitalBio's database is populated by another Overdrive system.
