# PRD — skeleton (Silicon Skeleton)

Evidence date: 2026-10-03. Source: `https://bitbucket.org/overdrive/skeleton.git`, `master` `43b5796` (2020-02-27, Jaco Brits, "Update repo").

Provenance labels: **Documented**, **Implemented**, **Inferred**, **Planned**, **Unknown**.

No formal PRD existed in the repository or in `silicon_ark/skeleton/` before this file. Requirements below are recovered from the README, routes, the one controller, the one application view, and framework config that ships in the tree. They are not a new product decision.

The commented database examples in `env` (`database` `ci4`, user `root`, password `root`) are the upstream template. They are commented out. They are not active credentials, and they are not repeated as values to use.

## Product purpose

A person requests the site root. The application answers with the body of `app/Views/login/login.php`, which is the text `jaco`. There is no form, no password check, and no second page.

**Implemented** in `app/Config/Routes.php`, `app/Controllers/Home.php`, and `app/Views/login/login.php`.

The README titles the project **Silicon Skeleton CodeIgniter 4 Application Starter** and describes a different product: password login, 2FA, a dashboard with a left menu, a weather widget, a profile with image upload, and user/role management. That description is **Documented** in `README.md`. It is not implemented. Whether that description is the product to build is **Unknown / Requires Confirmation**.

`app/Views/welcome_message.php` is the stock CodeIgniter welcome page. `Home::index` does not return it.

## Users and roles

| Actor | What the tree shows | Provenance |
|---|---|---|
| Any HTTP client | `GET /` returns the login view. No session is started by application code. `BaseController` leaves the session line commented out. | **Implemented** |
| Logged-in user | Named in the README (profile shows "Logged in user details" and a security group). No login action, user record, or session flag exists. | **Documented**. Not implemented. |
| Someone who manages users | README says this person can add and delete users and create roles or groups. No such screen, model, or permission check exists. | **Documented**. Not implemented. |
| CLI operator | Can run `spark` with `php-cli`. The script exits if the SAPI starts with `cgi`. | **Implemented** in `spark` |

No role names are defined in code. `app/Filters/` contains only `.gitkeep`. `Filters.php` has no `isLoggedIn` entry. The comment in that file mentions `isLoggedIn` only as a framework example.

How a person would be identified, and which groups exist, is **Unknown / Requires Confirmation**.

## Major workflows

### 1. Open the site root

Trigger: HTTP `GET /`.

Actor: any client that can reach the front controller.

Inputs: none read by `Home::index`.

Behaviour:

- `public/index.php` stops with a plain text message if PHP is older than 7.2.
- Otherwise it loads `app/Config/Paths.php` and the framework bootstrap at `vendor/codeigniter4/framework/system/bootstrap.php`.
- `app/Config/Routes.php` maps `/` to `Home::index`. Auto-routing is on (`setAutoRoute(true)`), so `/home` and `/home/index` reach the same method. No other controller class exists under `app/Controllers/`.
- `Home::index` returns `view('login/login')`.

Output: response body `jaco` (the view is `echo "jaco";`). No layout, no assets, no redirect.

Validation: none.

Error conditions:

- PHP below 7.2: `public/index.php` dies before the framework boots. **Implemented**.
- Framework missing (`vendor/` not installed): the `require` of `bootstrap.php` fails. **Inferred** from `Paths.php`. The README does not describe this failure.
- Unknown controller or method: CodeIgniter's 404. `set404Override()` is called with no handler, so the stock error view is used. Views live in `app/Views/errors/`. **Implemented** as configuration. The exact HTML is the stock error view plus framework behaviour.
- `app/` requested directly over Apache: `.htaccess` denies the request (`Require all denied`, and `Deny from all` when `authz_core_module` is absent). `app/index.html` is a static "403 Forbidden" page for directory access. **Implemented**.

### 2. Local run (documented)

Trigger: operator follows `README.md` "Quick run".

Documented steps:

1. Copy `env` to `.env` and set `baseURL` and database settings. The README says to keep `.env` out of git. `.gitignore` lists `.env`. **Documented** and **Implemented** as the ignore rule. No `.env` is committed.
2. Run `php spark serve`.
3. README says that launches `http://localhost:8080`. `app/Config/App.php` sets `$baseURL` to `http://localhost:8080/`.

`spark` itself does not contain a `serve` command. That command is provided by the framework package after `composer install`. The README does not mention Composer. The install step is **Inferred**.

The README "Installation & updates" lines (`git add`, `git commit`, `git pull`, `git push`) are a git loop. They do not install dependencies and they do not configure the app.

### 3. Sign in, 2FA, dashboard, profile, user management

Trigger: none in code.

The README lists these as the basic description of the product. There is no route, controller method, form, model, or migration for them. Status: **Documented**, not implemented. Do not treat them as current behaviour.

## Functional requirements

### FR-001 — Site root returns the login view

Requirement: An HTTP GET of `/` renders `app/Views/login/login.php`.

Who: any client.

Trigger: `GET /`. Also `GET /home` and `GET /home/index` while auto-routing stays on.

