# Module Manifest Contract (V1.2)

Letzte Aktualisierung: 2026-03-25

## Ziel

Verbindlicher Contract für `modules/*/module.php`, damit Module zur Runtime deterministisch geladen und validiert werden.

## Status

Dieses Dokument ist die kanonische (normative) Quelle für Manifest-, Runtime- und Guard-Regeln des Modul-Systems.
`/docs/reference-extension-readiness.md` referenziert diesen Contract und dupliziert keine Felddefinitionen.

## JSON-Schema-Enforcement

Zusatz zur Laufzeitvalidierung:

- JSON-Schema-Datei: `/.agents/contracts/module-manifest.schema.json`
- Laufzeit-Check bei Modul-Laden: `ModuleManifestSchemaValidator`
- CLI-Check: `php bin/console module:validate <module-id|--all>`

Ein Manifest muss sowohl:
1. das JSON-Schema erfuellen als auch
2. die Laufzeit-Contracts (Klassen/Interfaces/Namespace) bestehen.

## Namespace-Konvention

Alle PHP-Klassen eines Moduls müssen den Namespace `MintyPHP\Module\<Name>\*` verwenden.
Core-Namespaces (`MintyPHP\Service\*`, `MintyPHP\Repository\*`, `MintyPHP\Support\*`) sind für Modul-Code verboten.

Verzeichnis-Layout:

```
modules/<id>/lib/Module/<Name>/
  Service/              # BookmarkService, Factory, etc.
  Repository/           # Repository + Interfaces
  Providers/            # SessionProvider, LayoutProvider, SearchProvider
  Support/              # Modul-spezifische Utilities
  <Name>ContainerRegistrar.php
  <Name>AuthorizationPolicy.php
```

Der `ModuleAutoloader` mappt `MintyPHP\*` → `modules/<id>/lib/` per PSR-4.
Enforcement: `ModuleStructureContractTest::testModuleClassesUseModuleNamespace`.

## Aktivierung (`APP_ENABLED_MODULES`)

- nicht gesetzt: `config/modules.php` gilt.
- gesetzt und leer (`APP_ENABLED_MODULES=''`): keine Module.
- gesetzt mit Liste (`APP_ENABLED_MODULES='a,b'`): exakt diese Module.

## `i18n_path` Contract

Module mit eigenen Übersetzungsdateien deklarieren den relativen Pfad zum i18n-Verzeichnis:

```php
'i18n_path' => 'i18n',
```

Der `ModuleAutoloader` registriert daraus den absoluten Pfad `modules/<id>/i18n/` als zusätzliche Übersetzungsquelle.

## `permissions` Contract

`permissions` ist eine Objektliste:

```php
'permissions' => [
    [
        'key' => 'module.permission',
        'description' => 'Human readable text',
        'active' => true,      // optional, default true
        'is_system' => true,   // optional, default true
    ],
],
```

- `key` und `description` sind Pflichtfelder.
- Runtime-Sync erfolgt über `php bin/console module:sync`.
- `module:sync` führt diesen Sync automatisch aus.

## `scheduler_jobs` Contract

`scheduler_jobs` ist eine Objektliste mit vollem Default-Contract:

```php
'scheduler_jobs' => [
    [
        'job_key' => 'module.job',
        'handler' => App\\Scheduler\\MyJobHandler::class,
        'label' => 'Job label',
        'description' => 'Job description',
        'default_enabled' => true,
        'default_timezone' => 'Europe/Berlin',
        'default_schedule_type' => 'daily', // hourly|daily|weekly
        'default_schedule_interval' => 1,
        'default_schedule_time' => '02:00', // required for daily/weekly
        'default_schedule_weekdays_csv' => null, // weekly: e.g. '1,3,5'
        'default_catchup_once' => true,
        'allowed_schedule_types' => ['daily', 'weekly'],
    ],
],
```

- Manifest ist Source of Truth für Job-Metadaten und Defaults.
- Handler werden zur Laufzeit container-resolvable für `execute(...)` geladen.

## `ui_slots` Contract (V1.2)

Neue generische Slot-Typen:

- `topbar.right_item`: `{ key, template, permission?, order }`
- `layout.body_end_template`: `{ key, template, permission?, order }`
- `layout.head_style`: `{ key, path, permission?, order }`
- `runtime.component`: `{ key, script, export?, selector?, scope?, config_path?, default_config?, phase?, permission?, order }`

`runtime.component.phase` ist `early|default|late` (Default: `late`).

Abgrenzung CSS:

- `layout.head_style` = global auf allen Seiten.
- `asset_groups` = seiten-/kontextbezogen über `style_groups`.

## Guard-Regeln

- Route-Kollisionen sind fail-fast:
  - Modul vs Modul auf `target` und `path`.
  - Modul vs Core auf `path` beim Router-Boot.
- Layout-Provider duerfen reservierte Core-Keys nicht überschreiben (`moduleSlots`, `csrfKey`, `currentTenant`, ...).
- Layout-Provider-Top-Level-Keys müssen namespaced sein (`<module_id>.<key>`).

## Runtime-Fingerprint

`web/index.php` validiert bei jedem Request, dass der Runtime-Stand zur aktuellen Modul-Konfiguration passt.

- `php bin/console module:build` schreibt nach jedem Build `storage/runtime/.module-fingerprint` (sortierte Modul-IDs, kommasepariert).
- `web/index.php` vergleicht den Fingerprint mit der erwarteten Modul-Liste.
- Bei Mismatch: `RuntimeException` mit Hinweis auf `php bin/console module:sync`.
- Kein Auto-Build im Request-Pfad — Build ist ausschliesslich CLI-Aufgabe.

Methoden: `ModuleRuntimePageBuilder::expectedFingerprint()`, `readFingerprint()`, `writeFingerprint()`.

## API-Channel-Isolation

API-Requests (`api/`-Prefix) überspringen den gesamten Web-Session-Flow:

- Kein `Session::start()`, kein Cookie-Setting, kein CSRF-Token
- Kein Remember-Me, kein Session-Timeout, kein Tenant-UI-Refresh
- API-Auth erfolgt über `ApiBootstrap.php` (Bearer-Token)
- `AccessControl` listet `api/` als always-public

## Standard-Runbook

`php bin/console module:sync` führt in fester Reihenfolge aus:

1. `module:migrate`
2. `module:permissions-sync`
3. `module:build`
4. `module:assets-sync`

Der Output enthaelt eine deterministische Step-Summary (`migrate`, `permissions-sync`, `build`, `assets-sync`) inklusive `vendor_warnings_ignored=<n>`.
First-party Warnings/Notices/Deprecations gelten als Fehler (fail-fast).
