Add artifact-only Eve two-runtime sample

This commit is contained in:
Meta Vens 2026-08-17 20:21:41 +02:00
commit 32258f49c3
3 changed files with 320 additions and 5 deletions

View file

@ -0,0 +1,184 @@
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 "cultcache-ts";
import { CultMesh } from "cultmesh-ts";
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.any(),
});
const receiptDocument = defineDocumentType({
type: "sample.counter_receipt",
schemaId: "sample.counter_receipt.v1",
schema: z.object({
receiptId: z.string(),
operationId: z.literal("sample.counter.increment"),
idempotencyKey: z.string(),
count: z.number().int(),
}),
name: "receiptId",
});
function counterSurface() {
return {
providerId,
title: "CultMesh two-runtime counter",
version: 1,
surface: {
id: surfaceId,
root: {
id: "counter.root",
kind: "column",
children: [
{
id: "counter.value",
kind: "metric",
props: { label: "Canonical count", value: 0 },
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" },
},
],
},
},
};
}
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));
}
}
const statePath = join(await mkdtemp(join(tmpdir(), "cultmesh-eve-two-runtime-")), "state.ccmp");
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}`,
operationId: "sample.counter.increment",
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",
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({ providerId, surface: { id: "sample.closed" } }, 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,
browserCount: 1,
headlessCount: 1,
restartCount: 1,
}));

View file

@ -0,0 +1,115 @@
param(
[string] $EveRoot = (Join-Path (Split-Path -Parent $PSScriptRoot) "..\Eve")
)
$ErrorActionPreference = "Stop"
$cultLibRoot = Split-Path -Parent $PSScriptRoot
$samplePath = Join-Path $cultLibRoot "samples\eve-two-runtime\sample.mjs"
$eveBrowserPackage = Join-Path $EveRoot "packages\eve-browser-lowering"
$packageRoots = @(
(Join-Path $cultLibRoot "packages\cultcache-ts"),
(Join-Path $cultLibRoot "packages\cultnet-ts"),
(Join-Path $cultLibRoot "packages\cultmesh-ts"),
$eveBrowserPackage
)
$nodeCommand = Get-Command node.exe -ErrorAction SilentlyContinue
if (-not $nodeCommand) {
$standardNode = Join-Path $env:ProgramFiles "nodejs\node.exe"
if (Test-Path -LiteralPath $standardNode) {
$nodeCommand = Get-Item -LiteralPath $standardNode
}
}
if (-not $nodeCommand) {
throw "Node.js is required to verify the Eve two-runtime sample."
}
$nodePath = $nodeCommand.Source
$nodeDirectory = Split-Path -Parent $nodePath
$npmPath = Join-Path $nodeDirectory "npm.cmd"
if (-not (Test-Path -LiteralPath $npmPath)) {
throw "npm was not found beside Node.js: $npmPath"
}
$env:PATH = $nodeDirectory + [IO.Path]::PathSeparator + $env:PATH
if (-not (Test-Path -LiteralPath $samplePath)) {
throw "Sample not found: $samplePath"
}
foreach ($packageRoot in $packageRoots) {
if (-not (Test-Path -LiteralPath (Join-Path $packageRoot "package.json"))) {
throw "Package root not found: $packageRoot"
}
}
$tempBase = [IO.Path]::GetFullPath([IO.Path]::GetTempPath())
$workRoot = Join-Path $tempBase ("gamecult-eve-two-runtime-" + [guid]::NewGuid().ToString("N"))
$artifactRoot = Join-Path $workRoot "artifacts"
$consumerRoot = Join-Path $workRoot "consumer"
New-Item -ItemType Directory -Path $artifactRoot, $consumerRoot -Force | Out-Null
try {
foreach ($packageRoot in $packageRoots) {
Push-Location $packageRoot
try {
if ((Split-Path -Leaf $packageRoot) -eq "cultnet-ts") {
& $nodePath (Join-Path $packageRoot "tools\generate-swarm-contracts.mjs")
if ($LASTEXITCODE -ne 0) {
throw "CultNet TypeScript contract generation failed: $packageRoot"
}
}
$typescriptPath = if ($packageRoot -eq $eveBrowserPackage) {
Join-Path $packageRoot "node_modules\typescript\bin\tsc"
}
else {
Join-Path $cultLibRoot "node_modules\typescript\bin\tsc"
}
& $nodePath $typescriptPath -p (Join-Path $packageRoot "tsconfig.json") --pretty false
if ($LASTEXITCODE -ne 0) {
throw "Package build failed: $packageRoot"
}
& $npmPath pack --ignore-scripts --silent --pack-destination $artifactRoot | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "Package pack failed: $packageRoot"
}
}
finally {
Pop-Location
}
}
Push-Location $consumerRoot
try {
& $npmPath init --yes --silent | Out-Null
$tarballs = Get-ChildItem -LiteralPath $artifactRoot -Filter "*.tgz" |
Sort-Object Name |
ForEach-Object FullName
$installArguments = @(
"install",
"--silent",
"--no-audit",
"--no-fund",
"@msgpack/msgpack@3",
"ajv@8",
"jsdom@30",
"zod@3"
) + @($tarballs)
& $npmPath @installArguments
if ($LASTEXITCODE -ne 0) {
throw "Clean consumer package installation failed."
}
Copy-Item -LiteralPath $samplePath -Destination (Join-Path $consumerRoot "sample.mjs")
& $nodePath .\sample.mjs
if ($LASTEXITCODE -ne 0) {
throw "Eve two-runtime sample failed."
}
}
finally {
Pop-Location
}
}
finally {
$resolvedWorkRoot = [IO.Path]::GetFullPath($workRoot)
if ($resolvedWorkRoot.StartsWith($tempBase, [StringComparison]::OrdinalIgnoreCase) -and
(Test-Path -LiteralPath $resolvedWorkRoot)) {
Remove-Item -LiteralPath $resolvedWorkRoot -Recurse -Force
}
}

View file

@ -35,15 +35,31 @@ Run the same provider and connect at least two lowerers. Both must report the
same provider id, surface id, surface version, and command receipt ids. Native
pixels may differ; provider truth may not.
Current verification for the Unity consumer:
The package-artifact browser/headless checkpoint is executable from CultLib:
```powershell
cd CultLib
powershell -ExecutionPolicy Bypass -File .\scripts\verify-eve-two-runtime-sample.ps1 `
-EveRoot ..\Eve
```
The verifier builds and packs `cultcache-ts`, `cultnet-ts`, `cultmesh-ts`, and
`@gamecult/eve-browser-lowering`, installs the tarballs into an empty temporary
consumer, and runs `samples/eve-two-runtime/sample.mjs`. One provider-owned
counter surface is lowered by a browser runtime and observed by a headless
runtime. A browser command crosses the typed operation boundary, both runtimes
converge on the same state and receipt identity, a duplicate idempotency key is
not applied twice, and the state plus receipt survive reopening the `.ccmp`
store.
Current verification for the Unity consumer remains:
```powershell
cd E:\Projects\EveUnity
powershell -ExecutionPolicy Bypass -File .\scripts\run-release-consumer-tests.ps1
```
The browser/Electron counterpart and a single command-driven two-runtime sample
remain required before this chapter can be considered complete. Their absence
is tracked as developer-experience debt rather than papered over with bespoke
setup instructions.
This checkpoint proves clean package consumption and two lowering/observation
runtimes. It does not claim an Odin-discovered network hop or cross-language
command execution; those remain separate public Verse conformance gates.