Implement durable typed journals, subscriptions, and Reader document connections
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.
This commit is contained in:
@@ -9,6 +9,7 @@ import type {
|
||||
} 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];
|
||||
@@ -73,10 +74,105 @@ export function compileKernelProgram(plan: ExecutionPlan): unknown {
|
||||
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 === "register" ? "register" : "crdt",
|
||||
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);
|
||||
@@ -109,6 +205,13 @@ export function compileKernelProgram(plan: ExecutionPlan): unknown {
|
||||
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")
|
||||
@@ -231,7 +334,7 @@ export function compileKernelProgram(plan: ExecutionPlan): unknown {
|
||||
operation: operation.operationId,
|
||||
scope: operation.scope,
|
||||
effect: operation.execution.kind,
|
||||
targets: operation.execution.kind === "remote" ? [] : operation.execution.targets,
|
||||
targets: operation.availableTargets,
|
||||
input,
|
||||
...(inputChanges.length ? { inputChanges } : {}),
|
||||
output,
|
||||
|
||||
Reference in New Issue
Block a user