NodePilot 1.2.0
The first release that keeps running when something underneath it stops: a wedged database, a
network share that disappears, a trigger whose source dies. It also carries the 1.1.2 setup fixes,
which only ever shipped as a server installer attached to the 1.1.1 release.
Downloads
| Asset | For |
|---|---|
NodePilot-Server-Setup-1.2.0.exe |
Windows service — the production rollout. Wizard, or /VERYSILENT with an answer file. Carries the signed artifact inside; nothing else to download. |
NodePilot-Desktop-Setup-1.2.0.exe |
Desktop app — one machine. Bundles a local PostgreSQL and the .NET runtime, installs both as services, opens a native window. Offline, no prerequisites. |
NodePilot-1.2.0.zip + .manifest.json + .manifest.json.p7s |
The scripted path, for automation and troubleshooting: deploy/Install-NodePilot.ps1 -ArtifactPath …. This is exactly what the setup runs. |
NodePilot-1.2.0.SHA256SUMS.txt |
Checksums over everything above. |
nodepilot-release-signing.cer |
Public signing certificate (see below). |
| Source code (zip / tar.gz) | The full source of this tag, Apache-2.0. |
Surviving a database outage
When the database service wedges, NodePilot used to wedge with it: the process stayed up and
stopped answering. Every DbContext carried EnableRetryOnFailure(5) over a 120-second command
timeout, so a single query against a hung server burned roughly twelve minutes before an exception
surfaced — and token validation ran ahead of every authenticated request, so every request parked
in that budget, not just the slow one. Meanwhile the header's status pill polled /healthz/live,
which is 200 by design: the indicator stayed green while the product stood still.
A process-wide availability breaker now owns that state, with one rule everything else follows
from: after boot, only the recovery probe may publish Available; interceptors may only degrade.
The API answers 503 DATABASE_UNAVAILABLE immediately (with Retry-After), background services
park instead of throwing, the engine pauses before starting a new step rather than finalising a
failed one as succeeded, and recovery is automatic once the probe sees two clean reads.
Two measurements shaped it, both of which rule out the obvious implementation. Npgsql reports a
connect failure and a command timeout in an identical exception shape, so classification is by
context rather than form. And a command timeout never opens the breaker directly — a slow query is
not an outage; it only arms the probe. New health surface: /healthz/ready fails fast for a load
balancer, /healthz/database always answers 200 with a status the SPA renders as a banner and a
traffic light. See docs/adr/0011-database-availability.md.
Triggers that come back on their own
A fileWatcherTrigger on a UNC share stopped firing permanently when the share went away. Windows
tears down the watch handle, FileSystemWatcher raises Error, and the handler only logged it —
the dead source stayed registered with a matching config hash, so nothing evicted it and nothing
re-added it. Bringing the share back changed nothing; only a restart, a config edit, or disabling
and re-enabling the workflow helped.
ITriggerSource now answers Health, contractually a pure in-memory read because the orchestrator
evaluates it for every trigger inside its five-second pass. An unhealthy source is evicted and
routed back through the existing registration path, which already retries with exponential backoff
up to five minutes, indefinitely. A FileSystemWatcher cannot be re-armed in place — on the
re-issue failure path the runtime disposes the handle without clearing EnableRaisingEvents, so
the property still reads true on a corpse — so a fresh instance is created. A buffer overflow is
deliberately not a fault: the runtime re-issues the read, and evicting there would only flap.
Silence was the other half of the problem. A registration that keeps failing was caught per trigger
and retried, so every operator-facing surface kept showing green while a drop folder went unwatched.
The new trigger-unhealthy system-alert policy reports it, firing past 60 seconds — beyond what a
share restart explains.
Installing on a host that has never seen NodePilot
Three field reports, one theme: the setup checked everything except what actually stopped the
install.
- All nine prerequisite rows green, then a rollback.
Install-NodePilot.ps1verifies the
artifact's signature against the pinned publisher, which is self-signed — so every first
installation failed there, with a German certificate-chain message that names neither the
artifact nor the remedy. The remedy had shipped and been unreachable the whole time: the
readiness page had no row for the signer, and the wizard wrotetrustArtifactSigner: falseas a
constant, so it could not be asked for from the interface at all. - The trust decision itself is now unnecessary. Chain validation only ever confirmed what the
thumbprint pin already established — the publisher is its own trust anchor — at the price of a
permanent, machine-wide change on every target. The chain is no longer validated; what it
enforced on our behalf is now enforced explicitly (KeyUsage must permit signing, EKU must be
code-signing, validity window checked against the signing time). - 32-bit .NET hosts. NodePilot ships as win-x64. A machine whose only
dotnet.exewas 32-bit
passed the runtime row and then failed to start the service. The pre-flight now reads the machine
type out of the PE header —dotnet --infois localised and its architecture line is unfindable
on a German server — and reports the host red by path and architecture.
Also: the empty-certificate-field fix that was cut as 1.1.2, fix checkboxes that had no clickable
place on the page, and a setup log that narrated steps it had not taken.
Smaller, but visible
- AI buttons only when there is an LLM. The designer assistant, the script-editor generate
button and the AI workflow generation button rendered unconditionally and failed with a 503 when
no endpoint was configured. All three are now gated on a shared capability query, and the
script-editor button is additionally hidden for Viewers, matching its Admin/Operator endpoint. waitForConditioncan finally probe localhost. ThehttpOkprobe ran through the restApi
SSRF guard before consulting its own allow-list, so the shipped["localhost"]default — the one
documented as "check whether my own service is up" — was inert on every non-Development instance.
The probe path now has its own validation; link-local and cloud-metadata addresses stay blocked
for both probe types.- Locale-dependent formatting is routed through one module, so dates and numbers no longer differ
between components.
Under the floor
A coherence pass that leaves no user-visible trace but is worth knowing about: controller DTOs
moved out of controller files, folder RBAC routed through one authorization gate, LLM error mapping
and the agentic tool loop deduplicated, scheduler telemetry names and the DPAPI session entropy
single-sourced, and the ExternalIdentityResolutionController removed. Three new guard tests pin
the dependency direction, endpoint parity across API/CLI/MCP, and the deployment template grammar,
so the next drift fails a build instead of a review. Coverage now has exactly one authoritative
gate, in CI. And nanoid moves to 3.3.18 across all three npm trees (GHSA-2v37-7h3g-55p8, build-time
only) — a lockfile refresh, since the range that pulls it in already allowed the fixed version.
Verifying the download
Publisher : CN=NodePilot Release Signing
Thumbprint : 277EAB317A581C88302CE92BE805938C86B4650D
Valid until : 2031-08-02
Get-FileHash .\NodePilot-1.2.0.zip -Algorithm SHA256 # compare with NodePilot-1.2.0.SHA256SUMS.txt
(Get-PfxCertificate .\nodepilot-release-signing.cer).Thumbprint # must equal the value aboveBoth installers are Authenticode-signed with that certificate, RFC-3161 timestamped. It is a
self-signed publisher, not a public CA — that is why the thumbprint is published here and why
you should compare it before pinning it. Importing it into LocalMachine\Root is no longer
required for an installation; if you do it anyway, compare the thumbprint first. Expect a
SmartScreen warning; the publisher has no reputation to draw on. If your organisation will not
trust a self-signed publisher, build and sign both artifacts with your own enterprise certificate —
the walkthrough is in docs/deployment-guide.md.
Licensed under Apache-2.0.