# Business rules — edge_functions

Evidence date: 2026-10-16.

Source: `git@bitbucket.org:overdrive/edge_functions.git`, `main` at `7449104` (2024-05-02).

There is no business-rules document in the source repository. Every rule below is taken from a handler, `readme.txt`, or `supabase/config.toml`. Rules the code enforces are **Implemented**. Rules that are a reasonable reading of that code, and are not written down as policy, are **Inferred**. Nothing here is an approved product policy until someone confirms it.

Local Auth, signup, and JWT expiry in `config.toml` configure the `supabase start` stack. The three functions do not call Auth. Those template values are not listed as product rules.

## Access

### BR-001 — Handlers do not identify the caller

**Implemented.** `hello`, `callsheet`, and `upload_sj` never read `Authorization`, a user id, or `req` body. `upload_sj` accepts `(req, res)` and uses neither argument.

**Documented** for local serve and for deploy of `hello`: the CLI flag `--no-verify-jwt` (`readme.txt`). That flag is a gateway setting for the command that was written down. It is not a check inside the function.

**Unknown / Requires Confirmation:** whether `callsheet` and `upload_sj` were deployed, and whether that deploy verified JWTs. `config.toml` does not set `verify_jwt`.

**Inferred:** a client that can reach a function deployed like `hello` can invoke it without a user token. Policies on `sites`, `report_sj`, and Storage can still reject the anon key. Those policies are not in this repo.

### BR-002 — Data access uses the anon key

**Implemented.** All three functions create the data client with `SUPABASE_ANON_KEY`. None reads `SUPABASE_SERVICE_ROLE_KEY`.

`upload_sj` also reads `SUPABASE_ANON_STORAGE_KEY` for a second client. The name says anon. The value is not in git.

**Inferred:** the functions are intended to run with whatever the anon role is allowed to do. Whether that role can `select` `sites`, download the objects, or `upsert` `report_sj` is **Unknown / Requires Confirmation**.

### BR-003 — `upload_sj` depends on two extra environment names

**Implemented.** The storage client is built from `SUPABASE_STORAGE_URL` and `SUPABASE_ANON_STORAGE_KEY`. The other functions do not read those names.

They are not the usual hosted edge-function variables (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`). Whether anyone set the extra pair is **Unknown / Requires Confirmation**.

### BR-004 — The readme's example secret is not a rule

**Documented** example only: `supabase secrets set MY_NAME=Chewbacca`.

**Implemented:** no function calls `Deno.env.get("MY_NAME")`. Missing `MY_NAME` does not change behaviour.

## Workbook and table selection

### BR-005 — `callsheet` always reads one object

**Implemented.** Bucket `uploads`, path `callsheet/CMR_CALL_CYCLE_2023.xlsx`. The caller cannot substitute another path. The year `2023` is part of the constant.

**Inferred:** the workbook is a call cycle because the path contains `callsheet` and the filename contains `CALL_CYCLE`. The file is not in git, so the columns are **Unknown**.

### BR-006 — `callsheet` does not return the workbook

**Implemented.** After logging `SheetNames`, the response is `{ message: {}, result: {}, sent: {} }`. Failed downloads are not turned into an error status. `xlsx.read` still runs.

**Inferred:** sheet names were a debug step. The commented CSV loop is not an active rule.

### BR-007 — `upload_sj` always reads one Free State filename

**Implemented** as a string literal: `FState Region ALL CHANNEL REPORTS 10 MARCH 2024.xlsx`. The same filename appears only in a comment inside `callsheet`, not in `callsheet`'s live download.

The storage `from` argument is the literal `uploads/callsheet`. That is not the bucket-plus-path split used by `callsheet` (`from('uploads')` and path `callsheet/...`).

**Inferred:** `FState` means Free State. **Requires confirmation.** What "ALL CHANNEL REPORTS" contains is **Unknown**.

### BR-008 — `upload_sj` writes the parsed value with upsert

**Implemented** call: `from('report_sj').upsert(parsedData)` with no column list and no `onConflict`.

**Inferred:** a repeated invoke would update rows that conflict on the table's default conflict target, and insert the rest. The conflict target is **Unknown** because the table is not defined here. `parsedData` is the return value of `read(sheet, { header: 1 })`, which is unlikely to be an array of row objects (see technical documentation). The upsert therefore has no confirmed row shape.

The success and failure messages (`Uploaded successfully`, `Uploaded unsuccessfully`) are assignments to a `const`. They are not returned. **Implemented** defect, not a rule that the API answers with those messages.

### BR-009 — `hello` uses a single site row and ignores the caller's name

**Implemented.** `select('name').limit(1).single()`. The greeting is `Hello ${name?.name}!`. The scaffold body `{"name":"Functions"}` is not read.

There is no `order`. Which row `.limit(1)` returns when several sites exist is whatever Postgres returns without `ORDER BY`. **Inferred:** the row is not a defined "primary" site. **Requires confirmation** if a specific site was intended.

If the query errors, the HTTP status is still 200 and the text is `Hello undefined!`. **Implemented.** Empty `sites` is not a distinct error response.

## Local stack values that look like rules but are template

### BR-010 — Local API row cap and storage cap

**Implemented** in `config.toml` for `supabase start`: `max_rows = 1000`, storage `file_size_limit = "50MiB"`, database `major_version = 15`.

The edge functions do not read these keys. `hello` uses `.limit(1)`. `upload_sj` does not cap the upsert. Whether the hosted project uses the same caps is **Unknown / Requires Confirmation**.

### BR-011 — Project id spelling

**Implemented.** `project_id` and the lockfile package name are `datafy_edge_fuctions` (missing "n"). The git remote is `edge_functions`.

This is a name in config, not a validation rule. Do not "correct" it in documentation of the current tree. A rename would be a product decision.

## Pipeline

### BR-012 — Sonar runs only for `main`

**Implemented.** `bitbucket-pipelines.yml` key is `branches: '{main}'`. No pull-request pipeline, no deploy step, no test step.

**Inferred:** a branch that is not `main` does not get this scan. Other Bitbucket settings outside the file could still run pipelines. Those settings are **Unknown**.

## What is not a business rule

- Commented CSV export in `callsheet`.
- The YouTube link and the Supabase quickstart URLs in `readme.txt`. They are setup reading, not domain behaviour.
- Auth email confirmation, SMS, OAuth, and signup flags in `config.toml`. They are the local Auth template. No function signs a user in.
- The demo bearer token in the curl comments. It is the scaffold's local example, not a production credential policy.
