From e13fde36fb37788377bbcd7da98d47da9607903f Mon Sep 17 00:00:00 2001 From: "Timothy J. Aveni" Date: Thu, 29 Oct 2026 07:29:57 -0700 Subject: [PATCH] Resolve managed resource aliases within the canonical workbench (#588) --- src/capability-language/authoring-context.ts | 73 ++++++++++++++++---- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/src/capability-language/authoring-context.ts b/src/capability-language/authoring-context.ts index 4a88a40..911b023 100644 --- a/src/capability-language/authoring-context.ts +++ b/src/capability-language/authoring-context.ts @@ -21,6 +21,8 @@ export async function authoringContext(start: string) { } if (text !== undefined) { const graph = JSON.parse(text) as { resources: AuthoringResource[]; workspaceId?: string }; + if (!graph || typeof graph !== "object" || Array.isArray(graph)) + throw new Error("Managed resource inventory is malformed"); if ( graph.workspaceId !== undefined && (typeof graph.workspaceId !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9:._-]{0,255}$/.test(graph.workspaceId)) @@ -30,27 +32,68 @@ export async function authoringContext(start: string) { const resources: AuthoringResource[] = [{ kind: "workspace", directory: "root" }]; const identities = new Set(), directories = new Set(["root"]); - for (const entry of graph.resources) { - // Compiler graphs carry resolved paths; the authoring API presents - // stable workbench-relative names and validates containment here. - if (typeof entry.directory === "string" && path.isAbsolute(entry.directory)) - entry.directory = path.relative(workbench, entry.directory); + for (const [index, entry] of graph.resources.entries()) { + const invalid = (reason: string): never => { + throw new Error(`Invalid managed resource registration at entry ${index + 1}: ${reason}`); + }; if ( - !["interface", "package", "build"].includes(entry.kind) || - !entry.source || - !/^resources\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(entry.directory) - ) { - throw new Error("Invalid managed resource registration"); + !entry || + typeof entry !== "object" || + Array.isArray(entry) || + !["interface", "package", "build"].includes(entry.kind) + ) + invalid("unsupported resource kind"); + if ( + entry.resourceId !== undefined && + (typeof entry.resourceId !== "string" || + entry.resourceId.length < 1 || + entry.resourceId.length > 256 || + entry.resourceId.includes("\0")) + ) + invalid("resource identity must be a bounded nonempty identifier"); + const source = entry.source; + if ( + !source || + typeof source !== "object" || + Array.isArray(source) || + source.resolver !== "git" || + typeof source.repository !== "string" || + !source.repository || + source.repository.length > 4096 || + source.repository.includes("\0") || + typeof source.commit !== "string" || + !/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(source.commit) + ) + invalid("expected an immutable Git source"); + // Git transport policy belongs to the source resolver, including its + // maintained local/file fixtures; registration validates only its shape. + if ( + typeof entry.directory !== "string" || + !entry.directory || + entry.directory.length > 4096 || + entry.directory.includes("\0") + ) + invalid("directory must be a bounded path"); + // Bootstrap intentionally records stable aliases for native histories. + // Compare physical paths on BOTH sides, then expose the same canonical + // workbench-relative selection for alias and physical working dirs. + let directory: string; + try { + directory = path.relative(workbench, await realpath(path.resolve(workbench, entry.directory))); + } catch { + invalid("registered directory is unavailable"); } - const identity = `${entry.kind}\0${entry.resourceId ?? entry.source.repository}`; - if (identities.has(identity) || directories.has(entry.directory)) { - throw new Error(`Multiple editable selections for ${entry.resourceId ?? entry.source.repository}`); + if (!/^resources\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(directory!)) + invalid("registered directory must resolve beneath workbench resources"); + const identity = `${entry.kind}\0${entry.resourceId ?? source!.repository}`; + if (identities.has(identity) || directories.has(directory!)) { + throw new Error(`Multiple editable selections for ${directory!}`); } identities.add(identity); - directories.add(entry.directory); + directories.add(directory!); resources.push({ kind: entry.kind, - directory: entry.directory, + directory: directory!, resourceId: entry.resourceId, source: entry.source, });