# Quality gate, the coverage unknowns, 2026-10-10 Handoff 5 of the quality gate before 5.0.0 (Decision 21). Handoff 1 measured the C# coverage of the unit suite at `5a5d58b` and explained the unvisited paths from source; an explanation is not a test. This report measures what was still unmeasured and tests what the new numbers show. It does not close the gate: the stable 5.0.0 stays blocked, the open items of the [paths report](Quality-Gate-Paths-2026-10-09.md) stay with the maintainer, and a coverage percentage never closes a gate. Nothing was pushed, merged, tagged, or published. The work is on `ai/coverage-unknowns`, from `origin/master` at `6696f68` (PR #123). Every stage is described below; the [completion matrix](#completion-matrix) says which unknown is closed and which is open. ## Result in short - **The shipped code.** The unit suite of the host visits 3,198 of 3,649 sequence points (87.64%) of rc7 (`fa0701b`) and 1,090 of 1,168 explicit branch points (93.32%). All 232 unvisited methods are classified. The branch summary of the tool (64.15%) adds points that no person wrote, and it is not read here (Coverage of the shipped code). - **The live tests and the domain member.** The live suite (the first lab and three cells of the matrix lab) and the unit suite on a domain member give the counts of the uninstrumented acceptance, and together they add 9 sequence points and 4 explicit branch points, all of the effective-access code of `fdd7a8b`. The matrix adds nothing to the first lab. No test and no probe visits `AUDIT-OWNER-RETRY` or `AUDIT-READ-DENIED`; they stay open. - **The tests.** 79 new `It` blocks (140 rows) in four files close or narrow the gaps of the judged matrix of the 64 parameter sets: 7 sets closed, 51 partly, 1 open, 4 left to the maintainer. The unit suite then visits 45 more sequence points, all getters of parameters with pipeline input, which corrects an explanation of the paths report. At the final commit `61e936e` (built once, at `8a625c8`) the suite has 1,459 cases per configuration without a failure and covers 3,258 of 3,664 sequence points (88.92%); the same 187 methods stay unvisited, all classified. On the file servers of the matrix lab, one new test failed in every elevated configuration of the first run because of its oracle (`Get-Acl -Audit` reads the SACL of an item without audit entries as protected on a domain member; the module was right); five assertions now read the SACL alone (`f1f3d8f`), and the final build with the final tests passes the 22 files of the matrix suite on Windows Server 2019, 2022, and 2025 and on Windows 11 in all 16 configurations (1,156 cases each). - **Two defects fixed, one open.** Two comparisons were wrong in `tr-TR`, and both are fixed test-first: an identity compared with a name by `Equals` (`419cfb3`), and `Get-ChildItem2 -Filter` (`0028ccd`, `fbe0289`, `8a625c8`), which the independent review found after the first scan had missed its class of call. The first lab passes on the final build (245 passed, 1 skipped per edition). The hard-link cmdlets (`New-NTFSHardLink -PassThru`, `Get-NTFSHardLink`) fail for a path whose root is not the volume root; the defect is reproduced and left to the maintainer. - **The review.** One independent review found four Major findings and several Minor ones; each was resolved, three of the Major ones after a probe (the claim about `-eq` was wrong, the filter defect was real, the audit tests of a piped `AppliesTo` did not bind the set that they named). - **Environments.** `de-DE` and `tr-TR` are tested; Server Core has a plan; ARM64, a localized system, and non-Windows servers stay open. ## Tables | File | Content | | --- | --- | | [Methods](Quality-Gate-Unknowns-2026-10-10-Methods.csv) | the 232 unvisited methods of `fa0701b` with the rule, the category, the disposition, and what the other sources and the build of `b2cb47b` visit | | [Cmdlets](Quality-Gate-Unknowns-2026-10-10-Cmdlets.csv) | per cmdlet, the visited sequence and explicit branch points of each source (the final column is the build of `b2cb47b`; see Final measurement) | | [Reconciliation](Quality-Gate-Unknowns-2026-10-10-Reconciliation.csv) | the 232 methods against the 231 of `5a5d58b` | | [ParameterSets](Quality-Gate-Unknowns-2026-10-10-ParameterSets.csv) | the judged matrix of the 64 sets and its status after the new tests; the cited file:line references point at the tests of the final commit (mapped through the hunks of `git diff` from `fa0701b`, and for the new files from the commit that added each) | | [Mutations](Quality-Gate-Unknowns-2026-10-10-Mutations.csv) | the 35 mutations of the new tests | | [LiveRoles](Quality-Gate-Unknowns-2026-10-10-LiveRoles.csv) | the points that each role of the live suite visits | | [Cultures](Quality-Gate-Unknowns-2026-10-10-Cultures.csv) | the unit suite in `de-DE` and `tr-TR` | | [Runs](Quality-Gate-Unknowns-2026-10-10-Runs.csv) | the counts of every run of the report | | [Evidence](Quality-Gate-Unknowns-2026-10-10-Evidence.csv) | the files outside git, with size and SHA-256 | ## Candidate and source identity (Stage A) - Measured source: the tag `5.0.0-rc7`, commit `fa0701b591098ec6a6fab2ef12fe2c31d7fc7e88`, the code of the published rc7. `origin/master` holds the same code: since the tag, 13 files changed, all in the Memory Bank, the lab acceptance record, and the lab README. - Against `5a5d58b` (the measurement of the paths report), the production C# changed in three files by 70 insertions and 27 deletions: `fdd7a8b` (effective access for a user who is not an administrator, `Security2\Win32\Lib.cs`), `962887a` (audit inheritance by path and by descriptor, `Security2\FileSystem\FileSystemSecurity2.cs`), and `9205f40` (a comment in `NTFSSecurity\BaseCmdlets.cs`). The unit tests grew by 31 lines in `Tests\Inheritance.Tests.ps1`, the live tests by 623 lines. - Build: Release, .NET Framework 4.5.2, 0 errors and 317 warnings (296 of CS1591, 19 of CS1574, one of CS0169, one of CS0618), the same as at `5a5d58b`. None was suppressed. - Tools: Windows PowerShell 5.1.26100, PowerShell 7.6.6, Pester 5.7.1, AltCover 9.0.145 (net472), on one Windows Server 2025 host. - The measured assemblies are the Release files that the Validate run used; AltCover saved them unchanged (`__Saved`) before it instrumented them. The Release build is not byte-reproducible, so the hashes identify the measured files, not the source. | Assembly | SHA-256 | | --- | --- | | `NTFSSecurity.dll` | `6749E31430320EA92698F3475A33433A965BE9558C0C7B34797CDAAAF88700DB` | | `Security2.dll` | `BE8A186D4D542009B95792A8FC5EFC3F21EEC11DC1694BAFB7F4F7D616B066E0` | | `ProcessPrivileges.dll` | `BAD3C7AFAF328AE6002AC2F6E81F9F79236CA5B2F9A281BBAA9A7153D49E54B5` | | `PrivilegeControl.dll` | `81F15234907EBD4678E174DD6E4EC637B859C54FC54E00DAA1A68BC0C20B9F0B` | ## Method of Stage A The method of the paths report, in one frozen worktree outside the repository (`git worktree add --detach`, the junction `packages`). Three modes of `Run-Frozen.ps1`, in this order, because Coverage instruments `bin\Release` in place: - Build: native .NET Framework MSBuild with `/p:Configuration=Release /t:Rebuild`, the cached Roslyn 4.2.0 compiler (`CscToolPath`), and the .NET 4.5.2 reference assemblies (`TargetFrameworkRootPath`, `FrameworkPathOverride`). - Validate: the four configurations without instrumentation, with the CI wrappers `.github\scripts\Invoke-Tests.ps1` (elevated) and `Invoke-TestsAsBasicUser.ps1` (a restricted token), in Windows PowerShell 5.1 and PowerShell 7. The run fails when a configuration has a failure or another case count than the first. - Coverage: `AltCover.exe --inputDirectory= --report= --reportFormat=OpenCover --localSource --assemblyFilter=AlphaFS|System.Management.Automation --inplace`, the four configurations one after the other, then `AltCover.exe runner --collect --recorderDirectory=`. Never `--save`: that kept the hits of one process only. Skip eligibility is checked by row, on a second, uninstrumented copy of the same build (the same bytes, hash-checked): one Pester process per configuration records every row with its file, line, path, name, data, and result, and a skipped row needs the same row executed in another configuration. Raw names are not used: NUnit keeps the placeholders of a skipped data row. ## Local validation Each configuration discovered 1,314 test cases (1,310 at `5a5d58b`); no test failed. The four new cases are two `It` blocks of `Tests\Inheritance.Tests.ps1` with two data rows each (the audit state of an item by path and by descriptor, `962887a`); the second block skips in the basic-user configurations, because it changes audit entries. | Configuration | Passed | Failed | Skipped | Total | | --- | ---: | ---: | ---: | ---: | | Windows PowerShell 5.1, elevated | 1,290 | 0 | 24 | 1,314 | | Windows PowerShell 5.1, basic user | 1,078 | 0 | 236 | 1,314 | | PowerShell 7, elevated | 1,259 | 0 | 55 | 1,314 | | PowerShell 7, basic user | 1,047 | 0 | 267 | 1,314 | The instrumented runs gave the same counts. The skipped rows are 582 rows of 138 distinct tests (354 rows with data), against 578 and 137 at `5a5d58b`. Every skipped row has the same test, with the same data, executed in two other configurations; no skipped row lacks an executed counterpart. ## Coverage of the shipped code | Assembly | Sequence points | Sequence coverage | Branch points | Branch coverage | | --- | ---: | ---: | ---: | ---: | | NTFSSecurity | 1,912/2,168 | 88.19% | 683/1,094 | 62.43% | | Security2 | 1,061/1,240 | 85.56% | 509/753 | 67.60% | | ProcessPrivileges | 205/219 | 93.61% | 72/125 | 57.60% | | PrivilegeControl | 20/22 | 90.91% | 12/17 | 70.59% | | Aggregate | 3,198/3,649 | 87.64% | 1,276/1,989 | 64.15% | The ratios are visited sequence or branch points divided by their totals, not unique source lines. The branch summary of the tool is not a count of the branches that a person wrote: it has 821 points more than the 1,168 explicit branch points, and they are not patterns such as `foreach` and `using`, as the paths report described them. The tool adds one point to each of the 198 methods that have explicit branch points, which it counts as visited when the method runs (186 of the 198 did), and one point to each of the 623 methods that have none, which it never counts as visited, although 456 of those methods run (`inventory-fa0701b\branch-summary-decomposition.txt`, read from the summary of every method in the report). The summary percentage therefore falls when a method without a branch is added; this report reads the explicit branch points, 1,090/1,168 (93.32%). ### Against `5a5d58b` | | `5a5d58b` | `fa0701b` | Difference | | --- | ---: | ---: | --- | | Sequence points | 3,192/3,634 (87.84%) | 3,198/3,649 (87.64%) | +6 visited, +15 points | | Branch points | 1,273/1,978 (64.36%) | 1,276/1,989 (64.15%) | +3 visited, +11 points | | Explicit branch points | 1,088/1,158 (93.96%) | 1,090/1,168 (93.32%) | +2 visited, +10 points | | Unvisited methods | 231 | 232 | one new method | | Unvisited sequence points | 442 | 451 | +9 | | Unvisited explicit branch points | 70 | 78 | +8 | Only `Security2` changed (1,055/1,225 to 1,061/1,240 sequence points, 506/742 to 509/753 branch points); the other three assemblies have the same counts. The code is different, so the two measurements are not increments of one source. The ratios fell by 0.2 points because the new code is mostly paths that the host cannot run: `Security2` gained 15 sequence points and 6 of them are visited (`FileSystemSecurity2` +2 of 2, from `962887a`; `Win32` +4 of 13, from `fdd7a8b`). ### Reconciliation of the 231 methods By assembly, class, and method, against the inventory of `5a5d58b`: 220 methods are unchanged (the same points and the same lines); 8 only moved (`TryGetDriveRoot`, the two conversions of `FileSystemSecurity2`, `GetHashCode`, `GetByteSecurityDescriptor`, `GetRawSecurityDescriptor`, `GetEffectiveAccess`, and `GetEffectivePermissions_AuthzAccessCheck` changed lines, not points, because code was inserted above them); 3 are changed by the code of the commits; none is now fully visited and none is gone; one method is new (232 in all). | Method (`Security2.Win32`) | Unvisited at `5a5d58b` | Unvisited at `fa0701b` | What changed | | --- | --- | --- | --- | | `GetEffectivePermissions_AuthzInitializeResourceManager` | 3 sequence, 3 branch | 4 sequence, 6 branch | the remote interface is asked only for a name; the success path, and a refusal of this computer, are new branches | | `GetEffectivePermissions_AuthzInitializeContextFromSid` | 0 sequence, 1 branch | 6 sequence, 4 branch | the fallback to the local manager for a refusal of this computer | | `GetEffectivePermissions_AuthzInitializeLocalResourceManager` | (part of the method above) | 1 sequence, 1 branch | new method, split out by `fdd7a8b` | | `IsLocalComputer` | 2 sequence, 0 branch | 3 sequence, 1 branch | its guard for an empty name is no longer reachable: the only caller checks the name first | The net change is +9 sequence points and +8 explicit branch points, which is the whole difference of the table above. The audit read of `962887a` in `FileSystemSecurity2` adds no unvisited point: the unit suite runs it. ### Classification The rule of a method stays where the method and its code are unchanged. The old rule `WIN32-AUTHZ-FAILURE` matched by class and method name, so it had absorbed the four changed methods with an explanation that does not describe their new code. Its scope is narrowed to the two methods whose code did not change (`GetEffectiveAccess`, `GetEffectivePermissions_AuthzAccessCheck`), and the new code has rules of its own: `AUTHZ-INIT-ERROR`, `AUTHZ-LOCAL-FALLBACK`, `AUTHZ-LOCAL-RM-FAILURE`, and the extended `WIN32-LOCAL-NAME-LOOKUP`. Every one of the 232 methods has a rule; none is unclassified. One explanation of the paths report is corrected, and the new tests of Stage C showed it (the section on the result of those tests has the evidence). The rule `CMDLET-GETTER` said of all 103 getters of cmdlet parameters that PowerShell binds a parameter through its setter, so that no caller invokes the getter. That holds for a parameter without pipeline input (46 getters). PowerShell reads the getter of a parameter that it binds from a piped object, by value or by property name, so the 57 getters of parameters with pipeline input were reachable and untested, not explained. They have the new rule `CMDLET-GETTER-PIPELINE`, with the disposition Open. | Category | Disposition | Methods | Unvisited sequence points | Unvisited explicit branch points | | --- | --- | ---: | ---: | ---: | | unused by cmdlets | Explained | 60 | 173 | 34 | | parameter/API surface | Explained | 46 | 46 | 0 | | defensive | Explained | 34 | 79 | 18 | | environment-specific | Explained | 27 | 88 | 24 | | unused by cmdlets | Open | 8 | 8 | 2 | | parameter/API surface | Open | 57 | 57 | 0 | | **Total** | | **232** | **451** | **78** | "Explained" means a source-backed explanation exists, not that a test runs the path; the 8 open methods of the category "unused by cmdlets" are those of the paths report (`FileSecurity` conversions, `RemoveAll` account filters). ## Stage B: the live tests and the domain member The host that measured Stage A is in a workgroup and runs one user at a time, without a file server. Code stays out of its reach when it needs a computer in a domain (which offers the remote interface of its authorization manager to every caller), an account that is not an administrator of that computer, or SMB. This stage measures that code with the assemblies of Stage A. ### Design The module under test runs as three domain accounts (the roles Delegate, ServerAdmin, and Admin of `Tests\Lab\Invoke-NTFSSecurityLabTest.ps1`) on a client that receives a copy of the module folder, in both editions. AltCover fixes the path of its report when it instruments, and its recorder adds the visits of a process to that report when the process ends. So the design has to answer where the report lives, who may write it, and how the visits come back. The instrumented module is built on the host from the extracted package of the Stage A build, with the PDBs of the same build next to the assemblies. The four input assemblies are byte-identical to the measured files of Stage A (the hashes above), and the zero-hit report holds the same 3,649 sequence points and 1,989 branch points. The command: ```powershell AltCover.exe --inputDirectory= --outputDirectory=\NTFSSecurity ` --report=C:\NtfsMatrixProbe\Coverage\coverage.xml --reportFormat=OpenCover ` --localSource '--assemblyFilter=AlphaFS|System.Management.Automation' ``` Repeating this command on the same input gave the same four instrumented assemblies and the same report, byte for byte; only the generated recorder differs. | Instrumented file | SHA-256 | | --- | --- | | `NTFSSecurity.dll` | `64AC86D13CFB35650CE1386CD8E769569889FB54C9828DF27D5CC6AAF49E67B3` | | `Security2.dll` | `A415FC0D32E81A7D88E01B684939889F9604AF4602AA931ED3B61CDD5F14D280` | | `ProcessPrivileges.dll` | `2EDD77650122A12E27093943882DE872D52E06F63AB78C44378C27BFDE815B34` | | `PrivilegeControl.dll` | `3B025E3C3B64DBB99473F4D3416EED76E01D06C2AF26ACF48545AC12EFF7883F` | | `AltCover.Recorder.g.dll` | `A521BDBF19D711FF75D44B094CC808401251912DF08D3D158DF7C9A0F5F2F160` | | zero-hit report | `B29CAD26164A2667DCBE631DD74565638E192540CB48A45A1E4290CA8904BC06` | The measurement counts only if the instrumented run gives the counts of the uninstrumented acceptance: 245 passed and 1 skipped per edition in the first lab (roles 69, 36, 64, and 76), exit code 0 for every role, and one flush line of the recorder in the log of every role that runs the module. **First design, rejected: the report next to the module.** `--portable` makes the recorder write `coverage.xml` next to its own assembly, which is the folder that the controller copied to the client. It worked on the host in both editions. In the first lab (`fl1b`) the role ServerAdmin is not an administrator of the client, and the folder of the copied module (`C:\NTFSSecurityLab`) inherits its permissions from `C:\`, where Windows Server 2025 gives Authenticated Users no right to write (Windows 11 does). The report has no visit of that role (its log has no flush line), and in PowerShell 7 its process ended with the exit code `-532462766` (0xE0434352, the code of a .NET exception) after all 36 tests had passed. The Windows PowerShell run kept the counts (245, 0, 1) but lost a role, and the PowerShell 7 run was rejected (`LIVE_RESULT_NOT_ACCEPTED`). Evidence: `E\live-fl1b`. **Second design, accepted: a report outside the module and a watcher.** The report path is absolute and outside the payload, `C:\NtfsMatrixProbe\Coverage`. Per client, before the run, the host creates that folder with `icacls /grant *S-1-5-11:(OI)(CI)M` (Authenticated Users, modify), copies the zero-hit report into it, and starts a task as `SYSTEM` (`NtfsMatrixCoverageWatch`, `Watch-Coverage.ps1`) that copies the report to `snapshots\snap--