Repository navigation
The pre-lock check trusts the install's own service SID and an existing install gets exact steps (#5627) - #5628
Merged
Merged
Conversation
…talls (#5627) Four shapes on windows-2022 and windows-2025: the current release in Program Files, the same with a non-default service logon, the same with the service deleted, and a pre-3.9 folder under C:\. Runs on pull requests touching Darling/tools, in the nightly, and on demand.
…existing install exact steps (#5627)
… is also tried without -InstallRoot (#5627)
…ws PowerShell 5.1; the shape script exits 0 on pass (#5627)
…legs and triggers (#5627)
The darling service named :nightly since before 3.4.0, so a release user following the README ran the newest dev build. It now names :latest, the README says how to pin a version, a test pins the tag, and the release checklist's compose check uses the release's own tag.
…rs it writes to; the move steps follow the registered service and name configured paths (#5627)
…act service-written names, safe printing of configured paths (#5627)
erikdarlingdata
marked this pull request as ready for review
October 9, 2026 13:28
… fail on a setup that did not take (#5628)
erikdarlingdata
added a commit
that referenced
this pull request
Oct 9, 2026
Adds the Fixed entry to the 3.10.0 index and to docs/changelog/3.10.md. The text is a draft from the open PR; part B checks it against the merged PR.
erikdarlingdata
added a commit
that referenced
this pull request
Oct 9, 2026
erikdarlingdata
added a commit
that referenced
this pull request
Oct 9, 2026
The entry cited the two issue numbers, #5627 and #5629. It now cites the merged PR, [#5628], in both the index line and the full entry. The [#5628] link definition goes at the end of both link lists. [#5629] had no other citation in either file, so its definition is removed. [#5627] stays, because the Important entry still uses it.
erikdarlingdata
added a commit
that referenced
this pull request
Oct 9, 2026
changelog_archive.py split put the [#5628] link definition in numeric order in docs/changelog/3.10.md. census wrote the 3.10.0 row: 295 entries. verify and roundtrip both pass.
erikdarlingdata
added a commit
that referenced
this pull request
Oct 9, 2026
Adds the changelog entries for #5626 and #5628 to the 3.10.0 index and to docs/changelog/3.10.md. The #5628 entry cites its own PR. The 3.10.0 date is now 2026-10-09. A second Important entry covers the Windows upgrade stop for installs that ordinary users can write to. The Important note now names store migrations V158 through V173, as dev is at schema 173. The README gains the shared time range picker, the text column filters and Lite's DuckDB memory limit setting. The branch merges dev, which brings in the entries for #5636 and #5637, and every entry from both sides is kept. The archive split and census run last. The 3.10.0 census row is 295 entries, and verify and roundtrip both pass. No duplicate section headers and no missing reference links were found in the 3.10.0 entries. The last changelog splice for 3.10.0, and the README sync.
This was referenced Oct 9, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Claude posting for Erik Darling.
Fixes #5627.
Fixes #5629.
Why
An upgrade over a 3.7.1 install was refused with a long list. The same happens on a re-run over a 3.9.0 install. The list has one line for the folder itself. It then has about 1,740 lines like
<path> (owned by NT SERVICE\PerformanceMonitor Darling). Those lines are two different findings.C:\. It inherits a write grant from the volume root. That isAuthenticated Userson the runner images andBUILTIN\Usersin the field report. It is 1 line.pg-runtimefiles its service extracted. That is by design. It was listed 1,742 times on the repro runs. The check listed it whenever the trusted set at step 1a lacked the service's SID. That happens when the service was deleted since 3.7.1 (shape D1). It also happens when the logon was changed to another account (D2). The trusted set only ever held the service's current logon account, and only while the service was registered.Repro numbers come from windows-2022 and windows-2025, runs 37922364817 and 37922938743:
darling.json.The old advice also did not say what happens to the service,
darling.jsonor the store.What changes
In
Darling/tools/install-darling.ps1andDarling/tools/upgrade-darling.ps1. The shared functions stay byte-identical, and the existing identity test covers them.Get-DarlingServiceSidstakes the service name and the current logon account. It returns the SID ofNT SERVICE\<serviceName>and the logon account's SID.Get-DarlingPreLockTrustedSidsis the base set and names no service account.Get-DarlingPreLockTrustedSidsForRerunpasses the name on both paths, whether or not the service is registered. The L3 and L4 refusals (logon cannot be read or resolved) are unchanged.Get-UntrustedWriteGrantees -ServiceSids. The upgrade's-Sourcefolder check and its SHA256SUMS folder check use the base set only (see Review round 1).NT SERVICE\*SID is trusted, and neither is the authority as a whole. A tree owned by a different service's SID is still listed.S-1-5-80. That is whatsc.exe showsidprints. The ruling usedNTAccount.Translateinstead.TranslateofNT SERVICE\<name>fails with "Some or all identity references could not be translated". That is exactly the deleted-service shape. The runner repro translated only because the service had existed minutes earlier.Translateresult for registered services. I checkedWinmgmtandEventLog, and a test pins it. It also equals the SID the repro recorded for this product's name (...-944500918). If the computation throws, the SID is left out and the walk names what it finds, as before.Get-DarlingExistingInstallSteps, with the same text in both scripts.C:\Program Files\PerformanceMonitorDarling.darling.jsonis yours, then copy it first, or the sample config is copied in. If it names a certificate or key file in this folder, make a new one. Keep it outside the install folder and pointdarling.jsonat it. Do not copy adarling-keysfolder.install-darling.ps1from the new folder. It re-points the existing service. Only the program path changes. The logon account, credentials and the store underC:\ProgramData\PerformanceMonitorDarlingare untouched.darling.jsonnames inside the old folder. It says to keep that folder until they are moved.Darling/README.mdgets one sentence group naming the steps.There is no bypass, no new switch and no automatic permission repair.
The two findings
C:\<path> (owned by NT SERVICE\PerformanceMonitor Darling)NT SERVICE\PerformanceMonitor DarlingACE ondarling.json(3.7.1 step 4b)Repairing the folder in place: options
This PR does not decide it. It keeps option (a).
darling.jsonor files the service writes. It also needs the hash list in the zip to be trusted.-AcceptWritableExtractionalready does.Upgrade-shape check
This PR also adds a permanent check,
.github/workflows/darling-upgrade-shapes.yml, so the install and upgrade scripts are run over installs that real releases made. Until now nothing in CI ran either script over an older install.What runs: each leg downloads a released zip and runs that release's own
install-darling.ps1. It lets the service extractpg-runtime(it waits forpg-runtime.stamp), stops the service, and then runs this commit's script. Every leg runs onwindows-2022andwindows-2025. A leg fails the job with a one-line reason when its expectation is not met.C:\Program Files\PerformanceMonitorDarling-InstallRoot) and not registered (with it), and lists no service-owned filesC:\PerformanceMonitorDarlingC:\Program Filesanddarling.jsonWhen it runs:
Darling/tools/*.ps1, the workflow file or its script (pull_request, neverpull_request_target, withcontents: read).uses: ./.github/workflows/darling-upgrade-shapes.yml) gated like the other nightly jobs. It is not inpublish'sneeds.workflow_dispatch.The release versions (3.9.0 and 3.7.1) are named once, in the workflow's top-level
env. Every checkout usesgithub.sha. Each job has a 30-minute timeout, andconcurrencycancels a superseded run on the same ref.Time, measured on this PR's own run (run 37927343702, head
0cc327f81): all 14 legs passed. The run took 2.2 minutes wall, and the longest leg took 2.2 minutes (windows-2022 / U2-install). The shortest took 1.6 minutes. The 30-minute timeout is for a hung service, not for a slow leg.What the first run found: the first run (head
22ad9a466) failed 8 legs for two reasons, both fixed here.upgrade-darling.ps1run from a staging folder stopped with "No -Source given". Windows PowerShell 5.1 leaves$PSScriptRootempty inside theparam()default of a[CmdletBinding()]script started with-File(reproduced locally with a four-line script). The script now applies the default in its body. A pin inDarlingUpgradeShapesWorkflowTestsholds both lines.exit 0after every expectation passes.Guards:
DarlingUpgradeShapesWorkflowTestsis new, with 6 tests. It pins the trigger paths andpull_request(notpull_request_target). It pins the four shapes and both OS labels, and the leg list matching the script'sValidateSet.It pins read-only permissions, timeouts, concurrency,
github.shacheckouts, action refs on a release line and the versions named once. It also pins the nightly job, its gate and its absence frompublish'sneeds.DarlingPathFilterGateTestsgained a pin that the Darling gate lists the workflow and its script, and the gate file lists both.RepoFileAdoptionTestslists the new class as an LF reader.Red-plant: I edited the workflow four ways. Each time
DarlingUpgradeShapesWorkflowTestsfailed 1 test. The edits were droppingwindows-2025, dropping theDarling/tools/*.ps1trigger path, droppingU4-install, and usingpull_request_target. I restored the file after each.Release checklist
The release checklist (
.claude/skills/release-checklist/SKILL.md) had no Darling service install or upgrade step, which is how this shipped. A new section, 8a, now requires four things for every release:darling-upgrade-shapes.ymlmust be green at the release commit's sha, by name. The two pinned release versions in the workflow are checked too. If there is no run at that sha, start one by dispatch.upgrade-darling.ps1over a real install, run once from the last release and once from the oldest release still supported. The oldest supported release is the oldestmigration-ladder-v<version>.sqlfixture inDarling/Darling.Tests/Fixtures, so the section names the rule and no version. Pass means a clean script run and a service that is collecting. The store must be at the top rung with the earlier rows still there. The five store checks the script prints must hold. The section says what the*RungTestsclasses,MigrationUpgradeLadderLiveTestsandDarlingStoreUpgradeTestsalready cover, and what only a live run adds.install-darling.ps1run on a clean machine, thenuninstall-darling.ps1.The scope note no longer reads as if no service install testing happens. This part is docs only. No test reads the file except the repo-wide identifier scan, and I checked the new text against that scan's two patterns by hand: no match.
Review round 1
-Sourcefolder check and the SHA256SUMS folder check inupgrade-darling.ps1use$sourceTrusted, the base set with no service account. A service-owned file or write grant in a source folder is a finding.Get-DarlingServiceWrittenNames(pg-runtime,pg-runtime-prev,darling-keys,darling.jsonanddarling.json.bak-*).Lock-DarlingInstallTreeand the pre-lock walk both read it. The functions are byte-identical in both scripts and pinned.Test-PathIsAtOrUnder. A file it owns at the root, beside the exe or in any other folder is named.darling.jsonbefore copying it, then to go straight on to step 4.darling.json. It names every configured path under the old folder:postgres.dataDirectory,file:references and the certificate and key paths. When there are any, the delete step says to keep the old folder until they are moved. Step 4 says where the store is, including thepostgres.dataDirectorycase.C:\Program Filesthe steps and the fresh-folder wording ask for a new, empty folder with a different name underC:\Program Files.pg-runtime,pg-runtime-prev,darling.json) and 1,752 owner findings, all the service's. The proof's file counts put 1,653 files inpg-runtimeplusdarling.jsonoutside the zip, and the service writes nothing else in the root. I did not get a per-path list of the 1,752 here. The upgrade-shape run on this head is the check that they all sit under the list. Result: 14 of 14 legs passed (https://github.com/erikdarlingdata/PerformanceMonitor/actions/runs/37930205158).Review round 2
/grant:r ... (OI)(CI)RX). The step 4b2 header comment ininstall-darling.ps1says the same.Get-DarlingInstallRootFromService. Full paths are compared without regard to case or a trailing separator. A service registered elsewhere, or none, gets the fresh-folder wording.upgrade-darling.ps1applies the same rule to an explicit-InstallRoot.Test-DarlingSamePath,Get-DarlingInstallRootFromServiceand the newGet-DarlingRefusalAdviceare byte-identical in both scripts and pinned.Get-DarlingConfiguredPathsUnderreads each value on its own. A value holding a control character, a line break or any of< > | "is left out, and so is one over 260 characters. At most 10 are listed, each in quotes. The last step keeps the old folder when a value is left out, the list is cut ordarling.jsonis unreadable. In those cases it asks fordarling.jsonto be checked for paths inside the folder.darling.jsonnames one in this folder. The new file goes outside the install folder, anddarling.jsonis pointed at it.darling.jsonby exact name. Its backups match onlydarling.json.bak-yyyyMMdd-HHmmsswith an optional-Ncounter. The first name below the install folder is compared exactly. The listed files count only in the install folder itself, in the lock and in the check.darling-keyscounts only whenGet-DarlingExtraServiceWriteDirectoriesreturns it, and the check is handed the same list as the lock.SHA256SUMS.txtbefore extracting. A missing file, a missing or repeated listing or a different hash fails the leg and deletes the zip. Both pinned releases ship that file, so no hash is pinned in the script. A guard test pins the check.C:\Program Files, both scripts) went through the plain-English checker. All 12 renders are clean.Compose image tag
The compose quickstart pulled the nightly image, even on a release.
Darling/compose/docker-compose.ymlnamedperformancemonitor-darling:nightly, but since 3.4.0 each release pushes:<version>and:latest, so a release user who followed the README ran the newest dev build.darlingservice now names:latest. It is not a version pin. The next version's image does not exist until the release ships, so a pin makes the quickstart fail ondev. The comment above the line says the same and still tells people to substitute a version tag.:latest, how to pin a release, and that:nightlyis for testing unreleased changes.ComposeImageTagTestsreads the real compose file and fails unless the darling service's image isghcr.io/erikdarlingdata/performancemonitor-darling:latest. A second test runs the same parse over a copy that names:nightly, so a parse that matches nothing cannot pass.:<version>). Set it in the compose file or an override file.:lateststill points at the previous release until the new one publishes.:nightlyreaders arenightly.yml(it builds and pushes that tag) and a comment inbuild.yml. The CI jobs that start the stack use the locally builtperformancemonitor-darling:primage. The otherCompose*tests read other keys of the same file.Test plan
DarlingInstallLocationTests: 33 passed, 0 failed. There are 6 new tests.NT SERVICE\Winmgmtstill refuses, by that name.Get-Acl, which is the class's existing idiom, so they need no elevated token.Red-plant. I disabled the derived-SID block in
install-darling.ps1. Both behavior tests above failed, and so did the identity pin because the scripts then differed. I restored it and the class is green.The test classes that read either script, plus
DocCommentHygiene*,*Census*and*Ratchet*: 816 run, 0 failed, 1 skipped.The
Stage=Guardset: 1,474 run, 0 failed.Build: 0 warnings, 0 errors.
A runner run of the fix against the D1 and D2 shapes. The repro workflow lives on a separate branch. Expect only the root line.
The numbered steps end to end on a real 3.7.1 install. Another lane is proving them. I wrote the wording from the code.
The
BUILTIN\Usersroot grant of the older Server image was not measured. The check's write mask covers both grants.Upgrade-shape check, this PR's own run (
Darling upgrade shapes, run 37927343702, head0cc327f81): 14 of 14 legs passed, 2.2 minutes wall, longest leg 2.2 minutes. The first run (head22ad9a466) failed 8 legs. The cause is described above.DarlingUpgradeShapesWorkflowTests(6),DarlingPathFilterGateTests(10),RepoFileAdoptionTests(2): 0 failed. Red-plant: 4 plants, 4 reds.After merging this branch with
origin/dev, none of these failed. DarlingStage=Guardran 1,480. LiteStage=Guardran 469.*Census*ran 500 (1 skipped),*Ratchet*15,DocCommentHygiene*78 andDarlingInstallLocationTests33. Darling.Tests and Lite.Tests build with 0 warnings.darling-upgrade-shapes.ymlandnightly.ymlparse with PyYAML.The new check was not run against the old scripts (before this fix). The U2 and U3 expectations come from the repro that found the false positives. I did not re-run them on the unfixed scripts.
Review round 1.
DarlingInstallLocationTests: 38 passed, 0 failed. New tests cover six things. The service account is trusted only where the service writes. Listed paths pass. A file at the root or beside the exe is named. The source-build checks trust no service account. A folder holdingdarling.jsonwith no registered service gets the fresh-folder wording. Configured paths under the old folder are named. The Program Files wording and the SID warning are covered too. The identity pin covers every shared function including the new list.Red-plant. Trusting the service as owner everywhere in
Get-UntrustedWriteGranteesmade the new scope test and the identity pin fail. I restored it and the class is green.The test classes that read either script (including the upgrade-shape and path-filter classes): 246 run, 0 failed.
*Census*,*Ratchet*andDocCommentHygiene*: 593 run, 1 failed (a displaced doc comment in my test file), fixed and re-run green withDarlingInstallLocationTests(116 run).Stage=Guard: 1,480 run, with the same single failure before the fix. I did not re-run it in full after the fix. Build: 0 warnings, 0 errors.A runner run of the numbered steps on a real 3.7.1 install. Not done.
ComposeImageTagTests(2),ComposeDarlingHealthcheckTestsandCiComposeWorkerSizingTests: 9 run, 0 failed.Red-plant. I put
:nightlyback in the compose file.TheDarlingService_NamesTheLatestImagefailed. I restored:latest.After merging this branch and
origin/dev: build 0 warnings, 0 errors. DarlingStage=Guardran 1,480, 0 failed.*Census*,*Ratchet*andDocCommentHygiene*ran 593, 0 failed, 1 skipped.A real
docker compose upagainst the published:latestimage. Not run: no Docker on the machine that wrote this.Review round 2.
DarlingInstallLocationTests: 42 passed, 0 failed. New tests cover the service's grant on the folder in three service states and the advice gate. Others cover odd and over-long values, the list's exact names and the key folder.DarlingUpgradeShapesWorkflowTests: 7 passed (1 new, for the checksum check). The script-reading classes: 208 run, 0 failed.Red-plant. Putting the folder-grantee branch back failed the new folder-grant test and the identity pin. Dropping the folder comparison from the advice failed the new advice test. I restored both and the class is green.
Final run after the last code change:
Stage=Guard1,481 run, 0 failed.*Census*,*Ratchet*andDocCommentHygiene*593 run, 0 failed, 1 skipped. Build: 0 warnings, 0 errors.Upgrade-shape run on this head: run 37936483027 on head 7022cfd: 14 of 14 legs passed (https://github.com/erikdarlingdata/PerformanceMonitor/actions/runs/37936483027).
CI fix for run 37937037134. Four
DarlingInstallLocationTestsfailed on the runner and passed here. The cause is the runner's short 8.3 temp folder (C:\Users\RUNNER~1\AppData\Local\Temp). Windows PowerShell 5.1 returns the long form for every file it walks. So the test's stand-inGet-Aclnever matched a walked path, and every count came back 0. It was not a privilege problem, and the product scripts are unchanged. The probe now names its tree in the long form. Every probe that sets an owner or an ACE also prints asetup.*line. The test asserts that line, so a setup that did not take fails by name. PointingTEMPat the short form here reproduced the same four failures. With the fix, 42 of 42 pass under both forms. Dropping the long-form line fails 6 tests with the setup message. On a throwaway branch (now deleted),DarlingInstallLocationTestspassed 42 of 42 on windows-2022 and windows-2025. Those are run 37940768905 (long-form fix) and run 37941620495 (plus the setup checks). Here:Stage=Guard1,481 run, 0 failed.*Census*,*Ratchet*,DocCommentHygiene*andDarlingInstallLocationTests: 635 run, 0 failed, 1 skipped. Build: 0 warnings.