# ADR001 – WP2 Runtime Infrastructure

**Status:** Engineering design for implementation  
**Scope:** WP2 – Runtime Infrastructure and Composition Root  
**Date:** 2026-07-19  
**Roles:** LA – architecture; ED – engineering; PL – prioritisation and acceptance

## 1. Purpose

WP1 introduced the Domain and Application boundaries and routed productive OpenRefine reconciliation through them. WP2 removes the remaining technical assembly from `public/reconcile.php` and establishes a runtime infrastructure that can be reused by HTTP, CLI, batch jobs and later workers.

The objective is not to introduce a framework or a dependency-injection container. Reconcilix retains a small explicit object graph that can be understood and tested without hidden runtime behaviour.

## 2. Current problem

`public/reconcile.php` currently performs several unrelated responsibilities:

1. configures PHP error logging;
2. loads configuration files;
3. creates and populates the provider registry;
4. creates ranking and orchestration services;
5. creates a tenant-dependent application-service closure;
6. creates transport mappers, manifest and preview services;
7. constructs and runs the controller.

The endpoint works, but the entry point is both bootstrap and composition root. The same object graph cannot be reused cleanly by another transport.

## 3. Design correction: RuntimeContext is not a service locator

The initial WP2 idea placed Tenant, Provider, Vocabulary, Logger, Cache and DiscoveryFactory inside one `RuntimeContext`. That would create a service locator: application code could request arbitrary infrastructure through one broadly scoped object.

WP2 therefore uses a narrower definition:

> `RuntimeContext` contains immutable request-scoped runtime data, not technical services.

For the current phase it contains only the authenticated `TenantContext`. Additional request-scoped values may be added only when they are data required by several runtime services and cannot be passed explicitly with reasonable effort.

Provider registries, factories, loggers, caches and application services remain explicit constructor dependencies.

## 4. Target structure

```text
public/reconcile.php
        |
        v
CompositionRoot
        |
        +-- loads Configuration
        +-- creates shared infrastructure services
        +-- creates ReconciliationController
        |
        v
ReconciliationController
        |
        +-- authenticates request
        +-- creates RuntimeContext(TenantContext)
        +-- asks RuntimeFactory for tenant-scoped Application Service
        |
        v
RuntimeFactory
        |
        +-- LegacyCandidateDiscoveryAdapter
        +-- ReconciliationApplicationService
```

### 4.1 Process-scoped objects

Created once for the PHP request by the Composition Root and reused within that request:

- loaded configuration;
- `ProviderRegistry`;
- `ReconciliationOrchestrator`;
- `ApiKeyAuthenticator`;
- `ManifestBuilder`;
- `PreviewRenderer`;
- OpenRefine request and response mappers;
- `RuntimeFactory`;
- `ReconciliationController`.

### 4.2 Tenant-scoped objects

Created after successful authentication:

- `RuntimeContext`;
- `LegacyCandidateDiscoveryAdapter`;
- `ReconciliationApplicationService`.

This preserves the existing tenant-dependent adapter without storing the tenant in global state.

## 5. Components and responsibilities

### 5.1 `Configuration`

Namespace:

```text
App\Infrastructure\Runtime\Configuration
```

Responsibilities:

- load the approved PHP configuration files from one directory;
- expose typed top-level sections for tenants, vocabularies and providers;
- reject missing files or structurally invalid top-level values;
- remain immutable after construction.

Non-responsibilities:

- authentication;
- vocabulary resolution;
- provider creation;
- environment-variable or secret management.

Secrets remain in `credentials.local.php` and are consumed by the existing `CredentialStore`. The credentials file must not be copied into engineering packages and must later be excluded by `.gitignore`.

### 5.2 `RuntimeContext`

Namespace:

```text
App\Infrastructure\Runtime\RuntimeContext
```

Responsibilities:

- carry authenticated, immutable request-scoped data;
- currently expose `TenantContext`.

Non-responsibilities:

- service lookup;
- configuration loading;
- logging;
- caching;
- provider or application-service construction.

### 5.3 `RuntimeFactory`

Namespace:

```text
App\Infrastructure\Runtime\RuntimeFactory
```

Responsibilities:

- create tenant-scoped runtime services from explicit shared dependencies;
- create `LegacyCandidateDiscoveryAdapter` for a `RuntimeContext`;
- create `ReconciliationApplicationService` with the approved runtime URIs.

The factory is intentionally small. It is not a general-purpose dependency-injection container.

### 5.4 `CompositionRoot`

Namespace:

```text
App\Infrastructure\Runtime\CompositionRoot
```

Responsibilities:

- load configuration;
- construct the complete process-scoped object graph;
- register the existing providers;
- construct and return the `ReconciliationController`.

Non-responsibilities:

