# Business rules — siliconscan

Evidence date: 2026-10-03. Revision: `master` `56c90dc0`.

Rules below are taken from code and from the README. Nothing here is a new product decision. Where two parts of the code disagree, both are stated.

Labels:

- **Documented** — written in the README or in a route comment.
- **Implemented** — what the code does.
- **Inferred** — a reasonable reading of names or comments, not a separate specification.

## BR-IDENTITY — What this application is

The committed application records wine label submissions and produces a Wine and Spirit Board certificate. Provenance: **Implemented** (`label` columns, `labelApprovalPDF`).

The README calls the project Admin Overdrive and lists auth and menus only. Provenance: **Documented**.

The commit message calls the tree a copy of a label system. Provenance: **Documented**.

## BR-AUTH — Who may enter

1. A person signs in with `users.username` and a password that verifies against the stored hash. Provenance: **Implemented**.
2. Failed sign-in uses the same flash text whether the user is missing or the password is wrong. Provenance: **Implemented**.
3. Registration requires a unique username, a password, and a matching confirmation. Provenance: **Implemented**. The README lists registration. Provenance: **Documented**.
4. Registration stores role id 1. Provenance: **Implemented**. A business rule that new users should be designers or members was **not found**.
5. Requests without `isLoggedIn` go to `/`, except login, register, the organisations API, and the tracking pixel. Provenance: **Implemented**.
6. CSRF and honeypot filters are commented out. Provenance: **Implemented** configuration. A requirement to keep them off was **not found**.

## BR-MENU — Menu grant

The first URL segment must exist as `user_menu.url`, and `user_access` must grant that menu to the session role. Otherwise the user is sent to `/` (unknown menu) or `blocked` (known menu, no grant).

Exceptions that skip this check: home, login, register, blocked, `Welcome/*`, organisations, `chatGPT/*`, tracking pixel.

Provenance: **Implemented**.

The seed menu does not contain `labels`. Provenance: **Implemented**. Opening the label UI therefore depends on menu rows that are not in the seed. **Unknown / Requires Confirmation** for any database that was loaded from the seeder alone.

## BR-ROLE — Label visibility

Inferred names are comments, not seeded `role_name` values except id 1.

| Rule | Provenance |
|---|---|
| Role 5 sees only rows with `user_designer_id` equal to their user id, on the list and on the form | **Implemented** |
| Roles 1, 7, and 2 see every label on the list | **Implemented** |
| Role 3 sees only `label.status = 7` on the list, and may open the admin form | **Implemented** |
| Roles 1, 7, 2, and 3 receive `label/labelForm`. Role 5 receives `label/labelFormMember` | **Implemented** |
| Create and resubmit allow roles 1, 7, 5, and 6 only | **Implemented** |
| Status admin and meeting-group admin render only for role 1. Other roles redirect to home | **Implemented** |
| Email-template admin renders for role 1. Other roles receive the HTML 404 view | **Implemented** |
| The SAWIS outbound menu is omitted when role is 5 | **Implemented** |
| Meeting-group membership UI loads users in roles 1, 2, and 3 | **Implemented** |

Role 6 is named "Members [Farmers]" only in a comment. Provenance of the name: **Inferred**. The list action does not handle role 6. Provenance: **Implemented** gap.

Role 4 is unused. Provenance: **Unknown**.

Comments disagree on whether id 1 is Developer, Dev Admin, or Admin, and on whether id 2 or id 7 is "Admin". The seeder's only name is Developer for id 1. Provenance of the conflict: **Implemented** comments versus **Documented** seed. **Requires Confirmation** before the names are treated as official.

## BR-STATUS — Label status numbers

The dashboard and the write methods use one set of numbers. A display helper uses another. The PDF uses a third.

