diff --git a/.quixos-subtree-source.json b/.quixos-subtree-source.json index b3a52cb..85521d9 100644 --- a/.quixos-subtree-source.json +++ b/.quixos-subtree-source.json @@ -1,7 +1,7 @@ { "version": 1, "sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos", - "sourceCommit": "abc91ab1cbbd61ffd7f0a49dbc5768598ac8745c", + "sourceCommit": "932fe7714761fb8b5db4bd6401357cdff762a428", "sourcePath": "quixos-protocol", "exportName": "quixos-protocol", "mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git" diff --git a/src/execution-world/artifacts.ts b/src/execution-world/artifacts.ts index e5fbcea..85f05f1 100644 --- a/src/execution-world/artifacts.ts +++ b/src/execution-world/artifacts.ts @@ -11,7 +11,7 @@ export interface ArtifactFile { export interface ArtifactImplementation { id: string; target: string; - compatibility: "quixos-service-v1" | "quixos-js-module-v1"; + compatibility: "quixos-service-v1" | "quixos-js-module-v1" | "quixos-restricted-module-v1"; path: string; ports: "quixos-transaction-ports-v1"; registryExport?: string; @@ -225,7 +225,11 @@ export function parseArtifactGroup(value: unknown): ArtifactGroup { ["registryExport"], path, ); - if (entry.compatibility !== "quixos-service-v1" && entry.compatibility !== "quixos-js-module-v1") + if ( + !["quixos-service-v1", "quixos-js-module-v1", "quixos-restricted-module-v1"].includes( + String(entry.compatibility), + ) + ) fail(path, "Unsupported artifact ABI"); if (entry.ports !== "quixos-transaction-ports-v1") fail(path, "Unsupported transaction port ABI"); if ( @@ -233,7 +237,9 @@ export function parseArtifactGroup(value: unknown): ArtifactGroup { (entry.target !== "server" || entry.registryExport !== undefined) ) fail(path, "Service must target server and has no JS registry export"); - if (entry.compatibility === "quixos-js-module-v1") text(entry.registryExport, `${path}.registryExport`); + if (entry.compatibility !== "quixos-service-v1") text(entry.registryExport, `${path}.registryExport`); + if (entry.compatibility === "quixos-restricted-module-v1" && entry.target !== "server") + fail(path, "Restricted modules must target server"); const exports = array(entry.exports, `${path}.exports`).map((value) => text(value, `${path}.exports`)); unique(exports, `${path}.exports`); const files = parseArtifactFiles(entry.files, `${path}.files`); @@ -300,6 +306,11 @@ export function checkPackageCoverage( for (const id of implementation.exports) { const entry = expected.exports.find((e) => e.id === id); if (!entry) fail(implementation.id, `Unknown export ${id}`); + if ( + implementation.compatibility === "quixos-restricted-module-v1" && + (entry!.execution.kind === "remote" || !entry!.execution.targets.includes("server")) + ) + fail(implementation.id, `Export ${id} does not promise restricted server computation`); if ( implementation.target !== "server" && (!entry?.execution || diff --git a/src/execution-world/installation.ts b/src/execution-world/installation.ts index 3c9d26d..a6eafc3 100644 --- a/src/execution-world/installation.ts +++ b/src/execution-world/installation.ts @@ -113,6 +113,8 @@ export interface ExecutionInstallation { readonly root: string; readonly service?: ArtifactImplementation; readonly module?: ArtifactImplementation; + /** Checked candidate only; the qualified host verifies its import surface and SDK. */ + readonly restricted?: ArtifactImplementation; readonly browserImplementation?: ArtifactImplementation; readonly browserURL?: string; readonly browserRegistry?: string; @@ -251,6 +253,11 @@ function assembleExecutionInstallation( exports: exports.map((entry) => entry.id), }) : undefined; + const restrictedCandidates = manifest.implementations.filter( + (entry) => entry.compatibility === "quixos-restricted-module-v1", + ); + if (restrictedCandidates.length > 1) fail(revision, "Ambiguous restricted module candidates"); + const restricted = restrictedCandidates[0]; const browserExports = exports.filter( (entry) => entry.execution.kind !== "remote" && @@ -258,7 +265,13 @@ function assembleExecutionInstallation( ); if (!browserExports.length) { if (selection.browser) fail(revision, "Package has no browser exports to select"); - packages.push({ revision, root, ...(service ? { service } : {}), ...(module ? { module } : {}) }); + packages.push({ + revision, + root, + ...(service ? { service } : {}), + ...(module ? { module } : {}), + ...(restricted ? { restricted } : {}), + }); continue; } const browser = selectArtifactImplementation(manifest, { @@ -289,6 +302,7 @@ function assembleExecutionInstallation( root, ...(service ? { service } : {}), ...(module ? { module } : {}), + ...(restricted ? { restricted } : {}), browserImplementation: browser, browserURL: contentArtifactUrl(entry), browserRegistry: browser.registryExport, diff --git a/src/execution-world/placement.ts b/src/execution-world/placement.ts index 1b8fe01..5d19bdb 100644 --- a/src/execution-world/placement.ts +++ b/src/execution-world/placement.ts @@ -23,6 +23,39 @@ export interface ImportFinding { readonly detail: string; readonly line: number | null; } +export const restrictedSDKSpecifier = "quixos:restricted-sdk/v1"; +export const restrictedSDKPath = "share/quixos/restricted/core.mjs"; + +/** Mechanical candidate checks, not authorization or an engine-safety proof. + * The caller must first verify the manifest and all declared file digests. */ +export async function inspectRestrictedImplementation( + revision: string, + implementation: ArtifactImplementation, + read: ArtifactReader, + qualifiedSDKDigest?: string, +): Promise { + const entry = implementation.files.find((file) => file.path === implementation.path); + const sdk = implementation.files.find((file) => file.path === restrictedSDKPath); + if ( + implementation.compatibility !== "quixos-restricted-module-v1" || + implementation.target !== "server" || + implementation.files.length !== 2 || + !entry || + !sdk || + entry === sdk || + entry.mediaType !== "text/javascript" || + sdk.mediaType !== "text/javascript" || + !/^[$A-Z_a-z][$\w]*$/.test(implementation.registryExport ?? "") + ) + return [{ code: "RESTRICTED_CLOSURE", detail: "Expected one closed module and the restricted SDK", line: null }]; + if (qualifiedSDKDigest && sdk.digest !== qualifiedSDKDigest) + return [{ code: "RESTRICTED_SDK_UNSUPPORTED", detail: "SDK differs from the qualified host profile", line: null }]; + if (entry.bytes > 1024 * 1024 || sdk.bytes > 2 * 1024 * 1024) + return [{ code: "ANALYSIS_LIMIT", detail: "Restricted closure exceeds the source budget", line: null }]; + return inspectJavaScriptSurface(Buffer.from(await read(revision, entry.path, entry.bytes)).toString("utf8"), [ + restrictedSDKSpecifier, + ]); +} const nativeImports = new Set(builtinModules.flatMap((name) => [name, name.replace(/^node:/, "")])); const ambient = new Set([ "process", @@ -136,10 +169,8 @@ export function inspectJavaScriptSurface(source: string, checkedSDKImports: read return findings; } -/** Derived metadata only. Browser/server portability and available targets say - * nothing about sandbox eligibility. Build reports have no platform approval - * catalog, so they conservatively require VM placement. A host can override - * placement only for independently reviewed, exact restricted-code approvals. */ +/** Derived metadata only. A build report can describe a closed candidate but + * cannot qualify the host's engine, SDK, OS sandbox or resource controller. */ export async function reportExecutionPlacement( plan: ExecutionPlan, installation: ExecutionInstallation, @@ -156,11 +187,13 @@ export async function reportExecutionPlacement( const packages = await Promise.all( installation.packages.map(async (pkg) => { const implementations: { id: string; digest: string; compatibility: string; findings: ImportFinding[] }[] = []; - for (const implementation of [pkg.service, pkg.module].filter( + for (const implementation of [pkg.service, pkg.module, pkg.restricted].filter( (entry): entry is ArtifactImplementation => !!entry, )) { let findings: ImportFinding[]; - if (implementation.compatibility === "quixos-js-module-v1") { + if (implementation.compatibility === "quixos-restricted-module-v1") + findings = await inspectRestrictedImplementation(pkg.revision, implementation, read); + else if (implementation.compatibility === "quixos-js-module-v1") { const file = implementation.files.find((file) => file.path === implementation.path)!; if (!file || file.bytes > 1024 * 1024) findings = [ @@ -192,7 +225,14 @@ export async function reportExecutionPlacement( revision: pkg.revision, requiresVM: implementations.length > 0, restrictedEligible: false as const, - reason: "NO_QUALIFIED_RESTRICTED_RUNTIME", + reason: pkg.restricted ? "HOST_PROFILE_QUALIFICATION_REQUIRED" : "NO_RESTRICTED_IMPLEMENTATION", + restrictedCandidate: pkg.restricted + ? { + digest: pkg.restricted.contentDigest, + exports: pkg.restricted.exports, + compatible: implementations.find((entry) => entry.id === pkg.restricted!.id)!.findings.length === 0, + } + : null, implementations, }; }),