Skip to content

fix: post-release review defects, performance, NTFSSecurity migration guide, and CI upkeep - #5

Merged
raandree merged 20 commits into
mainfrom
ai/post-release-fixes
Oct 7, 2026
Merged

raandree merged 20 commits into
mainfrom
ai/post-release-fixes

Conversation

@raandree

@raandree raandree commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Pull Request (PR) description

This pull request lands the work left open after v0.3.0-preview0001. Each
part has its own commits, so any part can be reverted on its own.

  • Lab acceptance runner. tests/Lab/Invoke-WindowsAccessControlLabAcceptance.ps1
    no longer deletes directories it did not create. It marks the payload root,
    the package directory, and the installed module version it creates, and it
    refuses unmarked targets, owned trees that contain a junction or symbolic
    link, and payload roots that contain [ or ] (96d6671).
  • Task Scheduler. A DACL write may restore Local System to a DACL that has
    no Local System entry; a missing or null current DACL is still refused
    (5531824).
  • SMB shares. A share DACL write reads the share description just before
    the native call, keeps an edit made in the meantime, and warns instead of
    failing when the description cannot be restored after a committed write
    (618c6d5).
  • Active Directory. Get-ADObjectCallerEffectiveAccess no longer requests
    nTSecurityDescriptor, so a caller without read-control access still gets
    an answer (8b67058).
  • Performance. Shared descriptor processing and batch dispatch do less work
    per rule (fbced95). Re-measured in fresh processes with matching artifact
    hashes and output checksums: in PowerShell 7, rights display is 69 % faster,
    removed-entry detection 66 %, dispatch 18 %, and isolated CNG 15 %; in
    Windows PowerShell 5.1, removed-entry detection is 95 % faster. Details are in
    docs/performance-refactor-2026-09-10.md.
  • Documentation. A migration guide for NTFSSecurity users,
    docs/migration-from-ntfssecurity.md, maps every NTFSSecurity command to its
    replacement and is linked from the README, the documentation index, and the
    wiki Home page. The README section on certificate private keys now matches
    specification 0015 instead of calling the family read-only (d683f2a,
    add9273).
  • CI. actions/checkout, actions/upload-artifact, and
    actions/download-artifact move to their Node 24 releases, pinned by SHA,
    with a test that keeps every action pinned (a6697c3). The seven changed
    lines are identical to Dependabot chore(deps): bump actions/download-artifact from 4.3.0 to 8.0.1 #1, chore(deps): bump actions/upload-artifact from 4.6.2 to 7.0.1 #2, and chore(deps): bump actions/checkout from 4.4.0 to 7.0.1 #3.
  • Changelog and release. CHANGELOG.md regains the 0.2.0 section that the
    v0.2.0 release pushed to updateChangelogAfterv0.2.0 but never opened a pull
    request for (8be6892). A stable release now ends with an error when its
    changelog pull request does not exist, instead of reporting success
    (d55897f). Two test files end with a newline (023afe9). Do not merge
    updateChangelogAfterv0.2.0; this branch supersedes it.

GitVersion 5.12.0, run on a simulated merge commit of this branch into
main, reports 0.3.0-preview0002, so the merge publishes a preview.

This Pull Request (PR) fixes the following issues

What was run, and what the result was

On 023afe9, on Windows with PowerShell 7.6.6 and Windows PowerShell
5.1.26100, each step in its own process. The branch tip, 89d9c2e, adds only
a Memory Bank update on top of it.

Command Result
.\build.ps1 -Tasks build Succeeded: 7 tasks, 0 errors, 0 warnings
.\build.ps1 -Tasks test in PowerShell 7 1,931 passed, 0 failed, 2 skipped; asserted coverage 83.11 % (6,910 of 8,314 commands, threshold 80 %); 10 tasks, 0 errors, 2 warnings from the lab-runner failure-path tests
.\build.ps1 -Tasks test in Windows PowerShell 5.1 1,886 passed, 0 failed, 2 skipped; asserted coverage 80.95 % (6,730 of 8,314 commands, threshold 80 %); 10 tasks, 0 errors, 2 warnings from the same lab-runner tests; 11 min 21 s, with local WinRM running for the two DSC-engine tests
.\build.ps1 -Tasks pack (on add9273) Succeeded: 22 tasks, 0 errors, 0 warnings
Invoke-ScriptAnalyzer 1.25.0 over the 41 changed PowerShell files 0 findings

