2026-08-18 00:05:57 +02:00
|
|
|
# CultMesh Browser
|
|
|
|
|
|
|
|
|
|
`cultmesh-browser` is the browser-safe CultMesh client boundary. It owns one
|
2026-08-18 10:42:33 +02:00
|
|
|
WebSocket session for a stable Verse/authority-runtime target, retains explicit raw
|
2026-08-18 00:05:57 +02:00
|
|
|
document leases across route replacement, and correlates typed CultNet
|
|
|
|
|
operations with provider receipts.
|
|
|
|
|
|
|
|
|
|
It does not own application state and it does not expose direct document
|
|
|
|
|
writes. A renderer watches provider-owned records and invokes provider-owned
|
|
|
|
|
operations.
|
|
|
|
|
|
|
|
|
|
```ts
|
2026-08-18 00:34:44 +02:00
|
|
|
const odin = new CultMeshBrowserOdinRendezvous({
|
|
|
|
|
endpoints: ["wss://odin.example/verse"],
|
|
|
|
|
runtimeId: "counter-browser.discovery",
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-18 00:05:57 +02:00
|
|
|
const mesh = await CultMeshBrowserClient.connect({
|
|
|
|
|
verseId: "sample.counter",
|
2026-08-18 10:42:33 +02:00
|
|
|
authorityRuntimeId: "sample.counter-daemon",
|
2026-08-18 00:05:57 +02:00
|
|
|
runtimeId: "counter-browser",
|
2026-08-18 00:34:44 +02:00
|
|
|
rendezvous: odin,
|
2026-08-18 00:05:57 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const counter = await mesh.leaseRawDocument({
|
|
|
|
|
schemaId: "sample.counter_state.v1",
|
|
|
|
|
recordKey: "counter:main",
|
|
|
|
|
});
|
|
|
|
|
|
One typed owner of document-payload reads and writes: DocumentPayload is opaque, the definition is required
DocumentPayload is an opaque object type over the same runtime Uint8Array, keyed by a string
property, and types CultCacheEnvelope.payload, CultNetRawDocumentRecord.payload and
CultMeshRealtimeFrame.payload. decodeDocumentPayload(definition, payload) and
encodeDocumentPayload(definition, value) are the only reader and writer; documentPayloadFromBytes
and documentPayloadBytes are the two named escapes at the edges where bytes enter and leave.
resolveDocumentFormatter leaves the package index. The cache, cultnet replication, the mesh read
paths, the provider command (now a CultCache document), and the browser's decodeCultNetPayload
(definition required, cultcache-ts/document entry) all go through the owner.
Compile-fail fixtures per payload-carrying package pin the type with expect-error lines.
check-typescript-tests-wired.mjs fails when a test file is run by no script; cultcache-ts now
runs dist-test/test/*.test.js, and the portability workflow runs the check and the cache suite.
Builds in every package (tsc, src and tests); the suites run once, at the end of the pass.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 19:33:42 +02:00
|
|
|
counter.watch(record => renderCounter(decodeCultNetPayload(record, counterDocument)));
|
2026-08-18 00:05:57 +02:00
|
|
|
```
|
|
|
|
|
|
One typed owner of document-payload reads and writes: DocumentPayload is opaque, the definition is required
DocumentPayload is an opaque object type over the same runtime Uint8Array, keyed by a string
property, and types CultCacheEnvelope.payload, CultNetRawDocumentRecord.payload and
CultMeshRealtimeFrame.payload. decodeDocumentPayload(definition, payload) and
encodeDocumentPayload(definition, value) are the only reader and writer; documentPayloadFromBytes
and documentPayloadBytes are the two named escapes at the edges where bytes enter and leave.
resolveDocumentFormatter leaves the package index. The cache, cultnet replication, the mesh read
paths, the provider command (now a CultCache document), and the browser's decodeCultNetPayload
(definition required, cultcache-ts/document entry) all go through the owner.
Compile-fail fixtures per payload-carrying package pin the type with expect-error lines.
check-typescript-tests-wired.mjs fails when a test file is run by no script; cultcache-ts now
runs dist-test/test/*.test.js, and the portability workflow runs the check and the cache suite.
Builds in every package (tsc, src and tests); the suites run once, at the end of the pass.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 19:33:42 +02:00
|
|
|
A record's `payload` is a `DocumentPayload`, an opaque type over the same bytes, and
|
|
|
|
|
`decodeCultNetPayload(record, definition)` is how it is read: through the CultCache document
|
|
|
|
|
definition that says whether the bytes are a plain map, a codec's slot record or a
|
|
|
|
|
formatter's, then through the definition's schema. A plain MessagePack decode of a payload
|
2026-10-11 20:37:29 +02:00
|
|
|
does not compile, and `decodeCultNetPayload` takes no definition-less form. The type stops direct reads
|
|
|
|
|
and assignment; it does not stop runtime narrowing (`payload instanceof Uint8Array`) or a cast, which
|
|
|
|
|
would read the raw bytes. For the bytes themselves use `documentPayloadBytes`. The definition
|
One typed owner of document-payload reads and writes: DocumentPayload is opaque, the definition is required
DocumentPayload is an opaque object type over the same runtime Uint8Array, keyed by a string
property, and types CultCacheEnvelope.payload, CultNetRawDocumentRecord.payload and
CultMeshRealtimeFrame.payload. decodeDocumentPayload(definition, payload) and
encodeDocumentPayload(definition, value) are the only reader and writer; documentPayloadFromBytes
and documentPayloadBytes are the two named escapes at the edges where bytes enter and leave.
resolveDocumentFormatter leaves the package index. The cache, cultnet replication, the mesh read
paths, the provider command (now a CultCache document), and the browser's decodeCultNetPayload
(definition required, cultcache-ts/document entry) all go through the owner.
Compile-fail fixtures per payload-carrying package pin the type with expect-error lines.
check-typescript-tests-wired.mjs fails when a test file is run by no script; cultcache-ts now
runs dist-test/test/*.test.js, and the portability workflow runs the check and the cache suite.
Builds in every package (tsc, src and tests); the suites run once, at the end of the pass.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 19:33:42 +02:00
|
|
|
comes from `@gamecult/cultcache-ts` (this package imports only its `./document` entry, which
|
|
|
|
|
holds no Node built-ins).
|
|
|
|
|
|
2026-08-18 05:59:11 +02:00
|
|
|
`invoke(...)` returns the correlated provider response. Routing, schema, and
|
|
|
|
|
envelope failures reject immediately with `CultMeshBrowserOperationError`;
|
|
|
|
|
callers can branch on its stable `status` and `code` without parsing prose or
|
|
|
|
|
waiting for the request timeout. Domain rejections remain typed provider
|
|
|
|
|
responses because the domain still owns their receipt schema.
|
|
|
|
|
|
2026-08-18 00:34:44 +02:00
|
|
|
`CultMeshBrowserOdinRendezvous` speaks the canonical CultNet Verse-catalog
|
|
|
|
|
messages to one or more configured Odin WebSocket endpoints. Application code
|
2026-08-18 10:42:33 +02:00
|
|
|
supplies the Verse and authority-runtime identity; it does not own endpoint
|
|
|
|
|
selection, reconnect, or resubscription. This is deliberately distinct from an
|
|
|
|
|
Eve surface's `providerId`, which identifies the UI provider inside the Verse.
|
|
|
|
|
The physical socket is a replaceable route, not either stable identity.
|
2026-08-18 00:05:57 +02:00
|
|
|
|
2026-08-18 17:21:58 +02:00
|
|
|
Odin returns exact authority-route bindings. The browser selects only routes
|
|
|
|
|
bound to the requested authority and `cultmesh.documents.v1`, then performs a
|
|
|
|
|
session-open handshake. It does not report `connected` until the peer proves
|
|
|
|
|
the exact Verse, authority runtime, protocol, and route generation. Operation
|
|
|
|
|
responses must also name that authority. Legacy catalogs are accepted only
|
|
|
|
|
when one authority runtime makes the endpoint binding unambiguous.
|
|
|
|
|
|
2026-08-18 00:05:57 +02:00
|
|
|
The browser handshake carries authentication through the host application's
|
|
|
|
|
ordinary secure cookie or equivalent upgrade credential. The C# host adapter
|
|
|
|
|
rejects endpoints without an authorization predicate unless anonymous local
|
|
|
|
|
development is explicitly enabled.
|
|
|
|
|
|
|
|
|
|
Run `scripts/verify-eve-browser-network.mjs --eve-root <path-to-Eve>` for the
|
2026-08-18 17:21:58 +02:00
|
|
|
real Chromium + C# headless chronology. The witness publishes a lower-priority
|
|
|
|
|
decoy endpoint owned by another runtime and proves that neither client connects
|
|
|
|
|
to it before and after route rotation.
|
2026-10-19 15:28:46 +02:00
|
|
|
|
|
|
|
|
## Verified content
|
|
|
|
|
|
|
|
|
|
`fetchVerifiedContent(endpoint, manifest)` and
|
|
|
|
|
`fetchVerifiedChunk(endpoint, hash, size)` read CultMesh CDN content over HTTP.
|
|
|
|
|
The endpoint comes from CultMesh and never from content: a manifest carries no
|
|
|
|
|
URL. No byte is released that failed its chunk or whole-body SHA-256 (WebCrypto,
|
|
|
|
|
so a secure context in browsers). Only https is accepted, and http only on
|
2026-10-19 18:26:58 +02:00
|
|
|
loopback; a redirect is a `network` failure, and an endpoint spelling on which
|
|
|
|
|
.NET `UriBuilder` and WHATWG `URL` would build different request URLs (non-ASCII,
|
|
|
|
|
`'` `{` `}` `|` `^` `\` or a space, a stray `%`, an escaped unreserved character)
|
|
|
|
|
is `insecure-endpoint`. A size the runtime cannot allocate is `too-large`. Options: `maxConcurrentChunkRequests` (1 to 32, default 4), `maxBytes`
|
2026-10-19 15:28:46 +02:00
|
|
|
(default 256 MiB, checked before any request), `signal` (an abort rejects with
|
|
|
|
|
the signal's own reason). Failures are `CultMeshContentError` with a `code`
|
|
|
|
|
(`manifest-invalid`, `too-large`, `insecure-endpoint`, `webcrypto-unavailable`,
|
|
|
|
|
`network`, `http-status`, `length-mismatch`, `chunk-hash-mismatch`,
|
|
|
|
|
`content-hash-mismatch`); messages never carry the endpoint or body bytes. The
|
|
|
|
|
byte count actually received is judged, never a length header, because Fetch
|
|
|
|
|
decodes content codings.
|
|
|
|
|
|
2026-10-19 21:13:34 +02:00
|
|
|
Browser floor: the reader needs `String.prototype.toWellFormed` (ES2024) and
|
|
|
|
|
WebCrypto `crypto.subtle`; without WebCrypto it fails with
|
|
|
|
|
`webcrypto-unavailable`. Redirects are refused, including https to https: the
|
|
|
|
|
endpoint CultMesh resolved is the only host the reader trusts.
|
|
|
|
|
|
2026-10-19 15:28:46 +02:00
|
|
|
```ts
|
|
|
|
|
import { fetchVerifiedContent } from "cultmesh-browser";
|
|
|
|
|
|
|
|
|
|
const bytes = await fetchVerifiedContent(endpoint, manifest, {
|
|
|
|
|
signal: AbortSignal.timeout(30_000),
|
|
|
|
|
maxConcurrentChunkRequests: 8,
|
|
|
|
|
});
|
|
|
|
|
```
|