Validate Eve getting-started contracts

This commit is contained in:
Meta Vens 2026-08-17 23:18:15 +02:00
commit 54e4eacd13
7 changed files with 143 additions and 32 deletions

View file

@ -62,8 +62,14 @@ the stack. If a design needs another peer-to-peer category, first check whether
it is really Verse policy, peer exchange, or shard authority wearing a fake
mustache.
If you want the smallest durable "open node -> put typed doc -> get typed doc"
path, start with
If you want to build an application rather than tour the component drawer,
start with the maintained
[`CultMesh And Eve Getting Started`](src/GameCult.Mesh/docs/getting-started/README.md)
series. Its first executable package-consumer checkpoint is
[`samples/eve-two-runtime`](samples/eve-two-runtime/README.md).
If you want only the smallest durable "open node -> put typed doc -> get typed doc"
path, use
[`src/GameCult.Mesh/docs/durable-node-quickstart.md`](src/GameCult.Mesh/docs/durable-node-quickstart.md).
If you want the lower-level "typed local document -> raw wire document ->
mesh/runtime watch surface" handoff, follow it with

View file

@ -0,0 +1,32 @@
# Eve Two-Consumer Counter
This is the smallest executable package-consumer checkpoint for CultMesh plus
Eve. It intentionally has one owner for each decision:
- the provider node owns the counter document and command transaction;
- Eve owns the validated surface and command-receipt contracts;
- CultMesh owns typed observation and the operation boundary;
- the DOM lowerer owns HTML only;
- the headless observer owns no state.
Run it from the CultLib repository:
```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\verify-eve-two-runtime-sample.ps1 `
-EveRoot ..\Eve
```
The verifier builds package artifacts, installs them into a new empty temporary
consumer, and runs `sample.mjs`. Success proves:
1. no source-tree import is required by the consumer;
2. the provider surface and receipt pass Eve's generated runtime validators;
3. a DOM click executes one idempotent provider operation;
4. the DOM binding and a separate headless observer converge on the same count;
5. the canonical receipt and state survive reopening the `.cc` store.
This is not yet the promised networked multiplayer tutorial. The DOM lowerer is
hosted by jsdom in Node and both consumers share one local CultMesh node. A real
browser transport, Odin discovery, process separation, and a second language
runtime require distinct executable gates. Keeping that limitation visible is
part of the sample contract.

View file

@ -6,6 +6,10 @@ import { JSDOM } from "jsdom";
import { z } from "zod";
import { defineDocumentType } from "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";
@ -22,36 +26,38 @@ const counterDocument = defineDocumentType({
const surfaceDocument = defineDocumentType({
type: "gamecult.eve.surface",
schemaId: "gamecult.eve.surface.v1",
schema: z.any(),
schema: z.unknown().transform(value => parseEveSurfaceDocument(value)),
});
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(),
}),
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",
@ -64,10 +70,21 @@ function counterSurface() {
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",
}],
};
}
@ -97,7 +114,16 @@ async function waitFor(predicate, description) {
}
}
const statePath = join(await mkdtemp(join(tmpdir(), "cultmesh-eve-two-runtime-")), "state.ccmp");
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],
});
@ -120,7 +146,16 @@ const increment = CultMesh.operation("sample.counter.increment", async (_request
}));
const receipt = {
receiptId: `receipt:${idempotencyKey}`,
operationId: "sample.counter.increment",
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,
};
@ -162,7 +197,21 @@ assert.equal(duplicate.receiptId, browserReceipt.receiptId);
assert.equal(headless.current.count, 1);
headless.dispose();
renderEveSurface({ providerId, surface: { id: "sample.closed" } }, host);
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, {
@ -178,6 +227,7 @@ console.log(JSON.stringify({
providerId,
surfaceId,
receiptId: browserReceipt.receiptId,
loweringRuntime: "jsdom",
browserCount: 1,
headlessCount: 1,
restartCount: 1,

View file

@ -7,10 +7,12 @@ $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"
$eveContractsPackage = Join-Path $EveRoot "packages\eve-contracts"
$packageRoots = @(
(Join-Path $cultLibRoot "packages\cultcache-ts"),
(Join-Path $cultLibRoot "packages\cultnet-ts"),
(Join-Path $cultLibRoot "packages\cultmesh-ts"),
$eveContractsPackage,
$eveBrowserPackage
)
@ -56,7 +58,7 @@ try {
throw "CultNet TypeScript contract generation failed: $packageRoot"
}
}
$typescriptPath = if ($packageRoot -eq $eveBrowserPackage) {
$typescriptPath = if ($packageRoot -eq $eveBrowserPackage -or $packageRoot -eq $eveContractsPackage) {
Join-Path $packageRoot "node_modules\typescript\bin\tsc"
}
else {

View file

@ -12,15 +12,20 @@ using var mesh = new CultMeshClient(new CultMeshClientOptions
RendezvousEndpoints = new[] { "rudp://odin.gamecult.net:3076" }
});
using var pilotSurfaceLease = await mesh.LeaseDocumentAsync<EveSurfaceDocument>(
"aetheria.daemon",
"eve:surface:aetheria.pilot",
using var counterLease = await mesh.LeaseDocumentAsync<CounterState>(
"sample.counter-provider",
"counter:main",
cancellationToken);
var pilotSurface = pilotSurfaceLease.Handle;
var counter = counterLease.Handle;
using var surfaceWatch = pilotSurface.Watch().Subscribe(Render);
using var counterWatch = counter.Watch(value =>
Console.WriteLine($"Count: {value.Count}"));
```
`CounterState` is the application document from chapter 1. Eve documents enter
in chapter 3, where the sample installs the renderer-neutral Eve contract
package explicitly. This identity/session API does not depend on a UI runtime.
Keep `mesh` for the application lifetime and dispose each document lease when
the consuming screen or system closes. Leases for the same identity share one
subscription. The document handle is stable while leased: when

