# Business rules — theear_transcode

Evidence date: 2026-10-01.

Source: `git@bitbucket.org:overdrive/theear_transcode.git`, `master` at `fda49df` (2022-05-04).

No business-rules document shipped with the source. Rules below are taken from code. Each rule is marked **Inferred** when the code behaves that way and nobody wrote it down as policy. None of these are confirmed product decisions.

## What gets transcoded

**BR-001 — Only an `.mxf` key starts a job on the wired Ireland path.** **Inferred** from `region_ireland/transcode_process.js`. Any other key is ignored. The check is `endsWith('.mxf')`, so `.MXF` does not match.

**BR-002 — One job produces four outputs and one playlist.** **Inferred.** MP4 using preset `1351620000001-000001`, HLS transport streams at the three presets commented as 2 Mb/s, 1 Mb/s, and 600 kb/s, segment duration `2`, playlist format `HLSv3`. Thumbnails use the pattern `{stem}/{file}-thumbnail-{resolution}-{count}` on the MP4 output only.

**BR-003 — The pipeline id is fixed.** **Inferred.** Wired path uses `1635987395753-iukgee`. A second id, `1634634986727-4ij4ha`, remains in `region_south_africa/transcode_process.js` and as a comment in `region_south_africa/transcode.js`. Which pipeline is the production one outside this file is **Unknown / Requires Confirmation** if anything was deployed from the unwired file.

**BR-004 — Output keys are prefixed with `transcode/`.** **Inferred** from `OutputKeyPrefix`. The bucket that prefix applies to is the pipeline configuration in AWS, which is not in this repo.

**BR-005 — HLS content protection is not applied.** **Inferred.** The AES-128 block is commented out in every job builder. No other encryption step exists in the handlers.

**BR-006 — Input timing and picture are left to the service.** **Inferred.** Frame rate, resolution, aspect ratio, interlaced, and container are `auto`.

## What gets catalogued

**BR-007 — A resources-bucket object is stored only for a known lowercase extension.** **Inferred** from `region_south_africa/transcode.js`.

| Suffix | Stored type |
|---|---|
| `.m3u8` | `m3u8` |
| `.mxf` | `mxf` |
| `.pdf` | `pdf` |
| `.mid` | `midi` |
| `.png` | `png` |
| `.mp3` | `mp3` |
| `.mp4` | `mp4` |

`.mid` is stored as type `midi`, not `mid`.

**BR-008 — The stored URL is an `s3://` URI, not an HTTPS URL.** **Inferred.** Prod catalog uses `s3://the-ear-prod-resources/` plus the object key. The `.m3u8` branch does not rewrite the key into a `transcode/` path. The commented older line did.

**BR-009 — The stored name is the last path segment of the text before the first dot.** **Inferred.** `split('.')[0]` drops everything from the first dot onward, then the last `/` segment of that stem is stored. For key `transcode/Hein_Upright_Bass_Ep2_Harmonic_Minor_ac/Hein_Upright_Bass_Ep2_Harmonic_Minor_ac_1mb.ts.m3u8`, the name is `Hein_Upright_Bass_Ep2_Harmonic_Minor_ac_1mb`. A file `lesson.part2.mp4` is stored as `lesson`.

**BR-010 — Duration is not measured.** **Inferred.** Catalog inserts pass `"0"`. The comment `//1sec , 10, 20 , 30` is unused. The unwired Ireland `transcode.js` leaves `duration` as `""` on the `.mxf` path.

**BR-011 — Created time is UTC, to the second, as `YYYY-MM-DD HH:MM:SS`.** **Inferred** from `new Date().toISOString().slice(0, 19).replace('T', ' ')`.

**BR-012 — The prod catalog insert does not look for an existing row.** **Inferred.** Every matching object-created event inserts again, including overwrites of the same key.

**BR-013 — The staging backfill does look for an existing row.** **Inferred** from `sort_files.js`. It selects `name` and `type`. It does not compare `url`. Two different keys that share a name and type count as one row. The backfill bucket is `the-ear-staging-resources`, not the prod bucket. It does not classify `.mp4` or `.mxf`. For `.m3u8` it stores `s3://the-ear-staging-resources/transcode/{stem}/{file}.m3u8` rather than the listed key.

**BR-014 — `sort_files` is a GET with no authorizer in the template.** **Inferred** from `region_south_africa/serverless.yml` (`cors: false`, no `authorizer`). Calling it changes the database. Whether API Gateway is public is **Unknown / Requires Confirmation** (deployment, resource policy, and VPC are outside the handler).