Focused evidence for each part:

  • Lab runner: 79 lab-runner tests pass in both editions. Before the change
    they failed for the expected reason, and the runner deleted an unmarked
    payload root without an error.
  • Task Scheduler, SMB, and Active Directory: each change has a regression test
    that failed without it.
  • Performance: new tests pin the dispatcher's command and family guard and a
    worker's per-target parameter copy; each failed with its guard removed.
  • CI: the pinning test failed three of its seven cases before the update; the
    changelog pull request test failed seven cases before the workflow change.
  • Changelog: a before-and-after comparison of all 859 content lines shows no
    line lost or duplicated, and Get-ChangelogData parses the result.
  • Documentation: every published example in the migration guide ran verbatim
    against a scratch directory in both editions.

The domain acceptance lab was not run. This change touches the SMB share,
Active Directory, and Task Scheduler families, and the performance change
touches the batching every family shares. The lab host was not available, so
the new live Active Directory cases are written but have not run. Live
acceptance of this candidate is owed before the next stable release.

Task list

  • Added an entry to the change log under the Unreleased section of the file
    CHANGELOG.md. The entry says what changed for someone using the module and
    why, and references the issue being resolved (if applicable).
  • The behavior change starts in a specification under specs/, every
    requirement identifier it adds is unique, and each one traces to
    executable evidence in specs/0005-verification-and-traceability.md.
  • A durable architectural choice is recorded under specs/decisions/ and
    indexed in specs/decisions/README.md.
  • Comment-based help added or updated for every new or changed command,
    including a synopsis, a description, an example, and every parameter.
  • Documentation added or updated in README.md or under docs/.
  • Unit tests added or updated, and a bug fix keeps a regression test that
    fails without the fix.
  • Integration or live tests added or updated where the behavior can only be
    measured against a real object.
  • .\build.ps1 -Tasks test passes in both supported PowerShell editions,
    including the coverage threshold, with no assertion weakened to get there.
  • Invoke-ScriptAnalyzer is clean over everything that changed, and any
    suppression carries a Justification.

raandree and others added 20 commits October 6, 2026 09:36
Replace the stale release-pending status: v0.3.0-preview0001 shipped on 2026-09-07. Record the restored ai/performance-refactor branch, its review findings rechecked against main, the dependabot failure cause, issue #4, the uncreated v0.2.0 changelog pull request, and FIND-002.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eight sequential handoff prompts outside the repository now drive the triaged work. They commit to the local integration branch ai/post-release-fixes; only the final prompt touches the remote, with approval per action.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The domain-lab acceptance runner recursively deleted the payload root,
its package child, and any same-version WindowsAccessControl
installation on the management domain controller without checking
that it had created them.

The runner now marks each directory it may later replace and replaces
one only when it is absent, or marked with no junction or symbolic link
inside. The domain controller rejects a payload root that is not a
dedicated local directory before anything changes, in every mode. Each
remote step confirms its directory, because AutomatedLab reports a
remote throw without stopping the calling script.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Test-WindowsTaskSchedulerSystemAce refused every candidate when the
current DACL held no literal Local System ACE, so a write that restored
SYSTEM was rejected. Specification 0010 requires only that each current
Local System ACE stays byte-identical and that no Local System deny ACE
is added. History shows no intent behind the extra refusal: it arrived
with 442162a without a decision record, spec text, or test.

A current descriptor whose DACL is missing or null is still refused,
because neither preservation nor an exact rollback can be verified
against it; the setter now says so in its own message.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Set-WindowsSmbShareSecurityDescriptor compared the description after the
native SE_LMSHARE write with the value captured when the target was
resolved and wrote that snapshot back whenever they differed, so an edit
made after resolution was overwritten.

The setter now reads the description immediately before the native
write. Afterwards it restores that value only when the write cleared
it, reports the restoration in the verbose stream, and leaves any other
new value in place with a warning. A description that cannot be read
before the write stops the command before anything is written. A failed
restoration after a failed write still raises one AggregateException.

Following decision 48, no step after a committed write throws: a
description that cannot be checked or restored afterwards is reported as
a warning naming the earlier value, and those warnings ignore a Stop or
Inquire preference, because the DACL change is already live.

The setter is now exercised by unit tests, so it leaves the domain-lab-
only coverage list and its coverage is asserted.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Get-ADObjectCallerEffectiveAccess resolved every target, in the
dispatcher's prevalidation and again in the worker, through a request
for nTSecurityDescriptor that it never used. A domain controller omits
that attribute for a bind without READ_CONTROL, and
Get-WindowsADObjectRecord then failed with "Cannot index into a null
array" instead of returning the caller's effective access.

Resolve-WindowsADObjectTarget and Invoke-WindowsADCommandBatch gain an
opt-out switch that only this command sets; every descriptor-dependent
caller keeps requesting the descriptor, and a write target refuses the
switch. When a requested descriptor is absent or has no value,
Get-WindowsADObjectRecord now throws UnauthorizedAccessException naming
the likely missing read-control access.

