* fix(#4132): verify durable runtime surface sources * chore(#4132): record PR number in changeset * test(#4132): cover rejected commands source alias * fix(#4132): reject aliased package fallback * test(#4132): cover rejected agents source alias * test(#4132): cover partially aliased marker provider * fix(#4132): reject partially aliased source providers * test(#4132): cover routed source identity probes * fix(#4132): route installed source identity probes * refactor(#4132): tighten installer source metadata * test(#4132): cover corpus trust boundary attacks * fix(#4132): close installed corpus trust gaps * refactor(#4132): keep installer authority private * fix(#4132): preserve private installer fallback * test(#4132): preserve fixture source authority * fix(#4132): reject overlapping source fallback * fix(#4132): avoid redundant installed corpus reads * refactor(#4132): simplify provider resolution * test(#4132): sync install tree fixtures after rebase --------- Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
@@ -68,6 +68,163 @@ const { getDirName } = runtimeNamePolicy;
|
||||
|
||||
type ResolveAttribution = (runtime: string) => any;
|
||||
|
||||
type RuntimeSurfaceSourceClass = 'commands' | 'agents';
|
||||
|
||||
function withInstallerPackageSource<T>(
|
||||
configDir: string,
|
||||
fn: () => T,
|
||||
): T {
|
||||
// Reuse the existing compatibility-marker contract through the existing fs
|
||||
// seam. The marker exists only in this synchronous call tree: no disk state,
|
||||
// layout export, stage argument, or caller-settable authority flag is added.
|
||||
const markerPath = path.resolve(configDir, '.gsd-source');
|
||||
const packageCommandsRoot = runtimeArtifactLayout.findInstallSourceRoot();
|
||||
const markerBytes = Buffer.from(packageCommandsRoot + '\n');
|
||||
const base = installFs();
|
||||
const overlay = {
|
||||
...base,
|
||||
existsSync: (candidate: string): boolean =>
|
||||
path.resolve(candidate) === markerPath || base.existsSync(candidate),
|
||||
lstatSync: (candidate: string): ReturnType<typeof base.lstatSync> =>
|
||||
path.resolve(candidate) === markerPath
|
||||
? { isFile: () => true, isDirectory: () => false, isSymbolicLink: () => false }
|
||||
: base.lstatSync(candidate),
|
||||
readFileSync: ((candidate: string, encoding?: BufferEncoding): string | Buffer => {
|
||||
if (path.resolve(candidate) !== markerPath) {
|
||||
return encoding ? base.readFileSync(candidate, encoding) : base.readFileSync(candidate);
|
||||
}
|
||||
return encoding ? markerBytes.toString(encoding) : Buffer.from(markerBytes);
|
||||
}) as typeof base.readFileSync,
|
||||
};
|
||||
return withInstallFs(overlay, fn);
|
||||
}
|
||||
|
||||
function isRuntimeSurfaceSourceUnavailable(message: string): boolean {
|
||||
return message.startsWith('Runtime Surface source is unavailable or incomplete for ') &&
|
||||
message.endsWith('install or upgrade gsd-core before materializing this surface.');
|
||||
}
|
||||
|
||||
function assertCorpusTreeHasNoSymlinks(root: string): void {
|
||||
if (!installFs().existsSync(root)) return;
|
||||
const stat = installFs().lstatSync(root);
|
||||
if (stat.isSymbolicLink()) {
|
||||
throw new Error(`Runtime Surface corpus path is a symlink: ${root}`);
|
||||
}
|
||||
if (!stat.isDirectory()) return;
|
||||
for (const name of installFs().readdirSync(root)) {
|
||||
assertCorpusTreeHasNoSymlinks(path.join(root, name));
|
||||
}
|
||||
}
|
||||
|
||||
function previousOwnedCorpusFiles(configDir: string, prefix: string): string[] {
|
||||
try {
|
||||
const files = installerMigrations.readInstallManifest(configDir).files;
|
||||
return Object.keys(files)
|
||||
.filter((entry) => entry.startsWith(prefix))
|
||||
.map((entry) => entry.slice(prefix.length))
|
||||
.filter((entry) => entry !== '' && !path.posix.isAbsolute(entry) && !entry.split('/').some((part) => part === '' || part === '.' || part === '..'));
|
||||
} catch {
|
||||
// An absent or unreadable prior manifest provides no ownership evidence.
|
||||
// Preserve existing entries rather than guessing that they are stale.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function pruneEmptyCorpusParents(start: string, stop: string): void {
|
||||
let current = path.dirname(start);
|
||||
while (current !== stop && current.startsWith(stop + path.sep)) {
|
||||
if (installFs().readdirSync(current).length > 0) return;
|
||||
installFs().rmdirSync(current);
|
||||
current = path.dirname(current);
|
||||
}
|
||||
}
|
||||
|
||||
function syncRuntimeSurfaceCorpus(source: string, destination: string, configDir: string, manifestPrefix: string): void {
|
||||
if (hasExistingSymlinkBetween(path.resolve(configDir), destination, { allowOptInFollow: isSymlinkedDestOptIn() })) {
|
||||
throw new Error(
|
||||
`syncRuntimeSurfaceCorpus: destination "${destination}" contains a symlink the install root "${configDir}" does not trust — refusing to write.`,
|
||||
);
|
||||
}
|
||||
assertCorpusTreeHasNoSymlinks(destination);
|
||||
|
||||
// Remove only paths the previous manifest proves GSD owned and which the
|
||||
// executing package no longer ships. Unknown neighbouring files survive.
|
||||
for (const relative of previousOwnedCorpusFiles(configDir, manifestPrefix)) {
|
||||
const sourceEntry = path.join(source, ...relative.split('/'));
|
||||
let sourceIsFile = false;
|
||||
try {
|
||||
sourceIsFile = installFs().lstatSync(sourceEntry).isFile();
|
||||
} catch {
|
||||
sourceIsFile = false;
|
||||
}
|
||||
if (sourceIsFile) continue;
|
||||
|
||||
const target = path.join(destination, ...relative.split('/'));
|
||||
if (!installFs().existsSync(target)) continue;
|
||||
const targetStat = installFs().lstatSync(target);
|
||||
if (!targetStat.isFile()) {
|
||||
throw new Error(`Runtime Surface corpus ownership conflict at ${target}`);
|
||||
}
|
||||
installFs().rmSync(target, { force: true });
|
||||
pruneEmptyCorpusParents(target, destination);
|
||||
}
|
||||
|
||||
installFs().mkdirSync(path.dirname(destination), { recursive: true });
|
||||
installFs().cpSync(source, destination, { recursive: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Provision the raw, installation-owned input needed to re-materialize a
|
||||
* global Runtime Surface after the executing package tree disappears.
|
||||
*
|
||||
* The corpus deliberately lives below the already-installed `gsd-core/`
|
||||
* tree and is accepted only after its manifest ownership and hashes verify.
|
||||
* The compatibility marker does not replace that installed-corpus authority.
|
||||
*/
|
||||
function provisionRuntimeSurfaceCorpus(
|
||||
layout: { runtime: string; kinds: Iterable<{ kind?: string }> },
|
||||
configDir: string,
|
||||
scope: string,
|
||||
): void {
|
||||
const required = new Set<RuntimeSurfaceSourceClass>();
|
||||
if (isGlobalScope(scope as InstallScope)) {
|
||||
for (const kind of layout.kinds) {
|
||||
if (kind.kind === 'commands' || kind.kind === 'skills') required.add('commands');
|
||||
if (kind.kind === 'agents' || kind.kind === 'kimi-agents') required.add('agents');
|
||||
}
|
||||
}
|
||||
if (required.size === 0) return;
|
||||
|
||||
const corpusRoot = path.join(configDir, 'gsd-core');
|
||||
if (required.has('commands')) {
|
||||
const source = runtimeArtifactLayout.findInstallSourceRoot();
|
||||
const destination = path.join(corpusRoot, 'commands', 'gsd');
|
||||
syncRuntimeSurfaceCorpus(source, destination, configDir, 'gsd-core/commands/gsd/');
|
||||
}
|
||||
if (required.has('agents')) {
|
||||
const source = path.join(executingPackageRoot(), 'agents');
|
||||
const destination = path.join(corpusRoot, 'agents');
|
||||
syncRuntimeSurfaceCorpus(source, destination, configDir, 'gsd-core/agents/');
|
||||
}
|
||||
|
||||
const markerFile = _hostBehaviors(layout.runtime).sourceMarkerFile;
|
||||
if (typeof markerFile === 'string' && markerFile !== '' && required.has('commands')) {
|
||||
try {
|
||||
const markerPath = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, markerFile);
|
||||
if (hasExistingSymlinkBetween(path.resolve(configDir), markerPath, { allowOptInFollow: isSymlinkedDestOptIn() })) {
|
||||
throw new Error(`compatibility marker "${markerPath}" contains an untrusted symlink`);
|
||||
}
|
||||
installFs().writeFileSync(markerPath, path.join(corpusRoot, 'commands', 'gsd') + '\n', 'utf8');
|
||||
} catch {
|
||||
// The existing installer marker writer owns the user-facing warning and
|
||||
// keeps marker failure non-fatal. The installed corpus remains usable
|
||||
// without the compatibility marker.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function executingPackageRoot(): string { return path.dirname(path.dirname(runtimeArtifactLayout.findInstallSourceRoot())); }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// USER_OWNED_ARTIFACTS
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1042,9 +1199,15 @@ function installRuntimeArtifacts(
|
||||
resolvedProfile: any,
|
||||
resolveAttribution: ResolveAttribution = () => undefined,
|
||||
capabilityRegistry?: any,
|
||||
deps: { fs?: any; os?: any; env?: Record<string, string | undefined> } = {},
|
||||
deps: { fs?: any; os?: any; env?: Record<string, string | undefined>; packageRoot?: string } = {},
|
||||
): any {
|
||||
return withInstallFs(deps.fs, (): any => {
|
||||
const layout = runtimeArtifactLayout.resolveRuntimeArtifactLayout(
|
||||
runtime,
|
||||
configDir,
|
||||
scope as 'global' | 'local',
|
||||
capabilityRegistry,
|
||||
);
|
||||
// A removed descriptor kind is no longer visited by the layout loop, so it
|
||||
// cannot prune its own previous output. Clean manifest-proven retired files
|
||||
// before materializing the current layout (#2644).
|
||||
@@ -1064,19 +1227,34 @@ function installRuntimeArtifacts(
|
||||
// #2874 design row 2: this early return must ALSO return an executed
|
||||
// plan — installOpencodeFamilyArtifacts reports what it wrote, so a
|
||||
// whole runtime family returning undefined is no longer a hole.
|
||||
return installOpencodeFamilyArtifacts(runtime, configDir, scope, resolvedProfile, resolveAttribution, behaviors, capabilityRegistry, projectDir);
|
||||
// An injected filesystem supplies its own hermetic corpus fixture. The
|
||||
// real installer is the authority that refreshes package bytes into the
|
||||
// durable installed corpus; attempting that cross-filesystem copy through
|
||||
// an in-memory destination adapter would read from the wrong filesystem.
|
||||
if (!deps.fs) provisionRuntimeSurfaceCorpus(layout, configDir, scope);
|
||||
return installOpencodeFamilyArtifacts(
|
||||
runtime,
|
||||
configDir,
|
||||
scope,
|
||||
resolvedProfile,
|
||||
resolveAttribution,
|
||||
behaviors,
|
||||
capabilityRegistry,
|
||||
deps.packageRoot,
|
||||
projectDir,
|
||||
);
|
||||
}
|
||||
|
||||
// Legacy cleanup before layout-driven writes
|
||||
_runLegacyInstallMigrations(runtime, configDir, scope);
|
||||
|
||||
const layout = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, configDir, scope as 'global' | 'local', capabilityRegistry);
|
||||
if (!deps.fs) provisionRuntimeSurfaceCorpus(layout, configDir, scope);
|
||||
// #3712: a global `home` override escapes the sandboxed configDir. Refuse to
|
||||
// execute when a test run would land that escape in the developer's real home.
|
||||
testHomeGuard.assertTestHomeSandboxed('installRuntimeArtifacts', runtime, layout?.kinds, {
|
||||
os: deps.os, env: deps.env,
|
||||
});
|
||||
const planResult = runtimeArtifactInstallPlan.createRuntimeArtifactInstallPlan({
|
||||
const createPlan = () => runtimeArtifactInstallPlan.createRuntimeArtifactInstallPlan({
|
||||
// `Layout` is structurally identical across the layout/install-plan .cjs
|
||||
// modules but nominally distinct to tsc (untyped .cjs boundary) — bridge it.
|
||||
layout: layout as any,
|
||||
@@ -1086,6 +1264,16 @@ function installRuntimeArtifacts(
|
||||
resolveAttribution,
|
||||
projectDir,
|
||||
});
|
||||
let planResult = createPlan();
|
||||
if (
|
||||
scope === 'global' &&
|
||||
!planResult.ok &&
|
||||
planResult.kind === 'stage_failed' &&
|
||||
planResult.cleanupDirs.length === 0 &&
|
||||
isRuntimeSurfaceSourceUnavailable(planResult.message)
|
||||
) {
|
||||
planResult = withInstallerPackageSource(configDir, createPlan);
|
||||
}
|
||||
|
||||
const cleanupDirs = planResult.ok ? planResult.plan.cleanupDirs : planResult.cleanupDirs;
|
||||
// #2874 row 1/4/5: per-kind executed-plan entries, appended only as the
|
||||
@@ -1252,8 +1440,10 @@ function installRuntimeArtifacts(
|
||||
// is safe even when configDir has no .gsd-source marker (artifactLayout: []).
|
||||
let nativePluginInstalled = false;
|
||||
if (behaviors.nativePlugin) {
|
||||
const commandsGsdDir = runtimeArtifactLayout.findInstallSourceRoot(configDir);
|
||||
const src = path.dirname(path.dirname(commandsGsdDir));
|
||||
// Native plugin sources live only in the executing package, never in the
|
||||
// durable Runtime Surface corpus. Do not derive this package root from a
|
||||
// config-scoped provider that may now correctly resolve installed input.
|
||||
const src = deps.packageRoot ?? executingPackageRoot();
|
||||
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
||||
nativePluginInstalled = true;
|
||||
}
|
||||
@@ -1496,8 +1686,11 @@ function installAgentsKindStandalone(
|
||||
capabilityRegistry?: any,
|
||||
projectDir?: string | null,
|
||||
): { sourceDir: string; destDir: string } | null {
|
||||
const layout: any = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, targetDir, scope as 'global' | 'local', capabilityRegistry);
|
||||
const agentsKindEntry = layout.kinds.find((k: any) => k.kind === 'agents');
|
||||
const layout: Pick<
|
||||
ReturnType<typeof runtimeArtifactLayout.resolveRuntimeArtifactLayout>,
|
||||
'runtime' | 'kinds'
|
||||
> = runtimeArtifactLayout.resolveRuntimeArtifactLayout(runtime, targetDir, scope as 'global' | 'local', capabilityRegistry);
|
||||
const agentsKindEntry = layout.kinds.find((kind) => kind.kind === 'agents');
|
||||
if (!agentsKindEntry) return null;
|
||||
// #3712: this writer selects `agentsKindEntry.home` over targetDir below and then
|
||||
// prunes that destination via _removeGsdEntries, so it is a fifth route into the
|
||||
@@ -1513,7 +1706,13 @@ function installAgentsKindStandalone(
|
||||
// targetDir IS the install root the inline agent loop called `targetDir`.
|
||||
const attribution = resolveAttribution ? resolveAttribution(runtime) : undefined;
|
||||
const agentCtx = { runtime, pathPrefix, attribution, targetDir, projectDir: projectDir ?? targetDir };
|
||||
const stagedDir: string = agentsKindEntry.stage(resolvedProfile, agentCtx);
|
||||
let stagedDir: string;
|
||||
try {
|
||||
stagedDir = agentsKindEntry.stage(resolvedProfile, agentCtx);
|
||||
} catch (err) {
|
||||
if (scope !== 'global' || !isRuntimeSurfaceSourceUnavailable((err as Error).message)) throw err;
|
||||
stagedDir = withInstallerPackageSource(targetDir, () => agentsKindEntry.stage(resolvedProfile, agentCtx));
|
||||
}
|
||||
|
||||
const stagedAgentFiles: string[] = installFs().existsSync(stagedDir)
|
||||
? installFs().readdirSync(stagedDir).filter((f: string) => f.endsWith('.md'))
|
||||
@@ -1810,6 +2009,7 @@ function installOpencodeFamilyArtifacts(
|
||||
resolveAttribution: ResolveAttribution = () => undefined,
|
||||
behaviors: any = {},
|
||||
capabilityRegistry?: any,
|
||||
packageRoot?: string,
|
||||
projectDir?: string | null,
|
||||
): any {
|
||||
// #2870: `scope` keeps its exported required `string` signature (no
|
||||
@@ -1824,7 +2024,7 @@ function installOpencodeFamilyArtifacts(
|
||||
// into stageSkillsForProfile/stageSkillsForRuntimeAsSkills. The repo/package
|
||||
// root (needed below for the native plugin source) is two levels up.
|
||||
const commandsGsdDir = runtimeArtifactLayout.findInstallSourceRoot(configDir);
|
||||
const src = path.dirname(path.dirname(commandsGsdDir));
|
||||
const src = packageRoot ?? executingPackageRoot();
|
||||
const rawCommandsDir = installProfiles.stageSkillsForProfile(commandsGsdDir, resolvedProfile);
|
||||
|
||||
const pathPrefix = (runtimeArtifactConversion as any)._computePathPrefix({
|
||||
|
||||
Reference in New Issue
Block a user