# Contract: `Invoke-PersonaEngine.ps1` The engine entry point. Retrieval, evaluation, reporting, and controlled persistence. ## Signature ```powershell [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] param( [Parameter()][string] $ConfigPath = './config/persona-engine.json', [Parameter()][guid] $UserObjectId, [Parameter()][string] $OutputPath, [Parameter()][guid] $CorrelationId ) ``` `SupportsShouldProcess` supplies `-WhatIf` and `-Confirm`. `-Verbose` and `-Debug` are common parameters and are **not** declared. ## Parameter contract | Parameter | Required | Behaviour | | --- | --- | --- | | `-ConfigPath` | No | Path to the JSON configuration. Defaults to `./config/persona-engine.json`, resolved against the current directory, when omitted. Validated through all four layers before any connection is made (FR-002). | | `-WhatIf` | No | **The approved no-write control.** Reads, evaluation, comparison, console output, summaries, and audit records all behave identically to enforcement; zero write requests are issued (FR-017, SC-004). | | `-UserObjectId` | No | Single-user execution for validation. Skips enumeration; retrieves one user. | | `-OutputPath` | No | Overrides the configured audit output path where permitted. | | `-CorrelationId` | No | Supplied run identifier. Generated when absent. Appears on every audit record. | | `-Verbose` | No | Operational detail. **MUST NOT** alter write behaviour. | | `-Debug` | No | Enables condition-value tracing (Principle V). **MUST NOT** imply read-only — a `-Debug` run without `-WhatIf` writes. | ## Mode determination ```text $PSCmdlet.ShouldProcess() returns $false -> Preview mode -> no write request constructed or sent $PSCmdlet.ShouldProcess() returns $true -> Enforce mode -> write permitted, subject to FR-016 ``` Mode MUST be derived from `ShouldProcess` alone. A separate boolean "preview" flag is prohibited — two sources of truth for the write gate is precisely the defect class Principle III exists to prevent. ## Write gate (FR-016) A write is issued only when **all** hold: 1. Evaluation completed successfully (`Outcome != EvaluationError`). 2. `CalculatedPersona != StoredPersona` (ordinal comparison, case-sensitive for change detection). 3. The target attribute is non-blank and present in `approvedWritableAttributes`. 4. `ShouldProcess` returned `$true` for this user. Failing any of these yields `Unchanged`, `WouldUpdate`, or `Skipped` — never a silent write. ## Output contract - **Per user, immediately after evaluation** (FR-018, SC-012): one console line carrying UPN, Account Object ID, outcome, matched rule ID, stored value, calculated value, and action. - **Every `summaryInterval` users** (FR-019): a table of all business rules with match counts, plus outcome totals, elapsed wall-clock time since the run started, and a reconciliation check. - **At completion**: a final summary regardless of interval, including when the interval is `0` (FR-020). - **Every summary, interim and final**: `logging.resultsFileName` (default `results.csv`, written next to the audit log) is overwritten with one row per account processed so far — Account Object ID, UPN, the assigned persona or outcome status, company name, and department. - **Reconciliation** at every summary: `Processed = Matched + Unclassified + EvaluationError` (FR-021). A mismatch is logged as an engine defect, at `Error` severity. - **Audit records**: see [audit-record.md](audit-record.md). ## Exit codes | Code | Condition | | --- | --- | | `0` | Successful run; no fatal processing errors | | `1` | Configuration validation failure | | `2` | Authentication / authorization failure | | `3` | User enumeration failure | | `4` | Fatal required data-provider failure | | `5` | Reconciliation failure | | `6` | Unexpected fatal engine error | Every code MUST be reachable and returned for its documented condition (SC-011). A per-user `EvaluationError` does **not** by itself terminate the run; the final status reports the affected count and applies `evaluationErrorThreshold` when configured. ## Invariants (test-asserted) | Invariant | Assertion | | --- | --- | | Zero writes under `-WhatIf` | The write adapter is mocked; call count is `0` over a full synthetic population (SC-004) | | Single-attribute body | Every captured request body has exactly one key, equal to `engine.targetAttribute` (SC-005) | | Idempotence | Second consecutive run over unchanged input issues zero writes (SC-002) | | Determinism | Same fixture set, shuffled input order, identical results (SC-003) | | Exactly one outcome | Every processed user appears in exactly one outcome bucket (SC-001) |