| Code | `Home::index` | `LabelModel` writes | `statusDisplay()` in `Common.php` | PDF checkbox in `labelApprovalPDF` |
|---|---|---|---|---|
| 1 | Pending (grouped with 2 and 3) | Set on create | Submitted | Ticks **Approved**. Comment says Approved |
| 2 | Pending | Not written | Approved | No case |
| 3 | Pending | Not written | Rejected | No case |
| 4 | Rejected | Set on reject | "Error 0" (default) | Ticks Rejected |
| 5 | Approved | Set on approve when provisions flag is not 1 | "Error 0" | No case, so no tick |
| 6 | Approved with provision | Set on approve when `labelHasProvisions` is 1 | "Error 0" | Ticks "Approved, provided that" |
| 7 | Contested | Set on contest | "Error 0" | No case |

Provenance: **Implemented** in each location. They cannot all be the business rule. **Requires Confirmation**.

`label_status` is a table with name, priority, provision id, group, parent, and an active flag added by later migrations (`status` column is filtered as `status = 1` on the form and on templates). The view `labelStatus.php` also contains a PHP array `["Submitted", "Pending", "Rejected", "Approved", "Contested", "Resubmit"]`. That array is not inserted by a seeder. Provenance: **Implemented** as a view variable. It does not by itself define ids 1–7.

Manual status update writes whatever id is posted. Provenance: **Implemented**. No check was found that the id exists, or that the change follows 1 → 5 / 4 / 7.

## BR-FILE — Label documents

1. New and resubmitted label files must use extension `pdf`. Provenance: **Implemented**.
2. Stored types: 1 Label (default), 2 Supporting Document, 3 Front Label, 4 Back Label, 5 Neck Label. Provenance: **Implemented** in `createLabelFiles` and `contentTypeDisplay`.
3. Type 6 is written for the approval certificate. `contentTypeDisplay` has a case 6 labelled "Approval Document" and then falls through to the default because there is no `break`, so the function returns "Error 0". Provenance: **Implemented**.
4. Lists of files on the form keep `active = 1`, ordered by type descending and `file_version` descending. Provenance: **Implemented**.
5. Resubmit increments version and does not deactivate the older row. Provenance: **Implemented**. The model TODO treats deactivation as undecided.
6. Filenames are a random integer, an underscore, and the original name with spaces turned into underscores, then lowercased on the path. Provenance: **Implemented**.

## BR-FIELDS — Data captured on a label

Created row: designer user id, status 1, created_by, updated_by. Provenance: **Implemented**.

Later edit saves barcode, alcohol percentage, warning, bottle size, and year. Provenance: **Implemented**.

Columns that exist on `label` and are not in that update array include `member_id`, `cultivar_code`, `wine_type_id`, `wsb_number`, `production_area`, `type`. Provenance: **Implemented** schema versus update method. Whether the form posts them through another path was not confirmed field by field. **Unknown / Requires Confirmation** if those columns are still required in the business process.

Alcohol percentage is a `DECIMAL(10,2)` after migration `2023-12-11-072136`. Provenance: **Implemented**.

`warning` is an integer column. The note builder maps previous value `1` to Yes and `0` to No for every changed field, not only warning. Provenance: **Implemented**. That mapping will mis-label a previous barcode or year that happens to be `1` or `0`.

## BR-PROVISION — Approval provisions

If the approve request says `labelHasProvisions` is 1, status becomes 6 and the posted `provisions` string is stored. Otherwise status becomes 5. Provenance: **Implemented**.

The certificate ticks provision ids 1–8 with these sentences. Provenance: **Implemented** template.

| Id | Sentence on the certificate |
|---|---|
| 1 | Blend complies with section 23(4)(e) of the Wine of Origin Scheme |
| 2 | Wine complies with the relevant sugar class |
| 3 | Use only for export |
| 4 | Use only for export to the USA |
| 5 | Mandatory items (with the stated exceptions) in the same field of vision |
| 6 | Those items in letters at least 2 mm high |
| 7 | Filling date for bag-in-box, aseptic carton, or PET, not on the bottom panel |
| 8 | Filling date on the bottom of a can |

Ids 3 and 4 share a lead-in sentence. Ids 5 and 6 share a lead-in sentence. Provenance: **Implemented**.

`label_provision` is a separate table (id, name). The certificate does not read that table. It uses the numbers above. Provenance: **Implemented**. Whether table ids match 1–8 is **Unknown / Requires Confirmation**.

## BR-TAMPER — Form binding

