feat: add the windows-full manifest variant - #49
timo-tigges wants to merge 5 commits into
Conversation
Adds windows-full/herdr-plugin.toml against docs/windows-full-acceptance.md,
covering §1 and the staging half of §2. The root and lite/ manifests are
untouched, so Windows on Herdr 0.8.x keeps installing Lite and macOS/Linux keeps
the Unix wrapper and its gates.
The variant starts plannotator-tui as the pane process itself, which is what
requires Herdr 0.9.0. Measured on 0.9.0 (Windows 11, x86_64): a pane whose
program is written "./bin/<name>.exe" is resolved against the plugin root while
the review folder is passed separately as the pane cwd. Herdr's own refusal for
a missing program is the evidence, naming the path it handed CreateProcessW:
CreateProcessW `"\\?\<plugin root>\bin\nope.exe editor"`
in cwd `Some("<review folder>")` failed: os error 2
That also settles why no launcher may sit in between. The resolved program
reaches CreateProcessW in extended-length form, and while a native binary runs
from a \\?\ path unchanged, a powershell.exe image started that way exits
0xFFFF0000 at once. Isolated on one binary with only the prefix differing: the
plain absolute spelling exits 0, the \\?\-prefixed one 0xFFFF0000, and all four
relative spellings reproduce the prefixed result. So the direct-argv rule is not
a style preference on Windows; a wrapped pane cannot stay up.
The native annotation runtime is shared rather than duplicated, reached at
../bin/herdr-annotate.exe the way lite/ reaches it, so annotations and archives
keep one location across variants. Only plannotator-tui is staged inside the
variant, because the root must keep staging its own copy for Unix.
For that staging, scripts/fetch-plannotator-tui.ps1 gains an optional
-DestinationDirectory. Omitted, the destination is unchanged, so existing
callers behave exactly as before and both Windows fetcher cases still pass. A
thin windows-full/scripts/fetch-plannotator-tui.ps1 supplies it, deriving the
shared fetcher and the destination from its own location so the build depends on
neither the caller's cwd nor HERDR_PLUGIN_ROOT, and propagating the warn-and-
exit-zero contract so Lite stays available when a fetch fails. The release pin
stays single and at the repository root.
test-windows-full-manifest.py gains check_windows_full, which compares action
ids, titles, descriptions and contexts against the root manifest as sets rather
than restating them, and asserts the three panes and their shapes, the
direct-argv doc pane, the shared-runtime spellings, the absence of any inherited
platform gate, and link-handler parity. The existing root, Lite and development
assertions are unchanged. Verified against five mutations -- a platform gate on
an action, a wrapper in the doc pane, min_herdr_version 0.8.0, a renamed action
title, and an unshared native runtime -- each of which it rejects.
windows-full/** joins the CI path filters, and .gitignore learns
windows-full/bin/, which the root pattern did not cover because it is anchored.
Not yet covered, and not claimed: §3's runner proof beyond the manifest step,
§4's human qualification, and §5's README wording. Native ARM64 in §4 cannot be
closed on the hardware this was measured on, which is x86_64.
A build gated to macOS/Linux would stage nothing for this variant, leaving both manifest programs pointing at binaries that were never fetched -- the failure the variant exists to prevent, and one the command-equality check alone does not see. Verified against a macos/linux gate on the plannotator-tui build.
§1 requires that Herdr 0.8.2 reject the variant for its minimum while 0.9.0
accepts it, on isolated installs. Neither is visible to the manifest test, being
Herdr's behaviour rather than the file's contents, so this drives two pinned
releases and asserts each.
Both run against their own XDG_CONFIG_HOME, XDG_STATE_HOME and socket paths, so
neither can reach the machine's own server or each other's, and the plugin root
contains spaces. Verified locally on Windows 11 x86_64: the developer's running
0.9.0 server and its installed plugin were unchanged afterwards.
0.9.0 is pinned by SHA-256 the way 0.8.2 already was. RUNNER_TEMP falls back to
TEMP so the script runs unchanged outside CI, which is what recording §4's human
qualification will need.
Two observations the assertions had to accommodate.
The checklist names `plugin_requires_newer_herdr` as the rejection. What 0.8.2's
`plugin link` actually prints is unstructured:
Error: Custom { kind: Other, error: "plugin requires Herdr 0.9.0 or newer;
current Herdr is 0.8.2" }
Both spellings are accepted, the observed one is echoed rather than hidden
behind a pass, and the run additionally asserts nothing linked. If the code is
meant to surface on this path, that looks like a Herdr-side gap rather than a
manifest one.
An entry with no platform gate carries no `platforms` key at all -- Herdr omits
it rather than echoing the manifest default -- so under StrictMode it is a
missing property rather than an empty one, and is read through PSObject. That
absence is the assertion: every action, the doc pane and the link handler must
be effective on Windows, since an inherited Unix gate would leave the variant
installed with its review half silently unreachable.
|
Pushed two more commits covering the §1 version-gate boxes.
My running 0.9.0 server and its installed plugin were unchanged afterwards. A third question, smaller than the other two. §1 says 0.8.2 rejects the variant with The rejection itself is correct and the test also asserts nothing linked, so the box's intent is satisfied. But the code does not appear on this path. The test currently accepts either spelling. Is the code expected on One more thing worth flagging: an entry with no platform gate carries no CI is showing |
The remaining doubt about this variant was never the manifest, it was whether a native TUI can be the pane process on Windows at all. It can, and this drives it: a pinned Herdr 0.9.0 in an isolated config, state, socket and session, a staged checkout whose paths contain spaces, and the variant's own build commands run from the plugin root with both override variables absent, so the binary in the pane is the real release rather than a fixture. The run asserts the staged location and stamp before use, opens the doc pane with a review folder outside the checkout as cwd, and waits for a marker generated per run -- a stale buffer cannot satisfy it -- read back out of the pane's own terminal. Then `q` goes in through the pane, and the pane must disappear, the originating pane must survive, no staged plannotator-tui may still be running, and the server's own log must show the pane exiting with status zero. Teardown runs in finally, including when an assertion fails. Observed locally on Windows 11 x86_64 with Herdr 0.9.0: staged plannotator-tui 0.8.0 at windows-full/bin, native runtime one level up doc pane rendered HERDRFULL-0510C65C8F7B from a review folder outside the checkout q closed the review pane with status zero and left no process behind The developer's own server, session and installed plugin were unchanged afterwards, and no herdr or plannotator-tui process was left behind. Process identity is matched on the executable path rather than the image name, so a developer running their own plannotator-tui neither fails the run nor satisfies it. Still outstanding for §3: the long-path, extended-length and UNC rows of the path matrix, placements other than the default overlay, forced-termination exit status, and the open/open-link/last actions against controlled context fixtures. §4 is untouched, and its native ARM64 row is not closeable on x86_64.
|
The review pane works on Windows. Pushed Pinned Herdr 0.9.0, isolated config/state/socket/session, a staged checkout whose paths contain spaces, and the variant's own build commands run from the plugin root with The marker is generated per run and read back out of the pane's own terminal via After Pane boundary environmentCaptured with a separate native diagnostic probe (absolute program path), per §3's allowance. Everything the box asks for is present and correct:
Worth noting alongside the Still outstanding§3's long-path, extended-length and UNC path-matrix rows; placements other than the default overlay; forced-termination exit status; CI is still |
The matrix reuses the pane lifecycle rather than restating it: each case shells out to test-herdr-windows-full-pane.ps1, so a row proves exactly what the default case proves -- real fetch, per-run marker rendered, `q`, clean teardown. The lifecycle script gains -Checkout, -Review, -LinkPath and -HerdrExecutable for that, all defaulting to the previous standalone behaviour. Measured on Windows 11 22621 x86_64, LongPathsEnabled=1, Herdr 0.9.0: special-characters PASS (root 131, binary 155) extended-length-root PASS (root 123, binary 147) unc-loopback PASS (root 74, binary 98) long-path LIMITATION, reported below Three fixes to the shared PowerShell fetchers, each found by a row rather than by inspection, and each a real defect rather than a test accommodation. Square brackets in a checkout path. `Set-Location` and every -Path parameter read them as wildcards, so the fetchers could not find a directory that plainly existed. Worse, once the current directory contains brackets it is stored escaped, and even -LiteralPath with a relative path then resolves to a name with backticks in it. Both fetchers now anchor on their own location and use absolute literal paths throughout, depending on the current directory for nothing. Extended-length roots. `Split-Path` cannot parse a \\?\ path -- it reports a null drive and returns nothing -- and `Join-Path` refuses one for the same reason. Both are replaced with the .NET equivalents, which are prefix-agnostic. An empty PLANNOTATOR_TUI_RELEASE_BASE was treated as a release base rather than as absent, producing a URL with no host and an "invalid URI" that surfaced far from its cause, through the warn-and-exit-zero contract that hides it. Presence is now tested as non-empty. This is easy to hit: passing $null to SetEnvironmentVariable binds as an empty string and leaves the name defined. The long-path row is not claimed as passing. Staging now succeeds past the legacy limit, but a 267-character image cannot be started at all: process creation takes the image path through the MAX_PATH route and accepts no extended-length spelling, so a staged, checksum-verified, present binary still fails to launch. The checklist forbids resolving this by changing the root manifest, so it is measured and reported. A related constraint sits beside it: the manifest's build command passes a relative -File argument, and Herdr resolves only the program against the plugin root, not the arguments, so Windows PowerShell resolves that argument against the pane cwd under MAX_PATH. Both bear on how long an installed plugin root may be, and both are upstream's call. Two smaller things the rows forced. Build output is captured and shown when staging fails, because warn-and-exit-zero makes a silent failure the normal shape of trouble. And a process launched from a share reports its image in device form, UNC\server\share rather than \\server\share, which is folded back before comparing -- otherwise every UNC run looks like no process at all. UNC is a loopback share, not a remote file server, and is recorded as such.
|
Pushed §3's path matrix. Each row reuses the pane lifecycle rather than restating it, so a passing row means the same thing the default case means: real fetch, per-run marker rendered, Windows 11 22621, x86_64,
Three fetcher defects the matrix foundEach was found by a row, not by inspection, and each looks like a real defect rather than something to accommodate in a test. Square brackets in a checkout path. Extended-length roots. An empty All existing fetcher cases still pass ( The long-path row, and a second constraint beside itStaging now succeeds past the legacy limit, but a 267-character image cannot be started at all — process creation takes the image path through the MAX_PATH route and accepts no extended-length spelling, so a staged, checksum-verified, present binary still fails to launch. Beside it, and arguably the more interesting one: the manifest's build command passes a relative Both bear on how long an installed plugin root may be. The checklist forbids resolving this by changing the root manifest, so both are measured and reported rather than patched, and the row is not claimed as passing. Happy to implement whichever direction you prefer. Two smaller things the rows forcedBuild output is now captured and shown when staging fails, since warn-and-exit-zero makes a silent failure the normal shape of trouble here. And a process launched from a share reports its image in device form — Still outstandingPlacements other than the default overlay; forced-termination exit status; CI remains |
Draft, against
docs/windows-full-acceptance.md. Covers §1 and the staging half of §2; §3–§6 are not attempted yet and nothing here claims them. Opening early so the Windows CI runs and so the two questions at the bottom can be settled before I write the rest.Follows up #43. Measurements behind the design choices are in this comment; the short version is below.
What this adds
windows-full/herdr-plugin.toml—platforms = ["windows"],min_herdr_version = "0.9.0", root's plugin version, full action parity with the root manifest.windows-full/scripts/fetch-plannotator-tui.ps1— stages the TUI towindows-full/bin/.scripts/fetch-plannotator-tui.ps1— optional-DestinationDirectory. Omitted, behaviour is unchanged.check_windows_fullinscripts/test-windows-full-manifest.py. Existing root, Lite and development assertions untouched.windows-full/**in the CI path filters;windows-full/bin/in.gitignore, which the rootbin/*pattern does not cover because it is anchored.The root and
lite/manifests are unchanged, so Windows on 0.8.x keeps installing Lite and macOS/Linux keeps the Unix wrapper and its gates.Why 0.9.0, and why direct argv
Measured on Herdr 0.9.0, Windows 11 22621, x86_64. A pane program written
./bin/<name>.exeresolves against the plugin root while the review folder is passed separately as the pane cwd — Herdr's refusal for a missing program names the path it handedCreateProcessW:The same message shows the resolved program arriving extended-length. A native binary runs from a
\\?\path unchanged; apowershell.exeimage started that way exits0xFFFF0000immediately. Isolated on one binary, only the prefix differing:C:\…\bin\probe.exe0\\?\C:\…\bin\probe.exe4294901760./bin/probe.exe(and.\bin\,bin/,bin\)4294901760So "no launcher between Herdr and the TUI" is not a style rule on Windows — a wrapped pane cannot stay up.
plannotator-tuibeing native is what makes the direct-argvdocpane work.Layout
The native runtime is shared rather than duplicated, reached at
../bin/herdr-annotate.exethe waylite/reaches it, so annotations and archives keep one location across variants. Onlyplannotator-tuiis staged inside the variant, since the root must keep staging its own copy for Unix. Confirmed by running both fetchers fromwindows-full/:herdr-annotate.exelands in rootbin/,plannotator-tui.exeinwindows-full/bin/.Verified locally (Windows 11, x86_64, Windows PowerShell)
--versionreportsplannotator-tui 0.8.0; a second run is an idempotent no-download success.bin/as before;test-fetch-plannotator-tui.ps1-Case LocalOverrideand-Case Downloadboth exit 0.check_windows_fullrejects all six mutations tried: a platform gate on an action, a wrapper in thedocpane,min_herdr_version 0.8.0, a renamed action title, an unshared native runtime, and a build gated to macOS/Linux.Not covered
§2's checksum, rollback and architecture matrix routed through the wrapper; §3's runner proof beyond the manifest step; §4; §5's README wording; §6. Native ARM64 in §4 is not closeable on my hardware, which is x86_64 — flagging that early rather than at review.
Two questions
-DestinationDirectoryon the shared fetcher plus a thin wrapper that derives both the fetcher and the destination from its own location, so the build depends on neither the caller's cwd norHERDR_PLUGIN_ROOT, and other callers keep the old default. If you would rather the shared fetcher stayed untouched, or want the destination derived differently, say so — it changes which regression tests I add next, so I would rather not write them twice.