diff --git a/.quixos-subtree-source.json b/.quixos-subtree-source.json index dabb8cb..02680cb 100644 --- a/.quixos-subtree-source.json +++ b/.quixos-subtree-source.json @@ -1,7 +1,7 @@ { "version": 1, "sourceRepo": "https://gitea.internal.quixos.org/quixos/quixos.git", - "sourceCommit": "ca5b05584b89e90cb259b4596b1bc01d3d692136", + "sourceCommit": "806b0430d952b5a9aab5b2a63496c272aced8e50", "sourcePath": "quixos-protocol", "exportName": "quixos-protocol", "mirrorRemote": "https://repos.quixos.org/quixos-toolchain/quixos-protocol.git" 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, });