# CLAUDE.md — CoreCore

Multi-tenant admin application built on MintyPHP. Manages tenants, users, departments, roles, permissions, address books, mail, CSV imports, scheduled jobs, and Microsoft Entra ID SSO.

## Mandatory Workflow

All guard IDs (GR-\*) and gate IDs (QG-\*) from `.agents/checks/` are **binding — not advisory**.

- **Full workflow trigger is risk-based** and defined in `.agents/checks/enforcement-policy.json` (`full_workflow_required_when`). Matching changes MUST follow `.agents/workflow.md` (Analyst → Planner → Executor → Reviewers → Finalizer). Read `.agents/README.md` and the relevant role prompt in `.agents/prompts/` before starting.
- **Quick fixes** may skip full workflow only if they match `.agents/checks/enforcement-policy.json` (`quick_fix_allowlist`) and still pass required gates.
- **New modules** MUST conform to `.agents/contracts/module-manifest.schema.json` and pass all 9 security guards (GR-SEC-001 through GR-SEC-009).

Full workflow definition: `.agents/workflow.md` | Guard catalog: `.agents/checks/guard-catalog.json` | Enforcement policy: `.agents/checks/enforcement-policy.json` | Guard enforcement map: `.agents/checks/guard-enforcement-map.json` | Skills: `.agents/skills/`

## Tech Stack

- **Runtime:** PHP 8.5 (FPM) on MintyPHP framework (`mintyphp/core`)
- **Web server:** Nginx 1.25 (Alpine)
- **Database:** MariaDB 11.4
- **Cache:** Memcached 1.6
- **Frontend:** Vanilla JS (ES2022 modules), vanilla CSS with `@layer` system — no build step
- **Key PHP libs:** PHPMailer 7, Dompdf 3, endroid/qr-code 6, league/commonmark 2
- **Containerized:** Docker Compose (dev + prod variants)

## Quick Commands

```bash
# Start dev environment
cp config/config.php.example config/config.php
docker compose up --build -d
# App: http://localhost:8080 | phpMyAdmin: http://localhost:8081

# Run PHPUnit tests (phpunit.xml configures bootstrap + testsuite)
docker compose exec php vendor/bin/phpunit

# Run PHPStan (level 5)
docker compose exec php vendor/bin/phpstan analyse -c phpstan.neon --no-progress

# CLI commands (single entry point)
docker compose exec php php bin/console list
docker compose exec php php bin/console module:sync              # full module runtime sync
docker compose exec php php bin/console module:migrate           # apply module SQL migrations
docker compose exec php php bin/console module:permissions-sync  # sync + deactivate orphaned
docker compose exec php php bin/console module:build             # build runtime page root
docker compose exec php php bin/console module:assets-sync       # publish module web assets
docker compose exec php php bin/console module:deactivate <id> --confirm
docker compose exec php php bin/console scheduler:run
docker compose exec php php bin/console doctor

# Check unused Composer packages
docker compose exec php vendor/bin/composer-unused
```

## Project Structure

```
lib/                    # All backend PHP (namespace MintyPHP\)
  App/                  # DI container + bootstrap (AppContainer, Container/Registrars/)
    Module/             # Module platform (Registry, Manifest, Autoloader, Builder)
  Repository/           # SQL queries, prepared statements, filters/paging
  Service/              # Business logic, validation, orchestration
  Http/                 # Auth guards, API auth, request helpers
    Input/              # Request input handling (RequestInput, FormErrors)
  Console/              # CLI command framework (ConsoleApplication, Command base class)
    Commands/           # Command classes (Module/, Scheduler/, Tools/)
  Support/              # Crypto, flash, guard, search, helpers
modules/                # Self-contained feature modules (namespace MintyPHP\Module\<Name>\)
  <id>/module.php       # Module manifest (routes, slots, providers, permissions)
  <id>/lib/             # Module PHP classes (Service, Repository, Providers)
  <id>/pages/           # Module actions + views (symlinked into runtime)
  <id>/templates/       # Module-specific templates
  <id>/web/             # Module CSS/JS (published to web/modules/<id>/)
  <id>/tests/           # Module PHPUnit tests
pages/                  # Core actions (controllers) — file-based routing
  pages/**/*.php        # Action files (read input, check permissions, call services)
  pages/**/*.phtml      # View files (rendering only, no DB calls)
templates/              # Core layouts and partials
  partials/             # Reusable UI components
  emails/               # Email HTML templates
  pdfs/                 # PDF templates (Dompdf)
config/                 # App config (routes, assets, themes, modules)
web/                    # Document root (entry point, CSS, JS, static assets)
  js/core/              # DOM utilities, Grid.js factory, component runtime
  js/components/        # Reusable UI components
  css/                  # Layered CSS (base → components → layout → pages)
  modules/              # Published module assets (symlinks to modules/<id>/web/)
storage/                # Uploaded files + runtime state
  runtime/pages/        # Symlinked page tree (core + modules, built by module-build)
  runtime/settings.php  # Hot-path settings cache file (generated by SettingCacheService)
db/init/init.sql        # Full schema + seed data (no migration framework)
db/updates/             # Idempotent SQL update scripts for existing installs
tests/                  # PHPUnit tests (namespace MintyPHP\Tests\)
docs/                   # German-language documentation (Markdown)
i18n/                   # Translation files (de, en)
bin/                    # CLI entry points (bin/console, bin/dev) + shell tools
docker/                 # Dockerfiles, Nginx configs, PHP configs
.agents/                # Agent workflow system (contracts, prompts, checks, skills, runs)
```