Inputs: none.

Expected behaviour: `Home::index` returns that view.

Output: the characters `jaco`.

Validation: none.

Error conditions: see workflow 1.

Dependencies: Composer-installed `codeigniter4/framework` `^4`, PHP `>=7.2`.

Evidence: `app/Config/Routes.php`, `app/Controllers/Home.php`, `app/Views/login/login.php`.

Implementation: **Implemented**.

Confidence: High.

### FR-002 — PHP 7.2 floor on HTTP entry

Requirement: The HTTP front controller refuses to run below PHP 7.2.

Who: the process serving `public/index.php`.

Trigger: PHP version comparison at the top of that file.

Expected behaviour: `die` with the message `Your PHP version must be 7.2 or higher to run CodeIgniter. Current version: ` plus `phpversion()`.

Evidence: `public/index.php`, `composer.json` (`"php": ">=7.2"`), `README.md` "Server Requirements".

Implementation: **Implemented** in the front controller and **Documented** in the README. `spark` does not repeat this version check before loading the framework.

Confidence: High.

### FR-003 — Local base URL default

Requirement: The configured base URL is `http://localhost:8080/`.

Who: framework URL generation, when it reads `Config\App`.

Trigger: application boot.

Expected behaviour: `$baseURL` in `app/Config/App.php` is that value. The README tells the operator to tailor base URL in `.env`. The committed `env` template leaves `# app.baseURL = ''` commented, so an untouched `.env` does not override the PHP config.

Evidence: `app/Config/App.php`, `README.md`, `env`.

Implementation: **Implemented** default. Override via `.env` is **Documented** and not present in git.

Confidence: High for the committed default. Whether any deployed copy changed it is **Unknown**.

### FR-004 — Environment file stays uncommitted

Requirement: `.env` is not stored in git.

Who: anyone committing.

Trigger: git status.

Expected behaviour: `.gitignore` contains `.env`. The sample file committed under the name `env` is the template.

Evidence: `.gitignore`, `env`, `README.md`.

Implementation: **Implemented** ignore rule. **Documented** setup instruction.

Confidence: High.

### FR-005 — Password login and 2FA

Requirement (README only): The login screen is password protected and uses 2FA.

Who: a user, according to the README. The code does not identify one.

Trigger: not implemented.

Inputs, validation, outputs, errors: not present.

Evidence: `README.md` "Basic Description". No matching controller, view fields, library, or config key.

Implementation: **Documented**. Not implemented.

Confidence: High that it is absent. Whether it is required is **Unknown / Requires Confirmation**.

### FR-006 — Dashboard, profile, and user administration

Requirement (README only): After login, a dashboard offers Dashboard, Profile, Documentation, and Settings (User Management, User group), plus a weather widget. Profile shows the logged-in user, an uploaded image, and their security group. User management adds and deletes users and creates roles or groups.

Evidence: `README.md` only. `app/Models/`, `app/Database/Migrations/`, `app/Database/Seeds/`, `app/Libraries/`, and `app/Filters/` are empty aside from `.gitkeep`. `writable/uploads/` contains only `index.html`.

Implementation: **Documented**. Not implemented.

Confidence: High that it is absent. Product intent: **Unknown / Requires Confirmation**.

### FR-007 — MySQL connection settings exist and are unused

Requirement: A default database group is configured for MySQLi on `localhost` port `3306`, with empty username, password, and database name. A `tests` group uses SQLite3 `:memory:` when the environment is `testing`.

Who: the framework, if application code asks for a database. No application code does.

Trigger: construction of `Config\Database`. When `ENVIRONMENT === 'testing'`, `$defaultGroup` becomes `tests`.

Evidence: `app/Config/Database.php`. README names MySQL with no further settings.

Implementation: **Implemented** as config. No schema, no queries. **Documented** only as the word "MYSQL" in the README.

Confidence: High.

### FR-008 — Debug toolbar on responses

Requirement: The `toolbar` filter runs after every request. Outside production, `pre_system` also subscribes the database query collector and calls `Services::toolbar()->respond()`.

Evidence: `app/Config/Filters.php` (`after` includes `toolbar`), `app/Config/Events.php`.

CSRF and honeypot aliases exist and are commented out of `$globals`. They do not run.

Implementation: **Implemented** as starter config. Whether the toolbar filter emits HTML when `ENVIRONMENT` is `production` is decided in the framework package, which is not in this commit. That part is **Unknown** without `vendor/`.

Confidence: High for the filter registration.

### FR-009 — App and writable trees are not the web root

Requirement: HTTP access to `app/` and `writable/` is denied by Apache `.htaccess`. `public/.htaccess` rewrites non-file requests to `index.php`, redirects trailing slashes, and, when HTTPS is off, redirects `www.` hosts to the non-www host over `http`.

Evidence: `app/.htaccess`, `writable/.htaccess`, `public/.htaccess`, `app/index.html`.

Implementation: **Implemented** as Apache config. Behaviour on nginx or IIS is **Unknown / Requires Confirmation**. The `www` redirect runs only when HTTPS is off, and its substitution is `http://`, not the incoming scheme.