View file

@ -35,7 +35,7 @@ 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.
The package-artifact browser/headless checkpoint is executable from CultLib:
The package-artifact DOM-lowerer/headless checkpoint is executable from CultLib:
```powershell
cd CultLib
@ -43,14 +43,16 @@ powershell -ExecutionPolicy Bypass -File .\scripts\verify-eve-two-runtime-sample
-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.
The verifier builds and packs `cultcache-ts`, `cultnet-ts`, `cultmesh-ts`,
`@gamecult/eve-contracts`, and `@gamecult/eve-browser-lowering`, installs the
tarballs into an empty temporary consumer, and runs
`samples/eve-two-runtime/sample.mjs`. Eve's generated runtime validators reject
an invalid surface or receipt before either enters the store. One provider-owned
counter surface is lowered into a jsdom DOM host and observed by an independent
headless CultMesh observer. A DOM command crosses the typed operation boundary,
both consumers converge on the same state and canonical receipt identity, a
duplicate idempotency key is not applied twice, and the state plus receipt
survive reopening the `.cc` store.
Current verification for the Unity consumer remains:
@ -59,7 +61,9 @@ cd E:\Projects\EveUnity
powershell -ExecutionPolicy Bypass -File .\scripts\run-release-consumer-tests.ps1
```
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.
This checkpoint proves clean package consumption, canonical Eve contract
validation, and two independent lowering/observation consumers inside Node. It
does **not** claim a real browser process, an Odin-discovered network hop, or
cross-language command execution. Those are the next public Verse conformance
gates; do not cite this sample as evidence that they exist.

View file

@ -4,6 +4,18 @@ This series builds one typed application state and presents it through multiple
Eve runtimes. It is maintained as an API acceptance test: examples use public
APIs, name the owner of each decision, and include a verification checkpoint.
Run the current clean-consumer checkpoint first:
```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\verify-eve-two-runtime-sample.ps1 `
-EveRoot ..\Eve
```
It installs packed artifacts into an empty temporary project, validates the Eve
surface and receipt, executes one idempotent counter command, proves two local
consumers converge, and reopens the durable `.cc` store. It is deliberately not
called a networked-browser proof; chapter 3 names that remaining boundary.
Follow the chapters in order:
1. [Persist typed application state](01-persist-typed-state.md)