fix(setup): preserve registration identity and recovery ownership

Keep layered environment choices, current byte-exact recovery snapshots, and package-manager failure details. Report pinned package drift without claiming runner resolution. Retain failed Linux installer cleanup for host-owned retry while preserving the install outcome.

Reviewed fixes include #2632, #2769, #2783, #2959, #2979, #2983, #2995, #3009, and #2559.

Validation: 98 focused setup/registration tests passed; 45 Linux installer tests passed. Real Linux package installation remains unverified.
This commit is contained in:
morluto committed 2026-10-14 08:25:53 +08:00
1 parent 599a8ee11d
commit ec56c1da04
42 files changed
+2418 -213

No files matched your search

+46 -10
View File
@@ -11,6 +11,21 @@ import {
import type { SetupClient } from "./SupportedClients.js";
import { clientRegistrationEnvironment } from "./ClientRegistrationEnvironment.js";
import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import type { ConfigurationError } from "../domain/configurationErrors.js";
/** A parsed layer retains its path so rereading the write target replaces only that layer. */
export interface ClientConfigurationSource extends ClientConfigurationDocument {
readonly sourcePath: string;
}
const invalidEnvironment = (
path: string,
error: ConfigurationError,
): ClientConfigurationFileError => ({
kind: "malformed",
path,
detail: `Configuration ${path} has an invalid REA server environment: ${error.settings.map(({ setting, constraint }) => `${setting}: ${constraint}`).join("; ")}. Repair it before rerunning setup.`,
});
/** A named source failure that setup can explain without losing the bad path. */
export interface ClientConfigurationFileError {
@@ -74,9 +89,9 @@ export const readClientConfigurationFiles = async (
client: SetupClient,
resources?: ArtifactResourceScope,
): Promise<
Result<readonly ClientConfigurationDocument[], ClientConfigurationFileError>
Result<readonly ClientConfigurationSource[], ClientConfigurationFileError>
> => {
const documents: ClientConfigurationDocument[] = [];
const documents: ClientConfigurationSource[] = [];
for (const path of client.configPaths ?? [client.configPath]) {
const document = await readConfigurationFile(
path,
@@ -85,16 +100,21 @@ export const readClientConfigurationFiles = async (
);
if (!document.ok) return document;
if (document.value !== undefined) {
const environment = clientRegistrationEnvironment(document.value);
const environment = clientRegistrationEnvironment(
document.value,
{},
"fragment",
);
if (!environment.ok)
return err({
kind: "malformed",
path,
detail: `Configuration ${path} has an invalid REA server environment: ${environment.error.settings.map(({ setting, constraint }) => `${setting}: ${constraint}`).join("; ")}. Repair it before rerunning setup.`,
});
documents.push(document.value);
return err(invalidEnvironment(path, environment.error));
documents.push({ ...document.value, sourcePath: path });
}
}
const environment = clientRegistrationEnvironment(
effectiveClientConfiguration(documents),
);
if (!environment.ok)
return err(invalidEnvironment(client.configPath, environment.error));
return ok(documents);
};
@@ -117,8 +137,24 @@ const isObject = (value: unknown): value is Record<string, unknown> =>
/** Project a client's effective user registration without modifying its source files. */
export const effectiveClientConfiguration = (
documents: readonly ClientConfigurationDocument[],
sources: readonly ClientConfigurationSource[],
target?: {
readonly client: Pick<SetupClient, "configPath" | "configPaths">;
readonly document: ClientConfigurationDocument | undefined;
},
): ClientConfigurationDocument | undefined => {
const documents =
target === undefined
? sources
: (target.client.configPaths ?? [target.client.configPath]).flatMap(
(path) => {
const document =
path === target.client.configPath
? target.document
: sources.find(({ sourcePath }) => sourcePath === path);
return document === undefined ? [] : [document];
},
);
const last = documents.at(-1);
if (last === undefined) return undefined;
if (documents.length === 1) return last;