Skip to content

NodePilot 1.2.0

Choose a tag to compare

@Sev7eNup Sev7eNup released this 07 Aug 22:31
· 362 commits to main since this release
db3b20b

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.ps1 verifies 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 wrote trustArtifactSigner: false as 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.exe was 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 --info is 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.
  • waitForCondition can finally probe localhost. The httpOk probe 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 above

Both 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.