# Technical documentation — siliconscan

Evidence date: 2026-10-03. Revision: `master` `56c90dc065177466747b45ea43b8b533007877f2` (2024-01-31).

Remote: `git@bitbucket.org:overdrive/siliconscan.git`. Only branch: `master`.

## Architecture

CodeIgniter 4.4.1 MVC application. Front controller is `public/index.php`. Auto-routing is enabled (`Routes.php` `setAutoRoute(true)`), with a smaller set of explicit routes for login, users, labels, menu management, and the organisations resource.

```text
public/index.php
  → Config/Filters.php
       isLoggedIn (session) except login, register, organisations, tracking-pixel
       isGranted (menu url + user_access) except those plus home, blocked, Welcome/*, chatGPT/*
  → Controller
  → Model / query builder
  → MySQL (database name labelsystem)
```

Global CSRF and honeypot filters exist as aliases and are commented out of `$globals`.

A `Cors` filter class exists at `app/Filters/Cors.php` and is not registered in `Filters.php`.

## Application structure

| Path | Contents |
|---|---|
| `app/Controllers` | Welcome, Home, Users, Labels, label admin, Email, File, organisations, cars, master file, tests, ChatGPT, developers |
| `app/Models` | One model per area listed above, plus JWT is a library not a model |
| `app/Views` | `label/`, `users/`, `common/`, `layouts/` (including SAWIS header and menu), `projects/` (ChatGPT, master file, site health, Adminer), `developers/` |
| `app/Database/Migrations` | 2022-11-20 through 2024-01-29 |
| `app/Database/Seeds` | Users (calls the other user seeds), roles, menus, organisations, car type, uploaded file |
| `app/Helpers` | mail, menu, user access, chatgpt, salesforce (empty body), africastalking (commented body) |
| `app/Libraries/JwtLibrary.php` | HMAC JWT |
| `app/Common.php` | `statusDisplay`, `contentTypeDisplay`, `formatFieldName`, `labelFormsTamperProtection` |
| `app/Config` | Standard CI4 config plus `Routes.php` and `Filters.php` |
| `env` | Sample environment. Copied to `.env` by the README. Not a live secret store by itself, but it does set a real encryption key and a database password field |
| `vendor/` | Dependencies, including CodeIgniter 4.4.1. No `composer.lock` |
| `tests/` | CI4 examples and `HealthTest` |
| `writable/` | CI4 writable tree |
| `builds/` | Present at the root. Pipeline does not publish it |

`sonarqube_scan.bat` is not in this commit.

## Frameworks and languages