## Architecture Rules

### Strict Layering (enforce in all changes)

1. **App** (`lib/App/`): DI container bootstrap. Registers all service/repository factories. No business logic.
2. **Repository** (`lib/Repository/`): All SQL lives here. No HTTP, session, or rendering.
3. **Service** (`lib/Service/`): Business rules and validation. No HTML. Four internal sub-types:
   - **Service** (`*Service.php`): Business logic + orchestration. Coordinates repositories and gateways.
   - **Gateway** (`*Gateway.php`): Adapter for a single external dependency (repository, settings, HTTP API, scope). No orchestration flow.
   - **Factory** (`*Factory.php`): Only place inside `lib/Service/` that may call `new` on services/gateways/repositories. Registers via DI container.
   - **Policy** (`*Policy.php` in `lib/Service/Access/`): Authorization decisions per resource type. Returns `AuthorizationDecision`. No HTTP, no business logic.
4. **Action** (`pages/**/*.php`): Reads input, checks permissions + tenant scope, calls services, sets view vars.
5. **View** (`pages/**/*.phtml`): Pure rendering. **No DB calls.** HTML escaping via `e(...)`.
6. **Template** (`templates/`): Layouts and partials.

### Modules

- Modules are self-contained feature packages under `modules/<id>/`
- **Namespace:** `MintyPHP\Module\<Name>\*` — never use core namespaces (`MintyPHP\Service\*`, `MintyPHP\Repository\*`)
- **Manifest:** `module.php` declares routes, UI slots, providers, permissions, scheduler jobs
- **UI integration:** Only via platform slots (`aside.tab_panel`, `topbar.right_item`, `layout.head_style`, `runtime.component`, etc.) — no core template hardcoding
- **Session keys:** Prefixed with `module.<id>.*`
- **Activation:** `config/modules.php` or `APP_ENABLED_MODULES` env override
- **Runtime sync:** `php bin/console module:sync` after any module/manifest change (builds page symlinks, syncs permissions, publishes assets)
- **Fingerprint guard:** `web/index.php` validates runtime state matches active modules; fails fast if stale
- **API isolation:** API requests (`api/` prefix) skip session/auth/cookie flows entirely; modules using API endpoints rely on `ApiBootstrap.php`
- **Cross-module coupling:** Modules may depend on other modules but MUST declare `requires: [<module-id>]` in their manifest. Undeclared cross-module imports are forbidden. Prefer loose coupling via events and providers over direct service injection
- **Module DB changes:** Via `module:migrate`, not `db/updates/` (which is for core schema only)
- **Module permissions:** Registered in manifest, synced via `module:permissions-sync` — never hardcoded
- **Manifest contract:** Must conform to `.agents/contracts/module-manifest.schema.json`

### Routing

- File-based: `pages/admin/users/edit($id).php` → URL parameter `id`
- Module pages: `modules/<id>/pages/` symlinked into `storage/runtime/pages/` at build time
- `...(none).phtml` → no template layout
- `data().php` suffix → data/AJAX endpoints
- Named aliases in `config/routes.php` with `public` flag for auth guard
- Module routes declared in manifest, merged at boot (fail-fast on collision)

### PHP Conventions

