Files
personaEngine2/docs/ConfigurationReference.md
T

218 lines
11 KiB
Markdown
Raw Normal View History

# Configuration reference
Every field in `persona-engine.json`, and every finding code the validator can produce.
The authoritative schema is [`config/persona-engine.schema.json`](../config/persona-engine.schema.json)
(JSON Schema draft-07). A working example is
[`config/persona-engine.example.json`](../config/persona-engine.example.json), which is validated by
CI against its own schema — if the example the documentation points at could not pass, every reader's
first run would fail.
## Top level
| Field | Required | Notes |
| --- | --- | --- |
| `configVersion` | yes | Semantic version, `major.minor.patch`. A downgrade is a safety finding. |
| `metadata` | no | `owner`, `changeReference`, `description`. Free-form; not read by the engine. |
| `engine` | yes | Engine behaviour. |
| `dataSources` | yes | Which directory data may be retrieved. |
| `logging` | no | Audit output. |
| `personas` | yes | The declared persona catalogue. |
| `rules` | yes | Ordered business rules. |
## `engine`
| Field | Required | Default | Notes |
| --- | --- | --- | --- |
| `targetAttribute` | yes | — | The single attribute the engine may write. Must be a directory extension property and must appear in `approvedWritableAttributes`. |
| `approvedWritableAttributes` | yes | — | The allow-list. Comparison is **ordinal** — extension property names are case-sensitive in Graph. |
| `maxConditionDepth` | no | `5` | RE-004. Minimum 1, hard ceiling 10. |
| `summaryInterval` | no | `25` | Interim summary frequency. `0` suppresses interim summaries; a final summary always appears. |
| `defaultMembershipMode` | no | `direct` | Mode for membership conditions that do not specify one. |
| `evaluationErrorThreshold` | no | unset | Count of `EvaluationError` results above which the run reports exit code 4. Unset means report, do not fail. |
Setting `evaluationErrorThreshold` to `0` makes a single transient lookup failure fail the run. That
is occasionally what you want; it is rarely what you meant.
## `dataSources`
| Field | Required | Notes |
| --- | --- | --- |
| `groups.enabled` | yes | When false, no membership facet is retrieved. Rules needing it become `EvaluationError`. |
| `groups.membershipMode` | no | Pins a mode globally. **Leave it out unless you mean to restrict** — absent means "any mode is acceptable", and RE-007 makes mode a per-condition choice. Pinning it makes every per-condition override a `PE-SEM-014` warning. |
| `roles.enabled` | yes | Directory role assignments. |
| `roles.includeEligible` | no | PIM-eligible assignments. **Out of scope for v1**; no provider is implemented. |
The engine retrieves only the facets enabled rules actually reference. A configuration with no role
conditions never calls the role endpoint, so a tenant where role reads are unavailable can still run
property-only rules.
## `logging`
| Field | Required | Default | Notes |
| --- | --- | --- | --- |
| `destination` | no | `both` | `file`, `stream`, `both`, or `none`. `stream` writes records to the PowerShell Information stream. |
| `path` | no | — | NDJSON output file. One record per line. |
| `traceConditionValues` | no | `false` | Writes evaluated attribute values into audit records. |
| `acknowledgeConditionTracing` | no | `false` | **Required whenever `traceConditionValues` is true** (VR-003). |
## `personas`
The declared catalogue. A rule assigning a persona absent from this list is a `PE-SEM-010` error — the
catalogue is what stops a typo from writing a new persona value into the directory.
`Unclassified` and `EvaluationError` are processing results and may never be declared or assigned.
## `rules`
| Field | Required | Notes |
| --- | --- | --- |
| `id` | yes | Unique. Appears in every audit record; this is how a decision is traced to its rule. |
| `name` | yes | Human-readable. |
| `description` | yes | Why the rule exists. Required, because a rule nobody can explain cannot be safely changed. |
| `enabled` | yes | Disabled rules are excluded from evaluation but still appear in summaries with zero matches. |
| `priority` | yes | Unique integer. **Lower evaluates first.** |
| `persona` | yes | Must appear in `personas`. |
| `match` | yes | The root condition group. |
| `tags`, `owner`, `changeReference`, `effectiveDate`, `notes`, `testCases` | no | Metadata. `effectiveDate` is **not** evaluated — a date-dependent decision would break determinism. |
Priorities must be unique among enabled rules. The engine breaks ties by rule ID so results stay
deterministic, but the resulting order is an accident rather than a decision, so `PE-SEM-002` blocks it.
## Condition groups and conditions
A group has `operator` (`all` or `any`) and a `conditions` array. Each entry is either another group
or a condition.
| Field | Applies to | Notes |
| --- | --- | --- |
| `type` | all | `property`, `membership`, or `role`. |
| `property` | `property` | One of the supported names below, or an extension property. |
| `operator` | all | See the operator table. |
| `value` | most | Single comparison value. |
| `values` | `in`, `notIn` | Comparison set. |
| `groupObjectIds` | `membership` | Group Object IDs. Names are mutable; IDs are not (RE-009). |
| `roleIds` | `role` | Role **template** IDs, which are stable across tenants. |
| `membershipMode` | `membership` | `direct` or `transitive`, per condition. |
| `caseSensitive` | — | Reserved. Not implemented in v1; the schema accepts the key so a later version needs no breaking change. |
### Supported properties
`AccountObjectId` · `UserPrincipalName` · `DisplayName` · `UserType` · `AccountEnabled` ·
`CompanyName` · `JobTitle` · `Department`
Plus any directory extension property named `extension_<32-hex-app-id>_<name>`. Anything else is
`PE-SEM-015`: unsupported properties are never retrieved, so the condition would compare against a
permanently absent value and quietly never match.
### Operators (RE-005)
| Operator | Applies to | Notes |
| --- | --- | --- |
| `equals`, `notEquals` | property | Case-insensitive (RE-006). |
| `contains`, `notContains` | property | Case-insensitive substring. |
| `startsWith`, `endsWith` | property | Case-insensitive. |
| `matchesRegex` | property | Pattern compiled at validation time. An invalid pattern is `PE-SEM-016`, not a runtime failure. |
| `in`, `notIn` | property | Requires `values`. |
| `isNull`, `isNotNull` | property | Tests presence. **Must not carry a value** — it would be silently ignored (`PE-SEM-009`). |
| `memberOf`, `notMemberOf` | membership, role | Requires `groupObjectIds` or `roleIds`. |
Null and absent properties are treated as empty for ordinary comparisons and never cause an
evaluation failure (FR-012). Intentional null matching uses `isNull` / `isNotNull`.
## Tri-state evaluation
Conditions return `True`, `False`, or `Unknown`. `Unknown` means required data could not be
retrieved, and it propagates:
| Group | Contains | Result |
| --- | --- | --- |
| `all` | any `False` | `False` |
| `all` | only `True` plus at least one `Unknown` | `Unknown` |
| `any` | any `True` | `True` |
| `any` | only `False` plus at least one `Unknown` | `Unknown` |
An `Unknown` at a rule's root makes the account `EvaluationError`: the stored persona is preserved
and no write is attempted (FR-013, FR-014).
## Validation layers
Run in order, stopping at the first that produces `Error` findings. Running semantic checks over a
structurally invalid document yields noise, not signal.
| Layer | Mechanism | Codes |
| --- | --- | --- |
| 1 Syntax | `ConvertFrom-Json` | `PE-SYN-nnn` |
| 2 Schema | `Test-Json -SchemaFile` | `PE-SCH-nnn` |
| 3 Semantic | PowerShell checks | `PE-SEM-nnn` |
| 4 Safety | PowerShell checks | `PE-SAF-nnn` |
Codes are stable. Pipelines and runbooks match on them, so a code is never reused for a different
condition and never renumbered.
### Syntax — `PE-SYN`
| Code | Condition |
| --- | --- |
| `PE-SYN-001` | Configuration file not found, or is not a file |
| `PE-SYN-002` | File exists but could not be read |
| `PE-SYN-003` | File is not valid JSON |
### Schema — `PE-SCH`
| Code | Condition |
| --- | --- |
| `PE-SCH-001` | Document violates the schema |
| `PE-SCH-002` | Schema file not found |
| `PE-SCH-003` | Schema file exists but is not valid JSON Schema |
`PE-SCH-003` exists because of V-5a: `Test-Json` returns `$true` when the schema itself cannot be
parsed. A wrapper trusting the return value would report every configuration as schema-valid against
a schema that never ran.
### Semantic — `PE-SEM` (VR-002)
| Code | Condition | Severity |
| --- | --- | --- |
| `PE-SEM-001` | Duplicate rule ID | Error |
| `PE-SEM-002` | Duplicate priority among enabled rules | Error |
| `PE-SEM-003` | No rules, or no enabled rules | Error |
| `PE-SEM-004` | Blank target attribute | Error |
| `PE-SEM-005` | Target attribute absent from the approved list | Error |
| `PE-SEM-006` | Rule references a disabled data source | Error |
| `PE-SEM-007` | `memberOf` / `notMemberOf` with no group or role IDs | Error |
| `PE-SEM-008` | `in` / `notIn` with no `values` | Error |
| `PE-SEM-009` | `isNull` / `isNotNull` carrying a comparison value | Error |
| `PE-SEM-010` | Persona not in the declared catalogue | Error |
| `PE-SEM-011` | `Unclassified` used as a rule persona | Error |
| `PE-SEM-012` | Nesting deeper than `maxConditionDepth` | Error |
| `PE-SEM-013` | `maxConditionDepth` outside 110 | Error |
| `PE-SEM-014` | Condition mode differs from an explicitly pinned global mode | Warning |
| `PE-SEM-015` | Unsupported property name | Error |
| `PE-SEM-016` | Invalid regular expression | Error |
Several of these are also enforced by the schema. The overlap is deliberate: layer 2 can be bypassed
with `-SchemaPath`, and V-5a showed an unparseable schema passes silently. Anything that can
misclassify a privileged account is checked twice.
### Safety — `PE-SAF` (VR-003)
| Code | Condition | Severity |
| --- | --- | --- |
| `PE-SAF-001` | Blank target attribute | Error enforcing, Warning in preview |
| `PE-SAF-002` | Approved list contains a non-extension attribute | Error |
| `PE-SAF-003` | Enabled rules need data the data sources do not provide | Error |
| `PE-SAF-004` | `configVersion` lower than the deployed version | Error enforcing, Warning in preview |
| `PE-SAF-005` | Rules removed or reordered with no version change | Error enforcing, Warning in preview |
| `PE-SAF-006` | Tracing enabled without acknowledgement | Error |
| `PE-SAF-007` | Save would overwrite an existing configuration with no backup | Error |
`PE-SAF-004` and `PE-SAF-005` need `-PreviousConfigPath`. Without it they are **skipped**, and an
`Information` finding says so — silence would be read as approval.
## Escalation (VR-005)
`Error` blocks execution and saving. `Warning` blocks only under `-TreatWarningsAsErrors`.
`Information` never blocks. Passing `-TreatWarningsAsErrors` does not change a finding's severity;
it changes the caller's tolerance for it.