/** * The two mutating dependencies `senpi.update --apply` needs, lifted out of the boot closure. * * They lived as inline closures in `index.ts`, which no test constructs — so the two operations that * actually change a running strategy were the only part of the verb with no coverage at all. Both * have a failure mode that is silent at the time and expensive later: * * - swapping a runtime that is not running here, and reporting success for it * - leaving a stale `runtimeYamlPath`/`runtimeYamlContent` sibling on the registry entry, so a * RESTART replays the pre-update recipe while the live process runs the new one * * Extracted rather than copied into a test: a test over a duplicate of this logic proves only that * the duplicate works. */ import type { Finding } from "../validate/types.js"; import type { RuntimeConfig } from "./runtime-schema.js"; import type { RecipeSource } from "./update-handler.js"; /** The part of a `verifyProof` result this needs: whether it passed, and what it hashed. */ export interface ProofOutcome { ok: boolean; record?: { recipe: string; }; finding?: Finding; } /** * Decide whether a proof covers the recipe actually being applied. * * `verifyProof` answers a question about a directory: do these files still hash to what passed? * An update asks a narrower one, because the recipe it applies does not have to be the file in that * directory — `--content` supplies bytes directly, and `--dir` only says where relative scanner * paths resolve. So a caller could point `--dir` at a validated package, hand `--content` something * else entirely, and the gate would pass on the strength of a proof about a file it was not going * to run. * * `appliedRecipe` is the caller's text BEFORE the wallet placeholder is bound. The proof describes * bytes on disk and binding happens afterwards, so comparing the bound form would reject every * package that legitimately writes `${WALLET}`. */ export declare function checkProofCoversRecipe(packageDir: string, result: ProofOutcome, appliedRecipe: string | undefined, readFile: (path: string) => string): { ok: boolean; finding?: Finding; }; /** The slice of a `run()` handle an apply needs. */ export interface ApplicableRuntime { applyUpdatedConfig(next: RuntimeConfig): Promise; } /** * Swap the live runtime onto the new recipe. * * Refuses when the id has no live handle. A registry row without one is a runtime that is not * running on this gateway — rebuilding nothing and returning cleanly would tell the user their * change is live when it is not, which is worse than an error because nothing later contradicts it. */ export declare function makeApplyToRuntime(handles: { get(id: string): ApplicableRuntime | undefined; }, /** * Restart this runtime's external scanner children against the new package. * * Rebuilding the runtime swaps the IN-PROCESS components and nothing else. External scanners are * separate OS processes owned by the intake launcher: each is spawned once with a launch config * written from the recipe, and it reads its entrypoint at spawn. Nothing in the rebuild touches * them, so without this an apply left every external scanner running its old code, its old * `inputs` and its old cadence — while reporting success. For a strategy whose entries come from * an external scanner, that is the whole point of the update silently not happening. * * Deliberately UNCONDITIONAL rather than gated on "did the launch config change?". A pure scanner * code edit produces no recipe diff at all, so a change-detection gate would skip precisely the * case `--code-only` exists to serve. Restarting is cheap and safe: the state directory is keyed * on scanner name and position, so a relaunched child resumes its own state and delivery journal. * * Optional because a gateway with no intake mounted (tests, API-disabled deployments) has no * children to restart. It must not throw: by the time it runs, the recipe IS live in-process, so * a failed relaunch is a degraded runtime to report, not an apply to fail. The implementation * records that itself, the same way the hot-install path does. */ relaunchScanners?: (runtimeId: string, next: RuntimeConfig, recipe: RecipeSource) => Promise): (runtimeId: string, next: RuntimeConfig, recipe: RecipeSource) => Promise<{ scannersUnwired?: string; }>; /** * Record the recipe a restart should replay. * * Called only after the swap succeeded — this is the record a restart replays, so it must never * describe a recipe the runtime failed to build. * * The entry carries exactly one of `runtimeYamlPath` / `runtimeYamlContent`, mirroring how install * writes it. Both are cleared before the new one is set: leaving the old sibling in place would let * `listRuntimes` resolve the pre-update source on the next boot, which reads as the update having * silently reverted itself. * * Under {@link withRegistryLock}: this runs AFTER `applyToRuntime` returns — outside even the * per-runtime {@link applyMutex} — so two concurrent applies to DIFFERENT runtimes would otherwise * each write back a snapshot missing the other's change. The loser keeps trading its new recipe * in-process while the registry names the old one, the next restart silently reverts it, and no * `registryDiverged` finding fires, because its own write "succeeded". */ export declare function makeWriteRegistry(baseStateDir: string): (runtimeId: string, recipe: RecipeSource) => Promise; //# sourceMappingURL=update-apply-deps.d.ts.map