Host persistent Camino services and authenticated workspace bootstrap (WI-06)
This commit is contained in:
@@ -7,6 +7,7 @@ const [compilerPath, planPath, checksPath, output] = process.argv.slice(2);
|
|||||||
const compiler = await import(pathToFileURL(compilerPath));
|
const compiler = await import(pathToFileURL(compilerPath));
|
||||||
const plan = JSON.parse(await readFile(planPath, "utf8"));
|
const plan = JSON.parse(await readFile(planPath, "utf8"));
|
||||||
const checks = JSON.parse(await readFile(checksPath, "utf8"));
|
const checks = JSON.parse(await readFile(checksPath, "utf8"));
|
||||||
|
compiler.compileWorkspaceEntry(plan, JSON.parse(await readFile(join(output, "entry.json"), "utf8")));
|
||||||
const roots = Object.create(null);
|
const roots = Object.create(null);
|
||||||
for (const check of checks) {
|
for (const check of checks) {
|
||||||
if (Object.hasOwn(roots, check.packageRevisionId)) throw Error("Duplicate checked package revision");
|
if (Object.hasOwn(roots, check.packageRevisionId)) throw Error("Duplicate checked package revision");
|
||||||
|
|||||||
@@ -162,6 +162,16 @@ pkgs.runCommand (if contractOnly then "qx-contract" else "qx-checked-candidate")
|
|||||||
cp ${contract}/graph.json "$out/graph.json"
|
cp ${contract}/graph.json "$out/graph.json"
|
||||||
cp ${manifest} "$out/checks.json"
|
cp ${manifest} "$out/checks.json"
|
||||||
${pkgs.lib.optionalString execution ''
|
${pkgs.lib.optionalString execution ''
|
||||||
|
${
|
||||||
|
if builtins.pathExists "${root.directory}/quixos.entry.json" then
|
||||||
|
''
|
||||||
|
cp ${root.directory}/quixos.entry.json "$out/entry.json"
|
||||||
|
''
|
||||||
|
else
|
||||||
|
''
|
||||||
|
echo null > "$out/entry.json"
|
||||||
|
''
|
||||||
|
}
|
||||||
cp ${contract}/execution-plan.json "$out/execution-plan.json"
|
cp ${contract}/execution-plan.json "$out/execution-plan.json"
|
||||||
${pkgs.lib.optionalString (!contractOnly) ''
|
${pkgs.lib.optionalString (!contractOnly) ''
|
||||||
${pkgs.nodejs_24}/bin/node ${./check-execution.mjs} \
|
${pkgs.nodejs_24}/bin/node ${./check-execution.mjs} \
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { open, lstat } from "node:fs/promises";
|
|||||||
import { constants } from "node:fs";
|
import { constants } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import type { WorkspaceRevision } from "../capability-model/types.js";
|
import type { WorkspaceRevision } from "../capability-model/types.js";
|
||||||
import { compileExecutionPlan, hexDigest, ExecutionPlanError } from "./plan.js";
|
import { compileExecutionPlan, hexDigest, ExecutionPlanError, type ExecutionPlan } from "./plan.js";
|
||||||
import { loadExecutionInstallation, type PackageInstallationSelection } from "./installation.js";
|
import { loadExecutionInstallation, type PackageInstallationSelection } from "./installation.js";
|
||||||
import { compileKernelProgram } from "./kernel.js";
|
import { compileKernelProgram } from "./kernel.js";
|
||||||
import { compileQueryProgram } from "./queries.js";
|
import { compileQueryProgram } from "./queries.js";
|
||||||
@@ -26,11 +26,12 @@ export async function loadCheckedInstallationCandidate(
|
|||||||
) {
|
) {
|
||||||
if (!rootPattern.test(root) || !(await lstat(root)).isDirectory())
|
if (!rootPattern.test(root) || !(await lstat(root)).isDirectory())
|
||||||
throw Error("Candidate must be an immutable Nix output directory");
|
throw Error("Candidate must be an immutable Nix output directory");
|
||||||
const [candidate, recordedPlan, artifacts, source] = await Promise.all([
|
const [candidate, recordedPlan, artifacts, source, entryRequest] = await Promise.all([
|
||||||
readJson(root, "candidate.json"),
|
readJson(root, "candidate.json"),
|
||||||
readJson(root, "execution-plan.json"),
|
readJson(root, "execution-plan.json"),
|
||||||
readJson(root, "execution-artifacts.json"),
|
readJson(root, "execution-artifacts.json"),
|
||||||
readJson(root, "source.json"),
|
readJson(root, "source.json"),
|
||||||
|
readJson(root, "entry.json"),
|
||||||
]);
|
]);
|
||||||
const { executionContracts: _legacyContracts, ...workspace } = candidate;
|
const { executionContracts: _legacyContracts, ...workspace } = candidate;
|
||||||
const plan = compileExecutionPlan(workspace as WorkspaceRevision);
|
const plan = compileExecutionPlan(workspace as WorkspaceRevision);
|
||||||
@@ -70,6 +71,7 @@ export async function loadCheckedInstallationCandidate(
|
|||||||
"Package artifacts must be immutable Nix roots",
|
"Package artifacts must be immutable Nix roots",
|
||||||
);
|
);
|
||||||
const installation = await loadExecutionInstallation(plan, artifacts.roots, generator, selections);
|
const installation = await loadExecutionInstallation(plan, artifacts.roots, generator, selections);
|
||||||
|
const entry = compileWorkspaceEntry(plan, entryRequest);
|
||||||
const kernel = compileKernelProgram(plan),
|
const kernel = compileKernelProgram(plan),
|
||||||
queries = compileQueryProgram(plan);
|
queries = compileQueryProgram(plan);
|
||||||
// The isolated runner and managed host consume the same compiled programs.
|
// The isolated runner and managed host consume the same compiled programs.
|
||||||
@@ -91,7 +93,7 @@ export async function loadCheckedInstallationCandidate(
|
|||||||
};
|
};
|
||||||
const reference = {
|
const reference = {
|
||||||
root,
|
root,
|
||||||
digest: hexDigest({ plan: plan.digest, world: installation.world, provenance, roots: artifacts.roots }),
|
digest: hexDigest({ plan: plan.digest, world: installation.world, provenance, roots: artifacts.roots, entry }),
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
reference,
|
reference,
|
||||||
@@ -99,9 +101,47 @@ export async function loadCheckedInstallationCandidate(
|
|||||||
world: installation.world,
|
world: installation.world,
|
||||||
plan: plan.digest,
|
plan: plan.digest,
|
||||||
provenance,
|
provenance,
|
||||||
|
entry,
|
||||||
kernel,
|
kernel,
|
||||||
queries,
|
queries,
|
||||||
installation,
|
installation,
|
||||||
executionPlan: plan,
|
executionPlan: plan,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function compileWorkspaceEntry(plan: ExecutionPlan, entryRequest: unknown): { operation: string } | null {
|
||||||
|
let entry: { operation: string } | null = null;
|
||||||
|
if (entryRequest !== null) {
|
||||||
|
if (
|
||||||
|
typeof entryRequest !== "object" ||
|
||||||
|
Array.isArray(entryRequest) ||
|
||||||
|
Object.keys(entryRequest).sort().join(",") !== "atom,interface,operation"
|
||||||
|
)
|
||||||
|
throw new ExecutionPlanError(
|
||||||
|
"INVALID_ENTRY_FACTORY",
|
||||||
|
"entry",
|
||||||
|
"Entry declaration must name atom, interface and operation identities",
|
||||||
|
);
|
||||||
|
const operation = plan.definition.operations.find(
|
||||||
|
(op) =>
|
||||||
|
op.atomId === (entryRequest as Record<string, unknown>).atom &&
|
||||||
|
op.interfaceRevisionId === (entryRequest as Record<string, unknown>).interface &&
|
||||||
|
op.operationId === (entryRequest as Record<string, unknown>).operation,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!operation ||
|
||||||
|
operation.scope !== "class" ||
|
||||||
|
operation.execution.kind !== "transaction" ||
|
||||||
|
operation.input.kind !== "builtin" ||
|
||||||
|
operation.input.name !== "unit" ||
|
||||||
|
operation.output.kind !== "object-ref"
|
||||||
|
)
|
||||||
|
throw new ExecutionPlanError(
|
||||||
|
"INVALID_ENTRY_FACTORY",
|
||||||
|
"entry",
|
||||||
|
"Entry must be a checked unit-input class transaction returning one object reference",
|
||||||
|
);
|
||||||
|
entry = { operation: operation.key };
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,7 +35,11 @@ export function expectedArtifactReceipt(
|
|||||||
manifest.packageRevisionId,
|
manifest.packageRevisionId,
|
||||||
"Receipt package/schema/source mismatch",
|
"Receipt package/schema/source mismatch",
|
||||||
);
|
);
|
||||||
const available = new Set(manifest.implementations.map((i) => i.target));
|
// A process service may implement transactions in any language. Only the
|
||||||
|
// checked JS module path promises generated TypeScript adapter evidence.
|
||||||
|
const available = new Set(
|
||||||
|
manifest.implementations.filter((i) => i.compatibility === "quixos-js-module-v1").map((i) => i.target),
|
||||||
|
);
|
||||||
const targets = [
|
const targets = [
|
||||||
...new Set(
|
...new Set(
|
||||||
schemaPackage.exports.flatMap((e) => (e.execution && e.execution.kind !== "remote" ? e.execution.targets : [])),
|
schemaPackage.exports.flatMap((e) => (e.execution && e.execution.kind !== "remote" ? e.execution.targets : [])),
|
||||||
|
|||||||
@@ -139,3 +139,18 @@ test("remote JS modules witness generated adapters without imposing TypeScript o
|
|||||||
);
|
);
|
||||||
assert.deepEqual(expectedArtifactReceipt(plan, fixture.manifest, generator).receipt.generated, []);
|
assert.deepEqual(expectedArtifactReceipt(plan, fixture.manifest, generator).receipt.generated, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("transaction process services do not promise generated TypeScript modules", async () => {
|
||||||
|
const { expectedArtifactReceipt, checkArtifactReceipt } = await import("../src/execution-world/receipts.js");
|
||||||
|
const plan = compileExecutionPlan(await loadReaderWorkspace()),
|
||||||
|
fixture = buildArtifactFixture(plan);
|
||||||
|
fixture.manifest.implementations = fixture.manifest.implementations.filter(
|
||||||
|
(i) => i.compatibility === "quixos-service-v1",
|
||||||
|
);
|
||||||
|
const generator = "a".repeat(64),
|
||||||
|
{ receipt } = expectedArtifactReceipt(plan, fixture.manifest, generator);
|
||||||
|
assert.deepEqual(receipt.generated, []);
|
||||||
|
await checkArtifactReceipt(plan, fixture.manifest, receipt, generator, async () => {
|
||||||
|
throw Error("No adapter claimed");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
+41
-8
@@ -6,22 +6,52 @@ import { compileExecutionPlan } from "../src/execution-world/plan.js";
|
|||||||
import { compileKernelProgram } from "../src/execution-world/kernel.js";
|
import { compileKernelProgram } from "../src/execution-world/kernel.js";
|
||||||
import { compileQueryProgram } from "../src/execution-world/queries.js";
|
import { compileQueryProgram } from "../src/execution-world/queries.js";
|
||||||
const source = { repository: "https://example.test/device-owner.git", commit: "a".repeat(40) };
|
const source = { repository: "https://example.test/device-owner.git", commit: "a".repeat(40) };
|
||||||
const pkg = compileCapabilityResourceSource(
|
const factory = compileCapabilityResourceSource(
|
||||||
`package Owner id "package:owner" revision "package:owner@1" {
|
`external atom Entry id "atom:entry";
|
||||||
function identity id "identity" : int32 -> record { pid: int32; child: int32; } execution remote;
|
interface EntryFactory id "interface:entry-factory" revision "interface:entry-factory@1" {
|
||||||
|
static operation create id "entry:create" : unit -> atom-ref<Entry> {
|
||||||
|
call id "entry:create:call" execution transaction targets [server];
|
||||||
|
}
|
||||||
}`,
|
}`,
|
||||||
{ source },
|
{ source },
|
||||||
);
|
);
|
||||||
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
assert.ok(factory.ok && factory.resource.kind === "interface", JSON.stringify(factory.diagnostics));
|
||||||
for (const variant of ["base", "added"]) {
|
for (const variant of ["base", "added", "gateway", "gateway-next"]) {
|
||||||
const commit = (variant === "base" ? "a" : "b").repeat(40);
|
const gateway = variant.startsWith("gateway"),
|
||||||
|
commit = { base: "a", added: "b", gateway: "c", "gateway-next": "d" }[variant]!.repeat(40);
|
||||||
|
const pkg = compileCapabilityResourceSource(
|
||||||
|
`${gateway ? 'external atom Entry id "atom:entry";' : ""}
|
||||||
|
package Owner id "package:owner" revision "package:owner@1" {
|
||||||
|
function identity id "identity" : int32 -> record { pid: int32; child: int32; } execution remote;
|
||||||
|
${gateway ? 'function create id "create" : unit -> atom-ref<Entry> execution transaction targets [server] requires { creation entry id "entry:birth" : Entry input unit; };' : ""}
|
||||||
|
}`,
|
||||||
|
{ source },
|
||||||
|
);
|
||||||
|
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
||||||
const result = compileCapabilitySource(
|
const result = compileCapabilitySource(
|
||||||
`workspace Managed id "workspace:managed-process" revision "workspace:managed-process@${variant}" commit "${commit}" {
|
`workspace Managed id "workspace:managed-process" revision "workspace:managed-process@${variant}" commit "${commit}" {
|
||||||
import package Owner;
|
import package Owner;
|
||||||
${variant === "added" ? 'atom Independent id "atom:independent";' : ""}
|
${variant !== "base" ? 'atom Independent id "atom:independent";' : ""}
|
||||||
|
${
|
||||||
|
gateway
|
||||||
|
? `import interface EntryFactory; atom Entry id "atom:entry";
|
||||||
|
conform Entry as EntryFactory id "owner:entry-factory" {
|
||||||
|
bind create.call to package Owner.create with { entry to creation Entry with {}; };
|
||||||
|
}`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
}`,
|
}`,
|
||||||
"workspace.qx",
|
"workspace.qx",
|
||||||
{ packages: new Map([["Owner", pkg.resource.revision]]), packageClosure: [pkg.resource.revision] },
|
{
|
||||||
|
packages: new Map([["Owner", pkg.resource.revision]]),
|
||||||
|
packageClosure: [pkg.resource.revision],
|
||||||
|
...(gateway
|
||||||
|
? {
|
||||||
|
interfaces: new Map([["EntryFactory", factory.resource.revision]]),
|
||||||
|
interfaceClosure: [factory.resource.revision],
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||||
const plan = compileExecutionPlan(result.workspace),
|
const plan = compileExecutionPlan(result.workspace),
|
||||||
@@ -29,6 +59,9 @@ for (const variant of ["base", "added"]) {
|
|||||||
await mkdir(root, { recursive: true });
|
await mkdir(root, { recursive: true });
|
||||||
for (const [file, value] of Object.entries({
|
for (const [file, value] of Object.entries({
|
||||||
"candidate.json": result.workspace,
|
"candidate.json": result.workspace,
|
||||||
|
"entry.json": gateway
|
||||||
|
? { atom: "atom:entry", interface: "interface:entry-factory@1", operation: "entry:create:call" }
|
||||||
|
: null,
|
||||||
"execution-plan.json": plan,
|
"execution-plan.json": plan,
|
||||||
"kernel.json": compileKernelProgram(plan),
|
"kernel.json": compileKernelProgram(plan),
|
||||||
"queries.json": compileQueryProgram(plan),
|
"queries.json": compileQueryProgram(plan),
|
||||||
|
|||||||
Reference in New Issue
Block a user