Confidence: High for the file contents.

## Integrations

No outbound integration is called from `app/`.

| Name | What the repo shows | Direction | Provenance |
|---|---|---|---|
| CodeIgniter 4 framework | Composer package `codeigniter4/framework` `^4`. System path `vendor/codeigniter4/framework/system`. | Dependency pulled at install time | **Implemented** in `composer.json` and `Paths.php`. Installed tree not committed. |
| Packagist (default Composer repo) | No `repositories` key in `composer.json`. | Outbound at `composer install` / `update` | **Inferred** from the absence of a custom repository. |
| CodeIgniter GitHub | `builds` can add VCS repo `https://github.com/codeigniter4/codeigniter4` and switch the require to `dev-develop` when run as `php builds development`. Committed `composer.json` does not contain that repository. | Only if someone runs the script | **Implemented** as the script. Not applied in the committed manifest. |
| Upstream support links | `composer.json` `support` lists `http://forum.codeigniter.com/`, `https://github.com/codeigniter4/CodeIgniter4`, and a Slack URL. | Documentation links, not runtime calls | **Documented** |
| Bootstrap 4 | README link to `https://getbootstrap.com`. No asset files. | None at runtime | **Documented**. Not implemented. |
| Weather | README "Weather widget". No provider, URL, or widget view. | None | **Documented**. Not implemented. |
| MySQL / MariaDB | Driver name `MySQLi` only. | None until credentials and a schema exist | **Implemented** as an empty config. |

Authentication to external systems: none.

Failure behaviour for Composer or the database: not described in project docs. If the database settings stay empty and a future feature queries MySQL, failure behaviour is **Unknown**.

## Constraints

- PHP `>= 7.2` with the extensions named in the README (**Documented** and, for the version, **Implemented** on the HTTP entry).
- Web entry is `public/index.php`. `app` and `writable` are denied for Apache (**Implemented**).
- `$indexPage` is `index.php` while `public/.htaccess` rewrites to `index.php` without requiring it in the URL. Generated links may still include `index.php`. Both settings are **Implemented**. Whether that combination is intended is **Unknown / Requires Confirmation**.
- `$forceGlobalSecureRequests` is `false`. Cookie `cookieSecure` and `cookieHTTPOnly` are `false`. CSRF filter is off. Encryption key in `app/Config/Encryption.php` is an empty string. These are the committed starter values, not a written security policy.
- Default locale `en`. `$negotiateLocale` is `false`. Timezone `America/Chicago`. **Implemented** in `app/Config/App.php`. The README does not mention locale or timezone. Whether production should use another zone is **Unknown / Requires Confirmation**.
- Session driver, if something starts a session, is `CodeIgniter\Session\Handlers\FileHandler`, cookie `ci_session`, expiration 7200 seconds, save path `writable/session`, match IP `false`, id regeneration every 300 seconds, regenerate-destroy `false`. Application code does not start a session. **Implemented** as config only.
- `CI_ENVIRONMENT` in the committed `env` file is commented (`# CI_ENVIRONMENT = production`). No `.env` is in git. The default used when the variable is unset lives in the framework package. **Unknown** for this `^4` constraint, because `vendor/` and `composer.lock` are absent.
- `contributing.md` tells contributors to target the CodeIgniter `develop` branch and to GPG-sign commits. This repo's only branch is `master`. That file is upstream text. It is not evidence of an Overdrive review rule.
- License: `composer.json` says MIT. `LICENSE` copyright 2018 CodeIgniter 4. `license.txt` copyright 2014-2019 British Columbia Institute of Technology and 2019-2020 CodeIgniter Foundation. Both texts are MIT.

## Discrepancies

| Topic | README or other doc | Code at `43b5796` |
|---|---|---|
| Product surface | Login, 2FA, dashboard, profile, user management, weather | One view that prints `jaco` |
| Bootstrap 4 | Listed under "Tech Stack" | No Bootstrap files |
| MySQL | Listed under "Tech Stack" | Empty MySQLi config, no schema, no queries |
| Install | git clone / commit / pull / push, then copy `env`, then `php spark serve` | Boot requires `vendor/codeigniter4/framework`, which the README never installs |
| contributing.md | Contribute to CodeIgniter 4 on GitHub `develop` | This repository is `overdrive/skeleton` and has only `master` |
| Sonar | Not in the README | Not in git either. A 2026-09-28 folder listing named `sonar-project.properties` and `sonarqube_scan.bat`. **Unknown / Requires Confirmation** |
| Welcome page | Not mentioned | `app/Views/welcome_message.php` is present and unused by `Home` |

## What requires confirmation

- Is the README "Basic Description" still the product, or was it only a sketch?
- Who are the users, and what is a security group?
- Is there a deployed environment, and what is its base URL, database, and PHP version?
- Should timezone, HTTPS, CSRF, and cookie flags stay at the starter defaults?
- Were the Sonar files local-only on the scan host?
- Which name is canonical: Silicon Skeleton, `skeleton`, or `codeigniter4/appstarter`?
