Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 111 additions & 157 deletions .memory-bank/activeContext.md

Large diffs are not rendered by default.

118 changes: 117 additions & 1 deletion .memory-bank/debugging-insights.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
status: current
last-verified: 2026-10-06
last-verified: 2026-10-08
owner: software-engineer
source: implementation and test evidence
---
Expand Down Expand Up @@ -1036,3 +1036,119 @@ outcome in an `Exit-Build` block so a red suite still reports it. The trigger
that fired on 2026-08-11 was never named; the engine's own cache drop above 1024
entries reproduces it, but a real gate peaked at 528 entries. Removing the
precondition closes every trigger at once, so do not spend the day naming one.

## Lab-host evidence and completion checks can fail quietly

The lab host deletes per-session TEMP directories at logoff:
`DeleteTempDirsOnExit` and `PerSessionTempDir` are 1 under
`HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server`, and `$env:TEMP` in a
remote-desktop session is `...\Temp\<session id>`. The private evidence
directories of 2026-09-07 were gone a month later. Keep acceptance evidence in
the profile's base TEMP directory, `$env:LOCALAPPDATA\Temp`.

On 2026-10-07 five agent-side checks reported a wrong result, for four
reasons unrelated to the module:

- InvokeBuild colors its summary line, so `^Build succeeded\.` misses the ANSI
prefix in a redirected log, and a run with warnings prints
`Build succeeded with warnings.` Judge a child build by its exit code, and
strip `\x1b\[[0-9;]*[A-Za-z]` before matching any text.
- `Sort-Object` orders strings that contain hyphens and underscores
differently in Windows PowerShell 5.1 and PowerShell 7, so a joined, sorted
file list fingerprinted an identical tree differently on guest and host.
Sort with `[StringComparer]::Ordinal` on both sides, or compare sets.
- `Get-ChildItem -Recurse -ErrorAction SilentlyContinue` silently skips a
directory whose DACL denies the administrator, so the tree reads as empty
and its size as zero. Old ACL probes left such directories in TEMP. List
with backup semantics, `robocopy <dir> <absent> /L /E /B`, before calling a
tree empty, and confirm a deletion with `Test-Path`, not with the absence of
an error message.
- The same deny ACE made `Remove-Item` fail non-terminating inside a script
that then printed its success line. Report removal only after verifying it.

## Live tests that reach a guard need the real trigger, measured first

The 2026-10-07 follow-up gave four unit-only paths live cases. Each trigger
had to be measured before it could be trusted:

- Task Scheduler reapplies inherited ACEs. Removing the inherited Local System
ACEs from a subfolder or task changes nothing, so a DACL without Local
System must be protected, with the remaining ACEs made explicit.
`GenericAce.AceFlags` is byte-backed: `-bnot` yields a negative `Int32` that
Windows PowerShell 5.1 cannot cast back, so clear `INHERITED_ACE` with byte
arithmetic.
- A DACL-only `SE_LMSHARE` write clears the share description in the same step
that publishes the DACL; a native recorder polling every 0.25 ms never saw
one without the other. `NetShareGetInfo` supports levels 0, 1, 2, 501, 502,
503, and 1005 only; level 1501 is for `NetShareSetInfo`.
- A race watcher must fire on the exact change under test, such as the
grant's SID appearing in the descriptor, not on any byte difference, and it
must pace its polling. One tight, high-priority loop ran beside a share
write that stalled for a minute and then failed; paced polling at 1 ms won
every race in 40 stress writes. Give a race case retries and put the
per-attempt data in `-Because`, so a failure explains itself.
- A guard behind two identical reads, such as the SMB setter's description
read after target resolution, cannot be reached by any live condition.
Shadow the command in the module's script scope, fail only the call whose
caller is the guard, assert the exact call sequence, and remove the shadow
only when `Get-Command` shows it is the module's own.
- `-ErrorAction Stop` wraps the original error in an
`ActionPreferenceStopException` whose `ErrorRecord`, not `InnerException`,
holds it, so `GetBaseException()` stops at the wrapper.
`Invoke-WindowsAccessControl` writes one `$null` when its script block
returns nothing, so filter `$null` before counting its output.
- Prove each new live case red against a build of the parent commit: a
detached `git worktree` plus a copied `output\RequiredModules` builds in
about 15 seconds. The `96d6671` build reproduced the accepted `f5731f1`
module hash, so builds are byte-reproducible across worktrees.

## Remote prompts, coverage sessions, and error preferences in live suites

The 2026-10-08 round gave a test to every unexecuted lab-only command. Its
prototypes and one stopped pass established these facts:

