# Persona-domain architecture deep dive — v100.0.30

## Why this seam was chosen

`includes/agent-persona-service.php` is the largest remaining P0 module. A direct rewrite would combine semantic migration, output-schema risk, nondeterministic generation, and hundreds of call sites in one change. The first safe seam is identity construction because it is deterministic for a normalized request and source type, high fan-out, and separable from HTTP and rendering.

## Before

The legacy module owned input normalization, seeded selection, name parsing, title migration, identity assembly, biography, causal history, worldview, validation, realism audits, fingerprints, prompt rendering, and output projection. Small behavior changes could therefore alter distant output fields without an executable explanation.

## After

The application now has a bounded `Spiralist\Domain\Persona` layer:

- `PersonaDraft` preserves normalized request and source-type input as an immutable value object.
- `PersonaProfile` is an immutable wrapper around the canonical generated profile and its identity metadata.
- `SeededSelector` owns coordinate-stable deterministic selection.
- `NameNormalizer` owns role-like-name detection, formal-title parsing, cultural name splitting, and assembly.
- `PersonaIdentityDependencies` makes legacy catalog and policy callbacks explicit.
- `PersonaIdentityBuilder` owns the extracted identity stack.
- `PersonaInvariantValidator` owns ordered identity validation findings.

Legacy functions remain at their original names and signatures. They now delegate to the typed objects, which protects external callers while allowing incremental migration.

## Characterization architecture

The fixture contract is intentionally layered:

1. **Exact identity seam:** stable JSON bytes and SHA-256 must match.
2. **Normalized canonical profile:** volatile timestamps, fingerprints, and derivative digests are removed; all remaining bytes must match.
3. **Semantic projection:** the critical identity, demographic, provenance, behavior-count, and memory-count fields must match.
4. **Observed full output:** retained for diagnosis, but not promoted to an equality requirement until the clock/fingerprint defect is removed.

This structure prevents two opposite failures: shallow tests that miss semantic drift, and false determinism claims that fail only because the old code reads the current clock.

## Dependency direction

```text
HTTP/page/API facades
        |
        v
includes/agent-persona-service.php  (compatibility facade and orchestration)
        |
        v
src/Domain/Persona/*                (deterministic identity policy)
        |
        v
explicit closures/catalog inputs     (legacy data during migration)
```

The domain layer does not call the provider, emit headers, terminate the process, persist files, or render pages. That direction is enforced by the response-ownership policy and static-analysis contract.

## Determinism debt discovered

`agentRealismAudit()` records current UTC before the full fingerprint is computed. Therefore identical requests in separate moments can yield different fingerprints and derivative evidence. The current release does not "fix" this opportunistically because fingerprints are a public compatibility surface. The next phase must:

1. introduce an injected clock interface or deterministic generation timestamp;
2. add baseline fixtures for the current fingerprint behavior;
3. define migration/version semantics for fingerprint changes;
4. promote full raw profile bytes to a required snapshot gate;
5. only then extract biography, causal identity, and worldview projections.

## Response ownership

The codebase still has legacy endpoint files that directly call `header()` and `exit`. The safe policy is a ratchet: every new endpoint uses `ResponseEmitter`, and no new raw owner is allowed. This release preserves the baseline counts of 141 header owners and 30 exit owners while adding zero in the new persona domain.

## Static analysis

A level-6 PHPStan configuration now scopes the canonical source, configuration, and PHP tests. An empty baseline makes future suppressions explicit. PHPStan itself was not installed in the packaging environment, so the release keeps `phpstanRunPerformed` and `phpstanAcceptanceClaimed` false. This is a foundation, not a fabricated pass.

## Compatibility and future extraction rules

- Preserve public function signatures until call-site inventory reaches zero.
- Characterize before extracting.
- Keep deterministic domain policy below orchestration.
- Keep time, randomness, providers, and persistence injected at boundaries.
- Prefer immutable values over arrays at new seams; convert to arrays only at legacy boundaries.
- Record error ordering where consumers may depend on it.
- Never mix a schema change into a structural extraction release.
- Keep deployment and live acceptance separate from package-local code quality.
