Koovira Dev Docs
00-architecture/bootstrap-and-config.md

Bootstrap, Config & Database Connection

Validated against code on 2026-09-06.

How a Koovira request boots, where configuration comes from, and how it reaches the database. Everything else in this tree assumes the facts on this page.


Two bootstrap entry points

IncludeUse it forWhat it emits
config/common_includes.phpFull pages. The normal front-end bootstrap.Session, DB handle, plus five platform-wide inline <script> blocks (client lockdown, layout guard, module guard).
config/baseconfig/config.phpHeadless / API / read-only pages.The narrow bootstrap — just enough to get a DB handle and core defines. No inline JS.

config/baseconfig/config.php is the minimum that yields a working $conn. The public devportal/index.php deliberately uses the narrow one (it is a logged-out, no-JS page) — a good example of when not to pull the full bootstrap. Validation scripts in _tools/ also use the narrow path.

config/baseconfig/config.php pulls in config/baseconfig/coredb.php, which is where the DB connection and the guarded PDO subclass live.


Environment resolution

coredb.php decides local vs production from the request host ($_SERVER['HTTP_HOST'], defaulting to localhost under CLI):

  • Local when the host is localhost, 127.0.0.1, ::1, or ends in .local / .test /

.localhost (a trailing :port is stripped before the suffix check — a fix for a real bug where koovira_live_new.test:8899 fell through to the production branch and sent a local visitor to koovira.com; see coredb.php:266-285).

  • Production otherwise.

DB credentials and the site URL come from environment variables via kv_env() (loaded by config/baseconfig/env_loader.php + config/global_env.php), never from hard-coded literals. The literal defaults in coredb.php are last-resort only.

Validated local values (this machine, 2026-09-06):

DefineValueSource
DB_NAMEkoovira_newmasterdb_opt.env DB_NAME (code default is koovira_new_live)
DB_HOST127.0.0.1.env DB_HOST
DB_PORT3306.env DB_PORT (code default 3307)
engineMariaDB 10.11.14live SELECT VERSION()
tables1,797live information_schema
Drift caught here: the code defaults (koovira_new_live, port 3307) are not the live values — .env overrides them to koovira_newmasterdb_opt on 3306. Always read the live define, never the code default. This is why documentation must be validated, not assumed.

Koovira is MariaDB-only for local dev: coredb.php:563-574 blocks MySQL 8.x unless KV_ALLOW_LOCAL_MYSQL=1.


KVGuardedPDO — the guarded connection

The $conn handed to all code is not a plain PDO; it is KVGuardedPDO (coredb.php:136-232), a PDO subclass with three guarantees:

  1. No DDL from the web tier. exec(), query(), and prepare() refuse any statement

starting with CREATE / ALTER / DROP / TRUNCATE / RENAME (comments stripped first) unless kv_schema_mutations_allowed() is true — i.e. KV_SCHEMA_MIGRATION_MODE is set. A blocked prepare() throws PDOException('Schema mutations are disabled for web requests'); a blocked exec()/query() returns 0/false. All schema changes go through the padmin-gated migration system, never through app/cron/deploy code.

  1. Optional SQL audit. When KV_SQL_AUDIT_TABLES is on, every statement's verb + touched

tables are appended to a daily JSON log in the temp dir (kv_sql_audit_log()).

  1. Legacy-column shim. query()/prepare() rewrite cm.id → cm.rowId (a compatibility

patch for a renamed primary key on the customer_master alias cm).

PDO options set at connect (coredb.php:512-519): ERRMODE_EXCEPTION, FETCH_ASSOC default, EMULATE_PREPARES=false, buffered queries, SET NAMES utf8mb4.

Corrected 2026-09-06: this and the citations below were originally 497-504; a same-day code change (the KV_QA_TESTDB money-test-double branch, ~30 new lines around coredb.php:375) shifted everything after it down. Re-verified against the live file.

Clock alignment (money-critical)

After connecting, coredb.php:554-561 runs SET time_zone = '+05:30' so the DB session clock matches the app's Asia/Kolkata. This is done after connect (not via INIT_COMMAND) because persistent connections reuse pooled handles and INIT_COMMAND only fires on a new one. Revert without a deploy via KV_DB_TIMEZONE_IST=0. Rows written before this cutover (2026-08-18) are UTC; rows after are IST — relevant when reading historical timestamps.


Primary-key convention

Most tables use rowId as the primary key, not id (see the cm.id→cm.rowId shim above, and every SHOW COLUMNS in this tree). Foreign-ish references are by convention (buyer_id, seller_id, company_id, order_id, …) and are not enforced by declared FK constraints on most tables — joins are convention-based. Validate the exact column name per table; do not assume id.


Central includes loaded at bootstrap

coredb.php ends by requiring two platform services so they exist in every DB-touching context:

  • forms/doaction_helpers.php — shared helpers for the form-action dispatch layer (see the

module code maps; most writes flow through forms/doaction_*.php).

  • classs/KvFileStorage.php — the central upload sink (kv_store_upload()), FTP transport with

local fallback, wired so uploads are consistent platform-wide.

There is also a kv_payload_store overflow table (coredb.php:618-773): large text columns can be offloaded to kv_payload_store (gzip, dedup by hash) and hydrated back on read via kv_payload_store_hydrate_row(). If a text column reads as NULL but the feature clearly stored something, check kv_payload_store before concluding data loss.


See also