- handle HTTP requests;
- authenticate a tenant;
- contain matching rules;
- perform candidate discovery.

The front controller becomes:

```php
require_once __DIR__ . '/../src/bootstrap.php';

$controller = CompositionRoot::fromConfigDirectory(
    __DIR__ . '/../config'
)->buildReconciliationController();

$controller->handle();
```

Error-log setup may remain in the front controller until logging is addressed explicitly. Request-debug logging is not part of Runtime Infrastructure and should be removed or replaced in a separate work package.

## 6. Dependency direction

The dependency direction remains:

```text
Transport/Controller
        -> Application
        -> Domain

Infrastructure
        -> Application ports and legacy implementation

Composition Root
        -> all concrete implementations solely for assembly
```

Application and Domain do not depend on Runtime Infrastructure.

## 7. Configuration boundaries

WP2 does not redesign the existing configuration format. It wraps and validates it.

Current files remain authoritative:

```text
config/tenants.php
config/vocabularies.php
config/providers.php
config/credentials.local.php
```

The first three are loaded by `Configuration`. Credentials continue to be loaded lazily by `CredentialStore`.

The following legacy naming inconsistency is retained for compatibility in WP2:

```php
$providerConfig['service']['default_limit']
```

The existing code currently reads `default_limit` and `max_limit` at the wrong top level. WP2 must resolve these values from the `service` section while preserving explicit fallbacks. This is a wiring correction, not a public API change.

## 8. Runtime URIs

The current values in `public/reconcile.php` use the provisional host `reconcilix.example`:

```text
https://reconcilix.example/status/created
https://reconcilix.example/status/initial
https://reconcilix.example/strategy/legacy
```

WP2 centralises these values in `RuntimeFactory`, so they no longer appear in the front controller. Their canonical replacement remains a separate PL/LA decision and must occur before ADR001 is closed.

## 9. Error handling and logging

WP2 preserves existing HTTP error behaviour:

- known `RuntimeException` values are returned with their current status code;
- unexpected exceptions are logged and returned as HTTP 500;
- no internal exception details are exposed publicly.

WP2 does not introduce a logger abstraction. PHP `error_log()` remains in use until logging requirements are defined. Introducing a logger without requirements would add indirection without solving a current architectural problem.

## 10. Cache

No cache abstraction is introduced in WP2.

The current application does not have one coherent cache contract. Existing reconciliation stores and any provider-level caching are separate concerns. A generic cache in `RuntimeContext` would be premature and would obscure ownership and invalidation rules.

A cache port should only be introduced together with a concrete use case and lifecycle definition.

## 11. Test strategy

WP2-001 must add tests for:

1. configuration loading and structural validation;
2. runtime context immutability and tenant propagation;
3. runtime factory creation of a tenant-bound application service;
4. composition-root construction of the controller;
5. preservation of all WP1 tests.

The productive acceptance test remains the known ten-record OpenRefine reconciliation run.

## 12. Work-package split

### WP2-001 – Runtime Infrastructure

Deliver:

- `Configuration`;
- `RuntimeContext`;
- `RuntimeFactory`;
- focused CLI tests;
- no change to the productive entry point yet.

### WP2-002 – Composition Root

Deliver:

- `CompositionRoot`;
- replacement of manual wiring in `public/reconcile.php`;
- controller factory dependency changed from closure to `RuntimeFactory` or a narrow application-service factory interface;
- full CLI and OpenRefine regression test.

### WP2-003 – Provider Infrastructure

Only if the current provider creation still prevents independent replacement or testing after WP2-002. This package must be justified by observed coupling, not created automatically.

### WP2-004 – Tenant and Vocabulary Resolution

Only if resolution responsibilities remain duplicated after WP2-002. Existing behaviour and permissions must remain unchanged.

This conditional split avoids creating work packages merely because they appeared in an early roadmap.

## 13. Decisions

1. Reconcilix will not introduce Symfony, a service container or another framework in WP2.
2. Dependencies remain explicit and constructor-injected.
3. `RuntimeContext` contains request-scoped data only.
4. `RuntimeFactory` creates tenant-scoped services only.
5. `CompositionRoot` owns concrete process-scoped assembly.
6. Configuration formats remain compatible in WP2.
7. Logger and cache abstractions are deferred until concrete requirements exist.
8. Provider and resolver packages remain conditional on coupling observed after the Composition Root is implemented.

## 14. Acceptance criteria

The WP2 runtime design is accepted when:

- `public/reconcile.php` can eventually be reduced to environment setup, bootstrap, Composition Root invocation and controller execution;
- no global tenant state is introduced;
- Domain and Application remain independent of Runtime Infrastructure;
- the public OpenRefine contract and candidate behaviour remain unchanged;
- all existing WP1 tests and the ten-record OpenRefine test remain green.