- A remote prompt is answered by the host of the runspace that opened the
PSSession, not the one that runs or receives the command: `Receive-Job` in a
runspace without a user interface still asked the opening console. Open the
session from a `[powershell]::Create()` runspace and every prompt fails with
a `HostException` that names the operation and the target.
- Such a session starts with remote debugging off, so `Set-PSBreakpoint` in it
fails with "remote debugging is not supported by the current host", and no
coverage can be armed. `$session.Runspace.Debugger.SetDebugMode` with
`LocalScript, RemoteScript` enables it; a custom host whose `DebuggerEnabled`
is true does not. Focused runs without coverage cannot show this, so run one
focused Desktop pass with member coverage armed before a full pass.
- `Invoke-Command -Session` runs its script block at the session's top level,
so `$ErrorActionPreference = 'Stop'` inside it persists.
`Enter-WindowsAccessControlMemberCoverage` leaked it into member sessions, in
the coverage pass only, until `a572d3d` ran the body as
`& { param(...) ... } $ModulePath ...`: the child scope ends the preference
with the call, and `$global:` assignments still persist. A unit test
reproduces a session's top level with
`[powershell]::Create().AddScript($ScriptBlock.ToString())` plus
`AddArgument`, because `AddScript` runs a script in the runspace's global
scope; the test failed with `Stop` before the change.
- A `pwsh -File` script that sets `$ErrorActionPreference = 'Stop'` at its top
level sets the global value, which module functions inherit, so a module's
non-terminating `Write-Error` stops the caller. A case that asserts a
degraded, non-terminating report states `-ErrorAction Continue`.
- Windows PowerShell 5.1 misparses `@{ A = if (...) { ... }` when another key
follows, because it looks ahead for `else`; wrap the value in `$()`. A remote
script block is parsed by the 5.1 endpoint even from PowerShell 7.
- To call the original of a mocked module function from the mock body, capture
`& $module { ${function:Name} }` before `Mock` and invoke that script block;
a `-ParameterFilter` leaves every other call on the original.
- Red proof against a guard: copy the built `.psm1`, apply exact replacements
anchored on function-specific text, because guard messages repeat across
object families, require exactly one match each, and parse the result in
Windows PowerShell 5.1 before staging it.
- Sampler keeps only the first hyphen-separated part of a prerelease label, so
GitVersion's `0.3.0-record-post-rele0001` builds `0.3.0-record`.
- AutomatedLab's `Remove-LabVMSnapshot` pipes the named checkpoint to
`Remove-VMSnapshot -IncludeAllChildSnapshots`, so it also deletes every
checkpoint below the named one in the VM's checkpoint tree, which in a
linear chain is every newer one. To keep a newer
checkpoint, remove the older one with Hyper-V's `Remove-VMSnapshot`, without
that switch, one checkpoint and one merge at a time. A checkpoint that
survives on only some domain controllers is unsafe to apply, because it
rewinds their directory against the rest of the forest.
164 changes: 68 additions & 96 deletions .memory-bank/progress.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,76 @@
---
status: current
last-verified: 2026-10-06
last-verified: 2026-10-08
owner: software-engineer
source: repository evidence
source: repository and domain-lab evidence
---

# Progress

## Current status

`v0.3.0-preview0001` shipped from `40e364a` on 2026-09-07 with the accepted
FIND-001 correction; `v0.2.0` remains the stable release. A 2026-10-06 triage
found unmerged and untracked work and split it into eight handoffs on local
`ai/post-release-fixes`. Handoffs 01 to 06 fixed the lab-runner Blocker, the
Task Scheduler, SMB, and AD review defects, ported and re-measured the
performance refactor, wrote the NTFSSecurity migration guide, moved the build
workflow to the Node 24 releases of its three pinned actions, restored the
0.2.0 changelog section, and closed FIND-002. Handoff 07 is Blocked for want
of the lab host, and handoff 08 waits for the user's approval of each remote
action. `activeContext.md` holds the evidence and the order.
`v0.3.0-preview0002` shipped from `2ebb3a5` on 2026-10-07 and passed live
domain-lab acceptance the same day; `v0.2.0` remains the stable release. Pull
request #5 carried the post-release work that a 2026-10-06 triage split into
eight handoffs: the lab-runner Blocker, the Task Scheduler, SMB, and AD review
defects, the performance refactor, the NTFSSecurity migration guide, the
Node 24 action releases, the restored 0.2.0 changelog section with a release
guard, and FIND-002. All eight handoffs are done. `activeContext.md` holds
the open work.

## Recent milestones

