The portability workflow has been red since 2026-08-22. Three things,
each hidden behind the one before it:
- Eve started refusing to build a command intent for a surface that has
not advertised a command boundary and receipt schema (Eve 4338a42,
2026-08-22). The two-runtime sample never advertised one. It now passes
a provider advertisement with worldInteraction for its surface, which is
the declaration the lowering was right to demand.
- The sample and the package-closure script still imported "cultcache-ts";
the package became @gamecult/cultcache-ts on 2026-09-04 (8cb3b72) and
CI never got far enough to say so. Two READMEs carried the same import.
- The job pinned Eve to codex/eve-browser-host-isolation, a branch that was
never merged and stopped moving on 2026-08-25. The sample is written
against Eve main and passes there; the pin now says so.
The interop workflow's Python step ran one test file by path; it now
discovers the three split suites.
scripts/verify-eve-two-runtime-sample.ps1 -EveRoot F:\Projects\Eve
(Eve main ccc3ffe) -> browserCount 1, headlessCount 1, restartCount 1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
247 lines
7.8 KiB
JavaScript
247 lines
7.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { mkdtemp } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { JSDOM } from "jsdom";
|
|
import { z } from "zod";
|
|
import { defineDocumentType } from "@gamecult/cultcache-ts";
|
|
import { CultMesh } from "cultmesh-ts";
|
|
import {
|
|
parseEveCommandReceipt,
|
|
parseEveSurfaceDocument,
|
|
} from "@gamecult/eve-contracts";
|
|
import { renderEveSurface } from "@gamecult/eve-browser-lowering";
|
|
|
|
const providerId = "sample.counter-provider";
|
|
const surfaceId = "sample.counter";
|
|
const stateKey = "counter:main";
|
|
|
|
const counterDocument = defineDocumentType({
|
|
type: "sample.counter_state",
|
|
schemaId: "sample.counter_state.v1",
|
|
schema: z.object({ counterId: z.string(), count: z.number().int() }),
|
|
name: "counterId",
|
|
});
|
|
|
|
const surfaceDocument = defineDocumentType({
|
|
type: "gamecult.eve.surface",
|
|
schemaId: "gamecult.eve.surface.v1",
|
|
schema: z.unknown().transform(value => parseEveSurfaceDocument(value)),
|
|
});
|
|
|
|
const receiptDocument = defineDocumentType({
|
|
type: "gamecult.eve.command_receipt",
|
|
schemaId: "gamecult.eve.command_receipt.v1",
|
|
schema: z.unknown().transform(value => parseEveCommandReceipt(value)),
|
|
name: "receiptId",
|
|
});
|
|
|
|
function counterSurface() {
|
|
return {
|
|
type: "surface-state",
|
|
schema: "gamecult.eve.surface.v1",
|
|
providerId,
|
|
providerKind: "sample.daemon",
|
|
title: "CultMesh two-runtime counter",
|
|
version: 1,
|
|
updatedAtUtc: "2026-08-17T00:00:00Z",
|
|
surface: {
|
|
id: surfaceId,
|
|
title: "Counter",
|
|
root: {
|
|
id: "counter.root",
|
|
kind: "column",
|
|
props: {},
|
|
children: [
|
|
{
|
|
id: "counter.value",
|
|
kind: "metric",
|
|
props: { label: "Canonical count", value: 0 },
|
|
children: [],
|
|
stateBindings: [{
|
|
targetProp: "value",
|
|
pointerId: "sample.counter.count",
|
|
sourceId: `${counterDocument.type}:${stateKey}`,
|
|
schemaId: counterDocument.schemaId,
|
|
routeKind: "shared-memory",
|
|
}],
|
|
},
|
|
{
|
|
id: "counter.increment",
|
|
kind: "control.button",
|
|
props: { label: "Increment", command: "sample.counter.increment" },
|
|
children: [],
|
|
},
|
|
],
|
|
},
|
|
styles: {},
|
|
},
|
|
commands: [{
|
|
schema: "gamecult.eve.command.v1",
|
|
command: "sample.counter.increment",
|
|
label: "Increment",
|
|
surfaceId,
|
|
transport: "cultmesh",
|
|
authority: "provider-daemon",
|
|
result: "gamecult.eve.command_receipt.v1",
|
|
}],
|
|
};
|
|
}
|
|
|
|
function installDom() {
|
|
const dom = new JSDOM("<!doctype html><html><head></head><body><main id='surface'></main></body></html>", {
|
|
url: "https://eve.sample/",
|
|
pretendToBeVisual: true,
|
|
});
|
|
for (const name of [
|
|
"document",
|
|
"window",
|
|
"Element",
|
|
"HTMLElement",
|
|
"HTMLButtonElement",
|
|
"HTMLInputElement",
|
|
"HTMLTextAreaElement",
|
|
"HTMLImageElement",
|
|
]) globalThis[name] = dom.window[name];
|
|
return dom;
|
|
}
|
|
|
|
async function waitFor(predicate, description) {
|
|
const deadline = Date.now() + 2_000;
|
|
while (!predicate()) {
|
|
if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${description}.`);
|
|
await new Promise(resolve => setTimeout(resolve, 5));
|
|
}
|
|
}
|
|
|
|
assert.throws(
|
|
() => parseEveSurfaceDocument({ providerId, surface: { id: surfaceId } }),
|
|
/Invalid Eve surface/,
|
|
);
|
|
assert.throws(
|
|
() => parseEveCommandReceipt({ receiptId: "unowned" }),
|
|
/Invalid Eve commandReceipt/,
|
|
);
|
|
|
|
const statePath = join(await mkdtemp(join(tmpdir(), "cultmesh-eve-two-runtime-")), "state.cc");
|
|
const node = await CultMesh.startNode(statePath, {
|
|
documents: [counterDocument, surfaceDocument, receiptDocument],
|
|
});
|
|
await node.put(counterDocument, stateKey, { counterId: stateKey, count: 0 });
|
|
await node.put(surfaceDocument, surfaceId, counterSurface());
|
|
await node.flush();
|
|
|
|
const state = node.document(counterDocument, stateKey, { pollMs: 5 });
|
|
const headless = state.observe({ context: "headless-client" });
|
|
await headless.ready;
|
|
const receiptsByIdempotencyKey = new Map();
|
|
const increment = CultMesh.operation("sample.counter.increment", async (_request, context) => {
|
|
const idempotencyKey = context.idempotencyKey || "missing";
|
|
const existing = receiptsByIdempotencyKey.get(idempotencyKey)
|
|
|| node.get(receiptDocument, `receipt:${idempotencyKey}`);
|
|
if (existing) return existing;
|
|
const next = await state.authoritativeWriter("provider-daemon").update(current => ({
|
|
...current,
|
|
count: current.count + 1,
|
|
}));
|
|
const receipt = {
|
|
receiptId: `receipt:${idempotencyKey}`,
|
|
schema: "gamecult.eve.command_receipt.v1",
|
|
commandId: idempotencyKey,
|
|
command: "sample.counter.increment",
|
|
state: "accepted",
|
|
ownerRepo: "CultLib",
|
|
authority: "provider-daemon",
|
|
providerId,
|
|
surfaceId,
|
|
issuedAtUtc: new Date().toISOString(),
|
|
sourceVersion: next.count,
|
|
idempotencyKey,
|
|
count: next.count,
|
|
};
|
|
receiptsByIdempotencyKey.set(idempotencyKey, receipt);
|
|
await node.put(receiptDocument, receipt.receiptId, receipt);
|
|
await node.flush();
|
|
return receipt;
|
|
});
|
|
|
|
const dom = installDom();
|
|
const host = document.querySelector("#surface");
|
|
const browserVerse = CultMesh.verse("local", "browser-client");
|
|
let browserReceipt;
|
|
renderEveSurface(await node.document(surfaceDocument, surfaceId).latest(), host, {
|
|
activeSurfaceId: surfaceId,
|
|
clientId: "browser-client",
|
|
// Eve refuses to build a command intent for a surface that has not
|
|
// advertised where its commands go and what comes back. The provider
|
|
// advertisement is that declaration; a lowering cannot infer it.
|
|
provider: {
|
|
providerId,
|
|
surfaces: [{
|
|
surfaceId,
|
|
worldInteraction: {
|
|
commandBoundary: "sample.counter.commands",
|
|
receiptSchema: "gamecult.eve.command_receipt.v1",
|
|
},
|
|
}],
|
|
},
|
|
commandSink: async intent => {
|
|
browserReceipt = await increment.bind(browserVerse).invoke(intent.payload, {
|
|
idempotencyKey: "browser-click-1",
|
|
});
|
|
},
|
|
stateBindingResolver: async binding => {
|
|
if (binding.pointerId !== "sample.counter.count") return undefined;
|
|
return {
|
|
latest: async () => (await state.latest("browser-client")).count,
|
|
watch: callback => state.watch("browser-client", value => callback(value.count)),
|
|
};
|
|
},
|
|
});
|
|
|
|
await waitFor(() => host.querySelector(".metric-value")?.textContent === "0", "initial browser binding");
|
|
host.querySelector("button").click();
|
|
await waitFor(() => browserReceipt?.count === 1, "typed command receipt");
|
|
await waitFor(() => headless.current.count === 1, "headless convergence");
|
|
await waitFor(() => host.querySelector(".metric-value")?.textContent === "1", "browser convergence");
|
|
|
|
const duplicate = await increment.bind(browserVerse).invoke({}, { idempotencyKey: "browser-click-1" });
|
|
assert.equal(duplicate.receiptId, browserReceipt.receiptId);
|
|
assert.equal(headless.current.count, 1);
|
|
|
|
headless.dispose();
|
|
renderEveSurface(parseEveSurfaceDocument({
|
|
type: "surface-state",
|
|
schema: "gamecult.eve.surface.v1",
|
|
providerId,
|
|
providerKind: "sample.daemon",
|
|
title: "Closed counter",
|
|
version: 2,
|
|
updatedAtUtc: "2026-08-17T00:00:01Z",
|
|
surface: {
|
|
id: "sample.closed",
|
|
root: { id: "closed.root", kind: "column", props: {}, children: [] },
|
|
styles: {},
|
|
},
|
|
commands: [],
|
|
}), host);
|
|
dom.window.close();
|
|
await node.flush();
|
|
const reopened = await CultMesh.startNode(statePath, {
|
|
documents: [counterDocument, surfaceDocument, receiptDocument],
|
|
});
|
|
assert.equal(reopened.getRequired(counterDocument, stateKey).count, 1);
|
|
assert.equal(
|
|
reopened.getRequired(receiptDocument, "receipt:browser-click-1").receiptId,
|
|
browserReceipt.receiptId,
|
|
);
|
|
|
|
console.log(JSON.stringify({
|
|
providerId,
|
|
surfaceId,
|
|
receiptId: browserReceipt.receiptId,
|
|
loweringRuntime: "jsdom",
|
|
browserCount: 1,
|
|
headlessCount: 1,
|
|
restartCount: 1,
|
|
}));
|