Send-WindowsADSearchRequest now holds the LDAP send and its
NoSuchObject translation, so unit tests can supply a directory
response. The catch stays beside the native call, where $_.Exception is
still the DirectoryOperationException. Get-WindowsADObjectRecord and
Resolve-WindowsADObjectTarget move into asserted coverage, with unit
tests for the write-target path, and the new seam is declared
domain-lab-only. The domain-lab AD suite gains a restricted-caller case
and a missing-object case for the next live acceptance run.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cache immutable rights displays, filter NTFS SIDs before conversion, and streamline exact ACE comparison and batch parameter copying. Add regression cases and cross-edition benchmark evidence while preserving public contracts.

Core: 1811 passed with 82.64% asserted coverage. Final focused suites: 145 passed per edition. Full Desktop retains two existing WinRM-blocked DSC checks; no host configuration or remote was changed.

Ported from ai/performance-refactor onto ai/post-release-fixes. Its
Memory Bank hunks resolve to the current files, and the docs/README.md
row joins the current rows. The figures above are from the original
2026-09-10 run against 5991673.

(cherry picked from commit a3e3152a4d6c027204d4b075da27db779852c38e)

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrite the release notes to lead with measured impact and the unchanged command surface instead of internal mechanism, document the descriptor benchmark on the diagnostics page, and record why the ordinal comparer is required for Base64 directory identities but only defensive for uppercase-hexadecimal private-key identities.

Ported from ai/performance-refactor onto ai/post-release-fixes. Its
Memory Bank hunks resolve to the current files.

(cherry picked from commit 44dcc70247872a9bbb46f62265e38476a6bdc07a)

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
State that the refactor diff was reviewed in the session that wrote it with no finding of the two highest severities, that independent review is still recommended, and that a later repository-wide review requested changes elsewhere that also gate acceptance.

Ported from ai/performance-refactor onto ai/post-release-fixes. The
original body named the second review severity by its label, which
GitVersion.yml treats as a version bump keyword; this body describes the
severity instead.

(cherry picked from commit 5f06c03e1f894da3c5f1778b14aa86a89cf0ea30)

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pin the ArgumentException that Invoke-WindowsAccessControlBatch raises
when CommandName or ObjectFamily arrives without the other, and prove
that no worker ran. With the guard removed from source, both cases
failed in PowerShell 7.6.6 and Windows PowerShell 5.1 because no
exception was thrown; with it restored, all 14 dispatcher tests pass in
both editions.

Also pin that an NTFS adapter worker copies the parameters every
runspace shares before it writes its target path, as the independent
review asked. With the copy replaced by a reference, the shared table
kept the first target in both editions; with the copy restored, all 15
tests in the file pass in both editions.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rerun both benchmarks in fresh processes in PowerShell 7.6.6 and Windows
PowerShell 5.1 against the integration branch before and after the ported
perf commit, after checking module hashes and output checksums. The
documented rows keep their direction: on PowerShell 7 rights rendering is
69% faster, removed-entry detection 66%, single-target batch calls 18%,
and isolated private-key DACL comparison 15%. Label the 2026-09-10
sections historical, record the mixed-workload warm-up that makes one
Core CNG row slower, and cite the new figures in the release notes.

Record the final gates, the package hash, and the independent review,
and correct the private-key key description, the cache scope, and the
scope of the 256-entry figures that the review flagged. Port the durable
lessons of the refactor branch into the Memory Bank and close out
handoff 03.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WinRM now runs on the development machine, so the two DSC-engine tests
that need it pass, and the Windows PowerShell 5.1 gate asserts its
coverage here for the first time: 1,872 passed, zero failed, two
environment skips, and 80.95 percent of 8,314 commands. This session
changed no host setting; the service was already running.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…iptor benchmark in the specs

The user reviewed every recorded agent decision of handoffs 01 to 03.
The 79-row Memory Bank decision log moves unchanged to
topics/decision-log.md, and the index routes to it, which brings
systemPatterns.md within its line budget. Specs 0005 and 0006 now name
the descriptor-processing benchmark, its per-run checksums and module
hash, and the per-instance rights cache. The tech context records that
local WinRM runs, and the two lab-runner follow-ups become post-release
items.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
NTFSSecurity is deprecated in favor of this module, and issue #4 asks
whether this module replaces it. The documentation index pointed
NTFSSecurity users at the NTFSPermission rename map, which never
mentions NTFSSecurity, and the wiki Home page promised a map that did
not exist.

The guide maps every command of the NTFSSecurity 4.2.6 and 5.0.0-rc4
manifests, confirmed against the built module, and states the
parameter and output differences and the decision behind each missing
capability. Every published example ran verbatim in PowerShell 7.6.6
and Windows PowerShell 5.1.

Refs #4

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The README still called certificate private keys read-only and listed
mutation as out of scope, although specification 0015 superseded that
boundary and the usage guide already describes the write commands,
their fail-closed gates, backup and restore, and the DSC resources.

