fix(setup): preserve the selected destination during configuration publication

Replace configuration-specific write-file-atomic delegation with a verified
input snapshot, exclusive private staging, and an explicit canonical-parent
destination. Existing files publish by rename; first-run files use an exclusive
link so a newly created destination is retained. Both consumers read back the
publication owner's selected path.

Retain descriptor and staging cleanup capabilities through recovery and host
retry. Preserve mode and ownership, remove temporary permission handoffs, and
use positional restoration writes. Keep UTF-8 diagnostics with failed cleanup.

Validation: 284 focused tests passed, one skipped across 14 files; check:fast,
format:check, docs:check (including five consistency tests), two source reviews,
and 12 completed isolated public CLI setup/uninstall cost controls passed.
Native Windows/Linux, real editor concurrency, and providers were not verified.
Final-check/publication races and same-inode concurrent writers remain unexcluded.

Fixes #4904
This commit is contained in:
morluto
2026-10-18 07:06:15 +08:00
parent 2fb5e204a3
commit d0253c99d6
12 changed files with 771 additions and 215 deletions
+4 -2
View File
@@ -23,6 +23,7 @@ import {
import { dshDocument, DshCordisDocument } from "./DshCordisPatch.js";
import { mkdir } from "node:fs/promises";
import { dirname } from "node:path";
import { isClientConfigurationCleanupFailure } from "./ClientConfigurationCleanup.js";
import { ClientConfigurationPublication } from "./ClientConfigurationPublication.js";
import {
clientRegistrationEnvironment,
@@ -304,10 +305,11 @@ const persistClientConfiguration = async ({
...(legacyPath === undefined ? [] : [legacyPath]),
...(enableOmp ? [[OMP_DISABLED_SERVERS_KEY]] : []),
]),
original,
0o600,
);
} catch (cause: unknown) {
if (cause instanceof RegularFileCleanupFailure) throw cause;
if (isClientConfigurationCleanupFailure(cause)) throw cause;
// Write failure is reported by the status reason.
void cause;
return { status: "failed", reason: "write" };
@@ -315,7 +317,7 @@ const persistClientConfiguration = async ({
return publication.verify(async () => {
try {
const readback = parseClientConfiguration(
await readRegularFileText(transactionPath),
await readRegularFileText(publication.path),
format,
);
await publication.assertCurrent();