- PSR-4 autoload: `MintyPHP\` → `lib/`, `MintyPHP\Module\*` → `modules/*/lib/`, `MintyPHP\Tests\` → `tests/`
- All user-facing text uses `t('key')` translation helper — keys must exist in all language files under `i18n/`
- Request input via `requestInput()` — never raw `$_GET`/`$_POST`/`$_SESSION` (GR-CORE-003)
- Permissions + tenant scope enforced in actions/services, never just in UI
- Security/integration settings stay in DB, not in `storage/runtime/settings.php` file cache (GR-CORE-011)
- POST-Redirect-GET (PRG) pattern on form submissions; flash before redirect (GR-CORE-012)
- Constructor injection for dependencies; no hidden global state (GR-TEST-002)
- New/changed Service or Gateway must have PHPUnit tests — happy path + edge case (GR-TEST-001)

### Frontend Conventions

- Vanilla ES2022 modules, no bundler
- CSS classes prefixed with `app-`
- Defensive DOM checks (elements may not exist on every page)
- No business logic in frontend
- Assets served raw with `assetVersion()` cache-busting (filemtime-based)

### UI Patterns

- Edit pages: tabs → `Master data` | `Visibility` | optional `Danger zone`
- Titlebar partial for primary actions (Save, Save & close)
- Secondary actions in Aside block via `app-details-aside-actions.phtml`
- Lists use Grid.js via `initStandardListPage()` (GR-UI-LIST)
- Detail pages use shared partials and layout contracts (GR-UI-DETAIL)
- New views must handle loading, empty, error, and success states (GR-UI-014)
- All visible text wrapped in `t('...')`
- Check existing JS/CSS components before adding new ones (GR-UI-REUSE)

### Never Do This

- Direct `$_GET`/`$_POST`/`$_SESSION` access — use `requestInput()` (GR-CORE-003)
- SQL outside Repository classes (GR-SEC-003)
- `new Service()` or `new Gateway()` outside Factory classes (GR-TEST-002)
- Hardcoding module UI into core templates — use platform slots
- Core schema changes without idempotent `db/updates/` script (GR-CORE-010)
- Settings in config files instead of DB `settings` table (GR-CORE-011)
- Skipping PRG pattern on form submissions (GR-CORE-012)
- Loading assets from external CDNs (GR-SEC-004)
- Undeclared cross-module imports (must use `requires` in manifest)
- Inline SQL or string-concatenated queries (GR-SEC-003)
- OpenAPI changes without updating the spec (GR-CORE-007)

## Database

- Schema defined in `db/init/init.sql` (applied once on container init)
- No migration framework — core schema changes for existing installs are idempotent SQL scripts in `db/updates/`
- Module schema changes via `module:migrate` (separate from core)
- Settings stored in `settings` DB table (single source of truth); `storage/runtime/settings.php` is a partial file cache for hot-path UI reads only

## Security (non-negotiable)

These rules are enforced by guards GR-SEC-001 through GR-SEC-009 in `.agents/checks/guard-catalog.json`. Any violation is a blocking finding.

- CSRF token verified on every POST (GR-SEC-001)
- No PII or secrets in logs or audit metadata (GR-SEC-002)
- SQL only via Repository prepared statements — no inline queries (GR-SEC-003)
- No external CDN or remote asset loading (GR-SEC-004)
- Encryption only via `lib/Support/Crypto.php` AES-256-GCM (GR-SEC-005)
- File uploads: `storage/` only, authz-gated serving, MIME-validated (GR-SEC-006)
- API requests must not start sessions (GR-SEC-007)
- Authorization enforced server-side, not just in UI (GR-SEC-008)
- Tenant scope (`tenant_id`) enforced on all queries — no unscoped data access (GR-SEC-009)

## Quality Gates

9 gates defined in `.agents/checks/quality-gates.json`.
Required/optional enforcement is defined in `.agents/checks/enforcement-policy.json`.

| ID | Gate | Command |
|---|---|---|
| QG-001 | PHPUnit | `vendor/bin/phpunit` |
| QG-002 | PHPStan level 5 | `vendor/bin/phpstan analyse -c phpstan.neon` |
| QG-003 | Architecture Contract | `vendor/bin/phpunit tests/Architecture/CoreStarterkitContractTest.php` |
| QG-006 | PHP Style | `vendor/bin/php-cs-fixer fix --config=tools/php-cs-fixer/.php-cs-fixer.dist.php --dry-run --diff --verbose` |
| QG-008 | Docs link + drift integrity | `bin/docs-link-check.sh` (plus `bin/docs-drift-check.sh` via `bin/qa-required.sh`) |
| QG-009 | Codex skills sync | `bin/codex-skills-sync.sh --check` |

Additional non-blocking gates: QG-004 structural checks, QG-005 JS smoke, QG-007 composer-unused. See gate catalog + enforcement policy for details.

## Environment

- Config via `.env` (gitignored) — copy from `.env.example` for dev, `.env.prod.example` for prod
- `config/config.php` (gitignored) — copy from `config/config.php.example`
- `APP_CRYPTO_KEY` (64-char hex) needed for AES-256-GCM encryption of SSO secrets