Also record handoff 04 in the Memory Bank.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The three pinned actions declared node20, which GitHub now runs on Node.js 24
while warning on every job. Each pin moves to the SHA its target tag resolves
to, verified with git ls-remote; all six tags are lightweight, so no peeling
was needed:

- actions/checkout 11d5960a -> 3d3c42e5 (v4.4.0 to v7.0.1)
- actions/upload-artifact ea165f8d -> 043fb46d (v4.6.2 to v7.0.1)
- actions/download-artifact d3f86a10 -> 3e5f45b2 (v4.3.0 to v8.0.1)

Every release note from v4 to each target was read and checked against this
workflow. Nothing it relies on changed meaning: artifact names and paths, the
layout a download by name produces, hidden-file handling, if-no-files-found,
retention-days, fetch-depth, and credential persistence all behave as before,
and the Node.js 24 runner requirement is met by the hosted windows-latest and
ubuntu-latest runners. The new checkout refusal to check out fork pull request
code covers pull_request_target and workflow_run, neither of which this
workflow uses. No input needed an edit, so this is seven pins and a test.

One behavior does change and is kept: download-artifact v8 ends a job when a
downloaded artifact's digest does not match the server, where v4 only warned.
A damaged output tree must not reach the test or the publish job.

A new test in tests/Unit/Build holds every uses: reference to a 40-character
SHA pin with a version comment, keeps one action from being pinned to two
SHAs, and requires each of the three to sit on a release line that declares
node24. It failed three of its seven cases before this change.

Dependabot #1 to #3 propose exactly these seven lines; they are superseded.
The real proof is the pull-request CI, which is the last handoff's work.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
+semver: skip

The v0.2.0 release ran Create_ChangeLog_GitHub_PR, which pushed the branch
updateChangelogAfterv0.2.0 and never opened a pull request, so the version
heading the pipeline writes never reached main. Everything CHANGELOG.md
carried at tag v0.2.0 now sits under '## [0.2.0] - 2026-09-06', in the
heading format of that unmerged branch, and the entries written since the
release stay under '## [Unreleased]' in their own categories.

No content line moved out or appeared twice: the multiset of non-heading,
non-blank lines is identical before and after at 859 lines, the 188 entry
bullets are unchanged, and the 14 new lines are 5 headings and 9 blank
lines around them. Get-ChangelogData parses the result and reports one
released version, 0.2.0 of 2026-09-06.

CONTRIBUTING.md reserves version headings for the pipeline, so this
completes the pipeline step that never landed instead of hand-writing a
heading.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sampler's Create_ChangeLog_GitHub_PR wraps everything it does after creating
the changelog branch in a try/catch that writes the error to the build log
and returns, so Invoke-Build still exits zero and the workflow step concludes
successfully. The v0.2.0 release pushed updateChangelogAfterv0.2.0 at
21:00:25 UTC, inside a step that ran from 21:00:22 to 21:00:27 and was
reported as successful, yet no pull request from that branch exists in any
state. Everything the task does before the pull request call only writes
repository contents and demonstrably worked, so the likeliest cause is a
GitHubToken secret that grants contents but not pull requests.

The publish job now queries the pull requests of the changelog branch after
the task has run and ends the run when there is none, naming whether the
branch itself reached the remote. It runs only for a stable release tag,
because UpdateChangelogOnPrerelease is false and a pre-release build of main
returns before the task creates a branch.

A test in tests/Unit/Build holds the step, its position, its query, its token
and branch inputs, and its condition, and it runs the step's own script
against a mocked API: an empty answer has to end the release. Counting that
answer with '@(Invoke-RestMethod ...)' yields one element instead of none and
would confirm a pull request that does not exist, so the step counts through
the pipeline and the test guards that.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
FIND-002 of the 2026-09-07 security review. Each file ended with '}' and no
line terminator; both now end with the CRLF that .gitattributes declares for
*.ps1, and nothing else in either file changed. Their suites still pass in
PowerShell 7 and Windows PowerShell 5.1. The review document now records the
resolution beside the one it already carried for FIND-001.

The Memory Bank records what handoff 06 changed and points at handoff 07,
which needs the Hyper-V host that holds the lab; this machine has Hyper-V and
AutomatedLab but no lab virtual machines.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… publication

The lab host is not on this machine, so the live acceptance of the
combined candidate is still owed before the next stable release. The
publication steps are prepared up to the first remote action, and each
of them waits for the user. Both full gates pass on 023afe9.

Co-authored-by: AI Assistant <ai@example.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@raandree
raandree merged commit 2ebb3a5 into main Oct 7, 2026
4 checks passed
@raandree
raandree deleted the ai/post-release-fixes branch October 7, 2026 13:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant