Windows is a published platform. windows/amd64 joins linux/amd64 and
darwin/arm64 on the release page, in the installer, in kivgraph update and
in the MCP Registry entry. Nothing stored changed shape, so an existing
installation upgrades without a pass.
- The release publishes a
.zip, not a tarball, because that is what the
tools on that platform read. The archive is normalised to a fixed timestamp
and a sorted entry list before packing, so two builds of one tag produce a
byte-identical file; and it is checked for backslash separators where it is
made, rather than leaving a reader to discover at install time that their
packer wrote paths the installer refuses.
scripts/install.ps1 mirrors scripts/install.sh, check for check. The
two implement one set of pre-extraction rules and a gate fails the build if
either grows a rule the other does not have — a check that exists on the
platform you use and not on the one you do not is how an installer stays
wrong for a year. The installer also installs the Visual C++ redistributable,
because kivgraph.exe does not start without it and Windows Update services
the redistributable rather than any copy a bundle could carry.
kivgraph update works there. It looks for the platform’s own archive,
reads a zip through the same containment, count and size guards the tarball
reader uses, and replaces a bundle whose binary is still running — measured
rather than assumed, because Windows keeps an executing image open and the
rename that moves the old bundle aside is the step an update turns on.
- The daemon has an owner. A logon task through Task Scheduler, at
LeastPrivilege, matching what the launchd and systemd backends already do:
every existing backend is a user agent, and a service would have been a
different guarantee wearing the same name.
- The socket and the worker’s pipe carry a DACL this process sets, rather
than one inherited from wherever somebody pointed the state directory. Both
say the same sentence once, in
internal/privateobject.
Six defects were found by running the suite on a Windows host rather than by
reading it, and every one of them was a check that reads as careful and answers
the wrong question off Unix.
filepath.IsAbs is false for a rooted path with no volume. \sdk\x.dart
and /outside/manifest.json are resolved by Windows against the current
drive, so a validator that joined them to a repository root concluded they
were inside it. In internal/workspace that guard is a security boundary,
and it was returning nil for exactly the input it exists to refuse.
shlex.split deletes a Windows path separator, because it is a POSIX
shell lexer and a POSIX shell treats a backslash as an escape. A configured
Python analyzer arrived as C:toolsnodepyright-langserver, and the loader
reported it as unavailable rather than as destroyed.
- A file URI is not a file path.
file:///c%3A/x.py hides the drive behind
a percent-encoded colon, so the Python worker recorded 63 resolved
declarations as TARGET_NOT_INDEXED and emitted no exact edge at all. The
Dart loader had the mirror image: it built file://C:/x, in which the drive
is the authority, and the analysis server rejected every request.
CreateProcess searches PATH but does not append PATHEXT, so a bare
pyright-langserver was not found with pyright-langserver.CMD beside it —
which is how npm installs it.
filepath.Base(path) == "dart" is false for dart.exe, so the SDK was
launched with no subcommand, printed its usage and exited. Two of the three
places that asked answered wrongly; the third already carried
|| == "dart.exe" beside it, which is the drift executable.BaseName now
removes by asking once.
- Claude Desktop ships as an MSIX package, and MSIX redirects a packaged
application’s writes into its own container. It reads its configuration from
%LOCALAPPDATA%\Packages\Claude_<hash>\LocalCache\Roaming\Claude\, not from
the documented %APPDATA%\Claude — so kivgraph mcp install --target claude-desktop reported success and wrote a file the application never
reads. Both markers that detected it were absent on a real Store install.
The last one arrived with a sibling: removing an MCP entry deleted our server
and wrote the emptied mcpServers back as {}. The hook path already deleted
an emptied section and said why — a remove should leave a file that looks like
one nobody installed into — and only a configuration that existed beforehand,
holding somebody else’s key, could show that the other path did not.
Two smaller things fall out of the same work and apply everywhere. A
.gitattributes makes line endings a property of the repository rather than of
whoever cloned it, so a Windows contributor no longer gets every shell script
with CRLF terminators. And internal/durable states the fsync rule once
where four copies had agreed with each other.