Add checked native journal/subscription bindings, managed-reference retention, bounded history and backpressure, atomic delivery receipts, and fenced hosting. Preserve source publication prediction and replica reconciliation; subscription attachment, controls, and delivery remain authoritative in this initial version. Connect independent manual/Gutenberg document producers to Reader bookshelves through typed adapters. Add connection inspection, shared provider selection, authoring documentation, and restart/rejection/fan-out integration coverage. Verified compiler suite (287 passed, two existing skips), Nix runtime and PostgreSQL qualification, five example assemblies, headless Reader integration, Chromium Reader integration, resource pins, formatting, and lint. No pinned workspace runtime was deployed.
372 lines
16 KiB
TypeScript
372 lines
16 KiB
TypeScript
import { portableReflectionType, portableReflectionValue } from "./reflection.js";
|
|
import type {
|
|
ValueType,
|
|
InitializationField,
|
|
PersistentAttachment,
|
|
DependencyBinding,
|
|
BoundDependency,
|
|
PackageExport,
|
|
} from "../capability-model/types.js";
|
|
import { type ExecutionPlan, ExecutionPlanError, hexDigest } from "./plan.js";
|
|
import { portableValueSchema } from "../bindings/portable.js";
|
|
import { subscriptionContract } from "../capability-model/journals.js";
|
|
type PreparedStep = string | null | { case: string; id: string };
|
|
function preparedInputPaths(type: ValueType, path: PreparedStep[] = []): PreparedStep[][] {
|
|
if (type.kind === "change") return [path];
|
|
if (type.kind === "optional") return preparedInputPaths(type.value, path);
|
|
if (type.kind === "list") return preparedInputPaths(type.value, [...path, null]);
|
|
if (type.kind === "variant")
|
|
return Object.entries(type.cases).flatMap(([label, entry]) =>
|
|
preparedInputPaths(entry.value, [...path, { case: label, id: entry.id }]),
|
|
);
|
|
if (type.kind === "record")
|
|
return Object.entries(type.fields).flatMap(([key, value]) => preparedInputPaths(value, [...path, key]));
|
|
return [];
|
|
}
|
|
/** Small data-only interpreter input; no compiler, parser, Node module or SQL dependency reaches the browser. */
|
|
export function compileKernelProgram(plan: ExecutionPlan): unknown {
|
|
if (hexDigest(plan.definition) !== plan.digest)
|
|
throw new ExecutionPlanError("INVALID_PLAN", "kernel", "Plan digest mismatch");
|
|
const workspace = plan.definition.workspace;
|
|
if (workspace.sharedAttachments.some((attachment) => attachment.kind !== "edge"))
|
|
throw new ExecutionPlanError(
|
|
"UNSUPPORTED_SHARED_STORAGE",
|
|
"kernel",
|
|
"Shared scalar storage is not implemented; use conformance-owned state",
|
|
);
|
|
const schema = (type: ValueType): unknown | null => {
|
|
try {
|
|
return portableValueSchema(type);
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
const ownerBySlot = new Map<string, string>();
|
|
const edges: { id: string; owner: string | null; a: unknown; b: unknown }[] = [];
|
|
const lowerEdge = (storage: Extract<PersistentAttachment, { kind: "edge" }>, owner: string | null) => {
|
|
const endpoints = storage.endpoints.map((endpoint) => {
|
|
if (endpoint.ordered || (endpoint.keyType && endpoint.keyType !== "string") || endpoint.retainOther)
|
|
throw new ExecutionPlanError(
|
|
"UNSUPPORTED_GRAPH_FEATURE",
|
|
storage.id,
|
|
"Ordered relations, non-string keys and automatic retention are not implemented",
|
|
);
|
|
return {
|
|
projection: endpoint.projectionId,
|
|
constraint: {
|
|
kind: endpoint.constraint.kind,
|
|
id:
|
|
endpoint.constraint.kind === "atom" ? endpoint.constraint.atomId : endpoint.constraint.interfaceRevisionId,
|
|
},
|
|
cardinality: endpoint.cardinality,
|
|
...(endpoint.keyType ? { keyType: endpoint.keyType } : {}),
|
|
...(endpoint.onDelete ? { onDelete: endpoint.onDelete } : {}),
|
|
};
|
|
});
|
|
edges.push({ id: storage.id, owner, a: endpoints[0], b: endpoints[1] });
|
|
};
|
|
for (const storage of workspace.sharedAttachments) if (storage.kind === "edge") lowerEdge(storage, null);
|
|
const owners = plan.definition.owners.map((owner) => {
|
|
const slots: unknown[] = [];
|
|
for (const storage of owner.storage as PersistentAttachment[]) {
|
|
if (storage.kind === "state") {
|
|
const shape = schema(storage.valueType);
|
|
if (!shape)
|
|
throw new ExecutionPlanError("UNSUPPORTED_STORAGE_CODEC", storage.id, "Unsupported canonical value type");
|
|
ownerBySlot.set(storage.id, owner.id);
|
|
const pinsFor = (payload: ValueType) => {
|
|
const expectations = new Map<string, { kind: "atom" | "interface"; id: string }>();
|
|
const walk = (type: ValueType): void => {
|
|
if (type.kind === "object-ref") {
|
|
const expectation = {
|
|
kind: type.expectation.kind,
|
|
id: type.expectation.kind === "atom" ? type.expectation.atomId : type.expectation.interfaceRevisionId,
|
|
};
|
|
expectations.set(JSON.stringify(expectation), expectation);
|
|
} else if (type.kind === "list" || type.kind === "optional") walk(type.value);
|
|
else if (type.kind === "record") Object.values(type.fields).forEach(walk);
|
|
else if (type.kind === "variant") Object.values(type.cases).forEach((entry) => walk(entry.value));
|
|
};
|
|
walk(payload);
|
|
const pins = [...expectations.values()].map((expectation) => {
|
|
const id = `journal-pin:${hexDigest([storage.id, expectation])}`;
|
|
const projection = `${id}:source`;
|
|
edges.push({
|
|
id,
|
|
owner: owner.id,
|
|
a: {
|
|
projection,
|
|
constraint: { kind: "atom", id: owner.atom },
|
|
cardinality: "many",
|
|
keyType: "string",
|
|
onDelete: "detach",
|
|
},
|
|
b: { projection: `${id}:target`, constraint: expectation, cardinality: "many", onDelete: "restrict" },
|
|
});
|
|
return { edge: id, projection };
|
|
});
|
|
return pins;
|
|
};
|
|
let journal: unknown, subscription: unknown;
|
|
if (storage.storagePolicy.kind === "journal") {
|
|
journal = { limits: storage.storagePolicy.limits, pins: pinsFor(storage.valueType) };
|
|
}
|
|
if (storage.storagePolicy.kind === "subscription") {
|
|
const bindings = plan.definition.operations.filter(
|
|
(operation) =>
|
|
operation.atomId === owner.atom &&
|
|
operation.binding.kind === "subscription" &&
|
|
operation.binding.slotId === storage.id &&
|
|
operation.binding.primitive === "attach",
|
|
);
|
|
if (bindings.length !== 1)
|
|
throw new ExecutionPlanError(
|
|
"SUBSCRIPTION_ATTACHMENT_REQUIRED",
|
|
storage.id,
|
|
"Native subscription storage requires one checked attachment operation",
|
|
);
|
|
const binding = bindings[0]!;
|
|
const operation = workspace.interfaceImports
|
|
.find((iface) => iface.revisionId === binding.interfaceRevisionId)!
|
|
.members.flatMap((member) => member.operations)
|
|
.find((operation) => operation.id === binding.operationId)!;
|
|
const contract = subscriptionContract(
|
|
storage.storagePolicy.payload,
|
|
storage.valueType,
|
|
operation,
|
|
workspace.interfaceImports,
|
|
);
|
|
const expectation = (value: typeof contract.source) => ({
|
|
kind: value.kind,
|
|
id: value.kind === "atom" ? value.atomId : value.interfaceRevisionId,
|
|
});
|
|
const source = expectation(contract.source),
|
|
handler = expectation(contract.handler);
|
|
const configuration: ValueType = {
|
|
kind: "record",
|
|
fields: {
|
|
source: { kind: "object-ref", expectation: contract.source },
|
|
handler: { kind: "object-ref", expectation: contract.handler },
|
|
parameters: storage.valueType,
|
|
},
|
|
};
|
|
const edge = `subscription-active:${hexDigest(storage.id)}`,
|
|
projection = `${edge}:subscription`;
|
|
edges.push({
|
|
id: edge,
|
|
owner: owner.id,
|
|
a: { projection, constraint: { kind: "atom", id: owner.atom }, cardinality: "many", onDelete: "restrict" },
|
|
b: { projection: `${edge}:stream`, constraint: source, cardinality: "many", onDelete: "restrict" },
|
|
});
|
|
subscription = {
|
|
payload: schema(storage.storagePolicy.payload),
|
|
source,
|
|
handler,
|
|
operation: contract.operation,
|
|
pins: pinsFor(configuration),
|
|
active: { edge, projection },
|
|
};
|
|
}
|
|
slots.push({
|
|
id: storage.id,
|
|
schema: shape,
|
|
policy: storage.storagePolicy.kind === "crdt-document" ? "crdt" : storage.storagePolicy.kind,
|
|
...(journal ? { journal } : {}),
|
|
...(subscription ? { subscription } : {}),
|
|
...(Object.hasOwn(storage, "defaultValue") ? { defaultLiteral: storage.defaultValue } : {}),
|
|
});
|
|
} else lowerEdge(storage, owner.id);
|
|
}
|
|
return {
|
|
id: owner.id,
|
|
atom: owner.atom,
|
|
interface: owner.interface,
|
|
slots,
|
|
initialize: (owner.initialization as InitializationField[]).map((field) => ({
|
|
input: field.input,
|
|
binding:
|
|
field.binding.kind === "state"
|
|
? { kind: "state", slot: field.binding.slotId }
|
|
: { kind: "edge", edge: field.binding.edgeTypeId, projection: field.binding.projectionId },
|
|
})),
|
|
};
|
|
});
|
|
const slotOwner = (slot: string) => {
|
|
const owner = ownerBySlot.get(slot);
|
|
if (!owner) throw new ExecutionPlanError("MISSING_STORAGE_OWNER", slot, "No checked owner");
|
|
return owner;
|
|
};
|
|
const via = (binding: DependencyBinding) =>
|
|
"via" in binding && binding.via
|
|
? { via: { edge: binding.via.edgeTypeId, projection: binding.via.projectionId } }
|
|
: {};
|
|
const operations = plan.definition.operations.map((operation) => {
|
|
const binding = operation.binding;
|
|
let target: unknown;
|
|
let unsupported: string | null = null;
|
|
if (binding.kind === "lifecycle") target = { kind: "lifecycle", primitive: binding.primitive };
|
|
else if (binding.kind === "journal" || binding.kind === "subscription")
|
|
target = {
|
|
kind: binding.kind,
|
|
owner: slotOwner(binding.slotId),
|
|
slot: binding.slotId,
|
|
primitive: binding.primitive,
|
|
};
|
|
else if (binding.kind === "state")
|
|
target = { kind: "state", owner: slotOwner(binding.slotId), slot: binding.slotId, primitive: binding.primitive };
|
|
else if (binding.kind === "edge")
|
|
target = {
|
|
kind: "edge",
|
|
edge: binding.edgeTypeId,
|
|
projection: binding.projectionId,
|
|
primitive: binding.primitive,
|
|
};
|
|
else {
|
|
const pkg = workspace.packageImports.find((p) => p.revisionId === binding.packageRevisionId)!;
|
|
const entry = pkg.exports.find((e) => e.id === binding.exportId)!;
|
|
const compilePorts = (entry: PackageExport, dependencies: BoundDependency[]) =>
|
|
dependencies.map((dependency) => {
|
|
const requirement = entry.dependencyPorts.find((p) => p.id === dependency.portId)!.requirement;
|
|
const bound = dependency.binding;
|
|
const common = { id: dependency.portId, ...via(bound) };
|
|
if (bound.kind === "reflection" && requirement.kind === "reflection")
|
|
return {
|
|
...common,
|
|
kind: "reflection",
|
|
values: Object.fromEntries(
|
|
bound.selectors.map((selector) => [
|
|
selector,
|
|
{
|
|
schema: portableValueSchema(portableReflectionType(selector)),
|
|
value: portableReflectionValue(workspace, selector),
|
|
},
|
|
]),
|
|
),
|
|
};
|
|
if (bound.kind === "state" && requirement.kind === "state")
|
|
return {
|
|
...common,
|
|
kind: "state",
|
|
owner: slotOwner(bound.slotId),
|
|
slot: bound.slotId,
|
|
primitives: requirement.primitives,
|
|
};
|
|
if (bound.kind === "edge" && requirement.kind === "edge")
|
|
return {
|
|
...common,
|
|
kind: "edge",
|
|
edge: bound.edgeTypeId,
|
|
projection: bound.projectionId,
|
|
primitives: requirement.primitives,
|
|
};
|
|
if (bound.kind === "interface" && requirement.kind === "interface") {
|
|
const iface = workspace.interfaceImports.find((i) => i.revisionId === bound.interfaceRevisionId)!;
|
|
return {
|
|
...common,
|
|
kind: "interface",
|
|
interface: bound.interfaceRevisionId,
|
|
...(!bound.receiver &&
|
|
!bound.via &&
|
|
(requirement.operations ?? iface.members.flatMap((m) => m.operations.map((o) => o.id))).every(
|
|
(id) => iface.members.flatMap((m) => m.operations).find((o) => o.id === id)?.scope === "class",
|
|
)
|
|
? { staticAtom: operation.atomId }
|
|
: {}),
|
|
...(bound.receiver ? { receiver: bound.receiver } : {}),
|
|
operations: requirement.operations ?? iface.members.flatMap((m) => m.operations.map((o) => o.id)),
|
|
};
|
|
}
|
|
if (bound.kind === "creation" && requirement.kind === "creation")
|
|
return {
|
|
...common,
|
|
kind: "creation",
|
|
atom: bound.atomId,
|
|
schema: portableValueSchema(requirement.inputType),
|
|
recipe: hexDigest({
|
|
codec: "camino-creation-recipe-v1",
|
|
versions: plan.definition.versions,
|
|
operation: operation.key,
|
|
export: entry.id,
|
|
port: dependency.portId,
|
|
requirement,
|
|
binding: bound,
|
|
owners: owners.filter((owner) => owner.atom === bound.atomId),
|
|
edges: edges.filter((edge) =>
|
|
owners.some((owner) => owner.atom === bound.atomId && owner.id === edge.owner),
|
|
),
|
|
}),
|
|
owners: bound.owners.map((seed) => {
|
|
const owner = owners.find((o) => o.atom === bound.atomId && o.interface === seed.interfaceRevisionId);
|
|
if (!owner)
|
|
throw new ExecutionPlanError(
|
|
"MISSING_STORAGE_OWNER",
|
|
dependency.portId,
|
|
"Creation has no initialization owner",
|
|
);
|
|
return { owner: owner.id, inputPath: seed.inputPath };
|
|
}),
|
|
};
|
|
unsupported = `UNSUPPORTED_EXECUTION_PORT:${bound.kind}`;
|
|
return { ...common, kind: "unavailable" };
|
|
});
|
|
const ports = compilePorts(entry, binding.dependencies);
|
|
const check = entry.acceptanceExportId
|
|
? pkg.exports.find((candidate) => candidate.id === entry.acceptanceExportId)!
|
|
: undefined;
|
|
target = {
|
|
kind: "package",
|
|
package: binding.packageRevisionId,
|
|
export: binding.exportId,
|
|
ports,
|
|
...(entry.preparation ? { preparation: entry.preparation } : {}),
|
|
...(check
|
|
? { acceptance: { export: check.id, ports: compilePorts(check, binding.acceptanceDependencies ?? []) } }
|
|
: {}),
|
|
};
|
|
}
|
|
const inputChanges = preparedInputPaths(operation.input);
|
|
const input = schema(operation.input),
|
|
output = schema(operation.output);
|
|
return {
|
|
key: operation.key,
|
|
atom: operation.atomId,
|
|
interface: operation.interfaceRevisionId,
|
|
operation: operation.operationId,
|
|
scope: operation.scope,
|
|
effect: operation.execution.kind,
|
|
targets: operation.availableTargets,
|
|
input,
|
|
...(inputChanges.length ? { inputChanges } : {}),
|
|
output,
|
|
unsupported: unsupported ?? (input && output ? null : "UNSUPPORTED_OPERATION_CODEC"),
|
|
binding: target,
|
|
};
|
|
});
|
|
const packages = workspace.packageImports.map((pkg) => ({
|
|
revision: pkg.revisionId,
|
|
exports: pkg.exports.map((entry) => ({
|
|
id: entry.id,
|
|
...(entry.preparation ? { preparation: entry.preparation } : {}),
|
|
...(entry.acceptanceExportId ? { acceptance: entry.acceptanceExportId } : {}),
|
|
effect: entry.execution?.kind ?? "remote",
|
|
targets: entry.execution && entry.execution.kind !== "remote" ? entry.execution.targets : [],
|
|
receiver:
|
|
entry.kind === "function"
|
|
? null
|
|
: entry.kind === "operation" && entry.receiverRequirement.kind === "exact-atom"
|
|
? { kind: "atom", id: entry.receiverRequirement.atomId }
|
|
: "object",
|
|
input: schema(entry.inputType),
|
|
output: schema(entry.outputType),
|
|
})),
|
|
}));
|
|
const body = {
|
|
codec: "camino-kernel-program-v1",
|
|
model: { workspace: workspace.workspaceId, atoms: workspace.atoms.map((atom) => atom.id), owners, edges },
|
|
operations,
|
|
packages,
|
|
};
|
|
// The interpreter's plan key covers its real input, not source/build evidence.
|
|
return { ...body, plan: hexDigest({ versions: plan.definition.versions, program: body }) };
|
|
}
|