- 2026-10-08: `a572d3d` stops arming member coverage from leaving a member
session stopping on every error: `Invoke-Command -Session` runs a script
block at the session's top level, so the preference the harness set there
outlasted the call, and it now sets it in a child scope. A unit case
failed with `Stop` before the change, a live probe showed both behaviors
in each edition, and a built pass ran 109 of 109 in both editions with
line-identical lab coverage; the local gates passed with Core 1,938 and
Desktop 1,893 tests. The CopilotAtelier `automatedlab-deployment` Skill
gained the `Remove-LabVMSnapshot` warning on a local branch there.

- 2026-10-08: Every command in the declared domain-lab-only files now has a
test. `4096d02` routes the RootDSE and effective-access reads through
`Send-WindowsADSearchRequest`, so six unit cases reach their four guards;
`938bdff` and `bad1302` add eight live cases for the SMB share guards, the
confirmation prompt, the enrichment catches, and a deletion inside the
effective-access read. Each was red against a build with its guard
removed. `bad1302` passed the DSC gate, four passes of 109 cases, and both
local gates; domain-lab-only coverage is 100 percent
(`docs/lab-acceptance-2026-10-08.md`). The first built pass of `938bdff`
stopped because a session opened without a user interface cannot arm
coverage breakpoints until its remote debugging is enabled. Removing the
older checkpoint afterwards with `Remove-LabVMSnapshot` also deleted the
newer one on ten VMs; the lab now holds one checkpoint,
`wac07-after-bad1302-9d9776ca`, taken after the acceptance.

- 2026-10-07: A follow-up gave the four handoff 02 paths that only unit tests
covered live cases: Task Scheduler repair of a protected DACL without
Local System, and the SMB description that the native write clears, edits
concurrently, or cannot read before the write. Each case failed against
builds of `96d6671` and `5531824` and passed against the candidate; all
four passes ran 101 cases each, and both local gates passed again. The
SMB usage page and specification 0009 now name both windows in which a
concurrent description edit can be replaced.

- 2026-10-07: Handoff 07 accepted `2ebb3a5` in the domain lab, recorded in
`docs/lab-acceptance-2026-10-07.md`. The release-equivalent build matches
the Gallery package byte-for-byte; four full passes of 97 cases, the DSC
engine gate, and both local gates with fresh lab coverage passed; the two
lab-runner refusals were proven against decoys. At the user's request the
two older lab checkpoints, `C:\WacRepo`, and `C:\WacLive` were removed
afterwards; checkpoint `wac07-pre-2ebb3a5-b4de5d73` remains.

- 2026-10-07: Published `v0.3.0-preview0002`. The user answered every pending
question and ran each remote step from commands the agent handed over,
because the house rules block agent pushes; the agent verified each
result read-only. Pull request #5 passed CI in both editions without an
annotation and merged as `2ebb3a5`; the GitHub release, the Gallery entry,
and the wiki Home page carry the version GitVersion had predicted.
Dependabot #1-#3 are closed as superseded, issue #4 has its reply, and
the stale branches are deleted.

- 2026-10-06: With the user's overnight delegation, handoff 07 was recorded
as Blocked (no lab VMs on this machine) and handoff 08 was prepared up to
its first remote action: a pull request body on the repository template,
Expand Down Expand Up @@ -79,93 +128,16 @@ action. `activeContext.md` holds the evidence and the order.
that, only its two WinRM-bound DSC-engine tests failed. One
independent review approved; its Minor and three Nits were addressed.

- 2026-10-06: Handoff 02 fixed three review defects test-first on
`ai/post-release-fixes`, one commit each. A Task Scheduler write may
repair a DACL without a Local System ACE; a missing or null current DACL
is still refused. An SMB DACL write reads the description just before
the native call, keeps concurrent edits, and warns instead of failing
after a committed write. `Get-ADObjectCallerEffectiveAccess` no longer
requests `nTSecurityDescriptor`; an absent descriptor elsewhere raises a
typed read-control error. The Core gate passes 1,905 tests with two
environmental skips at 83.2 percent asserted coverage. The Desktop gate
fails only the two WinRM-bound DSC-engine tests; its scoped coverage,
computed from its document, is 81.03 percent. One review approved with
minor findings, most fixed. No live lab run; 07 owns two new AD cases.

- 2026-10-06: Handoff 01 made the domain-lab acceptance runner refuse to
delete what it did not create, test-first on `ai/post-release-fixes`. It
marks the payload root, `package`, and installed module version directory
it creates; replaces only absent or marked directories with no junction or
symbolic link inside; validates the payload root on the management
controller in every mode; and stops when a remote step does not confirm
its directory, because AutomatedLab does not stop on a remote throw. 79
lab-runner tests pass in Core 7.6.6 and Desktop 5.1; the full gate passes
1,873 tests with two environmental skips at 83.0 percent asserted coverage.
One independent review approved with Minor findings; seven fixed, three
recorded. No live lab run; handoff 07 owns it.

