# Feature Specification: Persona Engine **Feature Branch**: `001-persona-engine` **Created**: 2026-08-20 **Status**: Draft **Input**: Derived from the approved Phase 0 baseline, `Persona-Engine-Developer-Handoff.txt` (document status: Approved Phase 0 baseline; classification: Generic / Sanitized). **Summary**: Deterministic, configuration-driven persona classification for Microsoft Entra ID user objects. The system evaluates every in-scope user account against an ordered, JSON-defined rule set, assigns exactly one persona, and updates a single approved persona attribute only when the calculated value differs from the stored value. --- ## User Scenarios & Testing *(mandatory)* ### User Story 1 - Preview classification without changing the directory (Priority: P1) An identity administrator runs the engine against the tenant in preview mode. Every in-scope user is retrieved, evaluated against the configured rules, and reported with its current persona, its calculated persona, and the rule that produced it. Nothing in the directory changes. **Why this priority**: This is the MVP and the primary safety control. Impact analysis must be possible before any write capability exists, and the project's initial connected development stage is explicitly read-only. Delivered alone, this story provides complete classification visibility with zero risk to the tenant. **Independent Test**: Run the engine with `-WhatIf` against a tenant using a read-only identity. Verify a per-user result is displayed for every account and that the write adapter receives zero calls. **Acceptance Scenarios**: 1. **Given** a valid configuration and a read-only identity, **When** the engine runs in preview mode, **Then** every in-scope user is evaluated and reported, and no directory write is issued. 2. **Given** a user whose calculated persona differs from the stored persona, **When** the engine runs in preview mode, **Then** the result is reported as `WouldUpdate` with both values and the matched rule ID. 3. **Given** a user whose calculated persona equals the stored persona, **When** the engine runs in preview mode, **Then** the result is reported as unchanged and no write is proposed. 4. **Given** preview mode is active, **When** the run completes, **Then** logs and summaries are produced exactly as in enforcement mode. --- ### User Story 2 - Define classification rules without changing code (Priority: P1) A security engineer expresses business classification logic as ordered rules in a JSON configuration file. Rules combine user properties, group membership, and directory-role assignments using nested `all` / `any` groups. No PowerShell source is modified. **Why this priority**: Configuration-driven behaviour is a core design principle and a precondition for every other story. Without it the engine cannot classify anything. **Independent Test**: Author a configuration containing multiple ordered rules and evaluate it against synthetic user records offline, with no tenant connectivity, confirming the expected persona for each fixture. **Acceptance Scenarios**: 1. **Given** a rule set with unique numeric priorities, **When** a user is evaluated, **Then** rules are evaluated in ascending priority order. 2. **Given** a user matching rules at priority 10 and priority 20, **When** evaluation runs, **Then** the persona from priority 10 is assigned and evaluation stops. 3. **Given** all enabled rules evaluate successfully and none match, **When** evaluation completes, **Then** the calculated persona is `Unclassified`. 4. **Given** a rule with a nested `any` group inside an `all` group, **When** evaluation runs, **Then** the composed logic resolves correctly within the configured depth limit. 5. **Given** a disabled rule, **When** evaluation runs, **Then** that rule is skipped and excluded from the enabled rule count. --- ### User Story 3 - Preserve existing values when evaluation cannot be trusted (Priority: P1) When required data for a user cannot be reliably retrieved — for example a group lookup fails — the engine records `EvaluationError` for that account, preserves the stored persona, and continues processing other users. **Why this priority**: Fail-safe behaviour is a core design principle. An unavailable data source must never be silently interpreted as a non-match, because that would misclassify privileged accounts. **Independent Test**: Inject a group-lookup failure for a specific synthetic user and verify that user receives `EvaluationError`, no write is attempted for them, and the run continues. **Acceptance Scenarios**: 1. **Given** required group data cannot be retrieved for a user, **When** that user is evaluated, **Then** the result is `EvaluationError` and the stored persona is left unchanged. 2. **Given** a user has received `EvaluationError`, **When** persistence is considered, **Then** no write is attempted for that user. 3. **Given** some users receive `EvaluationError`, **When** the run completes, **Then** the final status reports the number of affected accounts. --- ### User Story 4 - Observe progress and reconcile results (Priority: P2) An operator watching a run sees each user's result appear immediately, receives a rule-by-rule summary table at a configurable interval, and receives a final summary whose totals reconcile. **Why this priority**: Operational visibility for long tenant-wide runs. Valuable but not required for correct classification. **Independent Test**: Run against a synthetic set of 60 users with a summary interval of 25 and confirm two interim summaries, one final summary, and a passing reconciliation check. **Acceptance Scenarios**: 1. **Given** a user has been processed, **When** processing completes for that user, **Then** the result is displayed immediately rather than buffered to the end. 2. **Given** a summary interval greater than zero, **When** that many users have been processed, **Then** a table of all business rules and their counts is displayed. 3. **Given** a summary interval of zero, **When** the run proceeds, **Then** no interim summaries are displayed but the final summary is still displayed. 4. **Given** any summary is produced, **When** totals are checked, **Then** `Processed = Matched + Unclassified + EvaluationError`; a mismatch is logged as an engine defect. --- ### User Story 5 - Validate and edit configuration safely (Priority: P2) A configuration owner validates a configuration file, adds, edits, and deletes rules interactively — including each rule's condition tree — tests rules against synthetic sample users, compares against another configuration, and saves only after validation passes — with a backup taken first. **Why this priority**: Makes the configuration-driven model usable and safe in practice, but the engine can be exercised with a hand-authored file first. **Independent Test**: Point the configuration tool at a deliberately malformed file and confirm each validation layer reports actionable findings and blocks the save. **Acceptance Scenarios**: 1. **Given** a configuration with duplicate rule priorities, **When** validation runs, **Then** an Error-severity finding is produced identifying the rule IDs and the save is blocked. 2. **Given** an edit session with unsaved valid changes, **When** the file is saved over an existing configuration, **Then** a timestamped backup or save-as output is produced first. 3. **Given** synthetic sample users are supplied, **When** rules are tested, **Then** the resulting persona for each sample is reported without any tenant connection. 4. **Given** a valid configuration, **When** validation runs, **Then** no Error findings are produced and the file is accepted. 5. **Given** an interactive edit session, **When** the configuration owner adds a new rule, **Then** they are prompted for every required field (`id`, `name`, `description`, `priority`, `persona`, `enabled`) and for the rule's condition tree — including nested `all`/`any` groups and, for each leaf condition, the property or membership source, operator, and comparison value — and the new rule is appended to the in-memory document without being written to disk until save. 6. **Given** an interactive edit session, **When** the configuration owner attempts to add a rule using an `id` or `priority` already present in the document, **Then** the attempt is rejected before the rule is added, with a message naming the conflicting rule. 7. **Given** an interactive edit session, **When** the configuration owner selects an existing rule to edit, **Then** they can change any of its top-level fields and its condition tree — adding, editing, removing, or renesting conditions and `all`/`any` groups within the configured depth limit — and the change is held in memory, unsaved, until the session validates and saves. 8. **Given** an interactive edit session, **When** the configuration owner deletes a rule, **Then** they are shown the rule's `id`, `name`, and `priority` and asked to confirm before it is removed from the in-memory document. 9. **Given** a rule add, edit, or delete has been made in an edit session, **When** the session re-validates or saves, **Then** the same four validation layers (VR-001) run over the modified document exactly as they would over a hand-edited file, and any resulting Error finding blocks the save. 10. **Given** an edit session with unsaved add, edit, or delete changes, **When** the operator quits without saving, **Then** they are warned that unsaved changes will be lost and the file on disk is unchanged. 11. **Given** a rule add, edit, or delete would exceed the configured maximum condition nesting depth, **When** the change is applied, **Then** it is rejected at edit time with the same finding the runtime validator would produce, rather than deferred to the next save. --- ### User Story 6 - Block invalid configuration in a pipeline (Priority: P2) A pipeline owner runs configuration validation as a non-interactive build step that fails the pipeline on invalid configuration. **Why this priority**: Prevents an invalid or unsafe configuration reaching the automation environment. Depends on Story 5's validation engine. **Independent Test**: Execute validation in non-interactive mode against a known-bad configuration and assert a non-zero process exit code with no prompt. **Acceptance Scenarios**: 1. **Given** an invalid configuration, **When** validation runs non-interactively, **Then** the process returns a non-zero exit code and never prompts. 2. **Given** a configuration producing only Warning findings and warnings-as-errors is enabled, **When** validation runs, **Then** the process returns a non-zero exit code. 3. **Given** a valid configuration, **When** validation runs non-interactively, **Then** the process returns zero. --- ### User Story 7 - Audit any classification decision (Priority: P2) An auditor reconstructs why any account received its persona, correlating the result to a UPN, Account Object ID, matched rule, configuration version, and run ID. **Why this priority**: Auditability is a stated non-functional requirement and a precondition for the security review that gates production write access. **Independent Test**: Complete a run, then select an arbitrary user event from the structured log and confirm every required correlation field is present and internally consistent with the run-level event. **Acceptance Scenarios**: 1. **Given** a completed run, **When** a user event is inspected, **Then** it contains the run ID, UPN, and Account Object ID. 2. **Given** a changed or proposed value, **When** the event is inspected, **Then** it identifies the rule that produced the persona. 3. **Given** a completed run, **When** the run-level event is inspected, **Then** it records the configuration version and configuration file hash. --- ### User Story 8 - Enforce changes in production (Priority: P3) Once security review and approval are complete, the engine runs in enforcement mode and writes the calculated persona to the approved attribute — only for accounts whose value actually changed, and only that attribute. **Why this priority**: Deliberately last. The baseline states write permissions are not granted during the initial read-only development stage, and enforcement is gated on preview evidence and security approval. **Independent Test**: In a controlled set, change one account's inputs so its persona changes, run in enforcement mode, and verify the request body contains only the approved attribute and only that account was written. **Acceptance Scenarios**: 1. **Given** evaluation succeeded and the calculated value differs from the stored value, **When** enforcement mode runs and the operation is confirmed, **Then** the approved attribute is updated. 2. **Given** the calculated value equals the stored value, **When** enforcement mode runs, **Then** no write is issued. 3. **Given** any write is issued, **When** the request body is inspected, **Then** it contains only the approved target attribute. 4. **Given** a target attribute absent from the approved writable attribute list, **When** the configuration is validated, **Then** execution is blocked before any connection is made. --- ### User Story 9 - Trace the values behind every rule decision (Priority: P3) A rule author or developer runs the engine with the native diagnostic switch and sees, for each rule evaluated against a user, every value that fed the decision: which property was read and what it actually resolved to, which groups were checked and what the membership lookup returned, the operator and comparison value applied, and the boolean result of each condition, group, and rule. **Why this priority**: Rule authoring and misclassification troubleshooting are otherwise guesswork — a normal-mode line reports *that* `PER-800` matched, not *why* the three conditions before it did not. Valuable across the whole project life, but the engine classifies correctly without it, so it follows the rule engine rather than gating it. **Independent Test**: Evaluate a synthetic user with a known rule set under the diagnostic switch and confirm the emitted trace names every property and group the rules reference, shows each resolved value, and produces per-condition results that compose to the reported outcome. **Acceptance Scenarios**: 1. **Given** the diagnostic switch is active, **When** a rule is evaluated for a user, **Then** the trace emits that rule's identifier, name, and priority, and for each condition the property path or membership source, the resolved value, the operator, the comparison value, and the condition result. 2. **Given** a user is evaluated against several rules, **When** the trace is inspected, **Then** every rule evaluated for that user appears in priority order — not only the matched rule. 3. **Given** evaluation stops at a matching rule, **When** the trace is inspected, **Then** it records which rule matched and that evaluation stopped there, and rules after it do not appear. 4. **Given** a property is null or absent, **When** the condition is traced, **Then** the resolved value is shown explicitly as null rather than as an empty string, so null handling is distinguishable from an empty match. 5. **Given** a membership lookup failed, **When** the condition is traced, **Then** the trace shows the retrieval status as failed rather than showing the membership as false. 6. **Given** a nested condition group, **When** the trace is inspected, **Then** each group's type, its child results, and the group's own result are shown at their nesting depth. 7. **Given** the diagnostic switch is active, **When** the run completes, **Then** the persona assigned to every user is identical to the persona assigned by the same run without the switch. 8. **Given** the diagnostic switch is active, **When** write behaviour is examined, **Then** it is unchanged — the switch carries no read-only meaning. 9. **Given** the diagnostic switch is active, **When** the trace is inspected, **Then** it contains no access token, authorization header, secret, or full raw directory response. --- ### Edge Cases - **Null or absent optional property**: treated as empty for ordinary string comparisons. If it does not match the requested value the condition is false — this is not an evaluation failure. Intentional null matching uses dedicated `isNull` / `isNotNull` operators (FR-012). - **Disabled accounts**: remain in scope and are evaluated normally; `accountEnabled` is logged and may be used by configured rules (FR-011). - **No enabled rules**: semantic validation rejects the configuration before processing. - **Condition nesting beyond the limit**: rejected by both the runtime validator and the editor, whether it exceeds the configured maximum or the hard software ceiling. - **Invalid regular expression** in a rule: rejected at validation time, before execution. - **`memberOf` with no group Object IDs**, or `in` / `notIn` with no values, or `isNull` / `isNotNull` carrying a comparison value: semantic validation errors. - **Rule producing an undefined or prohibited persona**, or using `Unclassified` as an ordinary business-rule persona: semantic validation error. - **Group rules enabled while group retrieval is globally disabled**: safety validation error. - **Reconciliation mismatch** (`Processed ≠ Matched + Unclassified + EvaluationError`): logged as an engine defect, not silently ignored. - **Overlapping scheduled runs** writing conflicting values: must be prevented (see OTD-009). - **Emergency-access accounts**: identified by immutable user Object IDs inside normal rule definitions. There must be no separate hard-coded classification path (§8.8). - **Service throttling and pagination**: bounded retry with backoff for retryable responses; non-retryable authorization or validation failures are not retried indefinitely. --- ## Requirements *(mandatory)* Requirement identifiers are carried forward unchanged from the Phase 0 baseline (§7) to preserve traceability. ### Functional Requirements - **FR-001** — Load configuration: the engine MUST load a specified JSON configuration file. - **FR-002** — Validate before processing: the engine MUST validate JSON syntax, schema, semantics, and safety constraints before connecting or processing users. A configuration with errors MUST NOT be used. - **FR-003** — Authenticate: the solution MUST support interactive/delegated authentication for approved local development and managed-identity authentication for the automation environment. Authentication logic MUST be isolated behind an adapter. - **FR-004** — Enumerate all users: production runs MUST support enumeration of all in-scope user accounts, including pagination. - **FR-005** — Select required properties: the engine MUST retrieve the properties required by enabled rules plus operational fields required for logging and updates, avoiding unused properties where practical. Initial properties: `id`, `userPrincipalName`, `displayName` (diagnostics), `userType`, `accountEnabled`, `companyName`, `jobTitle`, `department`, and the configured persona attribute. - **FR-006** — Retrieve related data: the engine MUST retrieve and cache related data required by enabled rules, including configured group membership and directory-role information. - **FR-007** — Normalize data: raw directory responses MUST be converted to normalized internal objects before evaluation. - **FR-008** — Evaluate ordered rules: rules MUST be sorted by priority and evaluated in that order. - **FR-009** — Stop after first match: authoritative evaluation MUST stop at the first matching rule. - **FR-010** — Unclassified result: if all enabled rules evaluate successfully and none match, the calculated persona MUST be `Unclassified`. - **FR-011** — Disabled accounts: disabled accounts remain in scope and are evaluated normally. - **FR-012** — Null handling: a null or absent optional property is treated as empty for ordinary string comparisons and MUST NOT cause an evaluation failure. `isNull` / `isNotNull` provide intentional null matching. - **FR-013** — Group-data failure: if required group data cannot be reliably retrieved, the affected user MUST receive `EvaluationError`. An unknown membership result MUST NOT be assumed false. - **FR-014** — Preserve current value on evaluation failure: a user with `EvaluationError` MUST retain the stored persona, with no write performed. - **FR-015** — Compare current and calculated values: the engine MUST compare stored and calculated personas before persistence. - **FR-016** — Write changed values only: a production write occurs only if evaluation completed successfully, the calculated value differs from the stored value, the target attribute is valid and approved, preview mode is not active, and the operation passes the confirmation gate. - **FR-017** — Preview safety: in preview mode, reads continue, rules evaluate normally, values are compared, intended updates are reported as `WouldUpdate`, **no write request is issued**, and logs and summaries are still produced. - **FR-018** — Real-time console output: each user's result MUST be displayed immediately after that user is processed. - **FR-019** — Periodic summary: after every configured number of processed users, a table of all business rules and counts MUST be displayed. - **FR-020** — Summary interval semantics: default interval is 25; a value greater than zero displays interim summaries at that interval; zero suppresses interim summaries; a final summary is always displayed. - **FR-021** — Final reconciliation: at each summary the engine MUST verify `Processed = Matched + Unclassified + EvaluationError`, and MUST log a failed reconciliation as an engine defect. - **FR-022** — Structured logs: the engine MUST produce structured audit records suitable for file output and future ingestion into an approved logging platform. - **FR-023** — Configuration editor: the project MUST include a second tool that validates and interactively edits the JSON configuration. - **FR-024** — Non-interactive validation: the configuration tool MUST support non-interactive validation and return a non-zero exit code on failure. - **FR-025** — Configuration test data: the editor MUST support testing rules against synthetic sample users without tenant connectivity. - **FR-026** — Backup before save: interactive edits MUST be validated before save and SHOULD create a timestamped backup or save-as output before replacing an existing configuration. - **FR-027** — Add a rule: the interactive editor MUST support adding a new rule, prompting for every field in RE-001 and for the rule's condition tree (nested `all`/`any` groups and leaf conditions), and MUST reject an `id` or `priority` that collides with an existing rule before the rule is added. - **FR-028** — Edit a rule: the interactive editor MUST support editing any field of an existing rule, including full editing of its condition tree — adding, changing, removing, and renesting conditions and groups within the configured depth limit (RE-004). - **FR-029** — Delete a rule: the interactive editor MUST support deleting an existing rule, and MUST require the operator to confirm against the rule's `id`, `name`, and `priority` before removal. - **FR-030** — Uniform validation for structural edits: a rule added, edited, or deleted in the interactive editor MUST be subject to the same four validation layers (VR-001) as a hand-edited file, at re-validation and at save; edits are held only in memory until save succeeds (FR-026). ### Rule Engine Requirements - **RE-001** — Each rule MUST carry `id`, `name`, `description`, `enabled`, `priority`, `persona`, and `match`. Optional fields: `tags`, `owner`, `changeReference`, `effectiveDate`, `notes`, `testCases`. - **RE-002** — Priorities MUST be unique numeric values; lower values evaluate first. - **RE-003** — Logical composition supports `all`, `any`, and nesting of both. - **RE-004** — Maximum nesting depth is configurable: default 5, minimum 1, hard ceiling 10. Both the runtime validator and the editor MUST reject configurations exceeding the configured limit or the ceiling. - **RE-005** — Initial operators: `equals`, `notEquals`, `contains`, `notContains`, `startsWith`, `endsWith`, `matchesRegex`, `in`, `notIn`, `isNull`, `isNotNull`, `memberOf`, `notMemberOf`. - **RE-006** — String comparison is case-insensitive by default. Regex patterns MUST be validated before execution. - **RE-007** — Group membership mode is configurable per condition: `direct` or `transitive`. - **RE-008** — The engine MUST support any configured combination of identity sources (security-group membership, directory-role assignments, and further sources added through data-provider modules). First match still wins. - **RE-009** — Special accounts (for example emergency-access accounts) are identified by immutable user Object IDs within normal rule definitions. No hard-coded classification path is permitted. ### Validation Requirements - **VR-001** — Validation runs in four layers: JSON syntax, JSON Schema, semantic, safety. - **VR-002** — Semantic validation MUST detect at minimum: duplicate rule IDs; duplicate priorities; no enabled rules; invalid or blank target attribute; target attribute absent from the approved writable list; reference to an unavailable data source; `memberOf` without group Object IDs; `in`/`notIn` without values; `isNull`/`isNotNull` carrying a comparison value; undefined or prohibited persona values; `Unclassified` used as an ordinary rule persona; condition depth over the configured maximum; configured maximum over the hard ceiling; group mode not enabled globally; unsupported property name; invalid regular expression. - **VR-003** — Safety validation MUST detect at minimum: production-capable configuration with a blank target attribute; unsupported writable attribute; enabled group rules while group retrieval is disabled; prohibited configuration version downgrade; rule deletion or reorder without a version change where enforcement is enabled; condition tracing enabled without explicit acknowledgement; save path overwriting the only valid configuration without a backup. - **VR-004** — Every finding MUST carry a severity (`Error`, `Warning`, `Information`), a JSON path or rule ID, a finding code, a clear description, and a suggested resolution. - **VR-005** — Errors block execution and saving. Warnings require acknowledgement in the interactive editor and block only when warnings-as-errors is configured. ### Non-Functional Requirements Carried forward from the baseline (§19): - **NFR-001** — Runtime targets PowerShell 7 in both the local and automation environments. - **NFR-002** — Performance: display each result shortly after evaluation; cache reusable group/role data where safe; avoid repeatedly retrieving the same static membership data; record per-user and total duration. No hard performance target is set until representative tenant testing is complete. - **NFR-003** — Reliability: handle pagination; bounded retry with backoff for retryable responses; respect service-provided retry guidance; do not retry non-retryable failures indefinitely. - **NFR-004** — Maintainability: public functions carry comment-based help; business rules are never hard-coded; functions have focused responsibilities; rule evaluation remains independently testable. - **NFR-005** — Auditability: every run has a unique run ID; every user event includes run ID, UPN, and Account Object ID; configuration hash and version are recorded; every changed or proposed value identifies the matched rule. - **NFR-006** — Security: least privilege; managed identity for automation; no secrets in source control; only the approved target attribute in any write payload; no write path in preview mode. - **NFR-007** — Portability: the core rule engine and configuration validation run locally without the automation platform; tenant access is isolated behind data-provider and authentication modules. - **NFR-008** — Compatibility: avoid Windows PowerShell-only dependencies unless explicitly approved; verify all required modules in the automation PowerShell 7 environment. ### Key Entities - **User Record** — the normalized representation of an in-scope account. Carries the Account Object ID, UPN, display name, user type, enabled state, organizational properties, and the stored persona value. Produced by normalization; consumed by the rule engine. The rule engine MUST NOT see raw directory responses. - **Membership Record** — normalized group and directory-role membership for a user, with the mode (direct or transitive) that produced it, and an explicit indicator of whether retrieval succeeded. The success indicator is what distinguishes "not a member" from "unknown" (FR-013). - **Business Rule** — an ordered, uniquely prioritized, enabled/disabled classification rule producing exactly one persona when its condition tree matches. - **Condition / Condition Group** — a leaf comparison (property or membership, with an operator) or an `all`/`any` group containing further conditions, bounded by the configured depth. - **Persona Decision Result** — the outcome for one user: `Matched` (with the matched rule ID), `Unclassified`, or `EvaluationError`; plus stored value, calculated value, and the resulting action (unchanged, would-update, updated, update-failed). - **Configuration** — the complete ordered decision process: engine settings, logging settings, identity data sources, approved writable attributes, target attribute, summary interval, depth limits, and the rule set. Versioned and hashed. - **Validation Finding** — severity, location, code, description, and suggested resolution (VR-004). - **Run Record** — run-level audit event carrying the run ID, timing, versions, configuration hash, execution mode, and all outcome counters (§13.2). ### Persona Catalogue Candidate business classifications — **not** hard-coded engine behaviour: `Guest`, `BreakGlass-Admin`, `Tier0-Admin`, `Tier1-Admin`, `Tier2-Admin`, `Restricted-User`, `Test-Account`, `Service-Account`, `Shared-Functional-Account`, `Meeting-Room-Device`, `Employee`, `Contractor`, `Student`. Future modules may add `Identity-Workload` and `Agentic-Account`. `Unclassified` is a processing result when no rule matches. `EvaluationError` is an execution result and MUST NOT be treated as a normal persona rule. --- ## Success Criteria *(mandatory)* ### Measurable Outcomes - **SC-001**: Every evaluated account receives exactly one outcome — a matched persona, `Unclassified`, or `EvaluationError`. Never zero, never more than one. - **SC-002**: Running the engine twice against an unchanged directory and unchanged configuration produces zero writes on the second run. - **SC-003**: The same user record and configuration produce the same persona on every evaluation, independent of ordering, host, or run. - **SC-004**: A preview-mode run over the full in-scope population issues **zero** directory write requests, proven by test assertion on the write adapter, not by inspection. - **SC-005**: Every write request body contains exactly one attribute — the approved target attribute. - **SC-006**: 100% of user-level audit events carry run ID, UPN, and Account Object ID; 100% of matched results identify the rule that produced them. - **SC-007**: Reconciliation (`Processed = Matched + Unclassified + EvaluationError`) passes at every interim and final summary. - **SC-008**: The rule engine and configuration validation execute to completion against synthetic fixtures with no tenant connectivity and no network access. - **SC-009**: Every invalid-configuration condition listed in VR-002 and VR-003 produces a finding with a severity, a location, and a suggested resolution, and blocks execution or saving as specified. - **SC-010**: An invalid configuration in non-interactive mode fails a pipeline run via a non-zero exit code, with no prompt and no hang. - **SC-011**: Every documented exit code is reachable and returned for its documented condition. - **SC-012**: A per-user result is visible to the operator before the next user is processed. - **SC-013**: No artifact in the repository contains an organization name, real domain, tenant or subscription ID, real UPN or Object ID, real group or role identifier, or any secret, token, or credential. --- ## Assumptions - **Scope**: version 1 processes user objects only. Service principals, managed identities, workload identities, and agentic identities are out of scope, and the architecture MUST NOT assume future identity types share user-object properties. - **Configuration format**: JSON is the only supported format for version 1, and the file represents the entire ordered decision process. Tenant-specific parameters are never embedded in source. - **Environment**: the connected environment is the production tenant. Early connected development and testing use read-only permissions; write permissions are not granted during the initial development stage. - **Automation**: one automation account and managed identity. A separate production write-capable identity is not required. - **Offline testing**: unit testing uses synthetic user and membership objects and does not require tenant connectivity. - **Cadence**: a recurring schedule is intended and is deployment-configurable. Hourly is a candidate starting point, subject to tenant size and performance testing. - **Processing model**: version 1 may perform full enumeration. A delta strategy is deferred (OTD-008). - **Priority convention**: lower numeric priority values evaluate first. - **Sanitization**: all artifacts use placeholders only — ``, ``, ``, ``, ``, ``, ``, ``. Synthetic data must be obviously fictional. --- ## Clarification Register Open items carried from the baseline (§24). These are implementation research items to be resolved in `plan.md` / `research.md` or an ADR — **not** unanswered business requirements. OTD-001 through OTD-005 must be closed before persistence implementation. **Updated 2026-08-20 (T114): OTD-001 through OTD-007 and OTD-010 are resolved in [research.md](research.md). The persistence gate is lifted for Stage A3 test accounts, and remains closed for the general population until V-4 security sign-off is recorded.** | ID | Item | Status | | --- | --- | --- | | OTD-001 | Exact persona attribute mechanism — data type, read/update method, discoverability, Conditional Access compatibility | **Resolved** — directory (schema) extension property on an app registration, addressable as `user.extension__` in dynamic group rules. `extensionAttributeN` rejected (unavailable for cloud writes on ever-synced and Exchange-originated objects); custom security attributes rejected (not exposed to the dynamic group engine). See research.md OTD-001. | | OTD-002 | Exact least-privilege directory permissions for users, groups, roles, and the selected attribute | **Resolved** — `User.Read.All`, `GroupMember.Read.All`, `RoleManagement.Read.Directory`; `User.ReadWrite.All` for enforcement only. Scopes are requested per enabled-rule need, not unconditionally. See research.md OTD-002. | | OTD-003 | Whether write authorization can be restricted to the individual target attribute; if not, compensating controls plus security approval | **Resolved: it cannot.** Graph application permissions have no per-property write scope. Six compensating controls are mandatory and implemented; see [docs/SecurityModel.md](../../docs/SecurityModel.md). **Security approval (V-4) is still outstanding and gates enforcement.** | | OTD-004 | Directory access approach — SDK cmdlets, direct REST, or a controlled combination | **Resolved** — direct REST via `Invoke-MgGraphRequest`, so request bodies are explicit values that tests can assert on. This is what makes SC-005 provable. Only `Microsoft.Graph.Authentication` is a runtime dependency. | | OTD-005 | JSON Schema validation approach compatible with PowerShell 7 locally and in automation | **Resolved locally** — `Test-Json -SchemaFile`, draft-07 only. Failure behaviour observed and pinned in [V-5a](verification/V-5a.md); note that an unparseable schema returns `$true`. **V-5b (Automation runtime) remains open.** | | OTD-006 | Structured-log destination and transport | **Resolved** — NDJSON through a single sink, `Write-PersonaAuditRecord`. Additional transports are added behind that function, not by widening call sites. See [docs/Logging.md](../../docs/Logging.md). | | OTD-007 | Retry policy — retryable status codes, max attempts, backoff, jitter, logging | **Resolved** — retry 429/500/502/503/504 and status-less transport failures; never 400/401/403/404/409; honour `Retry-After`; max 5 attempts; exponential backoff with full jitter capped at 60s. See research.md OTD-007. | | OTD-008 | Full versus incremental processing roadmap | Open — full enumeration only in v1. | | OTD-009 | Production schedule and concurrency lock to prevent overlapping runs | Open — deferred with Stage B (T120). Until then the schedule is the lock; see [docs/OperationsRunbook.md](../../docs/OperationsRunbook.md). | | OTD-010 | Rollback implementation — pre-change audit values or another approved mechanism | **Data captured; tool not built.** `previousValue` is recorded at write time on every `Updated` record, which is the part that cannot be reconstructed retroactively. The rollback tool itself is out of scope for v1. | **Mandatory research item**: determine the selected persona attribute mechanism and document the exact authorization boundary for updating it. If individual-attribute authorization is unavailable, document compensating controls before implementation approval. — **Done.** The mechanism is a directory extension property; the authorization boundary is *the whole user object*, because Graph offers nothing narrower; the compensating controls are documented in [docs/SecurityModel.md](../../docs/SecurityModel.md) and each one is test-enforced. Implementation approval for enforcement still requires the V-4 sign-off. --- ## Out of Scope for Version 1 - Classification of service principals, managed identities, workload identities, or agentic identities. - Configuration formats other than JSON. - Delta or incremental processing. - Condition-level case-sensitivity control (noted as a possible future addition). - Reusable rule fragments and the circular-reference validation they would require. - Eligible-role information, unless authorization is confirmed and the data provider is implemented.