Skip to content

fix(update): update and verify active install#942

Merged
NicholaiVogel merged 2 commits into
Signet-AI:mainfrom
LeuciRemi:remil/fix-native-self-update
Jul 21, 2026
Merged

fix(update): update and verify active install#942
NicholaiVogel merged 2 commits into
Signet-AI:mainfrom
LeuciRemi:remil/fix-native-self-update

Conversation

@LeuciRemi

Copy link
Copy Markdown
Contributor

Summary

Fix signet update install so it updates the installation owning the
currently active executable, verifies the exact installed version at its
canonical path, and only then requests a daemon restart.

Package-manager installations remain supported. When a native installation
is active alongside an inactive npm, Bun, pnpm, or Yarn installation, Signet
reports the conflict without removing anything automatically.

Changes

  • Add shared, typed Signet installation detection for native and
    package-manager installations.
  • Update native installations from the selected release artifact with
    manifest, platform, size, SHA-256, timeout, and atomic replacement checks.
  • Preserve the active package manager without silently falling back to npm.
  • Verify the canonical executable with --version before reporting success
    or setting pendingRestartVersion.
  • Return stable structured update error codes and observed version details.
  • Make the Rust daemon fail closed with unsupported_runtime_update until
    verifiable native replacement is packaged for that runtime.
  • Report concurrent installations after update and through structured
    signet doctor diagnostics.
  • Make the public installer stable by default, require explicit nightly
    opt-in, and always pass install --force.
  • Validate release packages and npm tags before promoting a GitHub release
    to stable.
  • Align CLI, API, installation, and release-channel documentation.

Type

  • feat — New feature
  • fix — Bug fix
  • refactor — Code change that neither fixes a bug nor adds a feature
  • docs — Documentation only
  • chore — Build, CI, tooling, or maintenance
  • perf — Performance improvement
  • test — Tests only

Packages affected

  • @signet/core
  • @signet/daemon
  • @signet/cli / dashboard
  • @signet/sdk
  • @signet/connector-*
  • @signet/web
  • predictor
  • Other — Rust daemon parity and release promotion workflow

Screenshots

N/A — CLI, daemon API, installer, and release workflow changes only.

PR Readiness (MANDATORY)

  • Spec alignment validated (INDEX.md + dependencies.yaml); no
    self-update feature dependency changes are required.
  • Agent scoping and isolation reviewed; no scoped user-data access changed.
  • Input/config validation and bounds checks added.
  • Error handling and fallback paths tested.
  • Existing security and authorization boundaries preserved.
  • CLI, API, installation, and release-channel docs updated.
  • Regression tests cover active-path selection, version verification,
    duplicate installation warnings, integrity failures, and stable/nightly
    installation.
  • Touched-surface lint, typecheck, builds, and tests pass locally; global
    baseline limitations are documented below.

Migration Notes

  • Migration idempotent — N/A, no database or configuration migration.
  • Daemon Rust parity reviewed; unsupported replacement now fails closed.
  • Rollback requires no data migration: revert the commit or reinstall the
    previous native release explicitly.

Testing

  • bun test — the full suite was started but not completed after unrelated
    baseline/sandbox failures involving protected home directories, local
    listeners, and stale generated parity/content indexes.
  • bun run typecheck
  • bun run lint — the repository-wide command currently reports the
    existing baseline; Biome passes on every touched TypeScript file.
  • Tested against running daemon — the installed production daemon was
    intentionally left unchanged.
  • N/A

Additional successful validation:

  • bun run build
  • bun run build:native-bun
  • 93 focused Bun regression and native installer smoke tests
  • targeted Rust update and parity tests
  • cargo clippy -p signet-daemon --all-targets
  • targeted rustfmt
  • bash -n web/marketing/public/install.sh
  • YAML parsing and git diff --check

cargo fmt --all -- --check remains blocked by pre-existing formatting in
the untouched Rust main.rs.

AI disclosure

  • No AI assistance was used.
  • AI assistance was used. OpenAI Codex assisted with implementation,
    validation, and documentation. The commit contains the required
    Assisted-by trailer.

Notes

The production LOC increase comes from the shared installation detector and
the integrity-checked native installer. These replace the silent npm fallback
and false-success runtime path, while the regression tests account for a
substantial part of the overall diff.

No duplicate installation is ever removed automatically.

@LeuciRemi
LeuciRemi force-pushed the remil/fix-native-self-update branch from 781dddb to 6e23a7d Compare July 20, 2026 17:50
@LeuciRemi
LeuciRemi marked this pull request as ready for review July 20, 2026 20:19
@NicholaiVogel

Copy link
Copy Markdown
Collaborator

The core of this is good. The native update path with SHA-256 verification, the executable version check at the active path, and the structured error codes are a real improvement over the old spawn-npm-regardless behavior. Most of the PR is correct and should land.

A few correctness items worth addressing before merge.

One. verifyExecutableVersion runs the active executable with --version. On Windows, if the running daemon still holds the binary (or the just-replaced binary is briefly locked by antivirus or the OS), the verify subprocess can fail even though the install succeeded. The installer itself handles this with the backup-rename pattern in native-install.ts. The verifier does not. The result would be a successful install reported as verification_failed. Worth a test that exercises the Windows-locked-binary path, or at least a note in the error message that verification failure does not always mean install failure on Windows.

Two. The new signet-installation.ts in core overlaps with detectFromExecPath in package-manager.ts. Both do package-manager-by-path detection over the same four families with overlapping path heuristics (.bun/, .pnpm/, .yarn/, node_modules). The new code is more thorough, but you now have two implementations of the same concept in the same package. They will drift. Consider extending detectFromExecPath rather than maintaining a parallel detector, or add a comment in both files pointing at each other so the next person knows the relationship.

