updated docs, removed testing files, fixed logging

This commit is contained in:
2026-08-24 10:01:21 -04:00
parent 01d08e635c
commit a54ff3c8f2
80 changed files with 587 additions and 8381 deletions
+33 -60
View File
@@ -6,24 +6,24 @@ The engine enumerates Entra user accounts, evaluates each one against an ordered
> **Project status: Stage A implementation complete — ready for tenant validation.**
>
> 109 of 121 tasks are done. **354 offline tests pass**, along with the engine-purity and
> sanitization gates. Every remaining task needs something a developer workstation does not have:
> a tenant connection (T055, T056, T101T103) or an Azure Automation account (T115T121).
> 109 of 121 tasks are done. Every remaining task needs something a developer workstation does not
> have: a tenant connection (T055, T056, T101T103) or an Azure Automation account (T115T121).
>
> **Nothing has ever been run against a real directory.** The next step is [Stage A2](#stage-a2--tenant-preview-read-only)
> — a delegated, read-only `-WhatIf` run. Follow the [testing checklist](#testing-checklist) in order.
> — a delegated, read-only `-WhatIf` run. Follow the [checklist](#tenant-validation-checklist) in order.
---
## Quick start
```bash
pwsh ./Edit-PersonaEngineConfig.ps1 -ConfigPath ./config/persona-engine.example.json -TestDataPath ./tests/TestData -ValidateOnly -NonInteractive
pwsh ./Edit-PersonaEngineConfig.ps1 -ConfigPath ./config/persona-engine.example.json -ValidateOnly -NonInteractive
```
That validates the configuration through all four layers and runs the real rule engine against
synthetic fixtures. No tenant, no credentials, no network. It is the fastest way to see what the
engine does.
That validates the configuration through all four layers. No tenant, no credentials, no network. It
is the fastest way to see what the engine does. Pass `-TestDataPath <dir>` with a directory of
synthetic user/membership fixtures to also run the real rule engine against them and see what
personas it would assign.
---
@@ -34,14 +34,12 @@ engine does.
| Requirement | Notes |
| --- | --- |
| PowerShell 7.2 or later | Developed on 7.6.5. `pwsh -v` to check. |
| `Microsoft.Graph.Authentication` | **Runtime only.** Not needed for the offline suites or the config editor. |
| Pester 5.0+ | Only to run the tests. Developed against 6.1.0. |
| PSScriptAnalyzer | Only for the lint gate. |
| `Microsoft.Graph.Authentication` | **Runtime only.** Not needed for the config editor's offline validation. |
| PSScriptAnalyzer | Only if you want to run the lint checks locally. |
| An Entra app registration | For the persona extension property and delegated scopes. |
```powershell
Install-Module Microsoft.Graph.Authentication -Scope CurrentUser
Install-Module Pester -MinimumVersion 5.0 -Scope CurrentUser -SkipPublisherCheck
Install-Module PSScriptAnalyzer -Scope CurrentUser
```
@@ -60,11 +58,11 @@ cd persona-engine
### Step 2 — Prove the machine can run it, before touching a tenant
```bash
pwsh -NoProfile -Command '& { $c = & ./tests/PesterConfiguration.ps1 -Suite Offline; Invoke-Pester -Configuration $c }'
pwsh ./Edit-PersonaEngineConfig.ps1 -ConfigPath ./config/persona-engine.example.json -ValidateOnly -NonInteractive
```
Expect **354 passed, 0 failed**. This needs no credentials and no network. If it does not pass, stop
nothing downstream is trustworthy.
Exit code `0` expected. This needs no credentials and no network. If it does not pass, stop
nothing downstream is trustworthy.
### Step 3 — Register the persona extension property
@@ -122,7 +120,10 @@ Then replace every placeholder:
| `<TIER0-ROLE-TEMPLATE-ID>` | Role **template** IDs (stable across tenants) |
| `00000000-0000-...` group IDs | Real group Object IDs |
| `<ORGANIZATION-NAME>` | Your company name as it appears in `companyName` |
| `<LOG-OUTPUT-PATH>` | Audit log path |
`logging.path` was removed from the copy — it's optional and defaults to
`<current-directory>/logs/persona-engine-audit.ndjson`. Set it (or pass `-OutputPath`) only if you
want the audit log somewhere else.
> **`config/persona-engine.json` must never be committed.** It contains real group Object IDs and
> your tenant's attribute name. Keep it in a protected configuration store, and confirm `.gitignore`
@@ -152,19 +153,14 @@ pwsh ./Invoke-PersonaEngine.ps1 -ConfigPath ./config/persona-engine.json -WhatIf
`-WhatIf` is the **only** approved no-write control. `-Debug` does not imply read-only.
### Step 8 — CI
Two pipelines are included. `pipelines/validate.yml` gates every pull request and runs entirely
offline; `pipelines/test.yml` publishes test results and coverage.
### Step 9 — Enforcement 🔒
### Step 8 — Enforcement 🔒
**Blocked on the V-4 security sign-off** (T101). Do not run without `-WhatIf` against anything other
than purpose-created test accounts until that is recorded. See [docs/SecurityModel.md](docs/SecurityModel.md).
---
## Testing checklist
## Tenant validation checklist
Work through these in order. Each stage assumes the previous one passed. **Do not skip ahead** — the
whole point of the staging is that a failure is cheap at stage A1 and expensive at stage A3.
@@ -177,28 +173,9 @@ Everything here runs on any machine with PowerShell 7.
`pwsh -NoProfile -Command "Test-ModuleManifest ./PersonaEngine.psd1"`
Fails without `Microsoft.Graph.Authentication` installed. Expected on a bare machine.
- [ ] **Offline suite: 354 passed, 0 failed**
`pwsh -NoProfile -Command "& { $c = & ./tests/PesterConfiguration.ps1 -Suite Offline; Invoke-Pester -Configuration $c }"`
- [ ] **Safety suite passes and is not empty**
`pwsh -NoProfile -Command "& { $c = & ./tests/PesterConfiguration.ps1 -Suite Safety; Invoke-Pester -Configuration $c }"`
A zero-test green run is the most dangerous possible result — it is what a mis-tagged file looks
like, and the assertions it silently drops are the zero-write and single-attribute ones.
- [ ] **Engine purity (Principle IV)**
`pwsh ./tests/Test-EnginePurity.ps1`
- [ ] **Sanitization (SC-013)**
`pwsh ./tests/Test-Sanitization.ps1`
Scans tracked **and** untracked non-ignored files, so it catches a leak before the commit.
- [ ] **Lint**
`pwsh -NoProfile -Command "Invoke-ScriptAnalyzer -Path . -Recurse -Settings ./PSScriptAnalyzerSettings.psd1"`
- [ ] **No Graph module was loaded during the offline suite**
`pwsh -NoProfile -Command "& { $c = & ./tests/PesterConfiguration.ps1 -Suite Offline; $c.Output.Verbosity='None'; $null = Invoke-Pester -Configuration $c; Get-Module Microsoft.Graph* }"`
Must print nothing. This is the proof that SC-008 holds.
- [ ] **Example configuration passes all four layers**
`pwsh ./Edit-PersonaEngineConfig.ps1 -ConfigPath ./config/persona-engine.example.json -ValidateOnly -NonInteractive`
@@ -206,9 +183,10 @@ Everything here runs on any machine with PowerShell 7.
`pwsh ./Edit-PersonaEngineConfig.ps1 -ConfigPath ./config/persona-engine.json -ValidateOnly -NonInteractive`
- [ ] **Synthetic rule test produces the personas you expect**
`pwsh ./Edit-PersonaEngineConfig.ps1 -ConfigPath ./config/persona-engine.json -TestDataPath ./tests/TestData -ValidateOnly -NonInteractive`
The fixtures include two accounts whose membership lookups failed. Both must show
`EvaluationError`, not a persona. If they show a persona, stop — FR-013 is broken.
`pwsh ./Edit-PersonaEngineConfig.ps1 -ConfigPath ./config/persona-engine.json -TestDataPath <fixtures-dir> -ValidateOnly -NonInteractive`
Point `-TestDataPath` at a directory of synthetic user/membership fixtures. If any of them
simulate a failed membership lookup, that account must show `EvaluationError`, not a persona.
If it shows a persona instead, stop — FR-013 is broken.
- [ ] **Drift check against the deployed configuration** (once one exists)
`pwsh ./Edit-PersonaEngineConfig.ps1 -ConfigPath ./config/persona-engine.json -PreviousConfigPath ./deployed/persona-engine.json -ValidateOnly -NonInteractive`
@@ -312,7 +290,7 @@ Specify -> Plan -> Tasks -> Implement
| Plan | `plan.md`, `research.md` | Complete — OTD-001…007, 010 resolved |
| Contracts | `data-model.md`, `contracts/`, `persona-engine.schema.json` | Complete |
| Tasks | `specs/001-persona-engine/tasks.md` | Complete — 121 tasks |
| Implement | `src/`, `tests/`, `pipelines/`, `docs/` | **109 / 121** — remainder needs a tenant or Automation |
| Implement | `src/`, `docs/` | **109 / 121** — remainder needs a tenant or Automation |
---
@@ -336,7 +314,6 @@ Specify -> Plan -> Tasks -> Implement
Without that, a transient Graph outage would make every privileged account look like a non-member of
its Tier 0 group, and one run would quietly demote the entire administrative population.
`tests/RuleEngine/UnknownNotFalse.Tests.ps1` exists solely to prevent that regression.
---
@@ -349,7 +326,7 @@ its Tier 0 group, and one run would quietly demote the entire administrative pop
| `-ConfigPath <string>` | Required. Validated through all four layers before any connection. |
| `-WhatIf` | **The approved no-write control.** |
| `-UserObjectId <GUID>` | Single-user execution. |
| `-OutputPath <string>` | Overrides `logging.path`. |
| `-OutputPath <string>` | Overrides `logging.path`. Both default to `<current-directory>/logs/persona-engine-audit.ndjson`. |
| `-CorrelationId <GUID>` | Run identifier; generated when absent. |
| `-SchemaPath <string>` | Schema override. |
| `-PreviousConfigPath <string>` | Enables the VR-003 drift checks. |
@@ -392,9 +369,8 @@ Invoke-PersonaEngine.ps1 thin wrapper: parameters, ShouldProcess, exit
└── Audit New-PersonaAuditRecord, Write-PersonaAuditRecord, Export-PersonaRunReport
```
The **rule engine is pure** — no Graph, no auth, no console, no filesystem, no clock. Enforced on
every build by `tests/Test-EnginePurity.ps1`, which parses each file with the PowerShell AST parser
and inspects only code tokens. That purity is why 354 tests run with no tenant.
The **rule engine is pure** — no Graph, no auth, no console, no filesystem, no clock. That purity is
what lets it be evaluated offline against synthetic fixtures with no tenant connection.
---
@@ -407,11 +383,8 @@ PersonaEngine.psd1 PersonaEngine.psm1
config/ persona-engine.example.json, persona-engine.schema.json
src/ Configuration/ Authentication/ DataProviders/ Normalization/
RuleEngine/ Persistence/ Presentation/ Engine/ Audit/
tests/ Unit/ RuleEngine/ Configuration/ Safety/ TestData/
Test-EnginePurity.ps1 Test-Sanitization.ps1 TestHelpers.ps1
docs/ Architecture.md SecurityModel.md ConfigurationReference.md
OperationsRunbook.md BusinessRules.md Logging.md
pipelines/ validate.yml test.yml
RuleAuthoringGuide.md OperationsRunbook.md BusinessRules.md Logging.md
specs/001-persona-engine/
spec.md plan.md tasks.md research.md data-model.md
quickstart.md traceability.md contracts/ verification/
@@ -448,12 +421,11 @@ No organization name, real domain, tenant or subscription ID, real UPN or Object
role identifier, environment-specific attribute name, or secret may appear in any file this
repository would commit.
`tests/Test-Sanitization.ps1` scans tracked **and** untracked non-ignored files. Reserved domains
(`example.com`, `.invalid`, `.test`) and the module manifest's own identity GUID are exempt; nothing
else is.
Reserved domains (`example.com`, `.invalid`, `.test`) and the module manifest's own identity GUID are
exempt; nothing else is. See [docs/SecurityModel.md](docs/SecurityModel.md) for the full policy.
Placeholders: `<ORGANIZATION-NAME>` · `<PRIMARY-DOMAIN>` · `<TENANT-ID>` · `<ACCOUNT-OBJECT-ID>` ·
`<GROUP-OBJECT-ID>` · `<APPROVED-PERSONA-ATTRIBUTE-NAME>` · `<AUTOMATION-ACCOUNT-NAME>` · `<LOG-OUTPUT-PATH>`
`<GROUP-OBJECT-ID>` · `<APPROVED-PERSONA-ATTRIBUTE-NAME>` · `<AUTOMATION-ACCOUNT-NAME>`
---
@@ -492,7 +464,8 @@ Full requirement-to-test mapping, including the gaps: [traceability.md](specs/00
| [Architecture.md](docs/Architecture.md) | Boundaries, and why the rule engine is pure |
| [SecurityModel.md](docs/SecurityModel.md) | OTD-003, the six controls, V-4 |
| [ConfigurationReference.md](docs/ConfigurationReference.md) | Every field and every finding code |
| [BusinessRules.md](docs/BusinessRules.md) | Writing and changing rules |
| [RuleAuthoringGuide.md](docs/RuleAuthoringGuide.md) | User manual: every condition type and operator, worked examples, the interactive editor |
| [BusinessRules.md](docs/BusinessRules.md) | Writing and changing rules — the judgement calls |
| [OperationsRunbook.md](docs/OperationsRunbook.md) | Kill switch, rollback, incidents |
| [Logging.md](docs/Logging.md) | Record types and querying |