`labelFormsTamperProtection` stops the request when posted `userID` or `labelID` differs from the session. Provenance: **Implemented**.

It is called on resubmit, update, delete file, approve, reject, contest, file approval, and manual status. It is not called on create (the call is commented out). It is not called on `getPreviousLabelFileVersions` or `reassignToMeetingGroup`. Provenance: **Implemented**.

## BR-MAIL — When mail goes out

1. Approve and reject email the designer user id from the request, using that user's `username` as the address. Provenance: **Implemented**.
2. The template row is chosen by `label_email_template.type` equal to the status number (4, 5, or 6). Provenance: **Implemented**.
3. Placeholders replaced: `{userName}`, `{LabelNumber}`, `{Note}`, `{labelURL}`. The label URL is `labels/labelform?id=` plus the label id. Provenance: **Implemented**.
4. A tracking pixel is appended. The pixel is recorded only when the request has no login session. Provenance: **Implemented**.
5. Contest does not send mail. Provenance: **Implemented**.
6. From address and from name are `admin@overdriveqa.co.za` and `Admin Overdrive`. Provenance: **Implemented** in `mail_helper.php`.
7. If the template row is missing, nothing is sent and a line is appended to `log.txt`. Provenance: **Implemented**.

## BR-CERT — Certificate text

The PDF states that labels are judged for local use under the Liquor Products Act of 1989 and then lists responsibilities (trade marks, company names, viticultural areas, GPS, other legislation, export country rules, certification accuracy, the health warning from 2009, SANS 289, permanent attachment). Provenance: **Implemented** as template copy. It is not enforced as validation on the form.

The signatory block is hardcoded. Provenance: **Implemented**. **Requires Confirmation** that the named person is still the signatory.

## BR-GROUP — Meeting groups

A group has a name and an `active` flag. Reassignment writes `meeting_group_id` onto the selected labels. Provenance: **Implemented**.

No rule was found that a label must have a group before approval, or that only group members may approve. **Unknown / Requires Confirmation**.

## BR-API — Organisations

1. Login matches `name` and `api_key` and returns a JWT valid for 6000 seconds. Provenance: **Implemented**.
2. Other organisation methods require a bearer token signed with the hardcoded secret. Provenance: **Implemented**.
3. The route comment describes form posts and a path `organisations/login`. The method reads JSON and is a controller action, so the auto-routed path is the method name on the resource controller. Provenance of the comment: **Documented**. Provenance of JSON: **Implemented**. **Requires Confirmation** of the URL clients actually call.
4. These routes are outside the session filters. Provenance: **Implemented**.

## BR-EDGE — Edge cases visible in code

| Case | Behaviour | Provenance |
|---|---|---|
| Role other than 1, 2, 3, 5, 7 opens the label list | `$data` is never set, then the view is returned | **Implemented** |
| Role 5 opens another designer's label | Form arrays are emptied | **Implemented** |
| Create with no file | Warning flash, JSON with empty data | **Implemented** |
| Create with a non-pdf | That file is skipped. If every file is skipped, `labelFiles` may be missing and the model still inserts the label before iterating files | **Implemented** order in `createLabelFiles` |
| Approve when the session `labelID` is empty | PDF uses `session labelID ?? 118` | **Implemented** |
| Email template missing | No send, `log.txt` | **Implemented** |
| Pixel requested while logged in | PNG returned, no log | **Implemented** |
| `statusDisplay` given 4, 5, 6, or 7 | Returns "Error 0" | **Implemented** |
| `contentTypeDisplay` given 6 | Returns "Error 0" because case 6 falls through | **Implemented** |
| Selenium run | `chdir(SIDEFILEPATH)` with no definition found, then `shell_exec` of `selenium-side-runner` | **Implemented** call. Constant **Unknown** |
| Registration role | Always 1, including a public register route that is outside the login filter | **Implemented** |

## Rules not found

No written rule was found for retention of uploads, for who may approve versus who may only comment, for duplicate barcodes, for alcohol or bottle-size ranges, or for a required set of file types (front, back, neck, supporting) before submit. The create method accepts whatever PDF inputs are posted.