- 2026-10-06: Wrote eight sequential handoff prompts for the triaged work,
outside the repository, with a ledger and per-prompt reports. They commit
to local `ai/post-release-fixes`; only the final prompt touches the
remote, with approval per action.

- 2026-10-06: Triaged open work. Verified from git, the public GitHub API,
and the PowerShell Gallery that `v0.3.0-preview0001` shipped on
2026-09-07, so the pending-authorization note was stale. Found the
2026-09-10 performance refactor and repository-wide review only on a
deleted, never-pushed local branch; restored `ai/performance-refactor` at
`5f06c03` before reflog expiry. Rechecked its findings against `main`:
the lab-runner deletion, Task Scheduler SYSTEM refusal, SMB description
overwrite, and AD descriptor overfetch remain; its CNG Major is FIND-001.
Dependabot failures are inherited from their parent `4806726`, fixed on
`main` by `0502254`. No code change and no remote mutation.

- 2026-09-07: Completed fresh reacceptance of `f5731f1` without replacing the
lab. All four built/installed passes have 95 passed tests, zero failures or
skips, and ready cleanup. Five DSC-engine tests and both fresh local gates
pass; Core has 91.15 percent asserted coverage and Desktop 90.41 percent.
Verified original installation bytes/ACLs, fixture restoration, and all 38
retained guest evidence files before staging removal. Thirteen VMs and
checkpoints remain. Final completion is 18:52:19 UTC; no git remote mutation.

- 2026-09-07: Resolved FIND-001 test-first. A uniquely named persisted CNG key
reproduced the third-read exception after its requested DACL was already
stored, then passed with exactly two helper reads after the one-line source
correction. Core and Desktop each pass all 52 affected CNG tests; a focused
Desktop live mutation restores the fixture descriptor byte-for-byte and
removes its binding and staging. Full local runs pass 1,806 and 1,761 tests
at 83.00 and 80.81 percent asserted coverage without prior lab evidence.
PSScriptAnalyzer is clean on both changed files, and independent review
approved with no finding. The 22-task pack is clean and package SHA-256 is
`D93B2644B31A38B37F9FAC08DBD1D28C85AF8AD452D81E1428E0A3054530588C`.
Prior accepted artifacts remain preserved.

- 2026-09-07: Completed one independent review of `3321350..358651e` plus the
CNG persistence path. Verified one Minor post-write read defect and two
missing-newline locations under one Nit; no Blocker or Major findings.
Reconciled raw-report tally and topology errors against the retained
evidence. No runtime or test code changed and no live tests were repeated.

- 2026-09-07: Checkpointed all thirteen existing lab VMs and verified signed,
sealed Kerberos LDAP, WSMan, test dependencies, and the renewable template.
Rebuilt and byte-verified the candidate; closed the five-test Desktop DSC
gate on the reserved member. Fixed the stale cleanup-list entry exposed by
live acceptance, retained the failed evidence, and verified exact-identity
recovery. All four repaired passes finish with 95 tests each and clean
fixtures. Original installation bytes and ACLs are restored; final lab
services, LDAP, and checkpoint checks pass. Local Core passes 1,805 tests
and 91.16 percent asserted coverage; Desktop passes 1,760 tests and 90.41
percent. Both imported the successful lab coverage byte-for-byte, with the
80 percent threshold unchanged. Validation completed at 12:08 UTC and
monitoring stopped. The dated lab acceptance report retains candidate
hashes and the failure, recovery, and successful-run evidence.

- 2026-09-03 to 2026-09-07: OI-31, the 105-command audit, the audit-gap
- 2026-09-03 to 2026-10-06: OI-31, the 105-command audit, the audit-gap
closure, the `6f7ba15` close-out, the `34026468199` wiki-publication hang
(DscResource.DocGenerator#111), the `34021812398` completer fix, the
`ai/access-rights-completion` review, the script-inventory audit, the
wiki-publisher investigation that run `34055979655` closed, and the
`ai/test-gap-audit` handoff push are in
wiki-publisher investigation that run `34055979655` closed, the
`ai/test-gap-audit` handoff push, the 2026-09-07 lab checkpoints, review,
FIND-001 resolution, `f5731f1` reacceptance, the 2026-10-06 triage, the
eight handoff prompts, and handoffs 01 and 02 are in
`git show a572d3d:.memory-bank/progress.md`,
`git show 2ebb3a5:.memory-bank/progress.md`,
`git show 023afe9:.memory-bank/progress.md`,
`git show 4461b90:.memory-bank/progress.md`,
`git show 8b67058:.memory-bank/progress.md`, and, for the oldest,
Expand Down
Loading