{"id":36992349,"url":"https://github.com/citomni/kernel","last_synced_at":"2026-01-13T23:44:31.193Z","repository":{"id":317062647,"uuid":"1061433469","full_name":"citomni/kernel","owner":"citomni","description":"CitOmni Kernel is a tiny PHP 8.2+ core that boots CitOmni apps. It provides the minimal App container, deep read-only config wrapper, deterministic vendor-\u003eproviders-\u003eapp merging, explicit service maps ($app-\u003eid), optional compiled caches, and a fail-fast philosophy. No magic-HTTP/CLI own delivery and error handling.","archived":false,"fork":false,"pushed_at":"2025-11-01T21:38:04.000Z","size":201,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-27T13:42:55.730Z","etag":null,"topics":["deterministic","framework","framework-php","frameworks","high-performance","kernel","lazy-loading","lazy-loading-components","lightweight","lightweight-framework","low-overhead","php-framework","php8","service-locator","service-map"],"latest_commit_sha":null,"homepage":"https://www.citomni.com/","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/citomni.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-21T22:22:45.000Z","updated_at":"2025-11-01T21:38:08.000Z","dependencies_parsed_at":"2025-10-24T02:23:07.449Z","dependency_job_id":"a05a4238-8ac8-491b-a439-11eada106757","html_url":"https://github.com/citomni/kernel","commit_stats":null,"previous_names":["citomni/kernel"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/citomni/kernel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/citomni%2Fkernel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/citomni%2Fkernel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/citomni%2Fkernel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/citomni%2Fkernel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/citomni","download_url":"https://codeload.github.com/citomni/kernel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/citomni%2Fkernel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28405190,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T21:51:37.118Z","status":"ssl_error","status_checked_at":"2026-01-13T21:45:14.585Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["deterministic","framework","framework-php","frameworks","high-performance","kernel","lazy-loading","lazy-loading-components","lightweight","lightweight-framework","low-overhead","php-framework","php8","service-locator","service-map"],"created_at":"2026-01-13T23:44:31.124Z","updated_at":"2026-01-13T23:44:31.177Z","avatar_url":"https://github.com/citomni.png","language":"PHP","readme":"# CitOmni Kernel\n\n\u003e Ultra-lean application kernel for CitOmni-based apps.  \n\u003e PHP 8.2+, PSR-4, deterministic boot, zero runtime \"magic\".\n\nThe **kernel** is the tiniest possible layer that:\n\n* builds a **read-only configuration object** from your app + providers,\n* builds a **service registry** (simple map -\u003e lazy singletons),\n* exposes one thing you use everywhere: **`$app`**.\n\nIt does **not** ship HTTP/CLI controllers, routers, error handlers, etc. Those live in the `citomni/http` and `citomni/cli` packages. The kernel stays infrastructure-only and small.\n\n\u003e **Pillars:** _Deterministic boot_ · _Low overhead_ · _TTFB as a KPI_ · _Green by design_\n\n---\n\n## Why this kernel exists\n\n* **Deterministic boot.** The kernel composes config and services in a *predictable*, \"last-wins\" order. No namespace scanning, no environment-dependent surprises.\n* **Zero magic, low overhead.** Arrays + a read-only config wrapper + a minimal service locator. Lazy singletons per `$app` instance.\n* **Mode-aware.** HTTP and CLI have different baselines. The kernel is mode-agnostic and takes a `Mode` enum so each delivery layer owns its concerns.\n* **Upgrade-safe apps.** Config/services live in your app, providers opt-in via whitelist. No vendor overrides inside your app code.\n* ♻️ **Green by design** - lower memory use and CPU cycles -\u003e less server load, more requests per watt, better scalability, smaller carbon footprint.\n\n---\n\n## Who should choose CitOmni?\n\n* **TTFB is a KPI.** You care about cold-start and p95 response times more than “batteries included.”\n* **Resource efficiency matters.** You track memory per request and CPU ms per request; overhead ≈ wasted budget.\n* **Determinism \u003e magic.** You want predictable boot order and explicit overrides (no reflection/autowiring surprises).\n* **Small, fast, edge-friendly.** Runs well in tiny containers and shared hosting; cache warmers are first-class.\n* ♻️ **Green by design.** Fewer CPU cycles and lower memory footprint per request reduce energy use and emissions.\n\n---\n\n### Green by design\n\nCitOmni's \"Green by design\" claim is empirically validated at the framework level.\n\n**Why it’s greener:** constant-driven merges (no reflection), zero boot scanning, and warmed caches minimize CPU cycles and memory churn per request.\nThis yields:\n* **Lower energy per 1k requests** (less CPU time),\n* **Higher requests-per-watt** (better consolidation density),\n* **Smaller instances/containers** (lower embodied \u0026 operating carbon).\n\nThe core runtime achieves near-floor CPU and memory costs per request on commodity shared infrastructure, sustaining hundreds of RPS per worker with extremely low footprint.\n\nSee the full test report here:\n[CitOmni Docs → /reports/2025-10-02-capacity-and-green-by-design.md](https://github.com/citomni/docs/blob/main/reports/2025-10-02-capacity-and-green-by-design.md)\n\n\n---\n## Installation\n\nRequire the kernel from your application:\n\n```bash\ncomposer require citomni/kernel\n```\n\nYour app will also require `citomni/http` and/or `citomni/cli` for delivery layers.\n\n**Composer autoload (in your app):**\n\n```json\n{\n\t\"autoload\": {\n\t\t\"psr-4\": {\n\t\t\t\"App\\\\\": \"src/\"\n\t\t}\n\t},\n\t\"config\": {\n\t\t\"optimize-autoloader\": true,\n\t\t\"apcu-autoloader\": true\n\t},\n\t\"suggest\": {\n\t\t\"ext-apcu\": \"Speed up Composer class loading in production\"\n\t}\n}\n```\n\nRun:\n\n```bash\ncomposer dump-autoload -o\n```\n\n---\n\n## Required constants \u0026 preconditions\n\nDelivery layers (HTTP/CLI) are expected to define a few constants early in the entrypoint so the kernel can resolve paths and caches deterministically:\n\n```php\ndeclare(strict_types=1);\n\n// Environment selection (affects config overlays)\ndefine('CITOMNI_ENVIRONMENT', getenv('APP_ENV') ?: 'dev'); // 'dev' | 'stage' | 'prod'\n\n// App/public roots (HTTP)\ndefine('CITOMNI_PUBLIC_PATH', __DIR__);           // no trailing slash; /public path\ndefine('CITOMNI_APP_PATH', \\dirname(__DIR__));    // app root; no trailing slash\n\nrequire CITOMNI_APP_PATH . '/vendor/autoload.php';\n```\n\n\u003e CLI entrypoints typically define `CITOMNI_APP_PATH` and `CITOMNI_ENVIRONMENT`. `CITOMNI_PUBLIC_PATH` is HTTP-only.\n\u003e **Security note:** Production builds should disable any dev-only providers and never rely on auto-detection for `http.base_url`.\n\n---\n\n## Concepts \u0026 responsibilities\n\n* **Kernel responsibilities**\n\n  * Build **config** by merging: *vendor baseline (by mode) -\u003e providers (whitelist) -\u003e app -\u003e env overlay*.\n  * Build **service map** in the same precedence.\n  * Expose `$app-\u003ecfg` (deep, read-only), `$app-\u003e__get('id')` for services, and utility getters.\n  * Prefer **compiled caches** when present (`/var/cache/cfg.{http|cli}.php` and `/var/cache/services.{http|cli}.php`) to minimize runtime overhead.\n\n* **Not the kernel's job**\n\n  * HTTP routing, sessions, controllers, templates.\n  * CLI command runner, scheduler.\n  * Error handlers (installed by the **delivery-layer/vendor packages**: `citomni/http`, `citomni/cli`).\n  * Business/domain code.\n\n## Further reading\n\n- **Runtime / Execution Mode Layer** - architectural rationale for HTTP vs CLI, baseline ownership, deterministic config/service merging, and why CitOmni deliberately supports only two execution modes.  \n  _Doc:_ [`concepts/runtime-modes.md`](https://github.com/citomni/docs/blob/main/concepts/runtime-modes.md)\n\n- **Provider Packages: Design, Semantics, and Best Practices** - explains how provider packages contribute `MAP_*` and `CFG_*` definitions, routes, precedence rules, and versioning; includes guidance on testing, consistency, and conflict avoidance.  \n  _Doc:_ [`concepts/services-and-providers.md`](https://github.com/citomni/docs/blob/main/concepts/services-and-providers.md)\n\n---\n\n## Directory layout (package internals)\n\n```\n\ncitomni/kernel/\n├─ composer.json\n├─ LICENSE\n├─ README.md\n├─ .gitignore\n├─ docs/\n│  └─ CONVENTIONS.md\n├─ src/\n│  ├─ App.php                 # Application kernel (config + services)\n│  ├─ Arr.php                 # Deterministic merge helpers\n│  ├─ Cfg.php                 # Deep, read-only config wrapper\n│  ├─ Mode.php                # Enum: HTTP | CLI\n│  ├─ Controller/\n│  │  └─ BaseController.php   # Thin abstract base - provides $this-\u003eapp and a second array arg (route/options config)\n│  ├─ Model/\n│  │  └─ BaseModel.php        # Thin abstract base - provides $this-\u003eapp and a second array arg (options/config)\n│  ├─ Service/\n│  │  └─ BaseService.php      # Thin abstract base - provides $this-\u003eapp and a second array arg (options/config)\n│  └─ Command/\n│     └─ BaseCommand.php      # Thin abstract base - provides $this-\u003eapp and a second array arg (options/config)\n└─ tests/                     # Unit/integration tests (see CitOmni Testing: [https://github.com/citomni/testing](https://github.com/citomni/testing))\n   └─ Command/\n      └─ BaseCommandTest.php\n \n```\n\nPSR-4: `\"CitOmni\\\\Kernel\\\\\": \"src/\"`.\n\n\u003e **Note on these base folders**  \n\u003e The `Controller/Model/Service` folders only contain **abstract bases** (infrastructure).  \n\u003e No delivery-layer controllers, routers, or error handlers live in the kernel — those belong in the delivery-layer packages (`citomni/http`, `citomni/cli`).  \n\u003e All three abstract bases follow the same idea: you get a protected `$this-\u003eapp` and a second array parameter for per-instance configuration (e.g., `$options` or route config). No hidden magic, just the plumbing.  \n\u003e Yes, the names sound grand for thin classes. No, they do not secretly spawn MVC.\n\n---\n\n## API reference\n\n### `CitOmni\\Kernel\\Mode`\n\n```php\nenum Mode: string {\n\tcase HTTP = 'http';\n\tcase CLI  = 'cli';\n}\n```\n\nPass this to `App` so the kernel can load the correct vendor baselines and provider constants.\n\n---\n\n### `CitOmni\\Kernel\\Arr`\n\nSmall helpers for deterministic, copy-on-write array merges.\n\n* `Arr::mergeAssocLastWins(array $a, array $b): array`\n  Recursive merge for **associative** arrays where **later** entries win per key.\n  Numeric arrays (lists) are **replaced** by the later side.\n\n* `Arr::normalizeConfig(mixed $x): array`\n  Accepts arrays, objects, and `Traversable`; returns a normalized array (objects and traversables are converted recursively).\n\n---\n\n### `CitOmni\\Kernel\\Cfg` (deep config wrapper)\n\nA **read-only**, **deep** wrapper that lets you write:\n\n```php\n$app-\u003ecfg-\u003etimezone;\n$app-\u003ecfg-\u003ehttp-\u003ebase_url;\n\n// 'routes' is intentionally left as a raw array for performance:\n$app-\u003ecfg-\u003eroutes['/']['controller'];\n```\n\nKey points:\n\n* Top-level and nested **associative arrays** are wrapped as `Cfg` nodes (object-like).\n  Numeric arrays (lists) are returned as **plain arrays**.\n* Certain keys are **intentionally left raw** (e.g. `routes`) for performance and ergonomics when large arrays are expected.\n* Unknown keys throw `OutOfBoundsException` (fail fast).\n* Read-only: attempts to set/unset throw `LogicException`.\n* Implements `ArrayAccess`, `IteratorAggregate`, `Countable`.\n* `toArray()` returns the underlying array.\n\n\u003e You get ergonomic `-\u003e` access where it helps, without paying for wrapping large lists as a heavy object tree.\n\n**Raw keys (performance):**  \nSome keys are intentionally returned as **raw arrays** for performance and ergonomics with large lists.  \nCurrently: `routes`. Example:\n```php\n$controller = $app-\u003ecfg-\u003eroutes['/']['controller'] ?? null;\n```\n\nThis list is considered part of the stable API and may be **extended** in minor versions (never silently removed).\n\n---\n\n### `CitOmni\\Kernel\\App`\n\nThe application kernel.\n\n```php\nfinal class App {\n\tpublic readonly Cfg $cfg;\n\n\tpublic function __construct(string $configDir, Mode $mode);\n\n\tpublic function __get(string $id): object;        // lazy service singletons\n\n\tpublic function getAppRoot(): string;             // absolute app root\n\tpublic function getConfigDir(): string;           // absolute /config\n\n\t// Build-time cache helper (may be called from HTTP or CLI)\n\tpublic function warmCache(bool $overwrite = true, bool $opcacheInvalidate = true): array;\n\n\t// Handy helpers\n\tpublic function hasService(string $id): bool;\n\tpublic function hasAnyService(string ...$ids): bool;\n\tpublic function hasPackage(string $slug): bool;\n\tpublic function hasNamespace(string $prefix): bool;\n\tpublic function memoryMarker(string $label, bool $asHeader = false): void;\n}\n```\n\n**Helper examples**\n```php\n\u003c?php\ndeclare(strict_types=1);\n\n// 1) Check availability (fail fast with something human-readable)\nif (!$app-\u003ehasService('router')) {\n\tthrow new RuntimeException('Router service missing. (No, routes do not self-assemble.)');\n}\n$app-\u003erouter-\u003erun();\n\n// 2) Pick the first available cache backend (explicit \u003e magic)\n$candidates = ['apcuCache', 'redisCache', 'fileCache'];\n$cacheId = null;\nforeach ($candidates as $id) {\n\tif ($app-\u003ehasService($id)) { $cacheId = $id; break; }\n}\n\nif ($cacheId !== null) {\n\t$app-\u003e{$cacheId}-\u003eset('healthcheck', 'ok', ttl: 60);\n} else {\n\t// Still okay; just a bit less fast.\n\t// (Feature flags are cool; explicit checks are cooler.)\n}\n\n// 3) Feature toggle by package slug (services or routes contributed by the package)\nif ($app-\u003ehasPackage('citomni/auth')) {\n\t// Example from CitOmni Auth; adjust to your app\n\t$app-\u003erole-\u003eenforce('ADMIN'); // Business as usual.\n} else {\n\t// Hide admin UI entirely. Stealth mode, but intentional.\n}\n\n// 4) Namespace presence (useful for optional modules/plugins)\nif ($app-\u003ehasNamespace('\\CitOmni\\Commerce')) {\n\t// Wire up commerce bits here\n\t// e.g., $app-\u003erouter-\u003eaddRoutes(...); (pseudo)\n} else {\n\t// Keep the brochure site lean. Your TTFB will thank you.\n}\n\n// 5) Lightweight timing/memory markers (dev only)\n// Header marker (shows as X-CitOmni-MemMark)\n$app-\u003ememoryMarker('boot', true);\n\n// ... do work ...\n\n// HTML comment marker (visible in page source in dev; users never see it)\n$app-\u003ememoryMarker('after-routing');\n\n// Tip: mark boundaries around expensive work;\n// resist the urge to benchmark every semicolon.\n```\n\n**Construction**\n\n* `__construct($configDir, $mode)` expects the **absolute path to your `/config`** directory and a `Mode` enum (`Mode::HTTP` or `Mode::CLI`).\n* If compiled cache files exist, the constructor **prefers** them:\n\n  * Config cache:  `\u003cappRoot\u003e/var/cache/cfg.{http|cli}.php`\n  * Services cache: `\u003cappRoot\u003e/var/cache/services.{http|cli}.php`\n    Both files must `return [ ... ]` (plain arrays, no side effects). If a cache is missing or invalid, the kernel falls back to the normal build pipeline.\n\n**Config**\n\n* `$app-\u003ecfg` is a deep, read-only wrapper over the final **merged** config. See [How configuration is built](#how-configuration-is-built-merge-model).\n\n**Services**\n\n* `$app-\u003e__get('id')` returns (and caches) a **singleton** instance per id. Instances are constructed lazily the first time they're requested.\n* A service definition is either:\n\n  * a **string** FQCN -\u003e instantiated as `new $class($app)`, or\n  * an **array**: `['class' =\u003e FQCN, 'options' =\u003e [...]]` -\u003e `new $class($app, $options)`.\n* Unknown ids throw `RuntimeException` (no magic fallback, no namespace scanning).\n\n**Precedence**\n\n* Service map precedence is: **app overrides provider overrides vendor**.\n\n**Cache warmer**\n\n* `warmCache(overwrite: true, opcacheInvalidate: true): array{cfg:?string,services:?string}`\n  Compiles the **current mode's** merged config and services, and writes them **atomically** to:\n\n  * `\u003cappRoot\u003e/var/cache/cfg.{http|cli}.php`\n  * `\u003cappRoot\u003e/var/cache/services.{http|cli}.php`\n    Returns the written paths, or `null` when a file was skipped (`overwrite=false` and it already existed). Best-effort calls `opcache_invalidate()` for the written files.\n\n---\n\n## How configuration is built (merge model)\n\n### TL;DR (final order)\nThe final config is built in this deterministic order (**last wins**):\n1) **Vendor baseline** (by mode)  \n2) **Providers** (in the order listed in `/config/providers.php`)  \n3) **App base config** (`/config/citomni_{http|cli}_cfg.php`)  \n4) **Env overlay** (`/config/citomni_{http|cli}_cfg.{env}.php`, where `{env}` = `dev|stage|prod`)\n\n\u003e If a compiled cache exists (`var/cache/cfg.{http|cli}.php`), it is used directly (fast path).\n\n### Fast path (compiled cache)\nIf `\u003cappRoot\u003e/var/cache/cfg.{http|cli}.php` exists and returns an array, the kernel loads it and skips the normal merge.  \nUse `$app-\u003ewarmCache()` to generate it atomically during deploy.\n\n### Normal path (fallback / dev)\nWhen you create `new App($configDir, Mode::HTTP)` (or `Mode::CLI`), the kernel does:\n\n1. **Vendor baseline (by mode)**\n   - HTTP: `\\CitOmni\\Http\\Boot\\Config::CFG`\n   - CLI:  `\\CitOmni\\Cli\\Boot\\Config::CFG`\n\n2. **Providers** (whitelisted in `/config/providers.php`, in order)\n   - If a provider class defines `CFG_HTTP` / `CFG_CLI`, that array is merged **on top** of the baseline.\n\n3. **App base config (last wins)**\n   - HTTP: `/config/citomni_http_cfg.php`\n   - CLI:  `/config/citomni_cli_cfg.php`\n\n4. **Environment overlay (final layer)**\n   - HTTP: `/config/citomni_http_cfg.{env}.php`\n   - CLI:  `/config/citomni_cli_cfg.{env}.php`  \n     `{env}` comes from `CITOMNI_ENVIRONMENT`.\n\n### Merge rules\n- **Associative arrays** -\u003e recursive merge; **later wins** per key.  \n- **Numeric arrays (lists)** -\u003e **replaced** by the later side.  \n- **Empty values** (`''`, `0`, `false`, `null`, `[]`) still override earlier values.\n\n\u003e **Precedence in one line:** app overrides provider overrides vendor. No magic, no environment-dependent surprises.\n\n### Provider contract (config + services)\nProviders contribute config and service-map entries via **class constants**.  \nOnly constants that exist are read (missing constants are simply ignored).  \n**Order matters:** providers are applied in the order they appear in `/config/providers.php`.\n\n```php\nnamespace Vendor\\Feature\\Boot;\n\nfinal class Services {\n\tpublic const MAP_HTTP = [\n\t\t'feature' =\u003e \\Vendor\\Feature\\Http\\Service\\FeatureService::class,\n\t];\n\tpublic const CFG_HTTP = [\n\t\t'feature' =\u003e ['enabled' =\u003e true, 'retries' =\u003e 2],\n\t];\n\n\tpublic const MAP_CLI = self::MAP_HTTP;\n\tpublic const CFG_CLI = [\n\t\t'feature' =\u003e ['enabled' =\u003e true],\n\t];\n}\n```\n\n*(Service map precedence is described in \"How services are resolved\".)*\n\n### Base URL policy (HTTP layer)\n\n* **dev:** if `http.base_url` is empty, the HTTP kernel auto-detects and defines `CITOMNI_PUBLIC_ROOT_URL`.\n* **stage/prod:** **no auto-detect** - set an **absolute** `http.base_url` (e.g. `https://www.example.com`) in the env overlay, otherwise boot fails fast.\n\n\u003e Goal: Predictable URLs in production. No, you cannot get \"surprise subpaths\" as a feature.\n\n---\n\n## How services are resolved\n\nThe final **service map** is built in the same order and precedence.\n\n**Fast path (if available)**\n\n1. Try to **load compiled cache**: `\u003cappRoot\u003e/var/cache/services.{http|cli}.php`.\n   If it exists and returns an array, use it.\n\n**Normal path**\n\n1. Vendor baseline map (by mode).\n2. Provider maps (`MAP_HTTP`/`MAP_CLI`) in the order listed in `/config/providers.php`.\n3. App `/config/services.php` (final overrides).\n\n**Definition shapes**\n\n```php\n// simplest\n'router' =\u003e \\CitOmni\\Http\\Service\\Router::class,\n\n// with options (kernel passes them as 2nd ctor arg)\n'log' =\u003e [\n\t'class'   =\u003e \\CitOmni\\Http\\Service\\Log::class,\n\t'options' =\u003e ['dir' =\u003e __DIR__ . '/../var/logs', 'level' =\u003e 'info'],\n],\n```\n\n**Instantiation**\n\n* FQCN -\u003e `new $class($app)`\n* With options -\u003e `new $class($app, $options)`\n\n\u003e Keep your service constructors on the convention: `__construct(App $app, array $options = [])`.\n\n\u003e **Precedence (services):**  \n\u003e `vendor baseline` -\u003e overridden by `providers` (listed order) -\u003e overridden by `app/services.php`.  \n\u003e Implemented via array union: `$map = $providerMap + $map; $map = $appMap + $map;`\n\n---\n\n## Folder layout (recommended)\n\n```\napp-root/\n└─ config/\n   ├─ citomni_http_cfg.php            # app HTTP config (base)\n   ├─ citomni_http_cfg.dev.php        # dev overlay (optional)\n   ├─ citomni_http_cfg.stage.php      # stage overlay (optional)\n   ├─ citomni_http_cfg.prod.php       # prod overlay (optional)\n   ├─ citomni_cli_cfg.php             # app CLI config (base)\n   ├─ citomni_cli_cfg.dev.php         # CLI overlay(s) (optional)\n   ├─ providers.php                   # list of provider FQCNs (whitelist)\n   ├─ services.php                    # app service map overrides (HTTP \u0026 CLI)\n   ├─ routes.php                      # HTTP routes (exact, regex, error routes)\n   └─ roles.php                       # ROLE_* constants (optional)\n```\n\n**Environment selection** is controlled by `CITOMNI_ENVIRONMENT` (`dev|stage|prod`) defined in `/public/index.php` (HTTP) or `/bin/app` (CLI).\n\n*(Optional in production builds)*\n`/var/cache/` may contain compiled artifacts generated by `warmCache()`:\n\n* `cfg.http.php`, `services.http.php`\n* `cfg.cli.php`,  `services.cli.php`\n\n---\n\n## Usage examples\n\n### Booting for HTTP\n\nNormally you'll use `\\CitOmni\\Http\\Kernel` (from `citomni/http`), which creates the `App` internally and sets runtime defaults.\n\n```php\n\u003c?php\nrequire __DIR__ . '/../vendor/autoload.php';\n\n// Pass the public/ directory (or /config; both are supported)\n\\CitOmni\\Http\\Kernel::run(__DIR__);\n```\n\n\u003e You can also instantiate `App` directly for debugging:\n\u003e\n\u003e ```php\n\u003e $app = new \\CitOmni\\Kernel\\App(__DIR__ . '/../config', \\CitOmni\\Kernel\\Mode::HTTP);\n\u003e ```\n\n**Note:** Direct `new App(..., Mode::HTTP)` is fine for debugging, but the **HTTP kernel** normally sets timezone/charset, defines the base URL in `dev`, and installs the HTTP error handler. For production, prefer `\\CitOmni\\Http\\Kernel::run(__DIR__)`.\n\n### Booting for CLI\n\n```php\n\u003c?php\nrequire __DIR__ . '/../vendor/autoload.php';\n\n\\CitOmni\\Cli\\Kernel::run(__DIR__ . '/../config', $argv);\n```\n\n### Reading config (deep access)\n\n```php\n$tz      = $app-\u003ecfg-\u003etimezone;\n$charset = $app-\u003ecfg-\u003echarset;\n\n$baseUrl    = $app-\u003ecfg-\u003ehttp-\u003ebase_url;\n$trustProxy = (bool)$app-\u003ecfg-\u003ehttp-\u003etrust_proxy;\n\n// Lists remain arrays (numeric-indexed arrays are not wrapped)\n$locales = $app-\u003ecfg-\u003elocales ?? ['en'];\n\n// 'routes' intentionally left raw\n$indexCtrl = $app-\u003ecfg-\u003eroutes['/']['controller'] ?? null;\n\n// Convert any node back to array if needed\n$httpArr = $app-\u003ecfg-\u003ehttp-\u003etoArray();\n```\n\n### Declaring \u0026 overriding services\n\n**Vendor (HTTP package) might declare:**\n\n```php\nfinal class Services {\n\tpublic const MAP = [\n\t\t'router'   =\u003e \\CitOmni\\Http\\Service\\Router::class,\n\t\t'request'  =\u003e \\CitOmni\\Http\\Service\\Request::class,\n\t\t'response' =\u003e \\CitOmni\\Http\\Service\\Response::class,\n\t\t'session'  =\u003e \\CitOmni\\Http\\Service\\Session::class,\n\t\t'view'     =\u003e \\CitOmni\\Http\\Service\\View::class,\n\t];\n}\n```\n\n**A provider contributes (opt-in via `/config/providers.php`):**\n\n```php\nfinal class Services {\n\tpublic const MAP_HTTP = [\n\t\t'cart'     =\u003e \\CitOmni\\Commerce\\Http\\Service\\Cart::class,\n\t\t'checkout' =\u003e \\CitOmni\\Commerce\\Http\\Service\\Checkout::class,\n\t];\n\tpublic const CFG_HTTP = [\n\t\t'payments' =\u003e ['gateway' =\u003e 'stripe', 'retry' =\u003e 2],\n\t];\n}\n```\n\n**Your app overrides one entry and adds your own:**\n\n```php\nreturn [\n\t// override vendor router with options\n\t'router' =\u003e [\n\t\t'class'   =\u003e \\App\\Service\\MyRouter::class,\n\t\t'options' =\u003e ['cacheDir' =\u003e __DIR__ . '/../var/cache/routes'],\n\t],\n\n\t// add your own services\n\t'log' =\u003e [\n\t\t'class'   =\u003e \\App\\Service\\Log::class,\n\t\t'options' =\u003e ['dir' =\u003e __DIR__ . '/../var/logs', 'level' =\u003e 'info'],\n\t],\n];\n```\n\n---\n\n## Performance notes\n\n* **Lazy services**: nothing is constructed until first use (per request/process).\n* **No scanning**: services are resolved by an explicit map, not by searching namespaces.\n* **Deep config wrapper**: ergonomic `-\u003e` access; large lists (like `routes`) remain arrays.\n* **Composer**:\n\n  ```json\n  \"config\": {\n  \t\"optimize-autoloader\": true,\n  \t\"apcu-autoloader\": true\n  }\n  ```\n\n  `composer dump-autoload -o`\n* **OPcache (prod)**: enable; consider `validate_timestamps=0` (reset on deploy).\n* **Compiled caches (prod)**: pre-merge config \u0026 services to `/var/cache/cfg.{http|cli}.php` and `/var/cache/services.{http|cli}.php`.\n  Use `$app-\u003ewarmCache()` to generate them atomically (best-effort `opcache_invalidate()`).\n\n\u003e For production images/pipelines, prefer `composer install --no-dev --classmap-authoritative`.\n\n## Operational KPIs to track\n\n* **TTFB (p50/p95)** per route\n* **CPU ms/request** (app layer only)\n* **RSS memory/request** (steady-state)\n* **Requests per core** at target p95 latency\n* **Energy per 1k requests** (if you can meter at the host or rack level)\n\nTip: use `App::memoryMarker()` around hot paths to validate improvements rather than guessing.\n\n### Compiled cache: Deploy snippet\n\nWarm caches atomically during deploy (HTTP and CLI as needed):\n\n```php\n$app = new \\CitOmni\\Kernel\\App(__DIR__ . '/../config', \\CitOmni\\Kernel\\Mode::HTTP);\n$app-\u003ewarmCache(overwrite: true, opcacheInvalidate: true);\n\n// If you also use CLI:\n$cli = new \\CitOmni\\Kernel\\App(__DIR__ . '/../config', \\CitOmni\\Kernel\\Mode::CLI);\n$cli-\u003ewarmCache(overwrite: true, opcacheInvalidate: true);\n```\n\nEnsure the process can write to `\u003cappRoot\u003e/var/cache/` and that your deploy invalidates OPcache (either via `opcache_invalidate()` as above or a full `opcache_reset()`).\n\n---\n\n## Dev vs prod checklist\n\n- **Base URL:**  \n  - `dev`: Auto-detected by the HTTP kernel when `http.base_url` is empty.  \n  - `stage/prod`: **Must** set an absolute `http.base_url` in the env overlay (no auto-detect).\n- **OPcache:** Enable in production; consider `validate_timestamps=0` (invalidate on deploy).\n- **Caches:** Warm `var/cache/cfg.{http|cli}.php` and `var/cache/services.{http|cli}.php` during deploy.\n\n---\n\n## Testing with CitOmni Testing (dev-only)\n\nCitOmni Testing is an integrated, dev-only toolkit for running correctness, regression, and integration tests **inside** a fully booted CitOmni app. Same boot, same config layering, zero production overhead. Results can be exported for CI and reporting. (Yes, it is lean. No, it will not install a testing monastery in your app.)\n\n**Repo:** https://github.com/citomni/testing\n\n### Quick start\n\n1) Install (dev only):\n```bash\ncomposer require --dev citomni/testing\n```\n\n2. Enable the provider in `/config/providers.php` **only in dev**:\n\n```php\n\u003c?php\ndeclare(strict_types=1);\n\nreturn array_values(array_filter([\n\t// ... other providers ...\n\n\t(defined('CITOMNI_ENVIRONMENT') \u0026\u0026 CITOMNI_ENVIRONMENT === 'dev')\n\t\t? \\CitOmni\\Testing\\Boot\\Services::class\n\t\t: null,\n]));\n```\n\n3. Boot your app as usual. The testing UI is mounted under a dev-only route (e.g. `/__tests`) and a POST endpoint to run tests. Exact routes come from the Testing provider’s `CFG_HTTP['routes']`.\n\n### What you get\n\n* **Real runtime, real answers.** Tests execute in the same environment model as prod: `vendor baseline -\u003e providers -\u003e app -\u003e env overlay`.\n* **Deterministic, reproducible runs.** No namespace scanning, no surprise toggles.\n* **Zero prod overhead.** Not enabled unless you opt in via provider (and typically only when `CITOMNI_ENVIRONMENT === 'dev'`).\n* **CI-friendly.** Outputs can be exported to common formats for pipelines and dashboards.\n\n### Safety checklist\n\n* Keep the provider gated to `dev` (see snippet above).\n* If you must expose it temporarily, put it behind IP allowlist and/or basic auth.\n* Do not ship the Testing provider to staging or production. Your future self will thank you.\n\n\u003e Tip: CitOmni Testing is optional. For pure unit tests you can use any harness you like; the value here is **integration** under a true CitOmni boot.\n\n---\n\n## Error handling philosophy\n\nThe kernel **does not** install an error/exception handler. Delivery layers do:\n\n* HTTP: `\\CitOmni\\Http\\Exception\\ErrorHandler::install([...])`\n* CLI:  `\\CitOmni\\Cli\\Exception\\ErrorHandler::install([...])`\n\nThe kernel's job is to **fail fast** and surface issues early (unknown cfg keys, unknown service ids, invalid provider classes). Your global handler logs.\n\n---\n\n## Exceptions \u0026 failure modes (fail fast)\n\nThe kernel does not swallow errors; typical exceptions include:\n\n- `RuntimeException(\"Config directory not found: ...\")` - `new App($configDir, $mode)` with an invalid path.\n- `RuntimeException(\"Provider class not found: ...\")` - a FQCN listed in `/config/providers.php` is not autoloadable.\n- `RuntimeException(\"Invalid service definition for 'id'\")` - malformed map entry (neither FQCN string nor `['class'=\u003e, 'options'=\u003e]`).\n- `OutOfBoundsException(\"Unknown cfg key: '...'\")` - strict access in `Cfg` for missing keys.\n- `LogicException('Cfg is read-only.')` - attempting to set/unset on `Cfg`.\n- `RuntimeException(\"Unable to create cache directory: ...\" | \"Failed writing cache tmp: ...\" | \"Failed moving cache into place: ...\")` - I/O errors from `warmCache()`.\n\n---\n\n## FAQ / common pitfalls\n\n**\"Unknown app component: app-\u003eX\"**\nThe id `X` is not present in the final service map. Add/override it in `/config/services.php` or enable a provider that contributes it. (Run `composer dump-autoload -o` if you just added a new class.)\n\n**\"Provider class not found ...\"**\nAn entry in `/config/providers.php` points to a non-autoloadable FQCN. Check package install and PSR-4 namespace. Providers must be loadable for their constants to be read.\n\n**\"Config must return array or object.\"**\nYour `citomni_http_cfg.php` (or CLI variant) must return an **array** (recommended) or an **object**; scalars are invalid. If you include files (like `routes.php`), ensure those return arrays too.\n\n**Deep config access throws `OutOfBoundsException`**\nThe `Cfg` wrapper is strict-unknown keys throw. Use `isset($app-\u003ecfg-\u003esomeKey)` to guard, or move the key into your cfg files.\n\n**Service constructor signature**\nStick to `__construct(App $app, array $options = [])`. The kernel passes `$options` only when your service map entry uses the `['class'=\u003e..., 'options'=\u003e...]` shape.\n\n**Compiled cache not picked up**\nEnsure files exist at:\n\n* `\u003cappRoot\u003e/var/cache/cfg.{http|cli}.php`\n* `\u003cappRoot\u003e/var/cache/services.{http|cli}.php`\n  They must `return [ ... ];` (plain arrays). If OPcache runs with `validate_timestamps=0`, either let `warmCache()` call `opcache_invalidate()` (default) or perform a full `opcache_reset()` as part of deploy.\n\n---\n\n## Versioning \u0026 BC\n\n* Targets **PHP 8.2+** only.\n* Semantic Versioning for the kernel's **public API** (class names, method signatures, merge behavior).\n* The kernel avoids catching exceptions-this is deliberate and part of the contract.\n\n---\n\n## Contributing\n\n* Code style: PHP 8.2+, PSR-4, **tabs**, K\u0026R braces.\n* Keep vendor files side-effect free (OPcache-friendly).\n* No exception swallowing; let the global error handler log.\n\n---\n\n## Coding \u0026 Documentation Conventions\n\nAll CitOmni and LiteX projects follow the shared conventions documented here:\n[CitOmni Coding \u0026 Documentation Conventions](https://github.com/citomni/docs/blob/main/contribute/CONVENTIONS.md)\n\n---\n\n## License\n\n**CitOmni Kernel** is open-source under the **MIT License**.  \nSee: [LICENSE](LICENSE).\n\n**Trademark notice:** \"CitOmni\" and the CitOmni logo are trademarks of **Lars Grove Mortensen**.  \nUsage of the name or logo must follow the policy in **[NOTICE](NOTICE)**. Do not imply endorsement or\naffiliation without prior written permission.\n\n### FAQ (licensing)\n\n**Can I build proprietary providers/plugins on top of CitOmni?**  \nYes. Providers/apps may be distributed under any terms (including proprietary). MIT places no copyleft obligations on your code.\n\n**Do I need to keep attribution?**  \nYes. Keep the copyright and license notice from `LICENSE` in distributions of the Software.\n\n**Can I call my product \"CitOmni \u003cSomething\u003e\"?**  \nNo. The name \"CitOmni\" and the logo are protected by trademark. Do not suggest sponsorship, endorsement, or affiliation without permission.\n\n---\n\n## Trademarks\n\n\"CitOmni\" and the CitOmni logo are trademarks of **Lars Grove Mortensen**.  \nYou may make factual references to \"CitOmni\", but do not modify the marks, create confusingly similar logos,  \nor imply sponsorship, endorsement, or affiliation without prior written permission.  \nDo not register or use \"citomni\" (or confusingly similar terms) in company names, domains, social handles, or top-level vendor/package names.  \nFor details, see the project's [NOTICE](NOTICE).\n\n---\n\n## Author\n\nDeveloped by **Lars Grove Mortensen** © 2012-present\nContributions and pull requests are welcome!\n\n---\n\nBuilt with ❤️ on the CitOmni philosophy: **low overhead**, **high performance**, and **ready for anything**.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcitomni%2Fkernel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcitomni%2Fkernel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcitomni%2Fkernel/lists"}