## Regions and buckets

**BR-015 — Job creation is specified in Ireland. Cataloguing is specified in Cape Town.** **Inferred** from the two `provider.region` values: `eu-west-1` and `af-south-1`.

**BR-016 — The South Africa service is attached to a VPC. The Ireland service is not.** **Inferred.** Ireland's VPC block is commented out. Subnet and security-group values are not in git.

**BR-017 — Copy from the transcode bucket to the resources bucket is described, not deployed by this template.** **Inferred.** The Lambda file and `policy/theear-lambda-transcode-resources.json` exist. `serverless.yml` does not create that function. A further copy to `the-ear-prod-video-upload` is only a policy file plus a commented block in `transcode.js`.

## Access

**BR-018 — `POST /create` requires AWS IAM signing.** **Inferred** from `authorizer: aws_iam` on both services. The handler still reads `cognitoIdentityId` and does not check a role or group of its own.

**BR-019 — S3 event handlers do not check who uploaded the object.** **Inferred.** They trust the bucket event.

**BR-020 — Lambda roles in the templates are broad.** **Inferred**, not a recommended policy. Both services allow S3 get and put on `arn:aws:s3:::*`, and Elastic Transcoder, S3 list, IAM list, and SNS list on `*`. South Africa also allows full item CRUD on every DynamoDB table in `eu-west-1`. The JSON files under `policy/` are narrower and are not attached by the templates.

**BR-021 — CORS on the notes route and on `handler-lib` allows any origin.** **Inferred** from `Access-Control-Allow-Origin: *` and `Access-Control-Allow-Credentials: true`. Gateway error responses do the same.

## Data rules that the code does not enforce

- No unique key on `resources` is declared here. The database might have one. **Unknown.**
- No maximum upload size, allowed content type, or virus check.
- No retention or delete path. Nothing in the active handlers deletes an S3 object or a `resources` row.
- No status field for "transcode started", "job id", or "job finished". The catalog does not store the Elastic Transcoder job id.
- `global.js` would accept action codes `s`, `u`, and `i` and concatenate them into SQL if it were deployed. It is not a function. The comment also mentions delete. The `switch` has no delete branch.

## Permissions mocks

`mocks/` contains bodies for user and permission calls (`getuser`, `updatepermissions`, `adduser`, and others). No active handler reads those files. They are not rules of this service. Provenance: leftover fixtures, **Unknown** which API they were captured from.

## Edge cases

| Situation | What the code does | Provenance |
|---|---|---|
| Key `video.MXF` | No job on the wired Ireland path | **Inferred** |
| Key with extra dots, `a.b.mp4` | Name stored is `a` | **Inferred** |
| Two object-created events for the same prod key | Two inserts | **Inferred** |
| `sort_files` and more than 1,000 objects | One `listObjects` page only | **Inferred** |
| `sort_files` key that matches no extension after one that did | `url` is not cleared, so the previous URL can be inserted again if `url.length > 2` still holds | **Inferred** |
| SQL error | Logged. Lambda is not failed by `throw` on the catalog path | **Inferred** |
| `createJob` error | Logged. No second attempt | **Inferred** |
| Job completion | `waitFor('jobComplete')` is called and not awaited, and its result is unused | **Inferred** |
| Several records in one S3 event | `forEach` starts them without awaiting. `index.js` uses only record 0 | **Inferred** |
| Empty `POST /create` body | `JSON.parse` throws. `create.js` does not use `handler-lib`, so the error follows the Node callback style (no local catch) | **Inferred** |
| Ireland `POST /create` | `tableName` is not set in that `serverless.yml` | **Inferred** |
| Non-`.mxf` on the transcode bucket | Ignored. Not copied, not catalogued | **Inferred** |
| `.m3u8` on the resources bucket | Catalogued as type `m3u8`. Not sent to Elastic Transcoder | **Inferred** |

## Conflicts inside the repo

- Ireland `transcode.js` (unwired) still inserts a row and creates a job. South Africa `transcode.js` (wired) only inserts. Treating both as current would double-define the MXF rule.
- South Africa `transcode_process.js` (unwired, commented function) uses a different pipeline id and a staging URL host.
- `README.md` describes a `hello` demo in `us-east-1`. The templates deploy `create` and transcode functions in `eu-west-1` and `af-south-1`.
- `create.js` comments describe Cognito Identity. The route uses `aws_iam`.

These are recorded as discrepancies, not resolved.