- PHP `^8.0` (`composer.json`). README states PHP 8.0.
- CodeIgniter 4.4.1 (`CI_VERSION` in `vendor/codeigniter4/framework/system/CodeIgniter.php`). Constraint in composer is `^4.0`.
- JavaScript in views (jQuery-style handlers on label screens). No separate frontend package manifest was found.
- mPDF 8.x for certificates.
- PHPSpreadsheet is required. Car import uses uploaded files; this document does not claim every import path was traced.
- OpenAI PHP client `orhanerday/open-ai`.
- AWS SDK is required. No `Aws\` reference was found under `app/`.

## Database

Driver: MySQLi. Sample `env`:

- host `localhost`
- database `labelsystem`
- username `root`
- password empty in the sample file

Session driver: `CodeIgniter\Session\Handlers\DatabaseHandler`, save path `sessions`, match IP true, time to update 1440.

Tables created by migrations (names only):

- Identity and menus: `users`, `user_role`, `user_access`, `user_menu`, `user_submenu`, `user_menu_category`, `sessions`
- Labels: `label`, `label_note`, `label_log`, `label_status`, `label_provision`, `file`, `meeting_groups`, `user_groups`, `label_email_template`, `sent_queue`
- Other: `organisations`, `car`, `car_type`, `car_tier`, `car_service`, `car_cost_category_a`, `car_cost_category_b`, `car_cost_category_c`, `app_session`, `app_session_log`, `master_file_model`, `master_file_overland`, `uploaded_file`, `media_file`, `test`, `test_log`, `settings`, `language`, `cronjob_actions`, `cronjob_log`

Later label columns: `meeting_group_id`, `provisions` (varchar 256), `alcohol_percentage` changed from int to `DECIMAL(10,2)`, file `file_approved`, file `file_version`, `sent_queue.tracking_code`. `label_status` gains `group` and a parent field (migration `AddParentChildFieldToLabelStatus`).

Seed `Users` inserts the developer tester, then calls `UserAccess`, `UserMenu`, `UserMenuCategory`, `UserRole`, `Organisations`, `CarType`, `UploadedFile`. It does not seed labels or label statuses.

README setup commands: `php spark db:create`, `php spark migrate`, `php spark db:seed users`, `php spark key:generate`, `php spark serve`.

## HTTP surface

Explicit routes in `app/Config/Routes.php`:

| Method and path | Controller |
|---|---|
| GET `/` and POST `GetLogin` | `Welcome::index` |
| GET `blocked` | `Welcome::forbiddenPage` |
| GET/POST `register` | `Welcome::register` / `registration` |
| GET `home` | `Home::index` |
| GET `chatGPT`, GET `chatGPT/sent` | `ChatGPT` |
| GET `tracking-pixel` | `EmailPixelTracking::index` |
| `users/*` permission and CRUD routes | `Users` |
| GET `system_settings` | `SystemSettings::index` |
| `labels/status`, `labels/groups`, `labels/email-templates` | Label admin controllers |
| Menu management posts and GET `menuManagement`, `crudGenerator` | Developers |
| GET `users/userlist` | `Users::userList` |
| `resource('organisations')` | `Organisations` |

Because auto-routing is on, other public methods are reachable as `/controller/method` when the menu filter allows the first segment. Label actions such as `labels/createLabel` and `labels/approveLabel` rely on that. The first segment `labels` must exist in `user_menu`.

`app.baseURL` is `''` in `Config/App.php`. `indexPage` is `index.php`.

## Authentication and authorization

Session keys after login: `username`, `role`, `isLoggedIn`, `userID`, `labelID`.

Passwords: `password_hash` / `password_verify`.

Menu authorization: `MenuModel::getMenuByUrl` on URI segment 1, then `UserModel::checkUserAccess`.

Organisations API: custom JWT, HS256, secret the literal string `secret`, expiry 6000 seconds from `time()`. Login body is JSON `name` and `api_key`. Bearer token on the other methods. List JSON key is `incident_type`.

JWT validation re-encodes the decoded header JSON. A token that was accepted in one environment can fail if header encoding differs. That is how `JwtLibrary::is_jwt_valid` is written.

## Email

`Config/Email.php`: protocol `smtp`, host `smtp.overdriveqa.co.za`, port 587, user `admin@overdriveqa.co.za`. The SMTP password is stored in that PHP file. It is not copied into this document. Treat it as exposed and rotate it.

`sendMail` sets from `admin@overdriveqa.co.za` / `Admin Overdrive`, sends, then `printDebugger(['headers'])`.

Templates and the queue are described in the PRD workflow W11.

## Files and storage

Label PDFs: directory `uploads/` relative to the process working directory (not clearly rooted at `writable/` or `public/`). Approval certificates: `uploads/approvalcerts/`, created with mode `0777` if missing.

`SIDEFILEPATH` is used by the Selenium runner and is not defined in `app/Config/Constants.php` or `env`.

No S3 upload call was found under `app/` despite the AWS SDK dependency.

## Background work

Tables `cronjob_actions` and `cronjob_log` exist. No `app/Commands` spark commands were found in the file search. No queue worker is configured in `bitbucket-pipelines.yml`. `sent_queue.processed` is set to 1 in the same request that sends mail, so the table is a log, not a worker queue, in the code that was read.

## Integrations

| Integration | Where configured | Active in code |
|---|---|---|
| SAWIS Online links | `sawis-menu.php` host switch | Links only |
| SonarQube | `sonar-project.properties`, pipeline variables `SONAR_HOST_URL`, `SONAR_TOKEN` | Pipeline step `sonarsource/sonarqube-scan:2.0.1` on branch `master` |
| OpenAI | `chatgpt_helper.php`, commented `OPENAI_API_KEY` | Called from `ChatGPT::sent` |
| SMTP | `Config/Email.php` | Called from label approve and reject |
| Salesforce | `salesforce_helper.php` | Function body commented out |
| Africa's Talking | `africastalking_helper.php` | Function body commented out. A commented sandbox key is in that file and is not repeated here |
| Selenium IDE runner | `Test::runsideproject` | `shell_exec('selenium-side-runner ...')` |

Pipeline image is `maven:3.3.9`. Clone depth is `full`. The only step is the Sonar scan. There is no test, migrate, or deploy step.

Sonar project key: `overdrive_label_system_AYr5qv21RY8iIL6fFGD4`.

## Security notes (evidence, not a review)

These are facts in the tree. They need a human decision. Secret values are omitted on purpose.

- SMTP password committed in `app/Config/Email.php`.
- Encryption key committed in `env` (`encryption.key`).
- JWT HMAC secret is the word `secret`.
- Users seeder contains a known weak password for `jaco@overdrive.co.za`.
- Public `register` assigns role 1 (the id that can open label admin).
- CSRF filter disabled.
- Organisations API is outside the session filter.
- `shell_exec` of a runner with the posted project name.
- Approval PDF owner password hardcoded in `labelApprovalPDF`.
- Certificate directory created mode `0777`.
- Adminer PHP source is stored under `app/Views/projects/`. No controller return of that view was found. It is not the web root, which is `public/`.
- `sendMail` prints the mail debugger.

## Local development

README:

1. `composer update`
2. Copy `env` to `.env` and set base URL and database.
3. `php spark db:create`
4. `php spark migrate`
5. `php spark db:seed users`
6. `php spark key:generate`
7. `php spark serve`

Apache alternative in the README: vhost `adminOverdrive.local` with document root `c:/wamp64/www/adminOverdrive/public`. That path is the starter's path, not `siliconscan`.

`env` sets `CI_ENVIRONMENT = development` and `CI_DEVELOPER = jaco`. The SAWIS menu does not read `CI_DEVELOPER`. It reads `base_url()`.

## Testing

`phpunit.xml.dist` bootstraps CodeIgniter's test bootstrap, covers `app/` except views and `Routes.php`, and sets `app.baseURL` to `http://example.com/`.

`tests/unit/HealthTest.php` asserts `APPPATH` is defined and that a base URL is present in `.env` or config. `App.php` has an empty `baseURL`, and the sample `env` does not set `app.baseURL`, so this test fails unless `.env` sets it. The other tests under `tests/` are framework examples (`ExampleSessionTest`, `ExampleDatabaseTest`).

No label, auth, or API test was found. This onboarding did not execute PHPUnit.

## Deployment and monitoring

No Dockerfile, no server panel config, and no deploy step in the pipeline were found.

Monitoring that is present: SonarQube on `master`, plus the in-app site-health controller and test-log tables. How site health is calculated was not fully read.

Error views are the CodeIgniter defaults under `app/Views/errors`.

## Configuration inventory

| Setting | Location | Note |
|---|---|---|
| `CI_ENVIRONMENT` | `env` | `development` |
| Database | `env` | `labelsystem` on localhost |
| Session | `env` | database handler, match IP |
| `encryption.key` | `env` | set; do not copy into tickets |
| SMTP | `app/Config/Email.php` | host, user, and password in PHP |
| Base URL | `app/Config/App.php` | empty |
| Sonar | `sonar-project.properties` | project key only |
| Pipeline secrets | Bitbucket variables | `SONAR_HOST_URL`, `SONAR_TOKEN` (named, not stored in git) |

## Source locations for the label workflow

| Concern | File |
|---|---|
| Routes | `app/Config/Routes.php` |
| Role list and form | `app/Controllers/Labels.php` |
| Status transitions | `app/Models/LabelModel.php` |
| Status names that disagree | `app/Common.php` `statusDisplay`, `app/Controllers/Home.php` |
| Certificate | `Labels::labelApprovalPDF` |
| Mail | `app/Controllers/Email.php`, `app/Helpers/mail_helper.php` |
| Pixel | `app/Controllers/EmailPixelTracking.php` |
| Filters | `app/Config/Filters.php` |
