# `reinstall-criteria` _(no submodules)_ ## Purpose Decides whether a manifest update from `prev` to `next` requires a full reinstall path (`uninstalled → installing → installed → starting → started`) versus a hot-reload (`stopped → starting`). The criteria mirror design §4 exactly: a `schemaVersion` bump, new scopes/events/jobs/secrets, or any `dependsOn` diff force the full path; a strict version downgrade throws `LIFECYCLE.REINSTALL_REQUIRED_VERSION_DOWNGRADE` because the ledger would then point at a higher schemaVersion than the running code can satisfy. ## Features - Pure `requiresReinstall(prev, next): boolean` — no I/O, no global state, no clock dependency. - Per-field detection of added items only (removals are intentionally not a reinstall trigger — the host can drop them in hot-reload). - Embedded `compareSemver(a, b)` implementing loose semver compare (`X.Y.Z` form, prerelease/build metadata ignored). The manifest validator in `api-manifest` already rejects non-semver values, so this is the only consumer. - Throws `LIFECYCLE.REINSTALL_REQUIRED_VERSION_DOWNGRADE` with `prev` / `next` versions in `error.context` for downgrade attempts. ## Usage: Basic ```ts import { requiresReinstall } from "@suaveplan/api-lifecycle/reinstall-criteria"; if (requiresReinstall(prev, next)) { // walk the full install path } else { // hot reload via stopped → starting } ``` ## Usage: Advanced Wrapping the call in admin tooling that wants to surface the specific trigger reason: ```ts import { compareSemver, requiresReinstall } from "@suaveplan/api-lifecycle/reinstall-criteria"; function explain(prev: PluginManifest, next: PluginManifest): string { if (compareSemver(next.version, prev.version) < 0) return "version downgrade"; if ((next.database?.schemaVersion ?? 0) > (prev.database?.schemaVersion ?? 0)) { return "schemaVersion bumped"; } return requiresReinstall(prev, next) ? "manifest changed materially" : "hot reload OK"; } ``` ## API Reference - `requiresReinstall(prev: PluginManifest, next: PluginManifest): boolean` - `compareSemver(a: string, b: string): -1 | 0 | 1` ## Implementation Notes The "added-items" detection uses `Set`s built from `prev` so the comparison is O(n) per field. `dependsOn` is compared via a key-union walk so a removed-but-then-readded dep with a different version is still caught. Removing items intentionally does not trigger a reinstall — design §4 defines this as a hot-reload-safe operation; the host driver drops the corresponding capability registrations in the `installed → starting` side-effect chain.