Three. The Rust daemon's run_update_install now fails closed with unsupported_runtime_update. That is the correct safety call given the Rust shadow cannot do native binary replacement yet. But it is a parity regression: the TS daemon updates, the Rust daemon refuses. Worth a follow-up issue tracking Rust native-update parity so this does not get forgotten. Not a blocker for this PR.

Four. parseExecutableVersion strips a leading v and requires exact semver. That is strict, which is good. But some builds print a prefix like Signet 1.2.3 or include build metadata with a commit hash. The test expect(parseExecutableVersion("Signet 1.2.3")).toBeNull() confirms the strict behavior is intentional. If any Signet build ever emits a non-bare version string, every update verification will fail. Confirm the --version output is guaranteed to be bare semver across all build configs, or loosen the parser to extract the semver token from a prefixed line.

Five. installNativeUpdate invokes the downloaded binary with install --bin-dir <dir> --force. If the downloaded binary is malformed in a way that passes SHA-256 (corruption in the source build, not in transit), it will execute untrusted code with whatever privileges the daemon has. SHA-256 verifies integrity, not trust. This is acceptable if the release pipeline is trusted, which it is (GitHub releases signed by the promote workflow). Just naming the trust assumption explicitly so it is on the record. The temp-dir cleanup in the finally block is correct.

The docs, install.sh hardening, and release workflow improvements all look correct. No issues there.

Assisted-by: OpenAI Codex:gpt-5
@LeuciRemi
LeuciRemi force-pushed the remil/fix-native-self-update branch from 6e23a7d to bf5c196 Compare July 21, 2026 00:06
@LeuciRemi

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review — I addressed the five points.

  1. Windows verification locking

verifyExecutableVersion now retries transient launch or execution failures up to three times on Windows, within the original global timeout. POSIX verification is not retried, and a successfully parsed but incorrect version is never retried.

After exhaustion, the result remains verification_failed and explicitly explains that replacement may already have succeeded, with instructions to stop or restart the daemon and verify the active executable.

Installer and verifier subprocess environments now remove SIGNET_VERSION and SIGNET_DAEMON_ENTRYPOINT case-insensitively, which is required for Windows environment semantics.

  1. Duplicated package-manager detection

Package-manager ownership inference is now centralized in one internal core helper used by both primary package-manager resolution and installation detection.

The shared inference uses the apparent path, resolved path, platform, home directory, and explicit npm, Bun, pnpm, and Yarn roots. Broad substring checks such as /bun/, /pnpm/, /yarn/, and generic node_modules were removed so a native executable cannot accidentally select the wrong package-manager updater.

Bun's configurable BUN_INSTALL_BIN and BUN_INSTALL_GLOBAL_DIR roots are also supported. Regression tests cover all four package managers, custom Bun roots, and native paths containing package-manager names.

  1. Rust daemon parity

The Rust daemon remains intentionally fail-closed with unsupported_runtime_update. I kept native replacement parity outside this MR. Here is the proposed follow-up issue:

Title: feat(update): add verified native self-update parity to the Rust daemon

The TypeScript daemon supports verified updates of the active Signet installation, while the Rust daemon currently fails closed with unsupported_runtime_update.

The Rust implementation should provide equivalent safety guarantees:

  • detect the canonical active executable and installation method;
  • select and validate the exact platform asset from the versioned native manifest;
  • enforce bounded downloads, declared size and SHA-256 validation, and a global timeout;
  • replace the executable atomically with appropriate POSIX and Windows handling;
  • preserve or restore the previous executable after installation failure;
  • remove inherited SIGNET_VERSION and SIGNET_DAEMON_ENTRYPOINT;
  • verify the canonical executable using strict SemVer output;
  • retry transient Windows launch failures within bounded attempts and time;
  • return structured results compatible with the TypeScript daemon;
  • mark restart pending only after successful verification.

Runtime tests should cover successful replacement, checksum and size mismatches, interrupted downloads, wrong paths or versions, stale inherited environment variables, Windows locking, and rollback behavior.

Follow-up to #942.

  1. Strict --version parsing

The parser remains strict and now follows SemVer identifier rules, including prerelease and build metadata while rejecting leading zeros, empty identifiers, and prefixed output such as Signet 1.2.3.

The same validation is shared with target-version normalization.

The build path sets the embedded package version, and Commander prints that value directly followed by a newline. A real rebuilt-native check also confirmed that verification ignores a stale inherited SIGNET_VERSION and observes the embedded version.

  1. SHA-256 trust model

The documentation now distinguishes the exact guarantees:

  • native self-updates validate the declared size and SHA-256;
  • the public direct installer validates SHA-256;
  • both compare against native-manifest.json from the same release.

It also states that these checks establish consistency with the manifest, not independent authenticity. Assets and manifests are not currently signed; the present trust anchor is the GitHub Actions release workflow, repository release permissions, and HTTPS delivery. Signatures and attestations remain separate work.

Validation completed with 62 focused tests, full typecheck, targeted Biome checks, the complete build, a native rebuild, and a real stale-version runtime verification.

@NicholaiVogel

Copy link
Copy Markdown
Collaborator

Thanks Rémi, all five points look good. The centralized path inference in package-manager-path.ts is the right call. Catching the SIGNET_VERSION env leak on top of point four was a good get.

Good to merge from my end.

@NicholaiVogel
NicholaiVogel merged commit b249356 into Signet-AI:main Jul 21, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants