diff --git a/examples/reference-host/README.md b/examples/reference-host/README.md
new file mode 100644
index 00000000..3ba03768
--- /dev/null
+++ b/examples/reference-host/README.md
@@ -0,0 +1,102 @@
+# Inkspan reference host
+
+Status: Active PR / partial reference-host implementation
+
+This directory is buyer-facing integration evidence for issue #377. It is intentionally **host code**, not a new Inkspan runtime surface. Protected `main` remains the shipped product authority, and this example is not production-ready until its remaining complete framework-host/collaboration/Office acceptance work is implemented and the release boundary permits integration.
+
+The current slice contains deterministic host fixtures and helpers, public-package presentation entrypoints, a buyer-facing native-form component, an SSR/hydration gate, exact-packed application-level SSR evidence, and exact-packed-artifact package/browser verification:
+
+- `synthetic-document-repository.mjs` demonstrates host-owned strong-validator / `If-Match` persistence behavior. `ambiguous_failure` models a pre-commit failure, while `ambiguous_commit_failure` commits durable state but returns the same ambiguous error without a replacement validator. After either ambiguous outcome, re-read durable state before retrying instead of advancing or blindly reusing the caller's last known validator. A stale validator returns a conflict. A confirmed failure can be retried with the unchanged current validator. A restore is a normal confirmed save against the current validator and advances it only after success. A fork requires the current validator, copies the current document into an independent reference repository, and starts that fork at a fresh validator.
+- `delayed-proposal.mjs` demonstrates a provider-free delayed proposal captured against one expected revision. If the current revision changes before application, the proposal returns a conflict instead of overwriting newer content. Host apply callback failures are normalized to a stable payload-free error rather than reflecting private causes through the proposal boundary.
+- `autosave-view-model.mjs` projects Inkspan autosave lifecycle snapshots into host-localizable `clean`, `saving`, `queued`, `conflict`, `failed`, `retrying`, `recovered`, `closing`, and `closed` presentation states. Recovery presentation is derived from observed blocked → saving → idle transitions, and validators are never returned as UI data.
+- `collaboration-provider-lifecycle.mjs` demonstrates that the embedding host creates one real `Y.Doc`, reuses that same document across provider reconnects, owns provider/document teardown, and replaces a provider whose `connect()` threw rather than retrying an indeterminate resource. The deterministic provider fixture has no provider SDK or network transport and does not treat room or actor identifiers as authorization evidence.
+- `host-authorized-collaboration.mjs` demonstrates a host-owned synchronous admission gate around provider construction. The authorization callback receives only bounded room, actor, and provider-generation identity; only the exact boolean `true` admits construction; thrown, asynchronous, false, or otherwise indeterminate decisions fail closed with a payload-redacted error. The host re-authorizes every provider generation before the host-owned `Y.Doc` reaches the provider constructor. This reference adapter contains no production identity service, credential store, provider SDK, or network transport and does not make Inkspan an authorization authority.
+- `single-flight-submission.ts` provides the synchronous host-local admission guard used by the form example so same-turn submit/reset races cannot invoke the durable host callback twice.
+- `hydration-gate.tsx` demonstrates an application-facing client hydration gate around the public Inkspan editor contract. It is deterministic boundary evidence, not a complete framework application or browser acceptance journey.
+- `reference-host-client.tsx` is the narrow `'use client'` composition boundary: it owns the hydration gate plus native-form editor lifecycle while leaving authorization and durable persistence behind the injected host callback.
+- `reference-host-app.tsx` is the server-safe deterministic application shell. It renders host chrome and delegates all browser-only editor behavior to `reference-host-client.tsx`; this remains source-contract evidence rather than complete framework/browser acceptance.
+- `browser-host.tsx` loads the local `presentation-full.css` buyer entrypoint, which resolves Inkspan's public `styles.css` plus complete multilingual `fonts.css`, and hydrates the deterministic `browser-host.html` shell into `ReferenceHostApp` for exact-packed Chromium/Firefox/WebKit execution. The browser harness binds the package root and public presentation subpaths to the exact extracted tarball rather than workspace/source authority. It supports deterministic read-only and deferred-submission states, native-form hydration/submission, narrow-viewport, print, forced-colors, and no-unexpected-network acceptance while recording only host-local test submissions. It remains repository test-host evidence, not an authorized durable host or the complete framework application required by #377.
+- `presentation-full.css` imports Inkspan's public `styles.css` and complete multilingual `fonts.css` subpaths for hosts that want the bundled offline multilingual font set.
+- `presentation-latin.css` imports the same public editor stylesheet plus the smaller public `fonts-latin.css` option for Latin-only hosts.
+- `native-form-host.tsx` demonstrates public-package native-form integration: Inkspan synchronizes `message_body` through `formFieldName`, reset behavior is expressed through `formResetValue`, the host reads `FormData` only on submit, and authorization plus durable persistence remain behind the injected `onAuthorizedSubmit` host boundary rather than being embedded in the component.
+- `office-handoff.mjs` maps bounded editor Markdown through Inkspan's public React-free Markdown projection into the strict DOCX paragraph-request shape expected by the Office component. It does not render Office bytes, authorize export, choose an output path, persist or distribute artifacts, use network/credential state, or claim Markdown-to-OOXML round-trip fidelity; those remain Office/host responsibilities.
+- `verify-office-handoff.mjs` consumes an already extracted exact package entry supplied by repository verification orchestration through `INKSPAN_BROWSER_PACKAGE_ENTRY`, maps bounded Markdown through that public React-free package handoff, renders the request through the local Inkspan Office CLI, validates the resulting DOCX title/body, and removes its temporary output. It does not build, pack, or choose the artifact itself. This is deterministic component-execution evidence only: host export authorization, output-location policy, durable storage, distribution, and broader document-fidelity acceptance remain outside the example's authority.
+- `verify-packed-office-journey.mjs` is the self-contained package-authority wrapper for that bounded Office path: `--self-test` builds and packs the current Inkspan source, extracts the exact tarball into an isolated consumer-shaped package root, binds `verify-office-handoff.mjs` to that public package entry, runs the bounded DOCX rendering/validation, and removes the temporary consumer; `--plan` reports the exact-packed-tarball authority without executing it. It still assumes the repository's local Office Python dependencies are installed and makes no production export/storage/distribution claim.
+- `verify-packed-artifact.mjs` builds and packs the current Inkspan source, installs that exact tarball into an isolated consumer, proves public ESM/CommonJS/SSR and React-free subpath consumption plus CSS/font resolution, exercises exact packed autosave observer wiring into the host lifecycle projection, and rejects source-tree authority leakage. This is exact package-consumer evidence, not a complete buyer framework application.
+- `verify-application-ssr.mjs` builds and packs the current Inkspan source, installs that exact tarball plus host-owned React/ReactDOM dependencies into an isolated buyer-shaped consumer, binds the real `ReferenceHostApp` application shell to the installed packed public entry, server-bundles it with Vite, and requires deterministic host chrome plus a deferred client-editor hydration boundary. The host submit callback must not execute during server rendering and the client-only `message_body` field must not cross the server boundary. The verifier removes its temporary consumer after execution. This is exact-packed application-level SSR evidence, not a complete Next.js or other production framework host.
+- `verify-browser-journey.mjs` builds and packs the current Inkspan source, extracts that exact tarball into an isolated temporary package root, binds the browser harness to the packed public ESM/CSS/font entries, and runs only the reference-host acceptance specs across Chromium, Firefox, and WebKit. `--plan` reports the exact engines/spec inventory without executing it; `--self-test` performs the scoped real-browser journey. The verifier assumes the repository's isolated browser-test dependencies and pinned browser revisions are already installed, makes no production transport/auth/persistence claim, and removes its temporary package root after the run.
+- `verify-current-reference-journey.mjs` is the single entrypoint for the **currently implemented partial** journey. `--plan` emits the exact ordered verification contract without running it; the default invocation runs the deterministic repository/proposal/autosave/collaboration checks followed by exact packed-artifact, self-contained exact-packed bounded Office, and scoped exact-packed real-browser verification. It deliberately does not claim the complete framework-host, authenticated transport/provider, richer Office-fidelity, or final clean-checkout acceptance still required by #377.
+
+Executable fixtures and helpers remain reference-only host code, require no service, database, credential, provider SDK, model, or external runtime connection for their deterministic repository checks, and are exercised by repository tests. The browser verifier uses only the local loopback harness at runtime and its browser specs reject unexpected external requests. The presentation/native-form/hydration examples reference only public package entrypoints. The complete reference-host directory is deliberately outside the package `files` inventory so example host logic cannot silently become published Inkspan runtime authority.
+
+## Copy this, replace that
+
+| Reference element | Buyer action |
+| --- | --- |
+| synthetic document repository | Replace with an authorized atomic durable store that enforces the host's RFC 9110 `If-Match` policy, preserves caller validators across confirmed failures, reconciles authoritative state after ambiguous transport outcomes, supports explicit retry/restore/fork recovery under current-validator checks, isolates fork history, and returns a new strong validator only after confirmed success. |
+| deterministic delayed proposal | Replace proposal generation with a host-approved model gateway and data-use policy while preserving exact-revision conflict checks and payload-redacted callback-failure handling before applying untrusted proposal data. |
+| autosave presentation projection | Preserve the exact packed autosave observer-to-view-model boundary in localized host UI; connect authenticated recovery actions in host code and do not display revision or durable validators as user-facing status. |
+| collaboration lifecycle and authorization fixtures | Keep host-owned `Y.Doc` lifecycle control and per-generation fail-closed authorization placement. Replace the deterministic `authorize` callback and provider factory with the host's authenticated policy decision and authorized Yjs transport provider while preserving ambiguous-connect replacement, reconnect, teardown, credential, and room-authorization policy. Credentials and durable authorization evidence remain host-owned and must not enter Inkspan runtime authority. |
+| presentation entrypoints | Choose the complete multilingual or Latin-only font entrypoint, keep imports on published package subpaths, and apply any host theme overrides without weakening Inkspan accessibility states. |
+| native form host | Keep Inkspan's `formFieldName` / `formResetValue` serialization boundary, then connect `onAuthorizedSubmit` to host-owned authorization and atomic durable persistence. Do not add a second hidden-field serializer or treat submitted form content as authorization evidence. |
+| Office handoff helper | Keep the provider-neutral public Markdown projection/request mapping or replace it with a host-approved richer mapping; invoke the strict Inkspan Office renderer only behind host-owned export authorization, output-location policy, storage, and distribution. |
+| exact packed-artifact verifier | Preserve exact-tarball build/install/SSR/autosave-observer/package-resolution and real-browser presentation checks in buyer CI, then add the complete framework application journey rather than treating repository test-host success as product acceptance. |
+| synthetic document and revision identifiers | Replace with authenticated/authorized host context; never infer tenant or actor authority from an Inkspan digest, form value, or example identifier. |
+| reference error handling | Map stable machine outcomes to localized host UX and audited host operations without copying document bodies, prompts, credentials, or private causes into generic telemetry. |
+
+## Ownership map
+
+```mermaid
+flowchart LR
+ User[Author / reviewer] --> Host[Embedding host]
+ Host --> Inkspan[Inkspan editor + deterministic evidence]
+ Host --> Repo[Host document repository]
+ Host --> Provider[Host collaboration provider]
+ Host --> Model[Host-approved model gateway]
+ Inkspan --> Proposal[Untrusted proposal data]
+ Proposal --> Host
+ Repo -->|strong validator / conflict| Host
+
+ classDef host stroke-width:2px;
+ class Host,Repo,Provider,Model host;
+```
+
+Inkspan owns deterministic editor/revision/autosave/conversion/package behavior. The host owns authenticated transport, authorization, tenancy, durable persistence, `Y.Doc` and collaboration-provider lifecycle, credentials, model policy, retention, deployment, and durable audit. A successful local editor operation, native-form submission, Yjs update, model response, or status check is not durable authorization or persistence evidence.
+
+## Executable fixture checks
+
+From a repository checkout with the supported Node runtime, root and isolated browser-test dependencies, pinned Playwright browser revisions, and the dependencies required by the exact packed-artifact and local Office verification already installed, the currently implemented partial reference journey can be exercised with one command:
+
+```sh
+node examples/reference-host/verify-current-reference-journey.mjs
+```
+
+Its deterministic execution plan can be inspected without running the constituent checks:
+
+```sh
+node examples/reference-host/verify-current-reference-journey.mjs --plan
+```
+
+The constituent deterministic reference-only fixture checks remain directly executable when a narrower causal check is needed:
+
+```sh
+node examples/reference-host/synthetic-document-repository.mjs --self-test
+node examples/reference-host/delayed-proposal.mjs --self-test
+node examples/reference-host/autosave-view-model.mjs --self-test
+node examples/reference-host/collaboration-provider-lifecycle.mjs --self-test
+node examples/reference-host/verify-application-ssr.mjs --self-test
+node examples/reference-host/verify-packed-office-journey.mjs --plan
+node examples/reference-host/verify-packed-office-journey.mjs --self-test
+node examples/reference-host/verify-browser-journey.mjs --plan
+node examples/reference-host/verify-browser-journey.mjs --self-test
+```
+
+The repository test suite independently exercises those fixtures plus the host-authorization, hydration/native-form/single-flight/Office-handoff contracts, runs `verify-packed-artifact.mjs` to build, pack, install, and consume the exact current tarball in an isolated consumer, and runs `verify-application-ssr.mjs` to install the same exact current package authority into a buyer-shaped application SSR consumer. The application-level SSR verifier binds the real server-safe reference-host shell to the installed packed editor, requires deterministic host chrome, proves the client editor remains behind the hydration gate, and proves host submit authority is not invoked during server rendering. The one-command helper invokes `verify-packed-office-journey.mjs` after the packed-artifact consumer check and then `verify-browser-journey.mjs`, so the currently implemented partial buyer journey supplies exact tarball authority to both the bounded Office path and the reference-host-only Playwright acceptance. The cross-engine Playwright suite additionally builds and extracts the exact tarball, binds the package root plus public styles/font subpaths to that artifact, and exercises the repository browser-test host shell with real native-form hydration/submission, deterministic read-only and delayed-submission transitions, a 320px narrow-viewport journey, print media, forced colors, and no-unexpected-network acceptance across Chromium, Firefox, and WebKit. The read-only journey proves the editor remains readable while its hidden native form field, Save action, Reset action, and host submission callback all fail closed. The narrow-viewport journey requires the editor and Save/Reset controls to remain visible and in-viewport while rejecting document or body horizontal overflow. The print journey requires the toolbar to be absent from print presentation, checks the package print overflow/border/surface/content contract, and rejects external runtime requests. The suite also asserts stale-write conflict, failure-safe retry, ambiguous pre-commit and post-commit reconciliation, restore, fork isolation, lifecycle recovery, exact packed-package autosave observer-to-host-view-model integration, real host-created `Y.Doc`, provider replacement after ambiguous connect failure, reconnect/teardown, per-generation host authorization before provider construction, exact-synchronous-true admission, authorization-failure redaction, proposal-failure redaction, full public presentation-package behavior, native-form published-package/host-authority boundaries, the bounded React-free Markdown-to-DOCX-request mapping, deterministic exact-packed Office rendering/validation through `verify-packed-office-journey.mjs`, exact-packed application-shell SSR, and exact package authority. These checks do **not** yet satisfy #377's complete packed-artifact framework-application acceptance. The verified Office journey is intentionally bounded to the documented Markdown subset and local renderer contract; broader fidelity and production export operations remain host-owned acceptance work.
+
+The one-command helper above consolidates only the currently implemented reference-host checks. It is **not** the final #377 clean-checkout command because the complete framework application, authenticated transport/provider/credential journey, richer application-level Office fidelity, and their acceptance evidence do not exist yet.
+
+## Deliberate omissions in this partial slice
+
+Still required before #377 can close: a complete reference-host application (preferably a supported Next.js App Router host) that installs and runs the exact packed tarball as a real framework application rather than only through repository acceptance harnesses; full framework hydration and interactive acceptance beyond the current deterministic exact-packed application-level SSR plus browser-shell evidence; a real authenticated transport-provider/credential integration journey that consumes the demonstrated per-generation host authorization boundary around the host-owned `Y.Doc` lifecycle; richer application-level converter/Office fidelity acceptance beyond the verified bounded handoff; and one documented clean-checkout command for the complete end-to-end reference journey.
+
+Do not use the synthetic repository, synthetic identifiers, deterministic proposal fixture, presentation projection, deterministic collaboration provider, host-authorization adapter, hydration gate, native-form example callback, or Office request helper as a production persistence, authentication, authorization, collaboration, model, deployment, export-authorization, storage, or distribution implementation.
diff --git a/examples/reference-host/autosave-view-model.mjs b/examples/reference-host/autosave-view-model.mjs
new file mode 100644
index 00000000..b5479c9d
--- /dev/null
+++ b/examples/reference-host/autosave-view-model.mjs
@@ -0,0 +1,310 @@
+const AUTOSAVE_STATES = new Set([
+ 'idle',
+ 'saving',
+ 'blocked',
+ 'closing',
+ 'closed',
+]);
+const BLOCKED_REASONS = new Set(['conflict', 'failure']);
+const MAX_STRONG_ENTITY_TAG_CODE_UNITS = 256;
+const STRONG_ENTITY_TAG_PATTERN = /^"[\x21\x23-\x7e\x80-\xff]*"$/u;
+const SNAPSHOT_KEYS = [
+ 'state',
+ 'blockedReason',
+ 'activeStrongEntityTag',
+ 'pendingStrongEntityTag',
+ 'lastSavedStrongEntityTag',
+];
+
+/** Marker used by repository contracts to prevent this host fixture becoming runtime authority. */
+export const REFERENCE_ONLY = true;
+
+function requireNullableStrongEntityTag(value, label) {
+ if (
+ value !== null &&
+ (typeof value !== 'string' ||
+ value.length > MAX_STRONG_ENTITY_TAG_CODE_UNITS ||
+ !STRONG_ENTITY_TAG_PATTERN.test(value))
+ ) {
+ throw new TypeError(`${label} is invalid.`);
+ }
+ return value;
+}
+
+function snapshotData(source) {
+ try {
+ if (typeof source !== 'object' || source === null) {
+ throw new TypeError('autosave snapshot is invalid.');
+ }
+ const values = Object.create(null);
+ for (const key of SNAPSHOT_KEYS) {
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
+ if (
+ descriptor === undefined ||
+ !Object.prototype.hasOwnProperty.call(descriptor, 'value')
+ ) {
+ throw new TypeError('autosave snapshot is invalid.');
+ }
+ values[key] = descriptor.value;
+ }
+ const ownKeys = Reflect.ownKeys(source);
+ if (
+ ownKeys.length !== SNAPSHOT_KEYS.length ||
+ ownKeys.some(
+ (key) => typeof key !== 'string' || !SNAPSHOT_KEYS.includes(key),
+ )
+ ) {
+ throw new TypeError('autosave snapshot is invalid.');
+ }
+ return values;
+ } catch {
+ throw new TypeError('autosave snapshot is invalid.');
+ }
+}
+
+function readSnapshot(source) {
+ const snapshot = snapshotData(source);
+ if (
+ !AUTOSAVE_STATES.has(snapshot.state) ||
+ (snapshot.blockedReason !== null &&
+ !BLOCKED_REASONS.has(snapshot.blockedReason))
+ ) {
+ throw new TypeError('autosave snapshot is invalid.');
+ }
+
+ if (
+ (snapshot.state === 'blocked' && snapshot.blockedReason === null) ||
+ (snapshot.state !== 'blocked' && snapshot.blockedReason !== null)
+ ) {
+ throw new TypeError('autosave snapshot lifecycle is inconsistent.');
+ }
+
+ return Object.freeze({
+ state: snapshot.state,
+ blockedReason: snapshot.blockedReason,
+ activeStrongEntityTag: requireNullableStrongEntityTag(
+ snapshot.activeStrongEntityTag,
+ 'activeStrongEntityTag',
+ ),
+ pendingStrongEntityTag: requireNullableStrongEntityTag(
+ snapshot.pendingStrongEntityTag,
+ 'pendingStrongEntityTag',
+ ),
+ lastSavedStrongEntityTag: requireNullableStrongEntityTag(
+ snapshot.lastSavedStrongEntityTag,
+ 'lastSavedStrongEntityTag',
+ ),
+ });
+}
+
+function presentation(viewState) {
+ return Object.freeze({
+ viewState,
+ messageKey: `referenceHost.autosave.${viewState}`,
+ busy:
+ viewState === 'saving' ||
+ viewState === 'queued' ||
+ viewState === 'retrying',
+ blocked: viewState === 'conflict' || viewState === 'failed',
+ canRetry: viewState === 'conflict' || viewState === 'failed',
+ });
+}
+
+/**
+ * Create one host-owned projection of Inkspan autosave lifecycle transitions.
+ *
+ * `observe()` consumes only programmatic queue/session snapshots. Snapshot fields
+ * must be an exact own-data-property shape so presentation never invokes
+ * caller-owned accessors or silently admits authority-looking metadata. Strong
+ * entity tags are validated as bounded RFC 9110 strong entity-tags before
+ * projection so untrusted snapshot metadata cannot masquerade as a durable
+ * validator or allocate unbounded retained strings at this reference boundary.
+ * A blocked to saving transition is presented as retrying, and only a later idle
+ * transition after that observed retry is presented as recovered. A blocked to
+ * idle transition without an intervening save returns to clean instead of
+ * manufacturing recovery evidence. The projection never returns local or durable
+ * validators; hosts localize `messageKey` and keep authenticated recovery controls
+ * outside Inkspan.
+ */
+export function createAutosaveViewModel() {
+ let retryPending = false;
+ let retryInFlight = false;
+
+ function clearRetryEvidence() {
+ retryPending = false;
+ retryInFlight = false;
+ }
+
+ function observe(snapshot) {
+ const current = readSnapshot(snapshot);
+
+ if (current.state === 'blocked') {
+ retryPending = true;
+ retryInFlight = false;
+ return presentation(
+ current.blockedReason === 'conflict' ? 'conflict' : 'failed',
+ );
+ }
+ if (current.state === 'closing') {
+ clearRetryEvidence();
+ return presentation('closing');
+ }
+ if (current.state === 'closed') {
+ clearRetryEvidence();
+ return presentation('closed');
+ }
+ if (current.state === 'saving' && (retryPending || retryInFlight)) {
+ retryPending = false;
+ retryInFlight = true;
+ return presentation('retrying');
+ }
+ if (current.state === 'saving' && current.pendingStrongEntityTag !== null) {
+ return presentation('queued');
+ }
+ if (current.state === 'saving') return presentation('saving');
+ if (retryInFlight) {
+ clearRetryEvidence();
+ return presentation('recovered');
+ }
+ retryPending = false;
+ return presentation('clean');
+ }
+
+ return Object.freeze({ observe });
+}
+
+function snapshot({
+ state,
+ blockedReason = null,
+ activeStrongEntityTag = null,
+ pendingStrongEntityTag = null,
+ lastSavedStrongEntityTag = null,
+}) {
+ return Object.freeze({
+ state,
+ blockedReason,
+ activeStrongEntityTag,
+ pendingStrongEntityTag,
+ lastSavedStrongEntityTag,
+ });
+}
+
+function runSelfTest() {
+ const steady = createAutosaveViewModel();
+ const clean = steady.observe(snapshot({ state: 'idle' })).viewState;
+ const saving = steady.observe(
+ snapshot({
+ state: 'saving',
+ activeStrongEntityTag: '"local-active"',
+ }),
+ ).viewState;
+ const queued = steady.observe(
+ snapshot({
+ state: 'saving',
+ activeStrongEntityTag: '"local-active"',
+ pendingStrongEntityTag: '"local-pending"',
+ }),
+ ).viewState;
+
+ const recovery = createAutosaveViewModel();
+ const conflict = recovery.observe(
+ snapshot({ state: 'blocked', blockedReason: 'conflict' }),
+ ).viewState;
+ const retrying = recovery.observe(
+ snapshot({
+ state: 'saving',
+ activeStrongEntityTag: '"local-retry"',
+ }),
+ ).viewState;
+ const recovered = recovery.observe(
+ snapshot({
+ state: 'idle',
+ lastSavedStrongEntityTag: '"local-saved"',
+ }),
+ ).viewState;
+
+ const failed = createAutosaveViewModel().observe(
+ snapshot({ state: 'blocked', blockedReason: 'failure' }),
+ ).viewState;
+ const closing = createAutosaveViewModel().observe(
+ snapshot({ state: 'closing' }),
+ ).viewState;
+ const closed = createAutosaveViewModel().observe(
+ snapshot({ state: 'closed' }),
+ ).viewState;
+
+ process.stdout.write(
+ `${JSON.stringify({
+ clean,
+ closed,
+ closing,
+ conflict,
+ failed,
+ queued,
+ recovered,
+ retrying,
+ saving,
+ })}\n`,
+ );
+}
+
+function runInvalidValidatorSelfTest() {
+ function observeError(candidate) {
+ try {
+ createAutosaveViewModel().observe(candidate);
+ return null;
+ } catch (error) {
+ return error instanceof Error ? error.message : 'unexpected error';
+ }
+ }
+
+ const activeError = observeError(
+ snapshot({ state: 'saving', activeStrongEntityTag: '' }),
+ );
+ const pendingError = observeError(
+ snapshot({
+ state: 'saving',
+ activeStrongEntityTag: '"local-active"',
+ pendingStrongEntityTag: '',
+ }),
+ );
+ const lastSavedError = observeError(
+ snapshot({ state: 'idle', lastSavedStrongEntityTag: '' }),
+ );
+
+ process.stdout.write(
+ `${JSON.stringify({ activeError, lastSavedError, pendingError })}\n`,
+ );
+}
+
+function runHostileAccessorSelfTest() {
+ let getterCalls = 0;
+ let error = null;
+ const hostile = {
+ blockedReason: null,
+ activeStrongEntityTag: null,
+ pendingStrongEntityTag: null,
+ lastSavedStrongEntityTag: null,
+ };
+ Object.defineProperty(hostile, 'state', {
+ enumerable: true,
+ get() {
+ getterCalls += 1;
+ return 'idle';
+ },
+ });
+ try {
+ createAutosaveViewModel().observe(hostile);
+ } catch (failure) {
+ error = failure instanceof Error ? failure.message : 'unexpected error';
+ }
+ process.stdout.write(`${JSON.stringify({ error, getterCalls })}\n`);
+}
+
+if (process.argv.includes('--invalid-validator-self-test')) {
+ runInvalidValidatorSelfTest();
+} else if (process.argv.includes('--hostile-accessor-self-test')) {
+ runHostileAccessorSelfTest();
+} else if (process.argv.includes('--self-test')) {
+ runSelfTest();
+}
diff --git a/examples/reference-host/browser-host.html b/examples/reference-host/browser-host.html
new file mode 100644
index 00000000..86db56ed
--- /dev/null
+++ b/examples/reference-host/browser-host.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Inkspan reference host
+
+
+ Inkspan reference host Loading buyer editor
+
+
+
diff --git a/examples/reference-host/browser-host.tsx b/examples/reference-host/browser-host.tsx
new file mode 100644
index 00000000..f9a47cf7
--- /dev/null
+++ b/examples/reference-host/browser-host.tsx
@@ -0,0 +1,50 @@
+import './presentation-full.css';
+import { hydrateRoot } from 'react-dom/client';
+
+import { ReferenceHostApp } from './reference-host-app.js';
+
+declare global {
+ interface Window {
+ referenceHostSubmissions: string[];
+ referenceHostResolveSubmission?: () => void;
+ }
+}
+
+const submissions: string[] = [];
+window.referenceHostSubmissions = submissions;
+
+const root = document.getElementById('reference-host-root');
+if (!root) {
+ throw new Error('Reference host root is missing.');
+}
+
+const searchParams = new URLSearchParams(window.location.search);
+const readOnly = searchParams.get('readOnly') === '1';
+const deferSubmission = searchParams.get('deferSubmission') === '1';
+const controlMode =
+ searchParams.get('controlMode') === 'controlled'
+ ? 'controlled'
+ : 'uncontrolled';
+let resolveDeferredSubmission: (() => void) | undefined;
+
+window.referenceHostResolveSubmission = () => {
+ resolveDeferredSubmission?.();
+};
+
+hydrateRoot(
+ root,
+ {
+ submissions.push(messageBody);
+ if (deferSubmission) {
+ await new Promise((resolve) => {
+ resolveDeferredSubmission = resolve;
+ });
+ resolveDeferredSubmission = undefined;
+ }
+ }}
+ readOnly={readOnly}
+ />,
+);
\ No newline at end of file
diff --git a/examples/reference-host/collaboration-provider-lifecycle.mjs b/examples/reference-host/collaboration-provider-lifecycle.mjs
new file mode 100644
index 00000000..3714c86e
--- /dev/null
+++ b/examples/reference-host/collaboration-provider-lifecycle.mjs
@@ -0,0 +1,570 @@
+import { Doc } from 'yjs';
+
+const MAX_CONTEXT_CODE_UNITS = 256;
+const MAX_RESOURCE_PROTOTYPE_DEPTH = 64;
+const INITIALIZATION_FAILURE = 'collaboration lifecycle initialization failed.';
+const CONNECTION_FAILURE = 'collaboration lifecycle connection failed.';
+const RECONNECT_FAILURE = 'collaboration lifecycle reconnect failed.';
+const TEARDOWN_FAILURE = 'collaboration lifecycle teardown failed.';
+const RESOURCE_VALIDATION_ERRORS = new WeakSet();
+
+/** Marker used by repository contracts to keep this lifecycle example out of runtime authority. */
+export const REFERENCE_ONLY = true;
+
+class ResourceValidationError extends TypeError {
+ constructor(message) {
+ super(message);
+ this.name = 'ResourceValidationError';
+ RESOURCE_VALIDATION_ERRORS.add(this);
+ }
+}
+
+function requireContextString(value, label) {
+ if (
+ typeof value !== 'string' ||
+ value.length === 0 ||
+ value.length > MAX_CONTEXT_CODE_UNITS
+ ) {
+ throw new TypeError(`${label} is invalid.`);
+ }
+ return value;
+}
+
+function requireFactory(value, label) {
+ if (typeof value !== 'function') {
+ throw new TypeError(`${label} is invalid.`);
+ }
+ return value;
+}
+
+function readOwnDataRecord(source, keys, message) {
+ try {
+ if (typeof source !== 'object' || source === null) {
+ throw new TypeError(message);
+ }
+ const values = Object.create(null);
+ for (const key of keys) {
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
+ if (
+ descriptor === undefined ||
+ !Object.prototype.hasOwnProperty.call(descriptor, 'value')
+ ) {
+ throw new TypeError(message);
+ }
+ values[key] = descriptor.value;
+ }
+ const ownKeys = Reflect.ownKeys(source);
+ if (
+ ownKeys.length !== keys.length ||
+ ownKeys.some(
+ (key) =>
+ typeof key !== 'string' ||
+ !keys.some((candidate) => candidate === key),
+ )
+ ) {
+ throw new TypeError(message);
+ }
+ return values;
+ } catch {
+ throw new TypeError(message);
+ }
+}
+
+function findDataMethod(source, key, message) {
+ try {
+ if ((typeof source !== 'object' && typeof source !== 'function') || source === null) {
+ throw new ResourceValidationError(message);
+ }
+ let cursor = source;
+ let depth = 0;
+ while (cursor !== null) {
+ if (depth >= MAX_RESOURCE_PROTOTYPE_DEPTH) {
+ throw new ResourceValidationError(message);
+ }
+ const descriptor = Object.getOwnPropertyDescriptor(cursor, key);
+ if (descriptor !== undefined) {
+ if (
+ !Object.prototype.hasOwnProperty.call(descriptor, 'value') ||
+ typeof descriptor.value !== 'function'
+ ) {
+ throw new ResourceValidationError(message);
+ }
+ return descriptor.value;
+ }
+ cursor = Object.getPrototypeOf(cursor);
+ depth += 1;
+ }
+ throw new ResourceValidationError(message);
+ } catch {
+ throw new ResourceValidationError(message);
+ }
+}
+
+function requireDocument(value) {
+ const message = 'documentFactory returned an invalid document.';
+ return Object.freeze({
+ value,
+ destroy: findDataMethod(value, 'destroy', message),
+ });
+}
+
+function requireProvider(value) {
+ const message = 'providerFactory returned an invalid provider.';
+ return Object.freeze({
+ value,
+ connect: findDataMethod(value, 'connect', message),
+ disconnect: findDataMethod(value, 'disconnect', message),
+ destroy: findDataMethod(value, 'destroy', message),
+ });
+}
+
+function initializationFailure() {
+ return new Error(INITIALIZATION_FAILURE);
+}
+
+function connectionFailure() {
+ return new Error(CONNECTION_FAILURE);
+}
+
+function reconnectFailure() {
+ return new Error(RECONNECT_FAILURE);
+}
+
+function teardownFailure() {
+ return new Error(TEARDOWN_FAILURE);
+}
+
+function consumeNonVoidResult(result) {
+ if (result === undefined) return false;
+ void Promise.resolve(result).catch(() => undefined);
+ return true;
+}
+
+/**
+ * Create one provider-neutral collaboration lifecycle owned entirely by the host.
+ *
+ * The host supplies the document/provider factories and authorized room/actor
+ * context. Inkspan receives the resulting stable document/provider references;
+ * it does not create, reconnect, disconnect, or destroy either resource. Option
+ * fields and resource methods are captured from data descriptors so lifecycle
+ * validation never executes accessor-backed host objects. Initial document and
+ * provider callback failures are payload-redacted, acquired documents unwind on
+ * initial provider failure, reconnect provider-construction failures remain
+ * retryable, and connect failures quarantine the indeterminate provider until
+ * reconnect/dispose tears it down. The reference lifecycle is intentionally
+ * synchronous: any non-void lifecycle callback result is treated as indeterminate,
+ * its promise/thenable settlement is consumed, and the operation fails closed
+ * instead of reporting false success. Cleanup failures do not prevent remaining
+ * teardown attempts. A failed provider or document destruction keeps only the
+ * incomplete cleanup live so a later dispose() retries it without repeating
+ * already-successful teardown. Once disposal starts, connect/reconnect stay closed
+ * while cleanup is pending.
+ */
+export function createHostCollaborationLifecycle(source) {
+ const options = readOwnDataRecord(
+ source,
+ ['documentFactory', 'providerFactory', 'roomId', 'actorId'],
+ 'collaboration options are invalid.',
+ );
+ const createDocument = requireFactory(options.documentFactory, 'documentFactory');
+ const createProvider = requireFactory(options.providerFactory, 'providerFactory');
+ const boundedRoomId = requireContextString(options.roomId, 'roomId');
+ const boundedActorId = requireContextString(options.actorId, 'actorId');
+ let documentCandidate;
+ try {
+ documentCandidate = createDocument();
+ } catch {
+ throw initializationFailure();
+ }
+ const documentResource = requireDocument(documentCandidate);
+ const document = documentResource.value;
+
+ let providerGeneration = 0;
+ let providerResource = null;
+ let connected = false;
+ let providerConnectionIndeterminate = false;
+ let documentDestroyed = false;
+ let disposeStarted = false;
+ let disposed = false;
+
+ function makeProvider(privateFailure) {
+ providerGeneration += 1;
+ let candidate;
+ try {
+ candidate = createProvider({
+ document,
+ roomId: boundedRoomId,
+ actorId: boundedActorId,
+ generation: providerGeneration,
+ });
+ } catch {
+ throw privateFailure();
+ }
+ providerResource = requireProvider(candidate);
+ connected = false;
+ providerConnectionIndeterminate = false;
+ }
+
+ function requireLive() {
+ if (disposed) {
+ throw new Error('collaboration lifecycle is disposed.');
+ }
+ if (disposeStarted) {
+ throw teardownFailure();
+ }
+ }
+
+ function connect() {
+ requireLive();
+ if (providerConnectionIndeterminate) throw connectionFailure();
+ if (connected) return false;
+ let connectionResult;
+ try {
+ connectionResult = providerResource.connect.call(providerResource.value);
+ } catch {
+ providerConnectionIndeterminate = true;
+ throw connectionFailure();
+ }
+ if (consumeNonVoidResult(connectionResult)) {
+ providerConnectionIndeterminate = true;
+ throw connectionFailure();
+ }
+ connected = true;
+ return true;
+ }
+
+ function teardownProvider() {
+ const resource = providerResource;
+ if (resource === null) return;
+ let failed = false;
+ let destroyFailed = false;
+ if (connected || providerConnectionIndeterminate) {
+ connected = false;
+ try {
+ const disconnectResult = resource.disconnect.call(resource.value);
+ if (consumeNonVoidResult(disconnectResult)) {
+ failed = true;
+ providerConnectionIndeterminate = true;
+ } else {
+ providerConnectionIndeterminate = false;
+ }
+ } catch {
+ failed = true;
+ providerConnectionIndeterminate = true;
+ }
+ }
+ try {
+ const destroyResult = resource.destroy.call(resource.value);
+ if (consumeNonVoidResult(destroyResult)) {
+ failed = true;
+ destroyFailed = true;
+ }
+ } catch {
+ failed = true;
+ destroyFailed = true;
+ }
+ if (!destroyFailed) {
+ providerResource = null;
+ providerConnectionIndeterminate = false;
+ }
+ if (failed) throw teardownFailure();
+ }
+
+ function reconnect() {
+ requireLive();
+ teardownProvider();
+ makeProvider(reconnectFailure);
+ connect();
+ return getSnapshot();
+ }
+
+ function dispose() {
+ if (disposed) return false;
+ disposeStarted = true;
+ let failed = false;
+ try {
+ teardownProvider();
+ } catch {
+ failed = true;
+ }
+ if (!documentDestroyed) {
+ try {
+ const destroyResult = documentResource.destroy.call(document);
+ if (consumeNonVoidResult(destroyResult)) {
+ failed = true;
+ } else {
+ documentDestroyed = true;
+ }
+ } catch {
+ failed = true;
+ }
+ }
+ disposed = providerResource === null && documentDestroyed;
+ if (failed || !disposed) throw teardownFailure();
+ return true;
+ }
+
+ function getSnapshot() {
+ return Object.freeze({
+ status: disposed ? 'disposed' : connected ? 'connected' : 'disconnected',
+ providerGeneration,
+ });
+ }
+
+ try {
+ makeProvider(initializationFailure);
+ } catch (error) {
+ let cleanupFailed = false;
+ try {
+ cleanupFailed = consumeNonVoidResult(documentResource.destroy.call(document));
+ } catch {
+ cleanupFailed = true;
+ }
+ if (RESOURCE_VALIDATION_ERRORS.has(error) && !cleanupFailed) {
+ throw error;
+ }
+ throw initializationFailure();
+ }
+
+ return Object.freeze({
+ document,
+ connect,
+ reconnect,
+ dispose,
+ getSnapshot,
+ });
+}
+
+function runSelfTest() {
+ const events = [];
+ let providerCounter = 0;
+ let firstProviderDocument = null;
+ let sameDocumentAcrossReconnect = true;
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ events.push('document:create');
+ const document = new Doc();
+ document.getText('document').insert(0, 'Buyer draft');
+ document.on('destroy', () => {
+ events.push('document:destroy');
+ });
+ return document;
+ },
+ providerFactory({ document }) {
+ providerCounter += 1;
+ const generation = providerCounter;
+ if (!(document instanceof Doc)) {
+ throw new TypeError('reference host must supply a Y.Doc.');
+ }
+ if (firstProviderDocument === null) {
+ firstProviderDocument = document;
+ } else if (document !== firstProviderDocument) {
+ sameDocumentAcrossReconnect = false;
+ }
+ events.push(`provider:create:${generation}`);
+ return {
+ connect() {
+ events.push(`provider:connect:${generation}`);
+ },
+ disconnect() {
+ events.push(`provider:disconnect:${generation}`);
+ },
+ destroy() {
+ events.push(`provider:destroy:${generation}`);
+ },
+ };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+
+ lifecycle.connect();
+ lifecycle.reconnect();
+ const hostDocumentIsYjs = lifecycle.document instanceof Doc;
+ const yjsText = lifecycle.document.getText('document').toString();
+ lifecycle.dispose();
+ lifecycle.dispose();
+
+ process.stdout.write(
+ `${JSON.stringify({
+ events,
+ hostDocumentIsYjs,
+ ...lifecycle.getSnapshot(),
+ sameDocumentAcrossReconnect,
+ yjsText,
+ })}\n`,
+ );
+}
+
+function runHostileAccessorSelfTest() {
+ let optionsGetterCalls = 0;
+ let optionsError = null;
+ const hostileOptions = {
+ providerFactory() {
+ return { connect() {}, disconnect() {}, destroy() {} };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ };
+ Object.defineProperty(hostileOptions, 'documentFactory', {
+ enumerable: true,
+ get() {
+ optionsGetterCalls += 1;
+ return () => ({ destroy() {} });
+ },
+ });
+ try {
+ createHostCollaborationLifecycle(hostileOptions);
+ } catch (error) {
+ optionsError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ let documentGetterCalls = 0;
+ let documentError = null;
+ try {
+ createHostCollaborationLifecycle({
+ documentFactory() {
+ const hostileDocument = {};
+ Object.defineProperty(hostileDocument, 'destroy', {
+ enumerable: true,
+ get() {
+ documentGetterCalls += 1;
+ return () => undefined;
+ },
+ });
+ return hostileDocument;
+ },
+ providerFactory() {
+ return { connect() {}, disconnect() {}, destroy() {} };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+ } catch (error) {
+ documentError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ let providerGetterCalls = 0;
+ let providerError = null;
+ try {
+ createHostCollaborationLifecycle({
+ documentFactory() {
+ return { destroy() {} };
+ },
+ providerFactory() {
+ const hostileProvider = { disconnect() {}, destroy() {} };
+ Object.defineProperty(hostileProvider, 'connect', {
+ enumerable: true,
+ get() {
+ providerGetterCalls += 1;
+ return () => undefined;
+ },
+ });
+ return hostileProvider;
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+ } catch (error) {
+ providerError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ process.stdout.write(
+ `${JSON.stringify({
+ documentError,
+ documentGetterCalls,
+ optionsError,
+ optionsGetterCalls,
+ providerError,
+ providerGetterCalls,
+ })}\n`,
+ );
+}
+
+function runInitializationFailureSelfTest() {
+ const privateCause = 'private-provider-construction-cause';
+ const events = [];
+ let error = null;
+ try {
+ createHostCollaborationLifecycle({
+ documentFactory() {
+ events.push('document:create');
+ return {
+ destroy() {
+ events.push('document:destroy');
+ },
+ };
+ },
+ providerFactory() {
+ events.push('provider:create');
+ throw new Error(privateCause);
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+ } catch (failure) {
+ error = failure instanceof Error ? failure.message : 'unexpected error';
+ }
+ process.stdout.write(
+ `${JSON.stringify({
+ error,
+ events,
+ leakedPrivateCause: typeof error === 'string' && error.includes(privateCause),
+ })}\n`,
+ );
+}
+
+function runCleanupFailureSelfTest() {
+ const privateCause = 'private-provider-disconnect-cause';
+ const events = [];
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ return {
+ destroy() {
+ events.push('document:destroy');
+ },
+ };
+ },
+ providerFactory() {
+ return {
+ connect() {
+ events.push('provider:connect');
+ },
+ disconnect() {
+ events.push('provider:disconnect');
+ throw new Error(privateCause);
+ },
+ destroy() {
+ events.push('provider:destroy');
+ },
+ };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+ lifecycle.connect();
+ let error = null;
+ try {
+ lifecycle.dispose();
+ } catch (failure) {
+ error = failure instanceof Error ? failure.message : 'unexpected error';
+ }
+ process.stdout.write(
+ `${JSON.stringify({
+ error,
+ events,
+ leakedPrivateCause: typeof error === 'string' && error.includes(privateCause),
+ status: lifecycle.getSnapshot().status,
+ })}\n`,
+ );
+}
+
+if (process.argv.includes('--cleanup-failure-self-test')) {
+ runCleanupFailureSelfTest();
+} else if (process.argv.includes('--initialization-failure-self-test')) {
+ runInitializationFailureSelfTest();
+} else if (process.argv.includes('--hostile-accessor-self-test')) {
+ runHostileAccessorSelfTest();
+} else if (process.argv.includes('--self-test')) {
+ runSelfTest();
+}
diff --git a/examples/reference-host/delayed-proposal.mjs b/examples/reference-host/delayed-proposal.mjs
new file mode 100644
index 00000000..d2d0e770
--- /dev/null
+++ b/examples/reference-host/delayed-proposal.mjs
@@ -0,0 +1,393 @@
+const MAX_REVISION_CODE_UNITS = 256;
+const MAX_PROPOSAL_CODE_UNITS = 65_536;
+
+/** Marker used by repository contracts to prevent this fixture being mistaken for a production model adapter. */
+export const REFERENCE_ONLY = true;
+
+function requireBoundedString(value, maximumCodeUnits, label) {
+ if (
+ typeof value !== 'string' ||
+ value.length === 0 ||
+ value.length > maximumCodeUnits
+ ) {
+ throw new TypeError(`${label} is invalid.`);
+ }
+ return value;
+}
+
+function requireBoundedReplacement(value) {
+ if (typeof value !== 'string' || value.length > MAX_PROPOSAL_CODE_UNITS) {
+ throw new TypeError('replacement is invalid.');
+ }
+ return value;
+}
+
+function readPlainDataRecord(source, keys, message) {
+ try {
+ if (
+ typeof source !== 'object' ||
+ source === null ||
+ Object.getPrototypeOf(source) !== Object.prototype
+ ) {
+ throw new TypeError(message);
+ }
+
+ const values = Object.create(null);
+ for (const key of keys) {
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
+ if (
+ descriptor === undefined ||
+ !Object.prototype.hasOwnProperty.call(descriptor, 'value')
+ ) {
+ throw new TypeError(message);
+ }
+ values[key] = descriptor.value;
+ }
+
+ const ownKeys = Reflect.ownKeys(source);
+ if (
+ ownKeys.length !== keys.length ||
+ ownKeys.some(
+ (key) =>
+ typeof key !== 'string' ||
+ !keys.some((candidate) => candidate === key),
+ )
+ ) {
+ throw new TypeError(message);
+ }
+
+ return values;
+ } catch {
+ throw new TypeError(message);
+ }
+}
+
+/**
+ * Produce one deterministic asynchronous proposal bound to the revision captured by the host.
+ *
+ * This fixture deliberately contains no provider SDK, credential, prompt log, or remote call.
+ * Real hosts replace proposal generation with an approved model boundary while preserving the
+ * expectedRevision conflict gate before applying untrusted proposal data. Candidate fields are
+ * snapshotted from an exact own-data-property shape without invoking caller-owned accessors.
+ */
+export async function createDelayedProposal(source) {
+ const input = readPlainDataRecord(
+ source,
+ ['expectedRevision', 'replacement'],
+ 'proposal creation is invalid.',
+ );
+ const boundedRevision = requireBoundedString(
+ input.expectedRevision,
+ MAX_REVISION_CODE_UNITS,
+ 'expectedRevision',
+ );
+ const boundedReplacement = requireBoundedReplacement(input.replacement);
+
+ await Promise.resolve();
+ return Object.freeze({
+ expectedRevision: boundedRevision,
+ replacement: boundedReplacement,
+ });
+}
+
+/**
+ * Apply one untrusted proposal only when the host's current revision still matches its capture.
+ * Top-level application metadata and model proposal fields must have an exact own-data-property
+ * shape so validation never executes accessor-backed untrusted proposal data or silently admits
+ * unknown authority-looking metadata. Host apply failures are normalized at this reference
+ * boundary so private callback causes are not reflected outward.
+ */
+export function applyDelayedProposal(source) {
+ const application = readPlainDataRecord(
+ source,
+ ['proposal', 'currentRevision', 'apply'],
+ 'proposal application is invalid.',
+ );
+ if (typeof application.apply !== 'function') {
+ throw new TypeError('proposal application is invalid.');
+ }
+ const proposal = readPlainDataRecord(
+ application.proposal,
+ ['expectedRevision', 'replacement'],
+ 'proposal application is invalid.',
+ );
+ const boundedCurrentRevision = requireBoundedString(
+ application.currentRevision,
+ MAX_REVISION_CODE_UNITS,
+ 'currentRevision',
+ );
+ const expectedRevision = requireBoundedString(
+ proposal.expectedRevision,
+ MAX_REVISION_CODE_UNITS,
+ 'expectedRevision',
+ );
+ const replacement = requireBoundedReplacement(proposal.replacement);
+
+ if (expectedRevision !== boundedCurrentRevision) {
+ return Object.freeze({ status: 'conflict' });
+ }
+
+ let applicationResult;
+ try {
+ applicationResult = application.apply(replacement);
+ } catch {
+ throw new Error('proposal application failed.');
+ }
+
+ if (
+ (typeof applicationResult === 'object' && applicationResult !== null) ||
+ typeof applicationResult === 'function'
+ ) {
+ let then;
+ try {
+ then = applicationResult.then;
+ } catch {
+ throw new Error('proposal application failed.');
+ }
+
+ if (typeof then === 'function') {
+ return new Promise((resolve, reject) => {
+ try {
+ then.call(
+ applicationResult,
+ () => resolve(),
+ () => reject(),
+ );
+ } catch {
+ reject();
+ }
+ }).then(
+ () => Object.freeze({ status: 'applied' }),
+ () => {
+ throw new Error('proposal application failed.');
+ },
+ );
+ }
+ }
+
+ return Object.freeze({ status: 'applied' });
+}
+
+async function runSelfTest() {
+ let staleDocument = 'Original draft';
+ let staleRevision = 'revision-v1';
+ const staleProposalPromise = createDelayedProposal({
+ expectedRevision: staleRevision,
+ replacement: 'Stale proposal',
+ });
+
+ staleDocument = 'User typed newer text';
+ staleRevision = 'revision-v2';
+ const staleProposal = await staleProposalPromise;
+ const staleResult = applyDelayedProposal({
+ proposal: staleProposal,
+ currentRevision: staleRevision,
+ apply(replacement) {
+ staleDocument = replacement;
+ },
+ });
+
+ let acceptedDocument = 'Current draft';
+ const acceptedRevision = 'revision-v3';
+ const acceptedProposal = await createDelayedProposal({
+ expectedRevision: acceptedRevision,
+ replacement: 'Accepted proposal',
+ });
+ const acceptedResult = applyDelayedProposal({
+ proposal: acceptedProposal,
+ currentRevision: acceptedRevision,
+ apply(replacement) {
+ acceptedDocument = replacement;
+ },
+ });
+
+ process.stdout.write(
+ `${JSON.stringify({
+ acceptedDocument,
+ acceptedStatus: acceptedResult.status,
+ staleDocument,
+ staleStatus: staleResult.status,
+ })}\n`,
+ );
+}
+
+async function runEmptyProposalSelfTest() {
+ const proposal = await createDelayedProposal({
+ expectedRevision: 'revision-v1',
+ replacement: '',
+ });
+ let appliedDocument = 'Non-empty draft';
+ const result = applyDelayedProposal({
+ proposal,
+ currentRevision: 'revision-v1',
+ apply(replacement) {
+ appliedDocument = replacement;
+ },
+ });
+
+ let emptyRevisionError = null;
+ try {
+ await createDelayedProposal({
+ expectedRevision: '',
+ replacement: '',
+ });
+ } catch (error) {
+ emptyRevisionError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ process.stdout.write(
+ `${JSON.stringify({
+ appliedDocument,
+ appliedStatus: result.status,
+ emptyRevisionError,
+ proposalReplacement: proposal.replacement,
+ })}\n`,
+ );
+}
+
+async function runHostileAccessorSelfTest() {
+ let creationGetterCalls = 0;
+ let creationError = null;
+ const hostileCreation = { replacement: 'Hostile proposal' };
+ Object.defineProperty(hostileCreation, 'expectedRevision', {
+ enumerable: true,
+ get() {
+ creationGetterCalls += 1;
+ return 'revision-v1';
+ },
+ });
+ try {
+ await createDelayedProposal(hostileCreation);
+ } catch (error) {
+ creationError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ const validProposal = await createDelayedProposal({
+ expectedRevision: 'revision-v1',
+ replacement: 'Valid proposal',
+ });
+ let applicationGetterCalls = 0;
+ let applicationError = null;
+ const hostileApplication = {
+ proposal: validProposal,
+ currentRevision: 'revision-v1',
+ };
+ Object.defineProperty(hostileApplication, 'apply', {
+ enumerable: true,
+ get() {
+ applicationGetterCalls += 1;
+ return () => undefined;
+ },
+ });
+ try {
+ applyDelayedProposal(hostileApplication);
+ } catch (error) {
+ applicationError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ let proposalGetterCalls = 0;
+ let proposalError = null;
+ const hostileProposal = { replacement: 'Hostile proposal' };
+ Object.defineProperty(hostileProposal, 'expectedRevision', {
+ enumerable: true,
+ get() {
+ proposalGetterCalls += 1;
+ return 'revision-v1';
+ },
+ });
+ try {
+ applyDelayedProposal({
+ proposal: hostileProposal,
+ currentRevision: 'revision-v1',
+ apply() {},
+ });
+ } catch (error) {
+ proposalError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ process.stdout.write(
+ `${JSON.stringify({
+ applicationError,
+ applicationGetterCalls,
+ creationError,
+ creationGetterCalls,
+ proposalError,
+ proposalGetterCalls,
+ })}\n`,
+ );
+}
+
+async function runUnknownFieldSelfTest() {
+ let creationError = null;
+ try {
+ await createDelayedProposal({
+ expectedRevision: 'revision-v1',
+ replacement: 'Untrusted proposal',
+ authorization: 'owner',
+ });
+ } catch (error) {
+ creationError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ const validProposal = await createDelayedProposal({
+ expectedRevision: 'revision-v1',
+ replacement: 'Valid proposal',
+ });
+ let applicationApplyCalls = 0;
+ let applicationError = null;
+ const applicationWithHiddenField = {
+ proposal: validProposal,
+ currentRevision: 'revision-v1',
+ apply() {
+ applicationApplyCalls += 1;
+ },
+ };
+ Object.defineProperty(applicationWithHiddenField, 'authorization', {
+ value: 'owner',
+ enumerable: false,
+ });
+ try {
+ applyDelayedProposal(applicationWithHiddenField);
+ } catch (error) {
+ applicationError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ let proposalApplyCalls = 0;
+ let proposalError = null;
+ const authorityKey = Symbol('authorization');
+ try {
+ applyDelayedProposal({
+ proposal: {
+ expectedRevision: 'revision-v1',
+ replacement: 'Untrusted proposal',
+ [authorityKey]: 'owner',
+ },
+ currentRevision: 'revision-v1',
+ apply() {
+ proposalApplyCalls += 1;
+ },
+ });
+ } catch (error) {
+ proposalError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ process.stdout.write(
+ `${JSON.stringify({
+ applicationApplyCalls,
+ applicationError,
+ creationError,
+ proposalApplyCalls,
+ proposalError,
+ })}\n`,
+ );
+}
+
+if (process.argv.includes('--empty-proposal-self-test')) {
+ await runEmptyProposalSelfTest();
+} else if (process.argv.includes('--hostile-accessor-self-test')) {
+ await runHostileAccessorSelfTest();
+} else if (process.argv.includes('--unknown-field-self-test')) {
+ await runUnknownFieldSelfTest();
+} else if (process.argv.includes('--self-test')) {
+ await runSelfTest();
+}
diff --git a/examples/reference-host/host-authorized-collaboration.mjs b/examples/reference-host/host-authorized-collaboration.mjs
new file mode 100644
index 00000000..b44b3558
--- /dev/null
+++ b/examples/reference-host/host-authorized-collaboration.mjs
@@ -0,0 +1,205 @@
+const AUTHORIZATION_FAILURE = 'collaboration provider authorization failed.';
+const OPTIONS_FAILURE = 'collaboration authorization options are invalid.';
+const CONTEXT_FAILURE = 'collaboration provider context is invalid.';
+
+/** Marker used by repository contracts to keep this host example out of runtime authority. */
+export const REFERENCE_ONLY = true;
+
+function readExactOwnDataRecord(source, keys, message) {
+ try {
+ if (typeof source !== 'object' || source === null) {
+ throw new TypeError(message);
+ }
+ const values = Object.create(null);
+ for (const key of keys) {
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
+ if (
+ descriptor === undefined ||
+ !Object.prototype.hasOwnProperty.call(descriptor, 'value')
+ ) {
+ throw new TypeError(message);
+ }
+ values[key] = descriptor.value;
+ }
+ const ownKeys = Reflect.ownKeys(source);
+ if (
+ ownKeys.length !== keys.length ||
+ ownKeys.some(
+ (key) =>
+ typeof key !== 'string' ||
+ !keys.some((candidate) => candidate === key),
+ )
+ ) {
+ throw new TypeError(message);
+ }
+ return values;
+ } catch {
+ throw new TypeError(message);
+ }
+}
+
+function requireFunction(value, message) {
+ if (typeof value !== 'function') {
+ throw new TypeError(message);
+ }
+ return value;
+}
+
+function requireContextString(value) {
+ if (typeof value !== 'string' || value.length === 0 || value.length > 256) {
+ throw new TypeError(CONTEXT_FAILURE);
+ }
+ return value;
+}
+
+function requireGeneration(value) {
+ if (!Number.isSafeInteger(value) || value < 1) {
+ throw new TypeError(CONTEXT_FAILURE);
+ }
+ return value;
+}
+
+/**
+ * Wrap one host-owned collaboration provider factory with a synchronous host
+ * authorization decision. The authorization callback receives only the bounded
+ * room/actor/generation identity needed for admission; the host-owned Y.Doc is
+ * supplied only to the provider constructor after authorization succeeds.
+ *
+ * Inkspan does not authenticate or authorize here. This reference-only adapter
+ * demonstrates where an integrating host must enforce its own policy before a
+ * provider is constructed. Only the exact boolean `true` admits construction;
+ * false, thrown, asynchronous, malformed, or otherwise indeterminate decisions
+ * fail closed with a payload-redacted error.
+ */
+export function createHostAuthorizedProviderFactory(source) {
+ const options = readExactOwnDataRecord(
+ source,
+ ['authorize', 'createProvider'],
+ OPTIONS_FAILURE,
+ );
+ const authorize = requireFunction(options.authorize, OPTIONS_FAILURE);
+ const createProvider = requireFunction(options.createProvider, OPTIONS_FAILURE);
+
+ return function authorizedProviderFactory(sourceContext) {
+ const context = readExactOwnDataRecord(
+ sourceContext,
+ ['document', 'roomId', 'actorId', 'generation'],
+ CONTEXT_FAILURE,
+ );
+ const roomId = requireContextString(context.roomId);
+ const actorId = requireContextString(context.actorId);
+ const generation = requireGeneration(context.generation);
+ const authorizationContext = Object.freeze({ roomId, actorId, generation });
+
+ let decision;
+ try {
+ decision = authorize(authorizationContext);
+ } catch {
+ throw new Error(AUTHORIZATION_FAILURE);
+ }
+ if (decision !== true) {
+ throw new Error(AUTHORIZATION_FAILURE);
+ }
+
+ return createProvider(
+ Object.freeze({
+ document: context.document,
+ roomId,
+ actorId,
+ generation,
+ }),
+ );
+ };
+}
+
+function runSelfTest() {
+ const events = [];
+ const document = Object.freeze({ reference: 'host-owned-document' });
+ const authorizedFactory = createHostAuthorizedProviderFactory({
+ authorize(context) {
+ if (Object.prototype.hasOwnProperty.call(context, 'document')) {
+ throw new Error('authorization context must not receive the host document.');
+ }
+ events.push(
+ `authorize:${context.actorId}:${context.roomId}:${context.generation}`,
+ );
+ return true;
+ },
+ createProvider(context) {
+ if (context.document !== document) {
+ throw new Error('provider construction lost the host-owned document.');
+ }
+ events.push(`provider:create:${context.generation}`);
+ return Object.freeze({ generation: context.generation });
+ },
+ });
+
+ const provider1 = authorizedFactory({
+ document,
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ generation: 1,
+ });
+ const provider2 = authorizedFactory({
+ document,
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ generation: 2,
+ });
+
+ let deniedProviderConstructed = false;
+ let deniedError = null;
+ const deniedFactory = createHostAuthorizedProviderFactory({
+ authorize() {
+ events.push('authorize:denied');
+ return false;
+ },
+ createProvider() {
+ deniedProviderConstructed = true;
+ return {};
+ },
+ });
+ try {
+ deniedFactory({
+ document,
+ roomId: 'reference-room',
+ actorId: 'denied-actor',
+ generation: 3,
+ });
+ } catch (error) {
+ deniedError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ const expectedEvents = [
+ 'authorize:reference-actor:reference-room:1',
+ 'provider:create:1',
+ 'authorize:reference-actor:reference-room:2',
+ 'provider:create:2',
+ 'authorize:denied',
+ ];
+ if (
+ provider1.generation !== 1 ||
+ provider2.generation !== 2 ||
+ deniedProviderConstructed ||
+ deniedError !== AUTHORIZATION_FAILURE ||
+ events.length !== expectedEvents.length ||
+ events.some((event, index) => event !== expectedEvents[index])
+ ) {
+ throw new Error('host-authorized collaboration self-test failed.');
+ }
+
+ process.stdout.write(
+ `${JSON.stringify({
+ authorizationBeforeConstruction: true,
+ deniedProviderConstructed,
+ deniedError,
+ events,
+ hostDocumentPreserved: true,
+ status: 'completed',
+ })}\n`,
+ );
+}
+
+if (process.argv.includes('--self-test')) {
+ runSelfTest();
+}
diff --git a/examples/reference-host/hydration-gate.tsx b/examples/reference-host/hydration-gate.tsx
new file mode 100644
index 00000000..d027a6d1
--- /dev/null
+++ b/examples/reference-host/hydration-gate.tsx
@@ -0,0 +1,25 @@
+'use client';
+
+import { useEffect, useState, type ReactNode } from 'react';
+
+export interface ReferenceHostHydrationGateProps {
+ loadingLabel: string;
+ renderEditor: () => ReactNode;
+}
+
+export function ReferenceHostHydrationGate({
+ loadingLabel,
+ renderEditor,
+}: ReferenceHostHydrationGateProps) {
+ const [hydrated, setHydrated] = useState(false);
+
+ useEffect(() => {
+ setHydrated(true);
+ }, []);
+
+ if (!hydrated) {
+ return {loadingLabel}
;
+ }
+
+ return <>{renderEditor()}>;
+}
diff --git a/examples/reference-host/native-form-host.tsx b/examples/reference-host/native-form-host.tsx
new file mode 100644
index 00000000..9a17803c
--- /dev/null
+++ b/examples/reference-host/native-form-host.tsx
@@ -0,0 +1,184 @@
+import { useRef, useState, type FormEvent } from 'react';
+import { CwlEditor } from '@contextualwisdomlab/cwl-editor';
+import {
+ createSingleFlightSubmission,
+ shouldBlockReferenceHostFormMutation,
+ type ReferenceHostSubmissionState,
+} from './single-flight-submission.js';
+
+type SubmissionState = 'idle' | ReferenceHostSubmissionState;
+
+export interface NativeFormHostProps {
+ /**
+ * Host-owned authorization and durable persistence boundary.
+ * The reference component deliberately does not choose transport,
+ * credentials, tenancy, or storage for the embedding application.
+ */
+ onAuthorizedSubmit(messageBody: string): Promise | void;
+ /**
+ * Demonstration mode for the public controlled and uncontrolled editor APIs.
+ * This changes only local React ownership of the current value; durable
+ * persistence and authorization remain host-owned through onAuthorizedSubmit.
+ */
+ controlMode?: 'controlled' | 'uncontrolled';
+ /**
+ * Host-owned write permission presentation.
+ *
+ * A read-only host keeps the editor readable while fail-closing native form
+ * submission/reset and disabling the named form field. This is presentation
+ * evidence only; the embedding host remains responsible for authorization at
+ * the durable write boundary.
+ */
+ readOnly?: boolean;
+}
+
+/**
+ * Buyer-facing native-form integration example.
+ *
+ * Inkspan owns synchronization of the editor document into the native form
+ * control. The host reads FormData at submit time and then applies its own
+ * authorization and durable-persistence policy through onAuthorizedSubmit.
+ * Both the public controlled and uncontrolled editor compositions are supported
+ * without changing that boundary. Overlapping submissions and form resets are
+ * blocked by the synchronous single-flight admission gate while the host
+ * callback is in flight; React presentation state is not used as the mutation
+ * authority. Host read-only state additionally fail-closes native form writes
+ * and disables the named field without moving authorization authority into
+ * Inkspan. Document edits invalidate stale saved/failed presentation, including
+ * when a newer edit occurs while an older host-owned persistence attempt is in
+ * flight. The host observes both the editor value callback and bubbling native
+ * input as independent local mutation signals; either signal advances the
+ * monotonic document generation, and duplicate advances for one logical edit
+ * are harmless because generation equality—not the numeric delta—is the
+ * persistence freshness invariant. A later successful reset returns the host
+ * presentation to an explicitly unsaved state and restores the controlled
+ * example value when that mode is selected.
+ */
+export function NativeFormHost({
+ onAuthorizedSubmit,
+ controlMode = 'uncontrolled',
+ readOnly = false,
+}: NativeFormHostProps) {
+ const [submissionState, setSubmissionState] =
+ useState('idle');
+ const [controlledValue, setControlledValue] = useState('# Draft');
+ const documentGenerationRef = useRef(0);
+ const onAuthorizedSubmitRef = useRef(onAuthorizedSubmit);
+ onAuthorizedSubmitRef.current = onAuthorizedSubmit;
+
+ const submitAuthorizedRef = useRef<
+ ReturnType | null
+ >(null);
+ const existingSubmitAuthorized = submitAuthorizedRef.current;
+ const submitAuthorized =
+ existingSubmitAuthorized ??
+ createSingleFlightSubmission(
+ (messageBody) => onAuthorizedSubmitRef.current(messageBody),
+ setSubmissionState,
+ );
+ if (existingSubmitAuthorized === null) {
+ submitAuthorizedRef.current = submitAuthorized;
+ }
+
+ function markDocumentDirty() {
+ documentGenerationRef.current += 1;
+ setSubmissionState((state) => (state === 'saving' ? state : 'idle'));
+ }
+
+ function handleDocumentChange(nextValue: string) {
+ markDocumentDirty();
+ if (controlMode === 'controlled') {
+ setControlledValue(nextValue);
+ }
+ }
+
+ function handleNativeInput() {
+ markDocumentDirty();
+ }
+
+ async function handleSubmit(event: FormEvent) {
+ event.preventDefault();
+ if (
+ shouldBlockReferenceHostFormMutation(
+ readOnly,
+ submitAuthorized.isInFlight,
+ )
+ ) {
+ return;
+ }
+
+ const messageBodyEntry = new FormData(event.currentTarget).get(
+ 'message_body',
+ );
+ if (typeof messageBodyEntry !== 'string') {
+ setSubmissionState('failed');
+ return;
+ }
+
+ const submittedGeneration = documentGenerationRef.current;
+ await submitAuthorized(messageBodyEntry);
+ if (documentGenerationRef.current !== submittedGeneration) {
+ setSubmissionState('idle');
+ }
+ }
+
+ function handleReset(event: FormEvent) {
+ if (
+ shouldBlockReferenceHostFormMutation(
+ readOnly,
+ submitAuthorized.isInFlight,
+ )
+ ) {
+ event.preventDefault();
+ return;
+ }
+ documentGenerationRef.current += 1;
+ if (controlMode === 'controlled') {
+ setControlledValue('# Draft');
+ }
+ setSubmissionState('idle');
+ }
+
+ return (
+
+ );
+}
diff --git a/examples/reference-host/office-handoff.mjs b/examples/reference-host/office-handoff.mjs
new file mode 100644
index 00000000..99716c3a
--- /dev/null
+++ b/examples/reference-host/office-handoff.mjs
@@ -0,0 +1,54 @@
+import { markdownToPlainText } from '@contextualwisdomlab/cwl-editor/markdown';
+
+function requireNonEmptyString(value, fieldName) {
+ if (typeof value !== 'string' || !/\S/u.test(value)) {
+ throw new TypeError(`${fieldName} must be a non-empty string.`);
+ }
+ return value;
+}
+
+function readOfficeHandoffInput(input) {
+ try {
+ return {
+ title: input.title,
+ markdown: input.markdown,
+ };
+ } catch {
+ throw new TypeError('Office handoff input is invalid.');
+ }
+}
+
+/**
+ * Create a bounded reference-only DOCX request from editor Markdown.
+ *
+ * The host deliberately projects Markdown to plain text through Inkspan's
+ * React-free public package surface, preserves that projection's deterministic
+ * block boundaries as separate DOCX paragraphs, and then constructs the strict
+ * Office request. This example does not claim Markdown-to-OOXML round-trip
+ * fidelity, perform Office rendering, authorize export, choose a filesystem
+ * path, or persist/distribute the resulting artifact; those remain host
+ * responsibilities. Host input reflection failures are normalized at this
+ * boundary instead of exposing caller-controlled exception values.
+ *
+ * @param {{ title: string, markdown: string }} input host-owned export input
+ * @returns {{ format: 'docx', title: string, blocks: readonly { type: 'paragraph', text: string }[] }}
+ */
+export function createReferenceDocxRequest(input) {
+ const { title, markdown } = readOfficeHandoffInput(input);
+ const acceptedTitle = requireNonEmptyString(title, 'title');
+ if (typeof markdown !== 'string') {
+ throw new TypeError('markdown must be a string.');
+ }
+
+ const text = markdownToPlainText(markdown);
+ const paragraphs = text.split('\n\n');
+ const blocks = Object.freeze(
+ paragraphs.map((paragraphText) => Object.freeze({ type: 'paragraph', text: paragraphText })),
+ );
+
+ return Object.freeze({
+ format: 'docx',
+ title: acceptedTitle,
+ blocks,
+ });
+}
diff --git a/examples/reference-host/presentation-full.css b/examples/reference-host/presentation-full.css
new file mode 100644
index 00000000..34f8fdd9
--- /dev/null
+++ b/examples/reference-host/presentation-full.css
@@ -0,0 +1,2 @@
+@import '@contextualwisdomlab/cwl-editor/styles.css';
+@import '@contextualwisdomlab/cwl-editor/fonts.css';
diff --git a/examples/reference-host/presentation-latin.css b/examples/reference-host/presentation-latin.css
new file mode 100644
index 00000000..17f95d3e
--- /dev/null
+++ b/examples/reference-host/presentation-latin.css
@@ -0,0 +1,2 @@
+@import '@contextualwisdomlab/cwl-editor/styles.css';
+@import '@contextualwisdomlab/cwl-editor/fonts-latin.css';
diff --git a/examples/reference-host/reference-host-app.tsx b/examples/reference-host/reference-host-app.tsx
new file mode 100644
index 00000000..8b3a268a
--- /dev/null
+++ b/examples/reference-host/reference-host-app.tsx
@@ -0,0 +1,33 @@
+import {
+ ReferenceHostClient,
+ type ReferenceHostClientProps,
+} from './reference-host-client.js';
+
+export interface ReferenceHostAppProps extends ReferenceHostClientProps {}
+
+/**
+ * Minimal buyer-facing application composition for the reference host.
+ *
+ * This module is deliberately server-safe: it owns only deterministic host
+ * chrome and delegates browser-only hydration/editor behavior to the narrow
+ * ReferenceHostClient boundary. Authorization and durable persistence remain
+ * behind the host-supplied onAuthorizedSubmit callback.
+ */
+export function ReferenceHostApp({
+ loadingLabel,
+ onAuthorizedSubmit,
+ controlMode = 'uncontrolled',
+ readOnly = false,
+}: ReferenceHostAppProps) {
+ return (
+
+ Inkspan reference host
+
+
+ );
+}
diff --git a/examples/reference-host/reference-host-client.tsx b/examples/reference-host/reference-host-client.tsx
new file mode 100644
index 00000000..2be2db2f
--- /dev/null
+++ b/examples/reference-host/reference-host-client.tsx
@@ -0,0 +1,40 @@
+'use client';
+
+import { ReferenceHostHydrationGate } from './hydration-gate.js';
+import {
+ NativeFormHost,
+ type NativeFormHostProps,
+} from './native-form-host.js';
+
+export interface ReferenceHostClientProps extends NativeFormHostProps {
+ /** Host-localized label rendered before the interactive editor hydrates. */
+ loadingLabel: string;
+}
+
+/**
+ * Narrow client boundary for the reference-host editor composition.
+ *
+ * Browser-only hydration and the native form/editor lifecycle stay behind this
+ * boundary. The embedding host still owns authorization and durable persistence
+ * through onAuthorizedSubmit; this component adds no transport, credential, or
+ * storage authority.
+ */
+export function ReferenceHostClient({
+ loadingLabel,
+ onAuthorizedSubmit,
+ controlMode,
+ readOnly,
+}: ReferenceHostClientProps) {
+ return (
+ (
+
+ )}
+ />
+ );
+}
diff --git a/examples/reference-host/single-flight-submission.ts b/examples/reference-host/single-flight-submission.ts
new file mode 100644
index 00000000..838a6070
--- /dev/null
+++ b/examples/reference-host/single-flight-submission.ts
@@ -0,0 +1,73 @@
+export type ReferenceHostSubmissionState = 'saving' | 'saved' | 'failed';
+
+export type AuthorizedSubmit = (
+ messageBody: string,
+) => Promise | void;
+
+export type SubmissionStateObserver = (
+ state: ReferenceHostSubmissionState,
+) => void;
+
+/**
+ * Return whether a reference-host native-form write must be rejected now.
+ *
+ * Read-only host policy and the synchronous durable-submission admission gate
+ * are the only authority inputs. Presentation state is deliberately excluded so
+ * a submit/reset event in the same turn cannot race a deferred React commit.
+ */
+export function shouldBlockReferenceHostFormMutation(
+ readOnly: boolean,
+ isDurableSubmissionInFlight: () => boolean,
+): boolean {
+ return readOnly || isDurableSubmissionInFlight();
+}
+
+/**
+ * Serialize host-owned authorized persistence attempts without assuming any
+ * transport or storage authority in Inkspan.
+ *
+ * Overlapping attempts are rejected while one host callback is in flight.
+ * The returned callable also exposes the synchronous admission gate so event
+ * handlers can reject same-turn mutations without depending on deferred UI
+ * state. Host failures are reduced to a stable boolean/state signal so private
+ * durable-store details do not cross the reference component boundary.
+ * Presentation-state observer failures are best-effort and cannot block,
+ * reclassify, or wedge the host-owned persistence attempt.
+ */
+export function createSingleFlightSubmission(
+ onAuthorizedSubmit: AuthorizedSubmit,
+ onStateChange: SubmissionStateObserver,
+) {
+ let inFlight = false;
+
+ const notifyState = (state: ReferenceHostSubmissionState) => {
+ try {
+ onStateChange(state);
+ } catch {
+ // Presentation observation must not acquire persistence authority.
+ }
+ };
+
+ const submit = async (messageBody: string): Promise => {
+ if (inFlight) {
+ return false;
+ }
+
+ inFlight = true;
+ notifyState('saving');
+ try {
+ await onAuthorizedSubmit(messageBody);
+ notifyState('saved');
+ return true;
+ } catch {
+ notifyState('failed');
+ return false;
+ } finally {
+ inFlight = false;
+ }
+ };
+
+ return Object.assign(submit, {
+ isInFlight: (): boolean => inFlight,
+ });
+}
diff --git a/examples/reference-host/synthetic-document-repository.mjs b/examples/reference-host/synthetic-document-repository.mjs
new file mode 100644
index 00000000..e8734b99
--- /dev/null
+++ b/examples/reference-host/synthetic-document-repository.mjs
@@ -0,0 +1,559 @@
+const MAX_DOCUMENT_ID_CODE_UNITS = 256;
+const MAX_DOCUMENT_CODE_UNITS = 65_536;
+
+/** Marker used by repository contracts to prevent this fixture being mistaken for a production adapter. */
+export const REFERENCE_ONLY = true;
+
+/** Stable failure raised by the synthetic reference persistence adapter. */
+export class ReferencePersistenceError extends Error {
+ constructor(code) {
+ super(`Reference persistence ${code}.`);
+ this.name = 'ReferencePersistenceError';
+ this.code = code;
+ Object.freeze(this);
+ }
+}
+
+function requireBoundedString(value, maximumCodeUnits, code) {
+ if (
+ typeof value !== 'string' ||
+ value.length === 0 ||
+ value.length > maximumCodeUnits
+ ) {
+ throw new ReferencePersistenceError(code);
+ }
+ return value;
+}
+
+function requireBoundedDocument(value, code) {
+ if (typeof value !== 'string' || value.length > MAX_DOCUMENT_CODE_UNITS) {
+ throw new ReferencePersistenceError(code);
+ }
+ return value;
+}
+
+function readPlainDataRecord(source, requiredKeys, optionalKeys, code) {
+ try {
+ if (
+ typeof source !== 'object' ||
+ source === null ||
+ Object.getPrototypeOf(source) !== Object.prototype
+ ) {
+ throw new ReferencePersistenceError(code);
+ }
+
+ const values = Object.create(null);
+ for (const key of requiredKeys) {
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
+ if (
+ descriptor === undefined ||
+ !Object.prototype.hasOwnProperty.call(descriptor, 'value')
+ ) {
+ throw new ReferencePersistenceError(code);
+ }
+ values[key] = descriptor.value;
+ }
+ for (const key of optionalKeys) {
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
+ if (descriptor === undefined) continue;
+ if (!Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
+ throw new ReferencePersistenceError(code);
+ }
+ values[key] = descriptor.value;
+ }
+
+ const allowedKeys = [...requiredKeys, ...optionalKeys];
+ if (
+ Reflect.ownKeys(source).some(
+ (key) => typeof key !== 'string' || !allowedKeys.includes(key),
+ )
+ ) {
+ throw new ReferencePersistenceError(code);
+ }
+
+ return values;
+ } catch {
+ throw new ReferencePersistenceError(code);
+ }
+}
+
+function validatorForVersion(version, validatorNamespace) {
+ const prefix = validatorNamespace === '' ? '' : `${validatorNamespace}-`;
+ return `"${prefix}v${version}"`;
+}
+
+function frozenRead(document, validator) {
+ return Object.freeze({ document, validator });
+}
+
+function frozenSave(status, validator) {
+ return Object.freeze({ status, validator });
+}
+
+function frozenConflict(currentValidator) {
+ return Object.freeze({ status: 'conflict', currentValidator });
+}
+
+function createRepository(configuration, validatorNamespace, forkAuthority) {
+ const documentId = requireBoundedString(
+ configuration.documentId,
+ MAX_DOCUMENT_ID_CODE_UNITS,
+ 'invalid_document_id',
+ );
+ let document = requireBoundedDocument(
+ configuration.initialDocument,
+ 'invalid_document',
+ );
+ let version = 1;
+ let validator = validatorForVersion(version, validatorNamespace);
+
+ function assertDocumentId(candidate) {
+ if (candidate !== documentId) {
+ throw new ReferencePersistenceError('document_not_found');
+ }
+ }
+
+ function read(candidateDocumentId) {
+ assertDocumentId(candidateDocumentId);
+ return frozenRead(document, validator);
+ }
+
+ function save(request) {
+ const candidate = readPlainDataRecord(
+ request,
+ ['documentId', 'document', 'ifMatch'],
+ ['outcome'],
+ 'invalid_request',
+ );
+
+ assertDocumentId(candidate.documentId);
+ const nextDocument = requireBoundedDocument(
+ candidate.document,
+ 'invalid_document',
+ );
+ const ifMatch = requireBoundedString(
+ candidate.ifMatch,
+ 256,
+ 'invalid_if_match',
+ );
+ const outcome = candidate.outcome ?? 'saved';
+ if (
+ outcome !== 'saved' &&
+ outcome !== 'ambiguous_failure' &&
+ outcome !== 'ambiguous_commit_failure' &&
+ outcome !== 'failure'
+ ) {
+ throw new ReferencePersistenceError('invalid_outcome');
+ }
+
+ if (outcome === 'ambiguous_failure') {
+ throw new ReferencePersistenceError('ambiguous_failure');
+ }
+ if (outcome === 'failure') {
+ throw new ReferencePersistenceError('failure');
+ }
+ if (ifMatch !== validator) {
+ return frozenConflict(validator);
+ }
+
+ document = nextDocument;
+ version += 1;
+ validator = validatorForVersion(version, validatorNamespace);
+ if (outcome === 'ambiguous_commit_failure') {
+ throw new ReferencePersistenceError('ambiguous_failure');
+ }
+ return frozenSave('saved', validator);
+ }
+
+ function fork(request) {
+ const candidate = readPlainDataRecord(
+ request,
+ ['documentId', 'forkDocumentId', 'ifMatch'],
+ [],
+ 'invalid_fork_request',
+ );
+
+ assertDocumentId(candidate.documentId);
+ const forkDocumentId = requireBoundedString(
+ candidate.forkDocumentId,
+ MAX_DOCUMENT_ID_CODE_UNITS,
+ 'invalid_fork_document_id',
+ );
+ const ifMatch = requireBoundedString(
+ candidate.ifMatch,
+ 256,
+ 'invalid_if_match',
+ );
+ if (ifMatch !== validator) {
+ return frozenConflict(validator);
+ }
+ if (forkDocumentId === documentId) {
+ throw new ReferencePersistenceError('invalid_fork_document_id');
+ }
+ if (!Number.isSafeInteger(forkAuthority.nextNamespace)) {
+ throw new ReferencePersistenceError('fork_namespace_exhausted');
+ }
+
+ const childNamespace = `f${forkAuthority.nextNamespace}`;
+ forkAuthority.nextNamespace += 1;
+ return Object.freeze({
+ status: 'forked',
+ repository: createRepository(
+ {
+ documentId: forkDocumentId,
+ initialDocument: document,
+ },
+ childNamespace,
+ forkAuthority,
+ ),
+ });
+ }
+
+ return Object.freeze({ fork, read, save });
+}
+
+/**
+ * Create an in-memory host-owned reference repository with exact If-Match semantics.
+ *
+ * This adapter is synthetic acquisition/support evidence only. Buyers must replace
+ * it with an authorized atomic durable store. Confirmed failures and the
+ * `ambiguous_failure` pre-commit fixture leave durable state unchanged. The
+ * `ambiguous_commit_failure` fixture deliberately commits before returning the
+ * same ambiguous error so consumers must re-read durable state instead of
+ * advancing or blindly reusing their last known validator. A confirmed fork
+ * requires the current strong validator and starts an independent repository at a
+ * fresh validator so source and fork cannot silently share revision authority.
+ * Configuration, save, and fork request fields are snapshotted from exact own
+ * data-property shapes without invoking caller-owned accessors or admitting
+ * unknown authority-looking metadata.
+ */
+export function createSyntheticDocumentRepository(options) {
+ const configuration = readPlainDataRecord(
+ options,
+ ['documentId', 'initialDocument'],
+ [],
+ 'invalid_options',
+ );
+ return createRepository(configuration, '', { nextNamespace: 1 });
+}
+
+function runSelfTest() {
+ const repository = createSyntheticDocumentRepository({
+ documentId: 'buyer-document',
+ initialDocument: 'Buyer draft v1',
+ });
+ const initial = repository.read('buyer-document');
+
+ let ambiguousFailureObserved = false;
+ try {
+ repository.save({
+ documentId: 'buyer-document',
+ document: 'Uncertain write',
+ ifMatch: initial.validator,
+ outcome: 'ambiguous_failure',
+ });
+ } catch (error) {
+ ambiguousFailureObserved =
+ error instanceof ReferencePersistenceError &&
+ error.code === 'ambiguous_failure';
+ }
+ if (!ambiguousFailureObserved) {
+ throw new Error('Synthetic ambiguous-failure evidence was not observed.');
+ }
+
+ const afterAmbiguous = repository.read('buyer-document');
+ if (
+ afterAmbiguous.document !== initial.document ||
+ afterAmbiguous.validator !== initial.validator
+ ) {
+ throw new Error('Ambiguous failure advanced synthetic durable state.');
+ }
+
+ const saved = repository.save({
+ documentId: 'buyer-document',
+ document: 'Buyer draft v2',
+ ifMatch: initial.validator,
+ });
+ if (saved.status !== 'saved') {
+ throw new Error('Synthetic save did not report success.');
+ }
+
+ const conflict = repository.save({
+ documentId: 'buyer-document',
+ document: 'Stale overwrite',
+ ifMatch: initial.validator,
+ });
+ if (conflict.status !== 'conflict') {
+ throw new Error('Synthetic stale If-Match write did not conflict.');
+ }
+
+ let failureObserved = false;
+ try {
+ repository.save({
+ documentId: 'buyer-document',
+ document: 'Buyer draft v3',
+ ifMatch: saved.validator,
+ outcome: 'failure',
+ });
+ } catch (error) {
+ failureObserved =
+ error instanceof ReferencePersistenceError && error.code === 'failure';
+ }
+ if (!failureObserved) {
+ throw new Error('Synthetic failure evidence was not observed.');
+ }
+
+ const afterFailure = repository.read('buyer-document');
+ if (
+ afterFailure.document !== 'Buyer draft v2' ||
+ afterFailure.validator !== saved.validator
+ ) {
+ throw new Error('Failure advanced synthetic durable state.');
+ }
+
+ const retried = repository.save({
+ documentId: 'buyer-document',
+ document: 'Buyer draft v3',
+ ifMatch: afterFailure.validator,
+ });
+ if (retried.status !== 'saved') {
+ throw new Error('Synthetic retry did not report success.');
+ }
+
+ const restored = repository.save({
+ documentId: 'buyer-document',
+ document: initial.document,
+ ifMatch: retried.validator,
+ });
+ if (restored.status !== 'saved') {
+ throw new Error('Synthetic restore did not report success.');
+ }
+
+ const forked = repository.fork({
+ documentId: 'buyer-document',
+ forkDocumentId: 'buyer-document-fork',
+ ifMatch: restored.validator,
+ });
+ if (forked.status !== 'forked') {
+ throw new Error('Synthetic fork did not report success.');
+ }
+ const forkInitial = forked.repository.read('buyer-document-fork');
+ const forkSaved = forked.repository.save({
+ documentId: 'buyer-document-fork',
+ document: 'Fork-only edit',
+ ifMatch: forkInitial.validator,
+ });
+ if (forkSaved.status !== 'saved') {
+ throw new Error('Synthetic fork save did not report success.');
+ }
+
+ const forkFinal = forked.repository.read('buyer-document-fork');
+ const sourceAfterFork = repository.read('buyer-document');
+ process.stdout.write(
+ `${JSON.stringify({
+ afterAmbiguousValidator: afterAmbiguous.validator,
+ afterFailureValidator: afterFailure.validator,
+ conflictCurrentValidator: conflict.currentValidator,
+ forkDocument: forkInitial.document,
+ forkFinalDocument: forkFinal.document,
+ forkInitialValidator: forkInitial.validator,
+ forkSavedValidator: forkSaved.validator,
+ initialValidator: initial.validator,
+ restoredValidator: restored.validator,
+ retrySavedValidator: retried.validator,
+ savedValidator: saved.validator,
+ sourceDocumentAfterFork: sourceAfterFork.document,
+ sourceValidatorAfterFork: sourceAfterFork.validator,
+ })}\n`,
+ );
+}
+
+function runEmptyDocumentSelfTest() {
+ const emptyRepository = createSyntheticDocumentRepository({
+ documentId: 'buyer-empty',
+ initialDocument: '',
+ });
+ const initialEmptyDocument = emptyRepository.read('buyer-empty').document;
+
+ const repository = createSyntheticDocumentRepository({
+ documentId: 'buyer-document',
+ initialDocument: 'Not empty',
+ });
+ const initial = repository.read('buyer-document');
+ const cleared = repository.save({
+ documentId: 'buyer-document',
+ document: '',
+ ifMatch: initial.validator,
+ });
+ if (cleared.status !== 'saved') {
+ throw new Error('Synthetic empty-document save did not report success.');
+ }
+ const afterClear = repository.read('buyer-document');
+
+ let emptyDocumentIdError = null;
+ try {
+ createSyntheticDocumentRepository({
+ documentId: '',
+ initialDocument: '',
+ });
+ } catch (error) {
+ emptyDocumentIdError =
+ error instanceof ReferencePersistenceError ? error.code : 'unexpected_error';
+ }
+
+ process.stdout.write(
+ `${JSON.stringify({
+ clearedDocument: afterClear.document,
+ clearedValidator: afterClear.validator,
+ emptyDocumentIdError,
+ initialEmptyDocument,
+ })}\n`,
+ );
+}
+
+function runHostileAccessorSelfTest() {
+ let optionGetterCalls = 0;
+ let optionErrorCode = null;
+ const hostileOptions = { initialDocument: 'Buyer draft v1' };
+ Object.defineProperty(hostileOptions, 'documentId', {
+ enumerable: true,
+ get() {
+ optionGetterCalls += 1;
+ return 'buyer-document';
+ },
+ });
+ try {
+ createSyntheticDocumentRepository(hostileOptions);
+ } catch (error) {
+ optionErrorCode =
+ error instanceof ReferencePersistenceError ? error.code : 'unexpected_error';
+ }
+
+ const repository = createSyntheticDocumentRepository({
+ documentId: 'buyer-document',
+ initialDocument: 'Buyer draft v1',
+ });
+ const initial = repository.read('buyer-document');
+ let requestGetterCalls = 0;
+ let requestErrorCode = null;
+ const hostileRequest = {
+ documentId: 'buyer-document',
+ ifMatch: initial.validator,
+ };
+ Object.defineProperty(hostileRequest, 'document', {
+ enumerable: true,
+ get() {
+ requestGetterCalls += 1;
+ return 'Hostile write';
+ },
+ });
+ try {
+ repository.save(hostileRequest);
+ } catch (error) {
+ requestErrorCode =
+ error instanceof ReferencePersistenceError ? error.code : 'unexpected_error';
+ }
+
+ let forkGetterCalls = 0;
+ let forkErrorCode = null;
+ const hostileFork = {
+ documentId: 'buyer-document',
+ ifMatch: initial.validator,
+ };
+ Object.defineProperty(hostileFork, 'forkDocumentId', {
+ enumerable: true,
+ get() {
+ forkGetterCalls += 1;
+ return 'buyer-document-fork';
+ },
+ });
+ try {
+ repository.fork(hostileFork);
+ } catch (error) {
+ forkErrorCode =
+ error instanceof ReferencePersistenceError ? error.code : 'unexpected_error';
+ }
+
+ process.stdout.write(
+ `${JSON.stringify({
+ forkErrorCode,
+ forkGetterCalls,
+ optionErrorCode,
+ optionGetterCalls,
+ requestErrorCode,
+ requestGetterCalls,
+ })}\n`,
+ );
+}
+
+function runUnknownFieldSelfTest() {
+ let optionErrorCode = null;
+ try {
+ createSyntheticDocumentRepository({
+ documentId: 'buyer-document',
+ initialDocument: 'Buyer draft v1',
+ authorization: 'owner',
+ });
+ } catch (error) {
+ optionErrorCode =
+ error instanceof ReferencePersistenceError ? error.code : 'unexpected_error';
+ }
+
+ const repository = createSyntheticDocumentRepository({
+ documentId: 'buyer-document',
+ initialDocument: 'Buyer draft v1',
+ });
+ const initial = repository.read('buyer-document');
+
+ let saveErrorCode = null;
+ const saveWithHiddenField = {
+ documentId: 'buyer-document',
+ document: 'Unauthorized write',
+ ifMatch: initial.validator,
+ };
+ Object.defineProperty(saveWithHiddenField, 'authorization', {
+ value: 'owner',
+ enumerable: false,
+ });
+ try {
+ repository.save(saveWithHiddenField);
+ } catch (error) {
+ saveErrorCode =
+ error instanceof ReferencePersistenceError ? error.code : 'unexpected_error';
+ }
+
+ let forkErrorCode = null;
+ const authorityKey = Symbol('authorization');
+ try {
+ repository.fork({
+ documentId: 'buyer-document',
+ forkDocumentId: 'buyer-document-fork',
+ ifMatch: initial.validator,
+ [authorityKey]: 'owner',
+ });
+ } catch (error) {
+ forkErrorCode =
+ error instanceof ReferencePersistenceError ? error.code : 'unexpected_error';
+ }
+
+ const afterRejectedSave = repository.read('buyer-document');
+ process.stdout.write(
+ `${JSON.stringify({
+ forkErrorCode,
+ optionErrorCode,
+ saveErrorCode,
+ savedDocument: afterRejectedSave.document,
+ savedValidator: afterRejectedSave.validator,
+ })}\n`,
+ );
+}
+
+if (process.argv.includes('--empty-document-self-test')) {
+ runEmptyDocumentSelfTest();
+} else if (process.argv.includes('--hostile-accessor-self-test')) {
+ runHostileAccessorSelfTest();
+} else if (process.argv.includes('--unknown-field-self-test')) {
+ runUnknownFieldSelfTest();
+} else if (process.argv.includes('--self-test')) {
+ runSelfTest();
+}
diff --git a/examples/reference-host/verify-application-ssr.mjs b/examples/reference-host/verify-application-ssr.mjs
new file mode 100644
index 00000000..330b31b1
--- /dev/null
+++ b/examples/reference-host/verify-application-ssr.mjs
@@ -0,0 +1,210 @@
+import assert from 'node:assert/strict';
+import { execFileSync } from 'node:child_process';
+import {
+ mkdtempSync,
+ mkdirSync,
+ readFileSync,
+ readdirSync,
+ realpathSync,
+ rmSync,
+ writeFileSync,
+} from 'node:fs';
+import { tmpdir } from 'node:os';
+import { dirname, join, relative, resolve, sep } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+import { build } from 'vite';
+
+const repositoryRoot = resolve(
+ dirname(fileURLToPath(import.meta.url)),
+ '..',
+ '..',
+);
+const packageMetadata = JSON.parse(
+ readFileSync(join(repositoryRoot, 'package.json'), 'utf8'),
+);
+const packageName = packageMetadata.name;
+const packageVersion = packageMetadata.version;
+
+function run(command, argumentsList, cwd) {
+ return execFileSync(command, argumentsList, {
+ cwd,
+ encoding: 'utf8',
+ env: process.env,
+ stdio: ['ignore', 'pipe', 'pipe'],
+ });
+}
+
+function isContained(parentPath, childPath) {
+ const relation = relative(realpathSync(parentPath), realpathSync(childPath));
+ return relation === '' || (!relation.startsWith(`..${sep}`) && relation !== '..');
+}
+
+const requestedMode = process.argv.slice(2);
+assert.equal(
+ requestedMode.length === 0 ||
+ (requestedMode.length === 1 && requestedMode[0] === '--self-test'),
+ true,
+ 'Expected no arguments or --self-test.',
+);
+
+const temporaryRoot = mkdtempSync(join(tmpdir(), 'inkspan-reference-host-app-ssr-'));
+
+try {
+ run('pnpm', ['build'], repositoryRoot);
+
+ const packDirectory = join(temporaryRoot, 'pack');
+ mkdirSync(packDirectory, { recursive: true });
+ run('pnpm', ['pack', '--pack-destination', packDirectory], repositoryRoot);
+
+ const tarballs = readdirSync(packDirectory).filter((name) => name.endsWith('.tgz'));
+ assert.equal(tarballs.length, 1, 'Expected exactly one packed Inkspan tarball.');
+ const tarballPath = join(packDirectory, tarballs[0]);
+
+ const hostDirectory = join(temporaryRoot, 'host');
+ mkdirSync(hostDirectory, { recursive: true });
+ writeFileSync(
+ join(hostDirectory, 'package.json'),
+ `${JSON.stringify(
+ {
+ name: 'inkspan-reference-host-app-ssr-consumer',
+ private: true,
+ type: 'module',
+ packageManager: packageMetadata.packageManager,
+ dependencies: {
+ [packageName]: `file:${tarballPath}`,
+ react: packageMetadata.devDependencies.react,
+ 'react-dom': packageMetadata.devDependencies['react-dom'],
+ },
+ },
+ null,
+ 2,
+ )}\n`,
+ 'utf8',
+ );
+ run(
+ 'pnpm',
+ ['install', '--prefer-offline', '--ignore-scripts', '--no-frozen-lockfile'],
+ hostDirectory,
+ );
+
+ const installedPackageDirectory = join(
+ hostDirectory,
+ 'node_modules',
+ ...packageName.split('/'),
+ );
+ const installedMetadata = JSON.parse(
+ readFileSync(join(installedPackageDirectory, 'package.json'), 'utf8'),
+ );
+ assert.equal(installedMetadata.name, packageName);
+ assert.equal(installedMetadata.version, packageVersion);
+ assert.equal(
+ isContained(join(hostDirectory, 'node_modules'), installedPackageDirectory),
+ true,
+ 'Installed application dependency escaped the isolated consumer.',
+ );
+
+ const packedEntry = join(installedPackageDirectory, 'dist', 'cwl-editor.js');
+ assert.equal(
+ relative(realpathSync(installedPackageDirectory), realpathSync(packedEntry)).startsWith(
+ `dist${sep}`,
+ ),
+ true,
+ 'Installed application dependency did not resolve through packed dist/.',
+ );
+
+ const applicationEntry = join(hostDirectory, 'application-ssr.tsx');
+ const referenceHostApplication = join(
+ repositoryRoot,
+ 'examples',
+ 'reference-host',
+ 'reference-host-app.tsx',
+ );
+ writeFileSync(
+ applicationEntry,
+ `import React from 'react';
+import { renderToString } from 'react-dom/server';
+import { ReferenceHostApp } from ${JSON.stringify(referenceHostApplication)};
+
+const loadingLabel = 'Loading exact-packed Inkspan editor';
+const serverHtml = renderToString(
+ React.createElement(ReferenceHostApp, {
+ loadingLabel,
+ onAuthorizedSubmit() {
+ throw new Error('Host submit authority must not execute during server rendering.');
+ },
+ }),
+);
+
+const applicationServerRendered =
+ serverHtml.includes('
+ name.endsWith('.tgz'),
+ );
+ assert.equal(tarballs.length, 1, 'Expected exactly one packed Inkspan tarball.');
+
+ const extractedDirectory = join(temporaryRoot, 'package');
+ mkdirSync(extractedDirectory, { recursive: true });
+ run(
+ 'tar',
+ [
+ '-xzf',
+ join(packDirectory, tarballs[0]),
+ '--strip-components=1',
+ '-C',
+ extractedDirectory,
+ ],
+ { timeout: 60_000 },
+ );
+
+ const extractedMetadata = JSON.parse(
+ readFileSync(join(extractedDirectory, 'package.json'), 'utf8'),
+ );
+ assert.equal(extractedMetadata.name, packageMetadata.name);
+ assert.equal(extractedMetadata.version, packageMetadata.version);
+
+ const packageEntry = join(extractedDirectory, 'dist/cwl-editor.js');
+ assert.equal(
+ existsSync(packageEntry),
+ true,
+ 'Packed browser journey is missing the public ESM entrypoint.',
+ );
+
+ for (const spec of specs) {
+ assert.equal(
+ existsSync(join(browserDirectory, 'specs', spec)),
+ true,
+ `Reference-host browser journey is missing ${spec}.`,
+ );
+ }
+
+ const playwrightArgs = [
+ '--dir',
+ 'tests/browser',
+ 'exec',
+ 'playwright',
+ 'test',
+ '--config',
+ 'playwright.config.ts',
+ ...specs,
+ ...projects.flatMap((project) => ['--project', project]),
+ ];
+ run('pnpm', playwrightArgs, {
+ env: {
+ ...process.env,
+ INKSPAN_BROWSER_PACKAGE_ENTRY: packageEntry,
+ },
+ timeout: 300_000,
+ });
+
+ writeJson({
+ contractVersion: 1,
+ packageAuthority: 'exact-packed-tarball',
+ projects: projects.length,
+ specs: specs.length,
+ status: 'completed',
+ });
+ } finally {
+ rmSync(temporaryRoot, { recursive: true, force: true });
+ }
+}
+
+function main(argv) {
+ if (argv.length === 1 && argv[0] === '--plan') {
+ writeJson(planReceipt());
+ return;
+ }
+ if (argv.length === 1 && argv[0] === '--self-test') {
+ verifyBrowserJourney();
+ return;
+ }
+ throw new Error(`Usage: ${command} --plan | --self-test`);
+}
+
+try {
+ main(process.argv.slice(2));
+} catch (error) {
+ const message =
+ error instanceof Error
+ ? error.message
+ : 'Reference-host browser journey verification failed.';
+ process.stderr.write(`${message}\n`);
+ process.exitCode = 1;
+}
diff --git a/examples/reference-host/verify-current-reference-journey.mjs b/examples/reference-host/verify-current-reference-journey.mjs
new file mode 100644
index 00000000..551af993
--- /dev/null
+++ b/examples/reference-host/verify-current-reference-journey.mjs
@@ -0,0 +1,111 @@
+import { spawnSync } from 'node:child_process';
+import { dirname, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const referenceHostDirectory = dirname(fileURLToPath(import.meta.url));
+const repositoryRoot = resolve(referenceHostDirectory, '..', '..');
+const command = 'node examples/reference-host/verify-current-reference-journey.mjs';
+const steps = Object.freeze([
+ Object.freeze({
+ args: Object.freeze(['--self-test']),
+ path: 'examples/reference-host/synthetic-document-repository.mjs',
+ }),
+ Object.freeze({
+ args: Object.freeze(['--self-test']),
+ path: 'examples/reference-host/delayed-proposal.mjs',
+ }),
+ Object.freeze({
+ args: Object.freeze(['--self-test']),
+ path: 'examples/reference-host/autosave-view-model.mjs',
+ }),
+ Object.freeze({
+ args: Object.freeze(['--self-test']),
+ path: 'examples/reference-host/collaboration-provider-lifecycle.mjs',
+ }),
+ Object.freeze({
+ args: Object.freeze(['--self-test']),
+ path: 'examples/reference-host/host-authorized-collaboration.mjs',
+ }),
+ Object.freeze({
+ args: Object.freeze([]),
+ path: 'examples/reference-host/verify-packed-artifact.mjs',
+ }),
+ Object.freeze({
+ args: Object.freeze(['--self-test']),
+ path: 'examples/reference-host/verify-application-ssr.mjs',
+ }),
+ Object.freeze({
+ args: Object.freeze(['--self-test']),
+ path: 'examples/reference-host/verify-packed-office-journey.mjs',
+ }),
+ Object.freeze({
+ args: Object.freeze(['--self-test']),
+ path: 'examples/reference-host/verify-browser-journey.mjs',
+ }),
+]);
+
+function writeJson(value) {
+ process.stdout.write(`${JSON.stringify(value)}\n`);
+}
+
+function planReceipt() {
+ return {
+ command,
+ contractVersion: 1,
+ status: 'plan',
+ steps,
+ };
+}
+
+function runStep(step) {
+ const result = spawnSync(
+ process.execPath,
+ [resolve(repositoryRoot, step.path), ...step.args],
+ {
+ cwd: repositoryRoot,
+ encoding: 'utf8',
+ maxBuffer: 4 * 1024 * 1024,
+ stdio: ['ignore', 'pipe', 'pipe'],
+ timeout: 360_000,
+ },
+ );
+
+ if (
+ result.error !== undefined ||
+ result.signal !== null ||
+ result.status !== 0
+ ) {
+ throw new Error('Reference-host current journey verification failed.');
+ }
+}
+
+function main(argv) {
+ if (argv.length === 1 && argv[0] === '--plan') {
+ writeJson(planReceipt());
+ return;
+ }
+ if (argv.length !== 0) {
+ throw new Error(`Usage: ${command} [--plan]`);
+ }
+
+ for (const step of steps) {
+ runStep(step);
+ }
+
+ writeJson({
+ contractVersion: 1,
+ status: 'completed',
+ steps: steps.length,
+ });
+}
+
+try {
+ main(process.argv.slice(2));
+} catch (error) {
+ const message =
+ error instanceof Error
+ ? error.message
+ : 'Reference-host current journey verification failed.';
+ process.stderr.write(`${message}\n`);
+ process.exitCode = 1;
+}
diff --git a/examples/reference-host/verify-office-handoff.mjs b/examples/reference-host/verify-office-handoff.mjs
new file mode 100644
index 00000000..b8df95dc
--- /dev/null
+++ b/examples/reference-host/verify-office-handoff.mjs
@@ -0,0 +1,135 @@
+import { spawnSync } from 'node:child_process';
+import { copyFile, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
+import { dirname, delimiter, resolve } from 'node:path';
+import { fileURLToPath, pathToFileURL } from 'node:url';
+
+const TITLE = 'Inkspan acquisition handoff';
+const MARKDOWN = '**Buyer-ready** body.\n\nSecond acquisition paragraph.';
+const EXPECTED_BODY = ['Buyer-ready body.', 'Second acquisition paragraph.'];
+
+function requiredEnvironment(name) {
+ const value = process.env[name]?.trim();
+ if (!value) {
+ throw new Error(`${name} is required`);
+ }
+ return value;
+}
+
+function run(command, args, options = {}) {
+ const result = spawnSync(command, args, {
+ cwd: options.cwd,
+ env: options.env,
+ encoding: 'utf8',
+ shell: false,
+ });
+ if (result.error) throw result.error;
+ if (result.status !== 0) {
+ throw new Error(
+ [
+ `${command} exited with status ${result.status}`,
+ result.stdout?.trim(),
+ result.stderr?.trim(),
+ ]
+ .filter(Boolean)
+ .join('\n'),
+ );
+ }
+}
+
+async function main() {
+ const repositoryRoot = resolve(
+ dirname(fileURLToPath(import.meta.url)),
+ '../..',
+ );
+ const packageEntry = resolve(requiredEnvironment('INKSPAN_BROWSER_PACKAGE_ENTRY'));
+ const packageRoot = resolve(dirname(packageEntry), '..');
+ const expectedEntry = resolve(packageRoot, 'dist/cwl-editor.js');
+ if (packageEntry !== expectedEntry) {
+ throw new Error('packed package entry must resolve to dist/cwl-editor.js');
+ }
+
+ const packageMetadata = JSON.parse(
+ await readFile(resolve(packageRoot, 'package.json'), 'utf8'),
+ );
+ if (packageMetadata.name !== '@contextualwisdomlab/cwl-editor') {
+ throw new Error('packed package has an unexpected package identity');
+ }
+
+ const nodeModulesRoot = resolve(packageRoot, '../..');
+ const consumerRoot = dirname(nodeModulesRoot);
+ const temporaryDirectory = await mkdtemp(
+ resolve(consumerRoot, '.inkspan-office-handoff-'),
+ );
+
+ try {
+ const copiedHandoff = resolve(temporaryDirectory, 'office-handoff.mjs');
+ const requestPath = resolve(temporaryDirectory, 'request.json');
+ const outputPath = resolve(temporaryDirectory, 'handoff.docx');
+ await copyFile(
+ resolve(repositoryRoot, 'examples/reference-host/office-handoff.mjs'),
+ copiedHandoff,
+ );
+
+ const { createReferenceDocxRequest } = await import(
+ pathToFileURL(copiedHandoff).href
+ );
+ const request = createReferenceDocxRequest({
+ title: TITLE,
+ markdown: MARKDOWN,
+ });
+ if (
+ request.format !== 'docx' ||
+ request.title !== TITLE ||
+ request.blocks?.length !== EXPECTED_BODY.length ||
+ request.blocks.some(
+ (block, index) =>
+ block?.type !== 'paragraph' || block.text !== EXPECTED_BODY[index],
+ )
+ ) {
+ throw new Error('packed Markdown handoff produced an unexpected Office request');
+ }
+ await writeFile(requestPath, `${JSON.stringify(request)}\n`, 'utf8');
+
+ const python = process.env.INKSPAN_OFFICE_PYTHON?.trim() || 'python';
+ const pythonPath = [
+ resolve(repositoryRoot, 'office/src'),
+ process.env.PYTHONPATH,
+ ]
+ .filter(Boolean)
+ .join(delimiter);
+ const pythonEnvironment = {
+ ...process.env,
+ PYTHONPATH: pythonPath,
+ };
+
+ run(
+ python,
+ ['-m', 'inkspan_office.cli', requestPath, outputPath],
+ { cwd: repositoryRoot, env: pythonEnvironment },
+ );
+
+ const validation = `
+from pathlib import Path
+import sys
+from docx import Document
+
+output = Path(sys.argv[1])
+if not output.read_bytes().startswith(b"PK"):
+ raise SystemExit("Office output is not an OOXML package")
+document = Document(output)
+if document.core_properties.title != ${JSON.stringify(TITLE)}:
+ raise SystemExit("DOCX title metadata does not match the handoff")
+paragraphs = [paragraph.text for paragraph in document.paragraphs]
+if paragraphs != ${JSON.stringify([TITLE, ...EXPECTED_BODY])}:
+ raise SystemExit(f"unexpected DOCX paragraphs: {paragraphs!r}")
+`;
+ run(python, ['-c', validation, outputPath], {
+ cwd: repositoryRoot,
+ env: pythonEnvironment,
+ });
+ } finally {
+ await rm(temporaryDirectory, { recursive: true, force: true });
+ }
+}
+
+await main();
diff --git a/examples/reference-host/verify-packed-artifact.mjs b/examples/reference-host/verify-packed-artifact.mjs
new file mode 100644
index 00000000..4547130b
--- /dev/null
+++ b/examples/reference-host/verify-packed-artifact.mjs
@@ -0,0 +1,331 @@
+import assert from 'node:assert/strict';
+import { execFileSync } from 'node:child_process';
+import {
+ copyFileSync,
+ mkdtempSync,
+ mkdirSync,
+ readFileSync,
+ readdirSync,
+ realpathSync,
+ rmSync,
+ writeFileSync,
+} from 'node:fs';
+import { tmpdir } from 'node:os';
+import { dirname, join, relative, resolve, sep } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const repositoryRoot = resolve(
+ dirname(fileURLToPath(import.meta.url)),
+ '..',
+ '..',
+);
+const packageMetadata = JSON.parse(
+ readFileSync(join(repositoryRoot, 'package.json'), 'utf8'),
+);
+const packageName = packageMetadata.name;
+const packageVersion = packageMetadata.version;
+
+function run(command, argumentsList, cwd) {
+ return execFileSync(command, argumentsList, {
+ cwd,
+ encoding: 'utf8',
+ env: process.env,
+ stdio: ['ignore', 'pipe', 'pipe'],
+ });
+}
+
+function isContained(parentPath, childPath) {
+ const relation = relative(realpathSync(parentPath), realpathSync(childPath));
+ return relation === '' || (!relation.startsWith(`..${sep}`) && relation !== '..');
+}
+
+const temporaryRoot = mkdtempSync(join(tmpdir(), 'inkspan-reference-host-'));
+
+try {
+ run('pnpm', ['build'], repositoryRoot);
+
+ const packDirectory = join(temporaryRoot, 'pack');
+ mkdirSync(packDirectory, { recursive: true });
+ run('pnpm', ['pack', '--pack-destination', packDirectory], repositoryRoot);
+
+ const tarballs = readdirSync(packDirectory).filter((name) => name.endsWith('.tgz'));
+ assert.equal(tarballs.length, 1, 'Expected exactly one packed Inkspan tarball.');
+ const tarballPath = join(packDirectory, tarballs[0]);
+
+ const hostDirectory = join(temporaryRoot, 'host');
+ mkdirSync(hostDirectory, { recursive: true });
+ copyFileSync(
+ join(repositoryRoot, 'examples/reference-host/autosave-view-model.mjs'),
+ join(hostDirectory, 'autosave-view-model.mjs'),
+ );
+ writeFileSync(
+ join(hostDirectory, 'package.json'),
+ `${JSON.stringify(
+ {
+ name: 'inkspan-reference-host-packed-consumer',
+ private: true,
+ type: 'module',
+ packageManager: packageMetadata.packageManager,
+ dependencies: {
+ [packageName]: `file:${tarballPath}`,
+ react: packageMetadata.devDependencies.react,
+ 'react-dom': packageMetadata.devDependencies['react-dom'],
+ },
+ },
+ null,
+ 2,
+ )}\n`,
+ 'utf8',
+ );
+
+ // A published library consumer resolves the dependency ranges declared in the
+ // packed manifest. Prefer the clean-checkout store, but permit the package
+ // manager to fetch a transitive version that is valid for the packed consumer
+ // even when that version is not present in Inkspan's development lockfile.
+ run(
+ 'pnpm',
+ ['install', '--prefer-offline', '--ignore-scripts', '--no-frozen-lockfile'],
+ hostDirectory,
+ );
+
+ const consumerPath = join(hostDirectory, 'consumer.mjs');
+ writeFileSync(
+ consumerPath,
+ `import assert from 'node:assert/strict';
+import React from 'react';
+import { renderToString } from 'react-dom/server';
+import { CwlEditor } from '${packageName}';
+import { createDocumentAutosaveQueue } from '${packageName}/autosave';
+import { dataUriToBytes } from '${packageName}/converter';
+import { markdownToHtml, markdownToPlainText } from '${packageName}/markdown';
+import { createAutosaveViewModel } from './autosave-view-model.mjs';
+
+const serverHtml = renderToString(
+ React.createElement(CwlEditor, {
+ mode: 'markdown',
+ defaultValue: '# Packed draft',
+ formFieldName: 'message_body',
+ hideToolbar: true,
+ }),
+);
+assert.match(serverHtml, /name="message_body"/u);
+assert.match(serverHtml, /value="# Packed draft"/u);
+assert.equal(typeof createDocumentAutosaveQueue, 'function');
+assert.deepEqual(Array.from(dataUriToBytes('data:text/plain;base64,SGk=').bytes), [72, 105]);
+const markdownSource = '# Packed handoff\\n\\nBuyer text';
+const projectedHtml = markdownToHtml(markdownSource);
+assert.equal(projectedHtml, markdownToHtml(markdownSource));
+assert.match(markdownToPlainText(markdownSource), /Packed handoff/u);
+assert.match(markdownToPlainText(markdownSource), /Buyer text/u);
+
+const digestHex = '41'.repeat(32);
+const evidence = Object.freeze({
+ envelope: Object.freeze({
+ schemaId: 'https://inkspan.io/schemas/document-envelope/v1',
+ schemaVersion: 1,
+ documentJson: Object.freeze({ type: 'doc' }),
+ }),
+ revision: Object.freeze({
+ algorithm: 'SHA-256',
+ digestHex,
+ strongEntityTag: \`"sha256-\${digestHex}"\`,
+ }),
+});
+const autosaveViewModel = createAutosaveViewModel();
+const autosaveViewStates = [];
+const autosaveQueue = createDocumentAutosaveQueue({
+ save() {
+ return { status: 'saved' };
+ },
+ onSnapshotChange(snapshot) {
+ autosaveViewStates.push(autosaveViewModel.observe(snapshot).viewState);
+ },
+});
+assert.equal(autosaveViewModel.observe(autosaveQueue.getSnapshot()).viewState, 'clean');
+const autosaveOutcome = await autosaveQueue.enqueue(evidence);
+await autosaveQueue.flush();
+await Promise.resolve();
+assert.equal(autosaveOutcome.status, 'saved');
+assert.ok(autosaveViewStates.includes('saving'));
+assert.equal(autosaveViewStates.at(-1), 'clean');
+assert.equal((await autosaveQueue.close()).state, 'closed');
+
+const rootEntry = import.meta.resolve('${packageName}');
+const autosaveEntry = import.meta.resolve('${packageName}/autosave');
+const converterEntry = import.meta.resolve('${packageName}/converter');
+const markdownEntry = import.meta.resolve('${packageName}/markdown');
+const styleEntry = import.meta.resolve('${packageName}/styles.css');
+const fullFontEntry = import.meta.resolve('${packageName}/fonts.css');
+const latinFontEntry = import.meta.resolve('${packageName}/fonts-latin.css');
+for (const entry of [
+ rootEntry,
+ autosaveEntry,
+ converterEntry,
+ markdownEntry,
+ styleEntry,
+ fullFontEntry,
+ latinFontEntry,
+]) {
+ assert.ok(entry.startsWith('file:'), 'Packed package export did not resolve to a file URL.');
+}
+
+process.stdout.write(JSON.stringify({
+ serverRenderedNamedField: true,
+ autosaveObserverWired: true,
+ converterRoundTrip: true,
+ markdownProjection: true,
+ rootEntry,
+ autosaveEntry,
+ converterEntry,
+ markdownEntry,
+ styleEntry,
+ fullFontEntry,
+ latinFontEntry,
+}));
+`,
+ 'utf8',
+ );
+
+ const consumerResult = JSON.parse(run(process.execPath, [consumerPath], hostDirectory));
+
+ const commonJsConsumerPath = join(hostDirectory, 'consumer.cjs');
+ writeFileSync(
+ commonJsConsumerPath,
+ `const assert = require('node:assert/strict');
+const React = require('react');
+const { renderToString } = require('react-dom/server');
+const { CwlEditor } = require('${packageName}');
+const { createDocumentAutosaveQueue } = require('${packageName}/autosave');
+const { dataUriToBytes } = require('${packageName}/converter');
+const { markdownToHtml, markdownToPlainText } = require('${packageName}/markdown');
+
+const serverHtml = renderToString(
+ React.createElement(CwlEditor, {
+ mode: 'markdown',
+ defaultValue: '# Packed CommonJS draft',
+ formFieldName: 'message_body',
+ hideToolbar: true,
+ }),
+);
+assert.match(serverHtml, /name="message_body"/u);
+assert.match(serverHtml, /value="# Packed CommonJS draft"/u);
+assert.equal(typeof createDocumentAutosaveQueue, 'function');
+assert.deepEqual(Array.from(dataUriToBytes('data:text/plain;base64,T0s=').bytes), [79, 75]);
+const markdownSource = '# Packed CommonJS handoff\\n\\nBuyer text';
+const projectedHtml = markdownToHtml(markdownSource);
+assert.equal(projectedHtml, markdownToHtml(markdownSource));
+assert.match(markdownToPlainText(markdownSource), /Packed CommonJS handoff/u);
+assert.match(markdownToPlainText(markdownSource), /Buyer text/u);
+
+process.stdout.write(JSON.stringify({
+ serverRenderedNamedField: true,
+ converterRoundTrip: true,
+ markdownProjection: true,
+ rootEntry: require.resolve('${packageName}'),
+ autosaveEntry: require.resolve('${packageName}/autosave'),
+ converterEntry: require.resolve('${packageName}/converter'),
+ markdownEntry: require.resolve('${packageName}/markdown'),
+}));
+`,
+ 'utf8',
+ );
+
+ const commonJsConsumerResult = JSON.parse(
+ run(process.execPath, [commonJsConsumerPath], hostDirectory),
+ );
+
+ const installedPackageDirectory = join(
+ hostDirectory,
+ 'node_modules',
+ ...packageName.split('/'),
+ );
+ const installedMetadata = JSON.parse(
+ readFileSync(join(installedPackageDirectory, 'package.json'), 'utf8'),
+ );
+
+ assert.equal(installedMetadata.name, packageName);
+ assert.equal(installedMetadata.version, packageVersion);
+ assert.equal(
+ isContained(join(hostDirectory, 'node_modules'), installedPackageDirectory),
+ true,
+ 'Installed package escaped the isolated host node_modules tree.',
+ );
+
+ const executableEntries = [
+ ['ESM root', fileURLToPath(consumerResult.rootEntry)],
+ ['ESM autosave', fileURLToPath(consumerResult.autosaveEntry)],
+ ['ESM converter', fileURLToPath(consumerResult.converterEntry)],
+ ['ESM markdown', fileURLToPath(consumerResult.markdownEntry)],
+ ['CommonJS root', commonJsConsumerResult.rootEntry],
+ ['CommonJS autosave', commonJsConsumerResult.autosaveEntry],
+ ['CommonJS converter', commonJsConsumerResult.converterEntry],
+ ['CommonJS markdown', commonJsConsumerResult.markdownEntry],
+ ];
+ for (const [label, entry] of executableEntries) {
+ assert.equal(
+ isContained(installedPackageDirectory, entry),
+ true,
+ `${label} import escaped the installed packed package.`,
+ );
+ assert.equal(
+ relative(realpathSync(installedPackageDirectory), realpathSync(entry)).startsWith(
+ `dist${sep}`,
+ ),
+ true,
+ `${label} executable import did not resolve through packed dist/.`,
+ );
+ }
+
+ const publicAssetEntries = [
+ ['stylesheet', consumerResult.styleEntry],
+ ['full font stylesheet', consumerResult.fullFontEntry],
+ ['Latin font stylesheet', consumerResult.latinFontEntry],
+ ];
+ for (const [label, entry] of publicAssetEntries) {
+ assert.equal(
+ isContained(installedPackageDirectory, fileURLToPath(entry)),
+ true,
+ `${label} export escaped the installed packed package.`,
+ );
+ }
+ const publicAssetEntriesContained = true;
+
+ const sourceImportDetected = executableEntries.some(([, entry]) =>
+ relative(realpathSync(installedPackageDirectory), realpathSync(entry)).startsWith(
+ `src${sep}`,
+ ),
+ );
+ assert.equal(
+ sourceImportDetected,
+ false,
+ 'Packed consumer unexpectedly resolved an executable import through source files.',
+ );
+
+ const esmServerRenderedNamedField = consumerResult.serverRenderedNamedField === true;
+ const commonJsServerRenderedNamedField =
+ commonJsConsumerResult.serverRenderedNamedField === true;
+
+ process.stdout.write(
+ `${JSON.stringify({
+ packageName,
+ packageVersion,
+ installedFromTarball: true,
+ consumerInstallCompleted: true,
+ // Preserve the established verifier receipt while extending it with
+ // format-specific evidence below.
+ serverRenderedNamedField: esmServerRenderedNamedField,
+ esmServerRenderedNamedField,
+ commonJsServerRenderedNamedField,
+ autosaveObserverWired: consumerResult.autosaveObserverWired === true,
+ esmConverterRoundTrip: consumerResult.converterRoundTrip === true,
+ commonJsConverterRoundTrip: commonJsConsumerResult.converterRoundTrip === true,
+ esmMarkdownProjection: consumerResult.markdownProjection === true,
+ commonJsMarkdownProjection: commonJsConsumerResult.markdownProjection === true,
+ publicAssetEntriesContained,
+ executableEntriesContained: true,
+ sourceImportDetected,
+ })}\n`,
+ );
+} finally {
+ rmSync(temporaryRoot, { recursive: true, force: true });
+}
diff --git a/examples/reference-host/verify-packed-office-journey.mjs b/examples/reference-host/verify-packed-office-journey.mjs
new file mode 100644
index 00000000..917babbc
--- /dev/null
+++ b/examples/reference-host/verify-packed-office-journey.mjs
@@ -0,0 +1,148 @@
+import assert from 'node:assert/strict';
+import { spawnSync } from 'node:child_process';
+import {
+ existsSync,
+ mkdtempSync,
+ mkdirSync,
+ readFileSync,
+ readdirSync,
+ rmSync,
+} from 'node:fs';
+import { tmpdir } from 'node:os';
+import { dirname, join, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const referenceHostDirectory = dirname(fileURLToPath(import.meta.url));
+const repositoryRoot = resolve(referenceHostDirectory, '..', '..');
+const command =
+ 'node examples/reference-host/verify-packed-office-journey.mjs';
+
+function writeJson(value) {
+ process.stdout.write(`${JSON.stringify(value)}\n`);
+}
+
+function planReceipt() {
+ return {
+ command,
+ contractVersion: 1,
+ packageAuthority: 'exact-packed-tarball',
+ status: 'plan',
+ };
+}
+
+function run(commandName, args, options = {}) {
+ const result = spawnSync(commandName, args, {
+ cwd: options.cwd ?? repositoryRoot,
+ encoding: 'utf8',
+ env: options.env ?? process.env,
+ maxBuffer: 8 * 1024 * 1024,
+ stdio: ['ignore', 'pipe', 'pipe'],
+ timeout: options.timeout ?? 180_000,
+ });
+
+ if (
+ result.error !== undefined ||
+ result.signal !== null ||
+ result.status !== 0
+ ) {
+ throw new Error('Reference-host packed Office journey verification failed.');
+ }
+
+ return result.stdout;
+}
+
+function verifyPackedOfficeJourney() {
+ const packageMetadata = JSON.parse(
+ readFileSync(resolve(repositoryRoot, 'package.json'), 'utf8'),
+ );
+ const temporaryRoot = mkdtempSync(join(tmpdir(), 'inkspan-office-journey-'));
+
+ try {
+ run('pnpm', ['build'], { timeout: 240_000 });
+
+ const packDirectory = join(temporaryRoot, 'pack');
+ mkdirSync(packDirectory, { recursive: true });
+ run('pnpm', ['pack', '--pack-destination', packDirectory]);
+
+ const tarballs = readdirSync(packDirectory).filter((name) =>
+ name.endsWith('.tgz'),
+ );
+ assert.equal(tarballs.length, 1, 'Expected exactly one packed Inkspan tarball.');
+
+ const packageDirectory = join(
+ temporaryRoot,
+ 'host',
+ 'node_modules',
+ '@contextualwisdomlab',
+ 'cwl-editor',
+ );
+ mkdirSync(packageDirectory, { recursive: true });
+ run(
+ 'tar',
+ [
+ '-xzf',
+ join(packDirectory, tarballs[0]),
+ '--strip-components=1',
+ '-C',
+ packageDirectory,
+ ],
+ { timeout: 60_000 },
+ );
+
+ const packedMetadata = JSON.parse(
+ readFileSync(join(packageDirectory, 'package.json'), 'utf8'),
+ );
+ assert.equal(packedMetadata.name, packageMetadata.name);
+ assert.equal(packedMetadata.version, packageMetadata.version);
+
+ const packageEntry = join(packageDirectory, 'dist', 'cwl-editor.js');
+ assert.equal(
+ existsSync(packageEntry),
+ true,
+ 'Packed Office journey is missing the public ESM entrypoint.',
+ );
+
+ run(
+ process.execPath,
+ [resolve(referenceHostDirectory, 'verify-office-handoff.mjs')],
+ {
+ env: {
+ ...process.env,
+ INKSPAN_BROWSER_PACKAGE_ENTRY: packageEntry,
+ },
+ timeout: 180_000,
+ },
+ );
+
+ writeJson({
+ contractVersion: 1,
+ packageAuthority: 'exact-packed-tarball',
+ status: 'completed',
+ });
+ } finally {
+ rmSync(temporaryRoot, { recursive: true, force: true });
+ }
+}
+
+function main(argv) {
+ if (argv.length === 1 && argv[0] === '--plan') {
+ writeJson(planReceipt());
+ return;
+ }
+ if (argv.length === 1 && argv[0] === '--self-test') {
+ verifyPackedOfficeJourney();
+ return;
+ }
+ throw new Error(`Usage: ${command} --plan | --self-test`);
+}
+
+try {
+ main(process.argv.slice(2));
+} catch (error) {
+ const message =
+ error instanceof Error
+ ? error.message
+ : 'Reference-host packed Office journey verification failed.';
+ process.stderr.write(`${message}\n`);
+ process.exitCode = 1;
+}
diff --git a/src/referenceHostApplicationHydration.test.tsx b/src/referenceHostApplicationHydration.test.tsx
new file mode 100644
index 00000000..210cf641
--- /dev/null
+++ b/src/referenceHostApplicationHydration.test.tsx
@@ -0,0 +1,73 @@
+import { existsSync, readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const applicationPath = resolve(
+ process.cwd(),
+ 'examples/reference-host/reference-host-app.tsx',
+);
+const clientBoundaryPath = resolve(
+ process.cwd(),
+ 'examples/reference-host/reference-host-client.tsx',
+);
+const applicationSource = readFileSync(applicationPath, 'utf8');
+const clientBoundarySource = existsSync(clientBoundaryPath)
+ ? readFileSync(clientBoundaryPath, 'utf8')
+ : '';
+const nativeFormSource = readFileSync(
+ resolve(process.cwd(), 'examples/reference-host/native-form-host.tsx'),
+ 'utf8',
+);
+
+describe('reference-host application hydration contract', () => {
+ it('keeps the deterministic server shell outside a narrow client hydration boundary', () => {
+ expect(applicationSource.startsWith("'use client';\n")).toBe(false);
+ expect(applicationSource).toContain(
+ "from './reference-host-client.js'",
+ );
+ expect(clientBoundarySource.startsWith("'use client';\n")).toBe(true);
+ expect(applicationSource).toContain(
+ '',
+ );
+ expect(applicationSource).toContain(' (');
+ expect(clientBoundarySource).toContain(' {
+ expect(applicationSource).toContain("controlMode = 'uncontrolled'");
+ expect(applicationSource).toContain('controlMode={controlMode}');
+ expect(clientBoundarySource).toContain('controlMode={controlMode}');
+ });
+
+ it('preserves the public-package and host-authority boundary for the hydrated form', () => {
+ expect(nativeFormSource).toContain(
+ "from '@contextualwisdomlab/cwl-editor'",
+ );
+ expect(nativeFormSource).toContain('formFieldName="message_body"');
+ expect(applicationSource).not.toContain('/src/');
+ expect(applicationSource).not.toContain('../../src');
+ expect(applicationSource).not.toContain('fetch(');
+ expect(applicationSource).not.toContain('localStorage');
+ expect(applicationSource).not.toContain('process.env');
+ expect(clientBoundarySource).not.toContain('/src/');
+ expect(clientBoundarySource).not.toContain('../../src');
+ expect(clientBoundarySource).not.toContain('fetch(');
+ expect(clientBoundarySource).not.toContain('localStorage');
+ expect(clientBoundarySource).not.toContain('process.env');
+ });
+});
\ No newline at end of file
diff --git a/src/referenceHostApplicationSsrJourney.test.ts b/src/referenceHostApplicationSsrJourney.test.ts
new file mode 100644
index 00000000..67f3305f
--- /dev/null
+++ b/src/referenceHostApplicationSsrJourney.test.ts
@@ -0,0 +1,47 @@
+import { execFileSync } from 'node:child_process';
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const repositoryRoot = process.cwd();
+const verifierPath = resolve(
+ repositoryRoot,
+ 'examples/reference-host/verify-application-ssr.mjs',
+);
+const packageMetadata = JSON.parse(
+ readFileSync(resolve(repositoryRoot, 'package.json'), 'utf8'),
+) as { name: string; version: string };
+
+describe('reference-host application SSR acceptance', () => {
+ it(
+ 'server-renders the application shell against the exact packed package while deferring the client editor boundary',
+ () => {
+ const output = execFileSync(
+ process.execPath,
+ [verifierPath, '--self-test'],
+ {
+ cwd: repositoryRoot,
+ encoding: 'utf8',
+ stdio: ['ignore', 'pipe', 'pipe'],
+ timeout: 180_000,
+ },
+ );
+
+ const result = JSON.parse(output.trim()) as {
+ packageName?: unknown;
+ packageVersion?: unknown;
+ packageAuthority?: unknown;
+ applicationServerRendered?: unknown;
+ clientEditorDeferred?: unknown;
+ };
+
+ expect(result.packageName).toBe(packageMetadata.name);
+ expect(result.packageVersion).toBe(packageMetadata.version);
+ expect(result.packageAuthority).toBe('exact-packed-tarball');
+ expect(result.applicationServerRendered).toBe(true);
+ expect(result.clientEditorDeferred).toBe(true);
+ },
+ 180_000,
+ );
+});
diff --git a/src/referenceHostAuthorizedCollaboration.test.ts b/src/referenceHostAuthorizedCollaboration.test.ts
new file mode 100644
index 00000000..bced5d44
--- /dev/null
+++ b/src/referenceHostAuthorizedCollaboration.test.ts
@@ -0,0 +1,164 @@
+import { execFileSync } from 'node:child_process';
+import { existsSync } from 'node:fs';
+import { pathToFileURL } from 'node:url';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const authorizationFixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/host-authorized-collaboration.mjs',
+);
+const lifecycleFixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/collaboration-provider-lifecycle.mjs',
+);
+
+describe('reference-host authorized collaboration journey', () => {
+ it('ships a host-owned authorization gate outside the published Inkspan runtime', () => {
+ expect(existsSync(authorizationFixturePath)).toBe(true);
+ });
+
+ it('redacts host authorization failures before provider construction', () => {
+ if (!existsSync(authorizationFixturePath)) return;
+ const moduleUrl = JSON.stringify(pathToFileURL(authorizationFixturePath).href);
+ const script = `
+ import { createHostAuthorizedProviderFactory } from ${moduleUrl};
+ const privateCause = 'private buyer authorization reason';
+ const events = [];
+ const providerFactory = createHostAuthorizedProviderFactory({
+ authorize(context) {
+ events.push('authorize:' + context.actorId + ':' + context.roomId + ':' + context.generation);
+ throw new Error(privateCause);
+ },
+ createProvider() {
+ events.push('provider:create');
+ return {};
+ },
+ });
+ let error = null;
+ try {
+ providerFactory({
+ document: {},
+ roomId: 'buyer-room',
+ actorId: 'buyer-actor',
+ generation: 1,
+ });
+ } catch (failure) {
+ error = failure instanceof Error ? failure.message : 'unexpected error';
+ }
+ process.stdout.write(JSON.stringify({
+ error,
+ events,
+ leakedPrivateCause: typeof error === 'string' && error.includes(privateCause),
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ error: 'collaboration provider authorization failed.',
+ events: ['authorize:buyer-actor:buyer-room:1'],
+ leakedPrivateCause: false,
+ });
+ });
+
+ it('requires an exact synchronous true decision before constructing a provider', () => {
+ if (!existsSync(authorizationFixturePath)) return;
+ const moduleUrl = JSON.stringify(pathToFileURL(authorizationFixturePath).href);
+ const script = `
+ import { createHostAuthorizedProviderFactory } from ${moduleUrl};
+ const events = [];
+ const providerFactory = createHostAuthorizedProviderFactory({
+ authorize() {
+ events.push('authorize');
+ return Promise.resolve(true);
+ },
+ createProvider() {
+ events.push('provider:create');
+ return {};
+ },
+ });
+ let error = null;
+ try {
+ providerFactory({ document: {}, roomId: 'buyer-room', actorId: 'buyer-actor', generation: 1 });
+ } catch (failure) {
+ error = failure instanceof Error ? failure.message : 'unexpected error';
+ }
+ process.stdout.write(JSON.stringify({ error, events }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ error: 'collaboration provider authorization failed.',
+ events: ['authorize'],
+ });
+ });
+
+ it('re-authorizes the exact host room and actor before every provider generation', () => {
+ if (!existsSync(authorizationFixturePath) || !existsSync(lifecycleFixturePath)) return;
+ const authorizationUrl = JSON.stringify(pathToFileURL(authorizationFixturePath).href);
+ const lifecycleUrl = JSON.stringify(pathToFileURL(lifecycleFixturePath).href);
+ const script = `
+ import { createHostAuthorizedProviderFactory } from ${authorizationUrl};
+ import { createHostCollaborationLifecycle } from ${lifecycleUrl};
+ const events = [];
+ const providerFactory = createHostAuthorizedProviderFactory({
+ authorize(context) {
+ events.push('authorize:' + context.actorId + ':' + context.roomId + ':' + context.generation);
+ return true;
+ },
+ createProvider(context) {
+ const generation = context.generation;
+ events.push('provider:create:' + generation);
+ return {
+ connect() { events.push('provider:connect:' + generation); },
+ disconnect() { events.push('provider:disconnect:' + generation); },
+ destroy() { events.push('provider:destroy:' + generation); },
+ };
+ },
+ });
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ return { destroy() { events.push('document:destroy'); } };
+ },
+ providerFactory,
+ roomId: 'buyer-room',
+ actorId: 'buyer-actor',
+ });
+ lifecycle.connect();
+ lifecycle.reconnect();
+ lifecycle.dispose();
+ process.stdout.write(JSON.stringify({ events, snapshot: lifecycle.getSnapshot() }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ events: [
+ 'authorize:buyer-actor:buyer-room:1',
+ 'provider:create:1',
+ 'provider:connect:1',
+ 'provider:disconnect:1',
+ 'provider:destroy:1',
+ 'authorize:buyer-actor:buyer-room:2',
+ 'provider:create:2',
+ 'provider:connect:2',
+ 'provider:disconnect:2',
+ 'provider:destroy:2',
+ 'document:destroy',
+ ],
+ snapshot: { providerGeneration: 2, status: 'disposed' },
+ });
+ });
+});
diff --git a/src/referenceHostAutosaveSnapshotShape.test.ts b/src/referenceHostAutosaveSnapshotShape.test.ts
new file mode 100644
index 00000000..f9284334
--- /dev/null
+++ b/src/referenceHostAutosaveSnapshotShape.test.ts
@@ -0,0 +1,97 @@
+import { execFileSync } from 'node:child_process';
+import { pathToFileURL } from 'node:url';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const fixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/autosave-view-model.mjs',
+);
+
+const baseSnapshotSource = `
+ const validSnapshot = (state = 'idle', blockedReason = null) => ({
+ state,
+ blockedReason,
+ activeStrongEntityTag: null,
+ pendingStrongEntityTag: null,
+ lastSavedStrongEntityTag: null,
+ });
+`;
+
+describe('reference-host autosave snapshot shape', () => {
+ it('rejects unknown authority-looking fields across enumerable, hidden, and symbol keys', () => {
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createAutosaveViewModel } from ${moduleUrl};
+ ${baseSnapshotSource}
+
+ function observeError(kind) {
+ const candidate = validSnapshot();
+ if (kind === 'enumerable') {
+ candidate.authorization = 'owner';
+ } else if (kind === 'hidden') {
+ Object.defineProperty(candidate, 'authorization', {
+ value: 'owner',
+ enumerable: false,
+ });
+ } else {
+ candidate[Symbol('authorization')] = 'owner';
+ }
+ try {
+ createAutosaveViewModel().observe(candidate);
+ return null;
+ } catch (error) {
+ return error instanceof Error ? error.message : 'unexpected error';
+ }
+ }
+
+ process.stdout.write(JSON.stringify({
+ enumerable: observeError('enumerable'),
+ hidden: observeError('hidden'),
+ symbol: observeError('symbol'),
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ enumerable: 'autosave snapshot is invalid.',
+ hidden: 'autosave snapshot is invalid.',
+ symbol: 'autosave snapshot is invalid.',
+ });
+ });
+
+ it('does not mutate retry presentation state when a malformed blocked snapshot is rejected', () => {
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createAutosaveViewModel } from ${moduleUrl};
+ ${baseSnapshotSource}
+
+ const viewModel = createAutosaveViewModel();
+ const malformed = validSnapshot('blocked', 'conflict');
+ malformed.authorization = 'owner';
+ let malformedError = null;
+ try {
+ viewModel.observe(malformed);
+ } catch (error) {
+ malformedError = error instanceof Error ? error.message : 'unexpected error';
+ }
+ const afterRejected = viewModel.observe(validSnapshot('saving')).viewState;
+ process.stdout.write(JSON.stringify({ afterRejected, malformedError }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ afterRejected: 'saving',
+ malformedError: 'autosave snapshot is invalid.',
+ });
+ });
+});
diff --git a/src/referenceHostAutosaveViewModel.test.ts b/src/referenceHostAutosaveViewModel.test.ts
new file mode 100644
index 00000000..052263d1
--- /dev/null
+++ b/src/referenceHostAutosaveViewModel.test.ts
@@ -0,0 +1,197 @@
+import { execFileSync } from 'node:child_process';
+import { existsSync, readFileSync } from 'node:fs';
+import { pathToFileURL } from 'node:url';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const fixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/autosave-view-model.mjs',
+);
+
+describe('reference-host autosave presentation contract', () => {
+ it('ships one host-owned autosave view-model fixture outside the published runtime', () => {
+ expect(existsSync(fixturePath)).toBe(true);
+ });
+
+ it('maps programmatic lifecycle transitions to localization keys without exposing validators', () => {
+ if (!existsSync(fixturePath)) return;
+ const source = readFileSync(fixturePath, 'utf8');
+
+ expect(source).not.toMatch(/(?:from|import\()\s*['"][^'"]*src\//u);
+ expect(source).not.toMatch(/\b(?:fetch|XMLHttpRequest|WebSocket)\b/u);
+ expect(source).toContain('REFERENCE_ONLY');
+ expect(source).toContain('messageKey');
+ expect(source).toContain('blockedReason');
+ expect(source).toContain('observe');
+ expect(source).not.toContain('recoveryPhase');
+ expect(source).not.toContain('document body');
+ });
+
+ it('derives clean, saving, queued, conflict, failed, retrying, recovered, closing, and closed states', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(process.execPath, [fixturePath, '--self-test'], {
+ encoding: 'utf8',
+ });
+
+ expect(JSON.parse(output)).toEqual({
+ clean: 'clean',
+ closed: 'closed',
+ closing: 'closing',
+ conflict: 'conflict',
+ failed: 'failed',
+ queued: 'queued',
+ recovered: 'recovered',
+ retrying: 'retrying',
+ saving: 'saving',
+ });
+ });
+
+ it('does not claim recovery unless a retrying save was observed', () => {
+ if (!existsSync(fixturePath)) return;
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createAutosaveViewModel } from ${moduleUrl};
+ const snapshot = (state, blockedReason = null) => ({
+ state,
+ blockedReason,
+ activeStrongEntityTag: null,
+ pendingStrongEntityTag: null,
+ lastSavedStrongEntityTag: null,
+ });
+ const viewModel = createAutosaveViewModel();
+ const blocked = viewModel.observe(snapshot('blocked', 'conflict')).viewState;
+ const idleWithoutRetry = viewModel.observe(snapshot('idle')).viewState;
+ process.stdout.write(JSON.stringify({ blocked, idleWithoutRetry }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ blocked: 'conflict',
+ idleWithoutRetry: 'clean',
+ });
+ });
+
+ it('rejects empty non-null validator fields instead of treating them as lifecycle evidence', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(
+ process.execPath,
+ [fixturePath, '--invalid-validator-self-test'],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ activeError: 'activeStrongEntityTag is invalid.',
+ lastSavedError: 'lastSavedStrongEntityTag is invalid.',
+ pendingError: 'pendingStrongEntityTag is invalid.',
+ });
+ });
+
+ it('bounds non-null validator fields before projecting autosave state', () => {
+ if (!existsSync(fixturePath)) return;
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createAutosaveViewModel } from ${moduleUrl};
+ const oversized = 'x'.repeat(257);
+ const candidate = {
+ state: 'saving',
+ blockedReason: null,
+ activeStrongEntityTag: null,
+ pendingStrongEntityTag: null,
+ lastSavedStrongEntityTag: null,
+ };
+ const errors = {};
+ for (const field of [
+ 'activeStrongEntityTag',
+ 'pendingStrongEntityTag',
+ 'lastSavedStrongEntityTag',
+ ]) {
+ try {
+ createAutosaveViewModel().observe({ ...candidate, [field]: oversized });
+ errors[field] = null;
+ } catch (error) {
+ errors[field] = error instanceof Error ? error.message : 'unexpected error';
+ }
+ }
+ process.stdout.write(JSON.stringify(errors));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ activeStrongEntityTag: 'activeStrongEntityTag is invalid.',
+ pendingStrongEntityTag: 'pendingStrongEntityTag is invalid.',
+ lastSavedStrongEntityTag: 'lastSavedStrongEntityTag is invalid.',
+ });
+ });
+
+ it('rejects weak and malformed values labeled as strong entity tags', () => {
+ if (!existsSync(fixturePath)) return;
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createAutosaveViewModel } from ${moduleUrl};
+ const candidate = {
+ state: 'saving',
+ blockedReason: null,
+ activeStrongEntityTag: null,
+ pendingStrongEntityTag: null,
+ lastSavedStrongEntityTag: null,
+ };
+ const candidates = [
+ ['weak', 'W/"weak"'],
+ ['unquoted', 'opaque'],
+ ['embeddedQuote', '"bad"quote"'],
+ ['control', '"line\\nbreak"'],
+ ['nonOctet', '"😀"'],
+ ];
+ const errors = {};
+ for (const [name, value] of candidates) {
+ try {
+ createAutosaveViewModel().observe({
+ ...candidate,
+ activeStrongEntityTag: value,
+ });
+ errors[name] = null;
+ } catch (error) {
+ errors[name] = error instanceof Error ? error.message : 'unexpected error';
+ }
+ }
+ process.stdout.write(JSON.stringify(errors));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ weak: 'activeStrongEntityTag is invalid.',
+ unquoted: 'activeStrongEntityTag is invalid.',
+ embeddedQuote: 'activeStrongEntityTag is invalid.',
+ control: 'activeStrongEntityTag is invalid.',
+ nonOctet: 'activeStrongEntityTag is invalid.',
+ });
+ });
+
+ it('rejects accessor-backed lifecycle snapshots without invoking them', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(
+ process.execPath,
+ [fixturePath, '--hostile-accessor-self-test'],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ error: 'autosave snapshot is invalid.',
+ getterCalls: 0,
+ });
+ });
+});
diff --git a/src/referenceHostCollaborationAmbiguousConnect.test.ts b/src/referenceHostCollaborationAmbiguousConnect.test.ts
new file mode 100644
index 00000000..2d285e22
--- /dev/null
+++ b/src/referenceHostCollaborationAmbiguousConnect.test.ts
@@ -0,0 +1,187 @@
+import { execFileSync } from 'node:child_process';
+import { pathToFileURL } from 'node:url';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const fixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/collaboration-provider-lifecycle.mjs',
+);
+
+describe('reference-host ambiguous collaboration connect contract', () => {
+ it('quarantines a provider after connect throws instead of retrying an indeterminate resource', () => {
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+ const events = [];
+ let generation = 0;
+ let firstProviderConnectAttempts = 0;
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ return {
+ destroy() { events.push('document:destroy'); },
+ };
+ },
+ providerFactory() {
+ generation += 1;
+ const current = generation;
+ events.push('provider:create:' + current);
+ return {
+ connect() {
+ events.push('provider:connect:' + current);
+ if (current === 1) {
+ firstProviderConnectAttempts += 1;
+ if (firstProviderConnectAttempts === 1) {
+ throw new Error('private ambiguous connect failure');
+ }
+ }
+ },
+ disconnect() { events.push('provider:disconnect:' + current); },
+ destroy() { events.push('provider:destroy:' + current); },
+ };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+
+ let firstError = null;
+ try {
+ lifecycle.connect();
+ } catch (error) {
+ firstError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ let retryError = null;
+ let retryResult = null;
+ try {
+ retryResult = lifecycle.connect();
+ } catch (error) {
+ retryError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ const afterFailure = lifecycle.getSnapshot();
+ const recovered = lifecycle.reconnect();
+ const disposeResult = lifecycle.dispose();
+ process.stdout.write(JSON.stringify({
+ afterFailure,
+ disposeResult,
+ events,
+ firstError,
+ recovered,
+ retryError,
+ retryResult,
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ afterFailure: { providerGeneration: 1, status: 'disconnected' },
+ disposeResult: true,
+ events: [
+ 'provider:create:1',
+ 'provider:connect:1',
+ 'provider:disconnect:1',
+ 'provider:destroy:1',
+ 'provider:create:2',
+ 'provider:connect:2',
+ 'provider:disconnect:2',
+ 'provider:destroy:2',
+ 'document:destroy',
+ ],
+ firstError: 'collaboration lifecycle connection failed.',
+ recovered: { providerGeneration: 2, status: 'connected' },
+ retryError: 'collaboration lifecycle connection failed.',
+ retryResult: null,
+ });
+ });
+
+ it('quarantines promise-returning connect attempts without false success or unhandled rejection', () => {
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+ const events = [];
+ let generation = 0;
+ let unhandledRejections = 0;
+ process.on('unhandledRejection', () => {
+ unhandledRejections += 1;
+ });
+
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ return {
+ destroy() { events.push('document:destroy'); },
+ };
+ },
+ providerFactory() {
+ generation += 1;
+ const current = generation;
+ events.push('provider:create:' + current);
+ return {
+ connect() {
+ events.push('provider:connect:' + current);
+ if (current === 1) {
+ return Promise.reject(new Error('private asynchronous connect failure'));
+ }
+ },
+ disconnect() { events.push('provider:disconnect:' + current); },
+ destroy() { events.push('provider:destroy:' + current); },
+ };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+
+ let firstError = null;
+ let firstResult = null;
+ try {
+ firstResult = lifecycle.connect();
+ } catch (error) {
+ firstError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ await new Promise((resolve) => setImmediate(resolve));
+ const afterFailure = lifecycle.getSnapshot();
+ const recovered = lifecycle.reconnect();
+ const disposeResult = lifecycle.dispose();
+ process.stdout.write(JSON.stringify({
+ afterFailure,
+ disposeResult,
+ events,
+ firstError,
+ firstResult,
+ recovered,
+ unhandledRejections,
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ afterFailure: { providerGeneration: 1, status: 'disconnected' },
+ disposeResult: true,
+ events: [
+ 'provider:create:1',
+ 'provider:connect:1',
+ 'provider:disconnect:1',
+ 'provider:destroy:1',
+ 'provider:create:2',
+ 'provider:connect:2',
+ 'provider:disconnect:2',
+ 'provider:destroy:2',
+ 'document:destroy',
+ ],
+ firstError: 'collaboration lifecycle connection failed.',
+ firstResult: null,
+ recovered: { providerGeneration: 2, status: 'connected' },
+ unhandledRejections: 0,
+ });
+ });
+});
diff --git a/src/referenceHostCollaborationAsyncTeardown.test.ts b/src/referenceHostCollaborationAsyncTeardown.test.ts
new file mode 100644
index 00000000..97c381e9
--- /dev/null
+++ b/src/referenceHostCollaborationAsyncTeardown.test.ts
@@ -0,0 +1,300 @@
+import { execFileSync } from 'node:child_process';
+import { pathToFileURL } from 'node:url';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const fixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/collaboration-provider-lifecycle.mjs',
+);
+
+describe('reference-host asynchronous teardown contract', () => {
+ it('quarantines promise-returning provider destruction and retries cleanup without an unhandled rejection', () => {
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+ const events = [];
+ let providerDestroyAttempts = 0;
+ let unhandledRejections = 0;
+ process.on('unhandledRejection', () => {
+ unhandledRejections += 1;
+ });
+
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ return {
+ destroy() { events.push('document:destroy'); },
+ };
+ },
+ providerFactory() {
+ return {
+ connect() { events.push('provider:connect'); },
+ disconnect() { events.push('provider:disconnect'); },
+ destroy() {
+ providerDestroyAttempts += 1;
+ events.push('provider:destroy:' + providerDestroyAttempts);
+ if (providerDestroyAttempts === 1) {
+ return Promise.reject(new Error('private asynchronous provider destroy failure'));
+ }
+ },
+ };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+
+ lifecycle.connect();
+ let firstError = null;
+ let firstResult = null;
+ try {
+ firstResult = lifecycle.dispose();
+ } catch (error) {
+ firstError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ await new Promise((resolve) => setImmediate(resolve));
+ const afterFailure = lifecycle.getSnapshot();
+ const retryResult = lifecycle.dispose();
+ const afterRetry = lifecycle.getSnapshot();
+ const idempotentResult = lifecycle.dispose();
+ process.stdout.write(JSON.stringify({
+ afterFailure,
+ afterRetry,
+ events,
+ firstError,
+ firstResult,
+ idempotentResult,
+ retryResult,
+ unhandledRejections,
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ afterFailure: { providerGeneration: 1, status: 'disconnected' },
+ afterRetry: { providerGeneration: 1, status: 'disposed' },
+ events: [
+ 'provider:connect',
+ 'provider:disconnect',
+ 'provider:destroy:1',
+ 'document:destroy',
+ 'provider:destroy:2',
+ ],
+ firstError: 'collaboration lifecycle teardown failed.',
+ firstResult: null,
+ idempotentResult: false,
+ retryResult: true,
+ unhandledRejections: 0,
+ });
+ });
+
+ it('retries promise-returning document destruction without repeating successful provider teardown', () => {
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+ const events = [];
+ let documentDestroyAttempts = 0;
+ let unhandledRejections = 0;
+ process.on('unhandledRejection', () => {
+ unhandledRejections += 1;
+ });
+
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ return {
+ destroy() {
+ documentDestroyAttempts += 1;
+ events.push('document:destroy:' + documentDestroyAttempts);
+ if (documentDestroyAttempts === 1) {
+ return Promise.reject(new Error('private asynchronous document destroy failure'));
+ }
+ },
+ };
+ },
+ providerFactory() {
+ return {
+ connect() { events.push('provider:connect'); },
+ disconnect() { events.push('provider:disconnect'); },
+ destroy() { events.push('provider:destroy'); },
+ };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+
+ lifecycle.connect();
+ let firstError = null;
+ let firstResult = null;
+ try {
+ firstResult = lifecycle.dispose();
+ } catch (error) {
+ firstError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ await new Promise((resolve) => setImmediate(resolve));
+ const afterFailure = lifecycle.getSnapshot();
+ const retryResult = lifecycle.dispose();
+ const afterRetry = lifecycle.getSnapshot();
+ process.stdout.write(JSON.stringify({
+ afterFailure,
+ afterRetry,
+ events,
+ firstError,
+ firstResult,
+ retryResult,
+ unhandledRejections,
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ afterFailure: { providerGeneration: 1, status: 'disconnected' },
+ afterRetry: { providerGeneration: 1, status: 'disposed' },
+ events: [
+ 'provider:connect',
+ 'provider:disconnect',
+ 'provider:destroy',
+ 'document:destroy:1',
+ 'document:destroy:2',
+ ],
+ firstError: 'collaboration lifecycle teardown failed.',
+ firstResult: null,
+ retryResult: true,
+ unhandledRejections: 0,
+ });
+ });
+
+ it('contains promise-returning document cleanup while unwinding initial provider failure', () => {
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+ const events = [];
+ let unhandledRejections = 0;
+ process.on('unhandledRejection', () => {
+ unhandledRejections += 1;
+ });
+
+ let initializationError = null;
+ try {
+ createHostCollaborationLifecycle({
+ documentFactory() {
+ events.push('document:create');
+ return {
+ destroy() {
+ events.push('document:destroy');
+ return Promise.reject(new Error('private asynchronous initialization cleanup failure'));
+ },
+ };
+ },
+ providerFactory() {
+ events.push('provider:create');
+ throw new Error('private provider construction failure');
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+ } catch (error) {
+ initializationError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ await new Promise((resolve) => setImmediate(resolve));
+ process.stdout.write(JSON.stringify({
+ events,
+ initializationError,
+ unhandledRejections,
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ events: ['document:create', 'provider:create', 'document:destroy'],
+ initializationError: 'collaboration lifecycle initialization failed.',
+ unhandledRejections: 0,
+ });
+ });
+
+ it('contains promise-returning provider disconnection before completing synchronous destruction', () => {
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+ const events = [];
+ let unhandledRejections = 0;
+ process.on('unhandledRejection', () => {
+ unhandledRejections += 1;
+ });
+
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ return {
+ destroy() { events.push('document:destroy'); },
+ };
+ },
+ providerFactory() {
+ return {
+ connect() { events.push('provider:connect'); },
+ disconnect() {
+ events.push('provider:disconnect');
+ return Promise.reject(new Error('private asynchronous provider disconnect failure'));
+ },
+ destroy() { events.push('provider:destroy'); },
+ };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+
+ lifecycle.connect();
+ let firstError = null;
+ let firstResult = null;
+ try {
+ firstResult = lifecycle.dispose();
+ } catch (error) {
+ firstError = error instanceof Error ? error.message : 'unexpected error';
+ }
+
+ await new Promise((resolve) => setImmediate(resolve));
+ const afterFailure = lifecycle.getSnapshot();
+ const retryResult = lifecycle.dispose();
+ process.stdout.write(JSON.stringify({
+ afterFailure,
+ events,
+ firstError,
+ firstResult,
+ retryResult,
+ unhandledRejections,
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ afterFailure: { providerGeneration: 1, status: 'disposed' },
+ events: [
+ 'provider:connect',
+ 'provider:disconnect',
+ 'provider:destroy',
+ 'document:destroy',
+ ],
+ firstError: 'collaboration lifecycle teardown failed.',
+ firstResult: null,
+ retryResult: false,
+ unhandledRejections: 0,
+ });
+ });
+});
diff --git a/src/referenceHostCollaborationFailureRedaction.test.ts b/src/referenceHostCollaborationFailureRedaction.test.ts
new file mode 100644
index 00000000..0baf2101
--- /dev/null
+++ b/src/referenceHostCollaborationFailureRedaction.test.ts
@@ -0,0 +1,129 @@
+import { execFileSync } from 'node:child_process';
+import { existsSync } from 'node:fs';
+import { pathToFileURL } from 'node:url';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const fixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/collaboration-provider-lifecycle.mjs',
+);
+
+describe('reference-host collaboration callback failure redaction', () => {
+ it('redacts document factory failures at initialization', () => {
+ if (!existsSync(fixturePath)) return;
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+ const privateCause = 'private document factory cause';
+ let error = null;
+ try {
+ createHostCollaborationLifecycle({
+ documentFactory() { throw new Error(privateCause); },
+ providerFactory() {
+ return { connect() {}, disconnect() {}, destroy() {} };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+ } catch (failure) {
+ error = failure instanceof Error ? failure.message : 'unexpected error';
+ }
+ process.stdout.write(JSON.stringify({
+ error,
+ leakedPrivateCause: typeof error === 'string' && error.includes(privateCause),
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ error: 'collaboration lifecycle initialization failed.',
+ leakedPrivateCause: false,
+ });
+ });
+
+ it('redacts ambiguous provider connect failures and requires provider replacement', () => {
+ if (!existsSync(fixturePath)) return;
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+ const privateCause = 'private provider connect cause';
+ const events = [];
+ let providerGeneration = 0;
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ return { destroy() { events.push('document:destroy'); } };
+ },
+ providerFactory() {
+ providerGeneration += 1;
+ const generation = providerGeneration;
+ events.push('provider:create:' + generation);
+ return {
+ connect() {
+ events.push('provider:connect:' + generation);
+ if (generation === 1) throw new Error(privateCause);
+ },
+ disconnect() { events.push('provider:disconnect:' + generation); },
+ destroy() { events.push('provider:destroy:' + generation); },
+ };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+ let error = null;
+ try {
+ lifecycle.connect();
+ } catch (failure) {
+ error = failure instanceof Error ? failure.message : 'unexpected error';
+ }
+ const afterFailure = lifecycle.getSnapshot();
+ let retryError = null;
+ try {
+ lifecycle.connect();
+ } catch (failure) {
+ retryError = failure instanceof Error ? failure.message : 'unexpected error';
+ }
+ const recovered = lifecycle.reconnect();
+ lifecycle.dispose();
+ process.stdout.write(JSON.stringify({
+ afterFailure,
+ error,
+ events,
+ leakedPrivateCause:
+ (typeof error === 'string' && error.includes(privateCause)) ||
+ (typeof retryError === 'string' && retryError.includes(privateCause)),
+ recovered,
+ retryError,
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ afterFailure: { providerGeneration: 1, status: 'disconnected' },
+ error: 'collaboration lifecycle connection failed.',
+ events: [
+ 'provider:create:1',
+ 'provider:connect:1',
+ 'provider:disconnect:1',
+ 'provider:destroy:1',
+ 'provider:create:2',
+ 'provider:connect:2',
+ 'provider:disconnect:2',
+ 'provider:destroy:2',
+ 'document:destroy',
+ ],
+ leakedPrivateCause: false,
+ recovered: { providerGeneration: 2, status: 'connected' },
+ retryError: 'collaboration lifecycle connection failed.',
+ });
+ });
+});
diff --git a/src/referenceHostCollaborationLifecycle.test.ts b/src/referenceHostCollaborationLifecycle.test.ts
new file mode 100644
index 00000000..83071c8c
--- /dev/null
+++ b/src/referenceHostCollaborationLifecycle.test.ts
@@ -0,0 +1,398 @@
+import { execFileSync } from 'node:child_process';
+import { existsSync, readFileSync } from 'node:fs';
+import { pathToFileURL } from 'node:url';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const fixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/collaboration-provider-lifecycle.mjs',
+);
+
+describe('reference-host collaboration lifecycle contract', () => {
+ it('ships one host-owned collaboration lifecycle fixture outside the published runtime', () => {
+ expect(existsSync(fixturePath)).toBe(true);
+ });
+
+ it('keeps provider creation and teardown host-owned and provider-neutral', () => {
+ if (!existsSync(fixturePath)) return;
+ const source = readFileSync(fixturePath, 'utf8');
+
+ expect(source).not.toMatch(/(?:from|import\()\s*['"][^'"]*src\//u);
+ expect(source).not.toMatch(/\b(?:fetch|XMLHttpRequest|WebSocket|process\.env)\b/u);
+ expect(source).toContain('REFERENCE_ONLY');
+ expect(source).toContain('providerFactory');
+ expect(source).toContain('reconnect');
+ expect(source).toContain('dispose');
+ });
+
+ it('reuses one real host-created Y.Doc across provider reconnects and tears it down exactly once', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(process.execPath, [fixturePath, '--self-test'], {
+ encoding: 'utf8',
+ });
+
+ expect(JSON.parse(output)).toEqual({
+ events: [
+ 'document:create',
+ 'provider:create:1',
+ 'provider:connect:1',
+ 'provider:disconnect:1',
+ 'provider:destroy:1',
+ 'provider:create:2',
+ 'provider:connect:2',
+ 'provider:disconnect:2',
+ 'provider:destroy:2',
+ 'document:destroy',
+ ],
+ hostDocumentIsYjs: true,
+ providerGeneration: 2,
+ sameDocumentAcrossReconnect: true,
+ status: 'disposed',
+ yjsText: 'Buyer draft',
+ });
+ });
+
+ it('retains a provider whose destroy failed so reconnect can retry cleanup before replacement', () => {
+ if (!existsSync(fixturePath)) return;
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+ const events = [];
+ let generation = 0;
+ let firstDestroyAttempts = 0;
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ return { destroy() { events.push('document:destroy'); } };
+ },
+ providerFactory() {
+ generation += 1;
+ const current = generation;
+ events.push('provider:create:' + current);
+ return {
+ connect() { events.push('provider:connect:' + current); },
+ disconnect() { events.push('provider:disconnect:' + current); },
+ destroy() {
+ events.push('provider:destroy:' + current);
+ if (current === 1 && firstDestroyAttempts === 0) {
+ firstDestroyAttempts += 1;
+ throw new Error('private transient destroy failure');
+ }
+ },
+ };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+ lifecycle.connect();
+ let firstError = null;
+ try {
+ lifecycle.reconnect();
+ } catch (error) {
+ firstError = error instanceof Error ? error.message : 'unexpected error';
+ }
+ lifecycle.reconnect();
+ process.stdout.write(JSON.stringify({
+ events,
+ firstError,
+ snapshot: lifecycle.getSnapshot(),
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ events: [
+ 'provider:create:1',
+ 'provider:connect:1',
+ 'provider:disconnect:1',
+ 'provider:destroy:1',
+ 'provider:destroy:1',
+ 'provider:create:2',
+ 'provider:connect:2',
+ ],
+ firstError: 'collaboration lifecycle teardown failed.',
+ snapshot: { providerGeneration: 2, status: 'connected' },
+ });
+ });
+
+ it('redacts reconnect provider construction failures and remains recoverable', () => {
+ if (!existsSync(fixturePath)) return;
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+ const privateCause = 'private reconnect provider cause';
+ const events = [];
+ let generation = 0;
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ return { destroy() { events.push('document:destroy'); } };
+ },
+ providerFactory() {
+ generation += 1;
+ const current = generation;
+ events.push('provider:create:' + current);
+ if (current === 2) throw new Error(privateCause);
+ return {
+ connect() { events.push('provider:connect:' + current); },
+ disconnect() { events.push('provider:disconnect:' + current); },
+ destroy() { events.push('provider:destroy:' + current); },
+ };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+ lifecycle.connect();
+ let reconnectError = null;
+ try {
+ lifecycle.reconnect();
+ } catch (error) {
+ reconnectError = error instanceof Error ? error.message : 'unexpected error';
+ }
+ const afterFailure = lifecycle.getSnapshot();
+ const recovered = lifecycle.reconnect();
+ lifecycle.dispose();
+ process.stdout.write(JSON.stringify({
+ afterFailure,
+ events,
+ leakedPrivateCause:
+ typeof reconnectError === 'string' && reconnectError.includes(privateCause),
+ reconnectError,
+ recovered,
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ afterFailure: { providerGeneration: 2, status: 'disconnected' },
+ events: [
+ 'provider:create:1',
+ 'provider:connect:1',
+ 'provider:disconnect:1',
+ 'provider:destroy:1',
+ 'provider:create:2',
+ 'provider:create:3',
+ 'provider:connect:3',
+ 'provider:disconnect:3',
+ 'provider:destroy:3',
+ 'document:destroy',
+ ],
+ leakedPrivateCause: false,
+ reconnectError: 'collaboration lifecycle reconnect failed.',
+ recovered: { providerGeneration: 3, status: 'connected' },
+ });
+ });
+
+ it('rejects accessor-backed lifecycle options and resource methods without invoking them', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(
+ process.execPath,
+ [fixturePath, '--hostile-accessor-self-test'],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ documentError: 'documentFactory returned an invalid document.',
+ documentGetterCalls: 0,
+ optionsError: 'collaboration options are invalid.',
+ optionsGetterCalls: 0,
+ providerError: 'providerFactory returned an invalid provider.',
+ providerGetterCalls: 0,
+ });
+ });
+
+ it('unwinds the acquired host document when initial provider construction fails', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(
+ process.execPath,
+ [fixturePath, '--initialization-failure-self-test'],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ error: 'collaboration lifecycle initialization failed.',
+ events: ['document:create', 'provider:create', 'document:destroy'],
+ leakedPrivateCause: false,
+ });
+ });
+
+ it('attempts provider and document cleanup after teardown failure without leaking private causes', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(
+ process.execPath,
+ [fixturePath, '--cleanup-failure-self-test'],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ error: 'collaboration lifecycle teardown failed.',
+ events: [
+ 'provider:connect',
+ 'provider:disconnect',
+ 'provider:destroy',
+ 'document:destroy',
+ ],
+ leakedPrivateCause: false,
+ status: 'disposed',
+ });
+ });
+
+ it('retries incomplete provider destruction without destroying the host document twice', () => {
+ if (!existsSync(fixturePath)) return;
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+ const events = [];
+ let providerDestroyAttempts = 0;
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ return {
+ destroy() { events.push('document:destroy'); },
+ };
+ },
+ providerFactory() {
+ return {
+ connect() { events.push('provider:connect'); },
+ disconnect() { events.push('provider:disconnect'); },
+ destroy() {
+ providerDestroyAttempts += 1;
+ events.push('provider:destroy:' + providerDestroyAttempts);
+ if (providerDestroyAttempts === 1) {
+ throw new Error('private transient provider destroy failure');
+ }
+ },
+ };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+ lifecycle.connect();
+ let firstError = null;
+ try {
+ lifecycle.dispose();
+ } catch (error) {
+ firstError = error instanceof Error ? error.message : 'unexpected error';
+ }
+ const afterFailure = lifecycle.getSnapshot();
+ const retryResult = lifecycle.dispose();
+ const afterRetry = lifecycle.getSnapshot();
+ const idempotentResult = lifecycle.dispose();
+ process.stdout.write(JSON.stringify({
+ afterFailure,
+ afterRetry,
+ events,
+ firstError,
+ idempotentResult,
+ retryResult,
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ afterFailure: { providerGeneration: 1, status: 'disconnected' },
+ afterRetry: { providerGeneration: 1, status: 'disposed' },
+ events: [
+ 'provider:connect',
+ 'provider:disconnect',
+ 'provider:destroy:1',
+ 'document:destroy',
+ 'provider:destroy:2',
+ ],
+ firstError: 'collaboration lifecycle teardown failed.',
+ idempotentResult: false,
+ retryResult: true,
+ });
+ });
+
+ it('allows only cleanup retries after disposal has started', () => {
+ if (!existsSync(fixturePath)) return;
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+ const events = [];
+ const lifecycle = createHostCollaborationLifecycle({
+ documentFactory() {
+ return {
+ destroy() { events.push('document:destroy'); },
+ };
+ },
+ providerFactory() {
+ return {
+ connect() { events.push('provider:connect'); },
+ disconnect() { events.push('provider:disconnect'); },
+ destroy() {
+ events.push('provider:destroy');
+ throw new Error('private persistent provider destroy failure');
+ },
+ };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+ lifecycle.connect();
+ let disposeError = null;
+ try {
+ lifecycle.dispose();
+ } catch (error) {
+ disposeError = error instanceof Error ? error.message : 'unexpected error';
+ }
+ const eventsAfterDispose = [...events];
+ let connectError = null;
+ try {
+ lifecycle.connect();
+ } catch (error) {
+ connectError = error instanceof Error ? error.message : 'unexpected error';
+ }
+ let reconnectError = null;
+ try {
+ lifecycle.reconnect();
+ } catch (error) {
+ reconnectError = error instanceof Error ? error.message : 'unexpected error';
+ }
+ process.stdout.write(JSON.stringify({
+ connectError,
+ disposeError,
+ events,
+ eventsAfterDispose,
+ reconnectError,
+ snapshot: lifecycle.getSnapshot(),
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ connectError: 'collaboration lifecycle teardown failed.',
+ disposeError: 'collaboration lifecycle teardown failed.',
+ events: [
+ 'provider:connect',
+ 'provider:disconnect',
+ 'provider:destroy',
+ 'document:destroy',
+ ],
+ eventsAfterDispose: [
+ 'provider:connect',
+ 'provider:disconnect',
+ 'provider:destroy',
+ 'document:destroy',
+ ],
+ reconnectError: 'collaboration lifecycle teardown failed.',
+ snapshot: { providerGeneration: 1, status: 'disconnected' },
+ });
+ });
+});
diff --git a/src/referenceHostCollaborationOptionShape.test.ts b/src/referenceHostCollaborationOptionShape.test.ts
new file mode 100644
index 00000000..245e6a95
--- /dev/null
+++ b/src/referenceHostCollaborationOptionShape.test.ts
@@ -0,0 +1,78 @@
+import { execFileSync } from 'node:child_process';
+import { pathToFileURL } from 'node:url';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const fixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/collaboration-provider-lifecycle.mjs',
+);
+
+describe('reference-host collaboration option shape', () => {
+ it('rejects unknown authority-looking option fields before invoking host factories', () => {
+ const moduleUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${moduleUrl};
+
+ function run(kind) {
+ let documentFactoryCalls = 0;
+ let providerFactoryCalls = 0;
+ const options = {
+ documentFactory() {
+ documentFactoryCalls += 1;
+ return { destroy() {} };
+ },
+ providerFactory() {
+ providerFactoryCalls += 1;
+ return { connect() {}, disconnect() {}, destroy() {} };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ };
+
+ if (kind === 'enumerable') {
+ options.authorization = 'owner';
+ } else if (kind === 'hidden') {
+ Object.defineProperty(options, 'authorization', {
+ value: 'owner',
+ enumerable: false,
+ });
+ } else {
+ options[Symbol('authorization')] = 'owner';
+ }
+
+ let error = null;
+ try {
+ const lifecycle = createHostCollaborationLifecycle(options);
+ lifecycle.dispose();
+ } catch (failure) {
+ error = failure instanceof Error ? failure.message : 'unexpected error';
+ }
+ return { documentFactoryCalls, error, providerFactoryCalls };
+ }
+
+ process.stdout.write(JSON.stringify({
+ enumerable: run('enumerable'),
+ hidden: run('hidden'),
+ symbol: run('symbol'),
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ const rejected = {
+ documentFactoryCalls: 0,
+ error: 'collaboration options are invalid.',
+ providerFactoryCalls: 0,
+ };
+ expect(JSON.parse(output)).toEqual({
+ enumerable: rejected,
+ hidden: rejected,
+ symbol: rejected,
+ });
+ });
+});
diff --git a/src/referenceHostCollaborationPrototypeTraversal.test.ts b/src/referenceHostCollaborationPrototypeTraversal.test.ts
new file mode 100644
index 00000000..515651ab
--- /dev/null
+++ b/src/referenceHostCollaborationPrototypeTraversal.test.ts
@@ -0,0 +1,58 @@
+import { execFileSync } from 'node:child_process';
+import { pathToFileURL } from 'node:url';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const fixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/collaboration-provider-lifecycle.mjs',
+);
+
+describe('reference-host collaboration prototype traversal', () => {
+ it('bounds hostile resource prototype traversal before caller-controlled work can continue indefinitely', () => {
+ const fixtureUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { createHostCollaborationLifecycle } from ${fixtureUrl};
+ const privateCause = 'private prototype traversal cause';
+ let prototypeReads = 0;
+ let hostileDocument;
+ hostileDocument = new Proxy({}, {
+ getPrototypeOf() {
+ prototypeReads += 1;
+ if (prototypeReads > 64) throw new Error(privateCause);
+ return hostileDocument;
+ },
+ });
+ let error = null;
+ try {
+ createHostCollaborationLifecycle({
+ documentFactory() { return hostileDocument; },
+ providerFactory() {
+ return { connect() {}, disconnect() {}, destroy() {} };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });
+ } catch (failure) {
+ error = failure instanceof Error ? failure.message : 'unexpected error';
+ }
+ process.stdout.write(JSON.stringify({
+ error,
+ leakedPrivateCause: typeof error === 'string' && error.includes(privateCause),
+ prototypeReads,
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ error: 'documentFactory returned an invalid document.',
+ leakedPrivateCause: false,
+ prototypeReads: 64,
+ });
+ });
+});
diff --git a/src/referenceHostDelayedProposal.test.ts b/src/referenceHostDelayedProposal.test.ts
new file mode 100644
index 00000000..19aafeec
--- /dev/null
+++ b/src/referenceHostDelayedProposal.test.ts
@@ -0,0 +1,91 @@
+import { execFileSync } from 'node:child_process';
+import { existsSync, readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const fixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/delayed-proposal.mjs',
+);
+
+describe('reference-host delayed proposal contract', () => {
+ it('ships one deterministic local proposal fixture outside the published runtime', () => {
+ expect(existsSync(fixturePath)).toBe(true);
+ });
+
+ it('keeps proposal generation provider-free and network-free', () => {
+ if (!existsSync(fixturePath)) return;
+ const source = readFileSync(fixturePath, 'utf8');
+
+ expect(source).not.toMatch(/(?:from|import\()\s*['"][^'"]*src\//u);
+ expect(source).not.toMatch(/\b(?:fetch|XMLHttpRequest|WebSocket)\b/u);
+ expect(source).toContain('REFERENCE_ONLY');
+ expect(source).toContain('expectedRevision');
+ });
+
+ it('conflicts stale delayed proposals instead of overwriting newer content', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(process.execPath, [fixturePath, '--self-test'], {
+ encoding: 'utf8',
+ });
+
+ expect(JSON.parse(output)).toEqual({
+ acceptedDocument: 'Accepted proposal',
+ acceptedStatus: 'applied',
+ staleDocument: 'User typed newer text',
+ staleStatus: 'conflict',
+ });
+ });
+
+ it('permits an empty replacement without weakening non-empty revision identity', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(
+ process.execPath,
+ [fixturePath, '--empty-proposal-self-test'],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ appliedDocument: '',
+ appliedStatus: 'applied',
+ emptyRevisionError: 'expectedRevision is invalid.',
+ proposalReplacement: '',
+ });
+ });
+
+ it('rejects accessor-backed untrusted proposal inputs without invoking them', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(
+ process.execPath,
+ [fixturePath, '--hostile-accessor-self-test'],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ applicationError: 'proposal application is invalid.',
+ applicationGetterCalls: 0,
+ creationError: 'proposal creation is invalid.',
+ creationGetterCalls: 0,
+ proposalError: 'proposal application is invalid.',
+ proposalGetterCalls: 0,
+ });
+ });
+
+ it('fails closed on unknown proposal and application fields before host mutation', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(
+ process.execPath,
+ [fixturePath, '--unknown-field-self-test'],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ applicationApplyCalls: 0,
+ applicationError: 'proposal application is invalid.',
+ creationError: 'proposal creation is invalid.',
+ proposalApplyCalls: 0,
+ proposalError: 'proposal application is invalid.',
+ });
+ });
+});
diff --git a/src/referenceHostDelayedProposalApplicationFailure.test.ts b/src/referenceHostDelayedProposalApplicationFailure.test.ts
new file mode 100644
index 00000000..9a5246fe
--- /dev/null
+++ b/src/referenceHostDelayedProposalApplicationFailure.test.ts
@@ -0,0 +1,96 @@
+import { execFileSync } from 'node:child_process';
+import { pathToFileURL } from 'node:url';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const fixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/delayed-proposal.mjs',
+);
+
+describe('reference-host delayed proposal application failure', () => {
+ it('redacts host apply failures instead of leaking private causes through the proposal boundary', () => {
+ const fixtureUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { applyDelayedProposal, createDelayedProposal } from ${fixtureUrl};
+ const privateCause = 'private host apply cause';
+ const proposal = await createDelayedProposal({
+ expectedRevision: 'revision-v1',
+ replacement: 'Accepted proposal',
+ });
+ let error = null;
+ try {
+ applyDelayedProposal({
+ proposal,
+ currentRevision: 'revision-v1',
+ apply() {
+ throw new Error(privateCause);
+ },
+ });
+ } catch (failure) {
+ error = failure instanceof Error ? failure.message : 'unexpected error';
+ }
+ process.stdout.write(JSON.stringify({
+ error,
+ leakedPrivateCause: typeof error === 'string' && error.includes(privateCause),
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ error: 'proposal application failed.',
+ leakedPrivateCause: false,
+ });
+ });
+
+ it('does not report applied when a host apply thenable rejects asynchronously', () => {
+ const fixtureUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import { applyDelayedProposal, createDelayedProposal } from ${fixtureUrl};
+ const privateCause = 'private async host apply cause';
+ const proposal = await createDelayedProposal({
+ expectedRevision: 'revision-v1',
+ replacement: 'Accepted proposal',
+ });
+ let error = null;
+ let status = null;
+ try {
+ const result = await applyDelayedProposal({
+ proposal,
+ currentRevision: 'revision-v1',
+ apply() {
+ return {
+ then(_resolve, reject) {
+ reject(new Error(privateCause));
+ },
+ };
+ },
+ });
+ status = result.status;
+ } catch (failure) {
+ error = failure instanceof Error ? failure.message : 'unexpected error';
+ }
+ process.stdout.write(JSON.stringify({
+ error,
+ leakedPrivateCause: typeof error === 'string' && error.includes(privateCause),
+ status,
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ error: 'proposal application failed.',
+ leakedPrivateCause: false,
+ status: null,
+ });
+ });
+});
diff --git a/src/referenceHostFrameworkFreePackedBoundary.test.ts b/src/referenceHostFrameworkFreePackedBoundary.test.ts
new file mode 100644
index 00000000..3213f701
--- /dev/null
+++ b/src/referenceHostFrameworkFreePackedBoundary.test.ts
@@ -0,0 +1,167 @@
+import { execFileSync } from 'node:child_process';
+import {
+ existsSync,
+ mkdirSync,
+ mkdtempSync,
+ readFileSync,
+ readdirSync,
+ renameSync,
+ rmSync,
+ writeFileSync,
+} from 'node:fs';
+import { tmpdir } from 'node:os';
+import { dirname, join, resolve } from 'node:path';
+
+import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+
+const repositoryRoot = process.cwd();
+const packageMetadata = JSON.parse(
+ readFileSync(resolve(repositoryRoot, 'package.json'), 'utf8'),
+) as { name: string; version: string };
+
+const temporaryRoot = mkdtempSync(
+ join(tmpdir(), 'inkspan-reference-host-framework-free-'),
+);
+const extractionDirectory = join(temporaryRoot, 'extracted');
+const consumerDirectory = join(temporaryRoot, 'consumer');
+const packageDirectory = join(
+ consumerDirectory,
+ 'node_modules',
+ ...packageMetadata.name.split('/'),
+);
+
+function run(command: string, argumentsList: string[], cwd = repositoryRoot) {
+ return execFileSync(command, argumentsList, {
+ cwd,
+ encoding: 'utf8',
+ stdio: ['ignore', 'pipe', 'pipe'],
+ timeout: 180_000,
+ });
+}
+
+beforeAll(
+ () => {
+ run('pnpm', ['build']);
+ mkdirSync(extractionDirectory, { recursive: true });
+ mkdirSync(dirname(packageDirectory), { recursive: true });
+
+ const packResult = JSON.parse(
+ run('npm', [
+ 'pack',
+ '--json',
+ '--ignore-scripts',
+ '--pack-destination',
+ temporaryRoot,
+ ]),
+ ) as Array<{ filename: string; name: string; version: string }>;
+
+ expect(packResult).toHaveLength(1);
+ expect(packResult[0]?.name).toBe(packageMetadata.name);
+ expect(packResult[0]?.version).toBe(packageMetadata.version);
+
+ const tarballPath = join(temporaryRoot, packResult[0]!.filename);
+ expect(existsSync(tarballPath)).toBe(true);
+ run('tar', ['-xzf', tarballPath, '-C', extractionDirectory]);
+ renameSync(join(extractionDirectory, 'package'), packageDirectory);
+ writeFileSync(
+ join(consumerDirectory, 'package.json'),
+ '{"name":"inkspan-reference-host-framework-free-consumer","private":true,"type":"module"}\n',
+ 'utf8',
+ );
+ },
+ 180_000,
+);
+
+afterAll(() => {
+ rmSync(temporaryRoot, { recursive: true, force: true });
+});
+
+describe('reference-host framework-free packed package boundary', () => {
+ it('contains no installed framework or editor dependency beside the exact packed Inkspan artifact', () => {
+ expect(readdirSync(join(consumerDirectory, 'node_modules'))).toEqual([
+ '@contextualwisdomlab',
+ ]);
+ expect(
+ readdirSync(join(consumerDirectory, 'node_modules', '@contextualwisdomlab')),
+ ).toEqual(['cwl-editor']);
+ });
+
+ it('executes the packed autosave, converter, and Markdown ESM subpaths without React, TipTap, Yjs, browser, network, credential, or model dependencies', () => {
+ const consumerPath = join(consumerDirectory, 'consumer.mjs');
+ writeFileSync(
+ consumerPath,
+ `import assert from 'node:assert/strict';
+Object.defineProperty(globalThis, 'document', {
+ configurable: true,
+ get() { throw new Error('browser document authority is forbidden'); },
+});
+Object.defineProperty(globalThis, 'window', {
+ configurable: true,
+ get() { throw new Error('browser window authority is forbidden'); },
+});
+globalThis.fetch = () => { throw new Error('network authority is forbidden'); };
+
+const autosave = await import('${packageMetadata.name}/autosave');
+const converter = await import('${packageMetadata.name}/converter');
+const markdown = await import('${packageMetadata.name}/markdown');
+
+assert.equal(typeof autosave.createDocumentAutosaveQueue, 'function');
+assert.deepEqual(
+ Array.from(converter.dataUriToBytes('data:text/plain;base64,SGk=').bytes),
+ [72, 105],
+);
+assert.equal(markdown.markdownToPlainText('# Buyer boundary'), 'Buyer boundary');
+assert.equal(
+ markdown.markdownToHtml('**Buyer boundary**'),
+ markdown.markdownToHtml('**Buyer boundary**'),
+);
+
+for (const specifier of ['autosave', 'converter', 'markdown']) {
+ const resolved = import.meta.resolve(\`${packageMetadata.name}/\${specifier}\`);
+ assert.match(resolved, /\\/dist\\/cwl-(?:autosave|converter|markdown)\\.js$/u);
+}
+`,
+ 'utf8',
+ );
+
+ expect(() => run(process.execPath, [consumerPath], consumerDirectory)).not.toThrow();
+ });
+
+ it('executes the same packed CommonJS subpaths without installing framework dependencies', () => {
+ const consumerPath = join(consumerDirectory, 'consumer.cjs');
+ writeFileSync(
+ consumerPath,
+ `const assert = require('node:assert/strict');
+Object.defineProperty(globalThis, 'document', {
+ configurable: true,
+ get() { throw new Error('browser document authority is forbidden'); },
+});
+Object.defineProperty(globalThis, 'window', {
+ configurable: true,
+ get() { throw new Error('browser window authority is forbidden'); },
+});
+globalThis.fetch = () => { throw new Error('network authority is forbidden'); };
+
+const autosave = require('${packageMetadata.name}/autosave');
+const converter = require('${packageMetadata.name}/converter');
+const markdown = require('${packageMetadata.name}/markdown');
+
+assert.equal(typeof autosave.createDocumentAutosaveQueue, 'function');
+assert.deepEqual(
+ Array.from(converter.dataUriToBytes('data:text/plain;base64,T0s=').bytes),
+ [79, 75],
+);
+assert.equal(markdown.markdownToPlainText('# Buyer boundary'), 'Buyer boundary');
+for (const specifier of ['autosave', 'converter', 'markdown']) {
+ assert.match(
+ require.resolve(\`${packageMetadata.name}/\${specifier}\`),
+ /\\/dist\\/cwl-(?:autosave|converter|markdown)\\.cjs$/u,
+ );
+}
+`,
+ 'utf8',
+ );
+
+ expect(() => run(process.execPath, [consumerPath], consumerDirectory)).not.toThrow();
+ });
+});
diff --git a/src/referenceHostHydrationGate.test.tsx b/src/referenceHostHydrationGate.test.tsx
new file mode 100644
index 00000000..4d396311
--- /dev/null
+++ b/src/referenceHostHydrationGate.test.tsx
@@ -0,0 +1,51 @@
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+
+import { cleanup, render, screen, waitFor } from '@testing-library/react';
+import { afterEach, describe, expect, it, vi } from 'vitest';
+import { renderToString } from 'react-dom/server';
+import { ReferenceHostHydrationGate } from '../examples/reference-host/hydration-gate.js';
+
+const hydrationGateSource = readFileSync(
+ resolve(process.cwd(), 'examples/reference-host/hydration-gate.tsx'),
+ 'utf8',
+);
+
+afterEach(cleanup);
+
+describe('reference-host hydration gate', () => {
+ it('declares the hydration boundary as a client component for App Router style hosts', () => {
+ expect(hydrationGateSource.startsWith("'use client';\n")).toBe(true);
+ });
+
+ it('keeps the browser editor out of server markup and mounts it only after client hydration', async () => {
+ const renderEditor = vi.fn(() => (
+ Hydrated editor
+ ));
+
+ const serverHtml = renderToString(
+ ,
+ );
+
+ expect(renderEditor).not.toHaveBeenCalled();
+ expect(serverHtml).toContain('aria-busy="true"');
+ expect(serverHtml).toContain('Loading editor');
+ expect(serverHtml).not.toContain('data-reference-editor="ready"');
+
+ render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(renderEditor).toHaveBeenCalledTimes(1);
+ expect(screen.getByText('Hydrated editor')).toBeInTheDocument();
+ });
+ expect(screen.queryByText('Loading editor')).not.toBeInTheDocument();
+ });
+});
diff --git a/src/referenceHostNativeFormAdmission.test.ts b/src/referenceHostNativeFormAdmission.test.ts
new file mode 100644
index 00000000..1fab32bc
--- /dev/null
+++ b/src/referenceHostNativeFormAdmission.test.ts
@@ -0,0 +1,54 @@
+import { describe, expect, it, vi } from 'vitest';
+
+import {
+ createSingleFlightSubmission,
+ shouldBlockReferenceHostFormMutation,
+} from '../examples/reference-host/single-flight-submission.js';
+
+describe('reference-host native-form synchronous admission', () => {
+ it('blocks a same-turn reset after durable submission admission before presentation commits', async () => {
+ let releaseAuthorizedSubmit: (() => void) | undefined;
+ const authorizedSubmit = new Promise((resolve) => {
+ releaseAuthorizedSubmit = resolve;
+ });
+ let savingPresentationCommitted = false;
+ const submit = createSingleFlightSubmission(
+ async () => authorizedSubmit,
+ (state) => {
+ if (state === 'saving') {
+ queueMicrotask(() => {
+ savingPresentationCommitted = true;
+ });
+ }
+ },
+ );
+
+ expect(
+ shouldBlockReferenceHostFormMutation(false, submit.isInFlight),
+ ).toBe(false);
+
+ const pendingSubmission = submit('# Durable write');
+
+ expect(submit.isInFlight()).toBe(true);
+ expect(savingPresentationCommitted).toBe(false);
+
+ const preventDefault = vi.fn();
+ if (shouldBlockReferenceHostFormMutation(false, submit.isInFlight)) {
+ preventDefault();
+ }
+
+ expect(preventDefault).toHaveBeenCalledOnce();
+ expect(savingPresentationCommitted).toBe(false);
+
+ await Promise.resolve();
+ expect(savingPresentationCommitted).toBe(true);
+
+ releaseAuthorizedSubmit?.();
+ await expect(pendingSubmission).resolves.toBe(true);
+ expect(submit.isInFlight()).toBe(false);
+ });
+
+ it('also blocks host writes while read-only without consulting presentation state', () => {
+ expect(shouldBlockReferenceHostFormMutation(true, () => false)).toBe(true);
+ });
+});
diff --git a/src/referenceHostNativeFormJourney.test.ts b/src/referenceHostNativeFormJourney.test.ts
new file mode 100644
index 00000000..0a5f4ff0
--- /dev/null
+++ b/src/referenceHostNativeFormJourney.test.ts
@@ -0,0 +1,130 @@
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { describe, expect, it } from 'vitest';
+
+const nativeFormHostSource = readFileSync(
+ resolve(process.cwd(), 'examples/reference-host/native-form-host.tsx'),
+ 'utf8',
+);
+
+describe('reference-host native form journey', () => {
+ it('uses the published editor package and delegates serialization to Inkspan native form integration', () => {
+ expect(nativeFormHostSource).toContain(
+ "from '@contextualwisdomlab/cwl-editor'",
+ );
+ expect(nativeFormHostSource).toContain('formFieldName="message_body"');
+ expect(nativeFormHostSource).toContain('formResetValue="# Draft"');
+ expect(nativeFormHostSource).toContain('new FormData(event.currentTarget)');
+ expect(nativeFormHostSource).toContain('type="submit"');
+ expect(nativeFormHostSource).toContain('type="reset"');
+
+ expect(nativeFormHostSource).not.toMatch(/ ]+type=["']hidden["']/i);
+ expect(nativeFormHostSource).not.toContain('/src/');
+ expect(nativeFormHostSource).not.toContain('../../src');
+ });
+
+ it('keeps host authorization and durable persistence explicitly outside the component submit callback', () => {
+ expect(nativeFormHostSource).toContain('onAuthorizedSubmit');
+ expect(nativeFormHostSource).toContain('createSingleFlightSubmission');
+ expect(nativeFormHostSource).toContain(
+ 'onAuthorizedSubmitRef.current(messageBody)',
+ );
+ expect(nativeFormHostSource).toContain(
+ 'await submitAuthorized(messageBodyEntry)',
+ );
+ expect(nativeFormHostSource).toContain(
+ "disabled={readOnly || submissionState === 'saving'}",
+ );
+ expect(nativeFormHostSource).not.toContain('fetch(');
+ expect(nativeFormHostSource).not.toContain('localStorage');
+ });
+
+ it('binds submit/reset admission to the synchronous durable gate rather than deferred presentation state', () => {
+ expect(
+ nativeFormHostSource.match(
+ /shouldBlockReferenceHostFormMutation\(\s*readOnly,\s*submitAuthorized\.isInFlight,\s*\)/gu,
+ ),
+ ).toHaveLength(2);
+ expect(nativeFormHostSource).not.toContain(
+ "if (readOnly || submissionState === 'saving') {",
+ );
+ });
+
+ it('blocks form reset while durable submission is in flight and marks a later reset unsaved', () => {
+ expect(nativeFormHostSource).toMatch(
+ /onInput=\{handleNativeInput\}\s+onSubmit=\{handleSubmit\}\s+onReset=\{handleReset\}/u,
+ );
+ expect(nativeFormHostSource).toContain(
+ 'shouldBlockReferenceHostFormMutation',
+ );
+ expect(nativeFormHostSource).toContain('submitAuthorized.isInFlight,');
+ expect(nativeFormHostSource).toContain('event.preventDefault();');
+ expect(nativeFormHostSource).toContain("setSubmissionState('idle');");
+ expect(nativeFormHostSource).toMatch(
+ /type="reset"\s+disabled=\{readOnly \|\| submissionState === 'saving'\}/u,
+ );
+ });
+
+ it('invalidates stale persistence presentation through independent editor and native-input mutation signals', () => {
+ expect(nativeFormHostSource).toContain('const documentGenerationRef = useRef(0);');
+ expect(nativeFormHostSource).toContain('function markDocumentDirty() {');
+ expect(nativeFormHostSource).toContain(
+ 'documentGenerationRef.current += 1;',
+ );
+ expect(nativeFormHostSource).toContain(
+ 'const submittedGeneration = documentGenerationRef.current;',
+ );
+ expect(nativeFormHostSource).toContain(
+ 'if (documentGenerationRef.current !== submittedGeneration) {',
+ );
+ expect(nativeFormHostSource).toContain(
+ "setSubmissionState((state) => (state === 'saving' ? state : 'idle'));",
+ );
+ expect(nativeFormHostSource).toContain('onChange={handleDocumentChange}');
+ expect(nativeFormHostSource).toContain('function handleNativeInput() {');
+ expect(nativeFormHostSource).toContain('onInput={handleNativeInput}');
+ expect(
+ nativeFormHostSource.match(/markDocumentDirty\(\);/gu),
+ ).toHaveLength(2);
+ });
+
+ it('makes host write permission explicit and fail-closes native form writes while read-only', () => {
+ expect(nativeFormHostSource).toContain('readOnly?: boolean;');
+ expect(nativeFormHostSource).toContain('readOnly = false');
+ expect(nativeFormHostSource).toContain('editable={!readOnly}');
+ expect(nativeFormHostSource).toContain('formFieldDisabled={readOnly}');
+ expect(nativeFormHostSource).toContain(
+ 'shouldBlockReferenceHostFormMutation',
+ );
+ expect(nativeFormHostSource).toMatch(
+ /type="submit"\s+disabled=\{readOnly \|\| submissionState === 'saving'\}/u,
+ );
+ expect(nativeFormHostSource).toMatch(
+ /type="reset"\s+disabled=\{readOnly \|\| submissionState === 'saving'\}/u,
+ );
+ });
+
+ it('demonstrates both uncontrolled and host-controlled editor composition without moving persistence authority into Inkspan', () => {
+ expect(nativeFormHostSource).toContain(
+ "controlMode?: 'controlled' | 'uncontrolled';",
+ );
+ expect(nativeFormHostSource).toContain("controlMode = 'uncontrolled'");
+ expect(nativeFormHostSource).toContain(
+ 'data-reference-host-control-mode={controlMode}',
+ );
+ expect(nativeFormHostSource).toContain(
+ "const [controlledValue, setControlledValue] = useState('# Draft');",
+ );
+ expect(nativeFormHostSource).toContain(
+ "value={controlMode === 'controlled' ? controlledValue : undefined}",
+ );
+ expect(nativeFormHostSource).toContain('onChange={handleDocumentChange}');
+ expect(nativeFormHostSource).toContain(
+ "defaultValue={controlMode === 'uncontrolled' ? '# Draft' : undefined}",
+ );
+ expect(nativeFormHostSource).toContain(
+ "if (controlMode === 'controlled') {",
+ );
+ expect(nativeFormHostSource).toContain("setControlledValue('# Draft');");
+ });
+});
diff --git a/src/referenceHostOfficeExecutionContract.test.ts b/src/referenceHostOfficeExecutionContract.test.ts
new file mode 100644
index 00000000..4e08595f
--- /dev/null
+++ b/src/referenceHostOfficeExecutionContract.test.ts
@@ -0,0 +1,63 @@
+import { existsSync, readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+function repositoryFile(path: string): string {
+ return readFileSync(resolve(process.cwd(), path), 'utf8');
+}
+
+describe('reference-host Office execution acceptance', () => {
+ it('executes the self-contained exact-packed Office journey and documents the package-authority split', () => {
+ const verifierPath = resolve(
+ process.cwd(),
+ 'examples/reference-host/verify-office-handoff.mjs',
+ );
+ const packedVerifierPath = resolve(
+ process.cwd(),
+ 'examples/reference-host/verify-packed-office-journey.mjs',
+ );
+ expect(existsSync(verifierPath)).toBe(true);
+ expect(existsSync(packedVerifierPath)).toBe(true);
+ if (!existsSync(verifierPath) || !existsSync(packedVerifierPath)) return;
+
+ const verifier = readFileSync(verifierPath, 'utf8');
+ const packedVerifier = readFileSync(packedVerifierPath, 'utf8');
+ const readme = repositoryFile('examples/reference-host/README.md');
+
+ expect(verifier).toContain('INKSPAN_BROWSER_PACKAGE_ENTRY');
+ expect(verifier).toContain('office-handoff.mjs');
+ expect(verifier).toContain('createReferenceDocxRequest');
+ expect(verifier).not.toContain('createDocxHandoff');
+ expect(verifier).toContain("'-m', 'inkspan_office.cli'");
+ expect(verifier).toContain('document.core_properties.title');
+ expect(verifier).toContain('Buyer-ready body.');
+ expect(verifier).toContain('mkdtemp');
+ expect(verifier).toContain('rm(temporaryDirectory');
+
+ expect(packedVerifier).toContain("packageAuthority: 'exact-packed-tarball'");
+ expect(packedVerifier).toContain("run('pnpm', ['build']");
+ expect(packedVerifier).toContain(
+ "run('pnpm', ['pack', '--pack-destination', packDirectory])",
+ );
+ expect(packedVerifier).toContain("'verify-office-handoff.mjs'");
+ expect(packedVerifier).toContain('INKSPAN_BROWSER_PACKAGE_ENTRY: packageEntry');
+ expect(packedVerifier).toContain("argv[0] === '--self-test'");
+ expect(packedVerifier).toContain('verifyPackedOfficeJourney();');
+
+ expect(readme).toContain('`verify-office-handoff.mjs`');
+ expect(readme).toContain('consumes an already extracted exact package entry');
+ expect(readme).toContain(
+ 'renders the request through the local Inkspan Office CLI',
+ );
+ expect(readme).toContain('does not build, pack, or choose the artifact itself');
+ expect(readme).toContain('`verify-packed-office-journey.mjs`');
+ expect(readme).toContain('self-contained package-authority wrapper');
+ expect(readme).not.toContain(
+ 'Complete Office-renderer execution and validation also remain pending',
+ );
+ expect(readme).not.toContain(
+ 'complete converter/Office execution and validation beyond the bounded request helper',
+ );
+ });
+});
diff --git a/src/referenceHostOfficeHandoff.test.ts b/src/referenceHostOfficeHandoff.test.ts
new file mode 100644
index 00000000..c6e5409c
--- /dev/null
+++ b/src/referenceHostOfficeHandoff.test.ts
@@ -0,0 +1,43 @@
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+function repositoryFile(path: string): string {
+ return readFileSync(resolve(process.cwd(), path), 'utf8');
+}
+
+describe('reference-host Office handoff', () => {
+ it('maps bounded editor Markdown through the public React-free projection before creating a DOCX request', () => {
+ const source = repositoryFile('examples/reference-host/office-handoff.mjs');
+
+ expect(source).toContain(
+ "import { markdownToPlainText } from '@contextualwisdomlab/cwl-editor/markdown';",
+ );
+ expect(source).toContain('export function createReferenceDocxRequest');
+ expect(source).toContain('markdownToPlainText(markdown)');
+ expect(source).toContain("format: 'docx'");
+ expect(source).toContain("type: 'paragraph'");
+ });
+
+ it('preserves deterministic plain-text block boundaries as separate DOCX paragraphs', () => {
+ const source = repositoryFile('examples/reference-host/office-handoff.mjs');
+
+ expect(source).toContain("const paragraphs = text.split('\\n\\n');");
+ expect(source).toContain(
+ "paragraphs.map((paragraphText) => Object.freeze({ type: 'paragraph', text: paragraphText }))",
+ );
+ expect(source).not.toContain('blocks: Object.freeze([paragraph])');
+ });
+
+ it('keeps Office rendering, authorization, storage, and transport outside the reference helper', () => {
+ const source = repositoryFile('examples/reference-host/office-handoff.mjs');
+
+ expect(source).not.toContain('fetch(');
+ expect(source).not.toContain('localStorage');
+ expect(source).not.toContain('process.env');
+ expect(source).not.toContain('inkspan_office');
+ expect(source).not.toContain('child_process');
+ expect(source).not.toContain('writeFile');
+ });
+});
diff --git a/src/referenceHostOneCommandJourney.test.ts b/src/referenceHostOneCommandJourney.test.ts
new file mode 100644
index 00000000..844e7b8e
--- /dev/null
+++ b/src/referenceHostOneCommandJourney.test.ts
@@ -0,0 +1,138 @@
+import { execFileSync } from 'node:child_process';
+import { existsSync } from 'node:fs';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const repositoryRoot = process.cwd();
+const verifierPath = resolve(
+ repositoryRoot,
+ 'examples/reference-host/verify-current-reference-journey.mjs',
+);
+const applicationSsrVerifierPath = resolve(
+ repositoryRoot,
+ 'examples/reference-host/verify-application-ssr.mjs',
+);
+const browserVerifierPath = resolve(
+ repositoryRoot,
+ 'examples/reference-host/verify-browser-journey.mjs',
+);
+const packedOfficeVerifierPath = resolve(
+ repositoryRoot,
+ 'examples/reference-host/verify-packed-office-journey.mjs',
+);
+
+describe('reference-host one-command journey contract', () => {
+ it('exposes one deterministic command for the currently implemented buyer journey', () => {
+ expect(existsSync(verifierPath)).toBe(true);
+
+ const output = execFileSync(process.execPath, [verifierPath, '--plan'], {
+ cwd: repositoryRoot,
+ encoding: 'utf8',
+ stdio: ['ignore', 'pipe', 'pipe'],
+ timeout: 10_000,
+ });
+
+ expect(JSON.parse(output.trim())).toEqual({
+ command:
+ 'node examples/reference-host/verify-current-reference-journey.mjs',
+ contractVersion: 1,
+ status: 'plan',
+ steps: [
+ {
+ args: ['--self-test'],
+ path: 'examples/reference-host/synthetic-document-repository.mjs',
+ },
+ {
+ args: ['--self-test'],
+ path: 'examples/reference-host/delayed-proposal.mjs',
+ },
+ {
+ args: ['--self-test'],
+ path: 'examples/reference-host/autosave-view-model.mjs',
+ },
+ {
+ args: ['--self-test'],
+ path: 'examples/reference-host/collaboration-provider-lifecycle.mjs',
+ },
+ {
+ args: ['--self-test'],
+ path: 'examples/reference-host/host-authorized-collaboration.mjs',
+ },
+ {
+ args: [],
+ path: 'examples/reference-host/verify-packed-artifact.mjs',
+ },
+ {
+ args: ['--self-test'],
+ path: 'examples/reference-host/verify-application-ssr.mjs',
+ },
+ {
+ args: ['--self-test'],
+ path: 'examples/reference-host/verify-packed-office-journey.mjs',
+ },
+ {
+ args: ['--self-test'],
+ path: 'examples/reference-host/verify-browser-journey.mjs',
+ },
+ ],
+ });
+ });
+
+ it('keeps exact-packed application SSR in the one-command buyer journey', () => {
+ expect(existsSync(applicationSsrVerifierPath)).toBe(true);
+ });
+
+ it('binds the scoped Office step to a self-contained exact packed tarball', () => {
+ expect(existsSync(packedOfficeVerifierPath)).toBe(true);
+
+ const output = execFileSync(
+ process.execPath,
+ [packedOfficeVerifierPath, '--plan'],
+ {
+ cwd: repositoryRoot,
+ encoding: 'utf8',
+ stdio: ['ignore', 'pipe', 'pipe'],
+ timeout: 10_000,
+ },
+ );
+
+ expect(JSON.parse(output.trim())).toEqual({
+ command:
+ 'node examples/reference-host/verify-packed-office-journey.mjs',
+ contractVersion: 1,
+ packageAuthority: 'exact-packed-tarball',
+ status: 'plan',
+ });
+ });
+
+ it('binds the scoped browser step to an exact packed tarball and all supported engines', () => {
+ expect(existsSync(browserVerifierPath)).toBe(true);
+
+ const output = execFileSync(
+ process.execPath,
+ [browserVerifierPath, '--plan'],
+ {
+ cwd: repositoryRoot,
+ encoding: 'utf8',
+ stdio: ['ignore', 'pipe', 'pipe'],
+ timeout: 10_000,
+ },
+ );
+
+ expect(JSON.parse(output.trim())).toEqual({
+ command: 'node examples/reference-host/verify-browser-journey.mjs',
+ contractVersion: 1,
+ packageAuthority: 'exact-packed-tarball',
+ projects: ['chromium', 'firefox', 'webkit'],
+ specs: [
+ 'reference-host-dirty-state.browser.spec.ts',
+ 'reference-host-forced-colors.print.browser.spec.ts',
+ 'reference-host-hydration.browser.spec.ts',
+ 'reference-host-readonly.browser.spec.ts',
+ 'reference-host.print.browser.spec.ts',
+ ],
+ status: 'plan',
+ });
+ });
+});
diff --git a/src/referenceHostPackagingBoundary.test.ts b/src/referenceHostPackagingBoundary.test.ts
new file mode 100644
index 00000000..e706cc6c
--- /dev/null
+++ b/src/referenceHostPackagingBoundary.test.ts
@@ -0,0 +1,89 @@
+import { readdirSync, readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const referenceHostDirectory = resolve(process.cwd(), 'examples/reference-host');
+
+function repositoryFile(path: string): string {
+ return readFileSync(resolve(process.cwd(), path), 'utf8');
+}
+
+function referenceHostExecutableFiles(): string[] {
+ return readdirSync(referenceHostDirectory).filter((path) =>
+ /\.(?:[cm]?[jt]s|[jt]sx)$/u.test(path),
+ );
+}
+
+function referenceHostRuntimeFiles(): string[] {
+ return referenceHostExecutableFiles().filter(
+ (path) => !path.startsWith('verify-'),
+ );
+}
+
+describe('reference-host package authority boundary', () => {
+ it('keeps every reference-host file outside the npm publish inventory', () => {
+ const packageMetadata = JSON.parse(repositoryFile('package.json')) as {
+ files: string[];
+ };
+
+ expect(packageMetadata.files.some((path) => path.startsWith('examples'))).toBe(
+ false,
+ );
+ });
+
+ it('rejects source-relative and workspace-alias imports in executable reference files', () => {
+ const executableFiles = referenceHostExecutableFiles();
+
+ expect(executableFiles).toEqual(
+ expect.arrayContaining([
+ 'hydration-gate.tsx',
+ 'native-form-host.tsx',
+ 'single-flight-submission.ts',
+ ]),
+ );
+ expect(executableFiles.length).toBeGreaterThan(0);
+ for (const file of executableFiles) {
+ const source = readFileSync(resolve(referenceHostDirectory, file), 'utf8');
+ expect(source).not.toMatch(/(?:from|import\()\s*['"][^'"]*src\//u);
+ expect(source).not.toContain('workspace:');
+ }
+ });
+
+ it('rejects runtime environment-variable authority in reference-host runtime files', () => {
+ const runtimeFiles = referenceHostRuntimeFiles();
+
+ expect(runtimeFiles).toEqual(
+ expect.arrayContaining([
+ 'browser-host.tsx',
+ 'reference-host-app.tsx',
+ 'native-form-host.tsx',
+ ]),
+ );
+ expect(runtimeFiles.length).toBeGreaterThan(0);
+ for (const file of runtimeFiles) {
+ const source = readFileSync(resolve(referenceHostDirectory, file), 'utf8');
+ expect(source).not.toMatch(/\bprocess\s*\.\s*env\b/u);
+ expect(source).not.toMatch(/\bimport\s*\.\s*meta\s*\.\s*env\b/u);
+ }
+ });
+
+ it('keeps the buyer-facing inventory synchronized without claiming complete app acceptance', () => {
+ const readme = readFileSync(resolve(referenceHostDirectory, 'README.md'), 'utf8');
+ const executableFiles = referenceHostExecutableFiles();
+
+ for (const file of executableFiles) {
+ expect(readme).toContain(`\`${file}\``);
+ }
+ expect(readme).toContain('`presentation-full.css`');
+ expect(readme).toContain('`presentation-latin.css`');
+ expect(readme).toContain(
+ 'exact packed autosave observer wiring into the host lifecycle projection',
+ );
+ expect(readme).not.toContain('packed-package wiring of the autosave observer');
+ expect(readme).toContain('complete reference-host application');
+ expect(readme).toContain(
+ 'do **not** yet satisfy #377\'s complete packed-artifact framework-application acceptance.',
+ );
+ });
+});
\ No newline at end of file
diff --git a/src/referenceHostPackedArtifact.test.ts b/src/referenceHostPackedArtifact.test.ts
new file mode 100644
index 00000000..42537235
--- /dev/null
+++ b/src/referenceHostPackedArtifact.test.ts
@@ -0,0 +1,53 @@
+import { execFileSync } from 'node:child_process';
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const repositoryRoot = process.cwd();
+const verifierPath = resolve(
+ repositoryRoot,
+ 'examples/reference-host/verify-packed-artifact.mjs',
+);
+const packageMetadata = JSON.parse(
+ readFileSync(resolve(repositoryRoot, 'package.json'), 'utf8'),
+) as { name: string; version: string };
+
+describe('reference-host packed artifact acceptance', () => {
+ it(
+ 'builds, packs, installs, and SSR-imports the exact tarball in an isolated consumer',
+ () => {
+ const output = execFileSync(process.execPath, [verifierPath], {
+ cwd: repositoryRoot,
+ encoding: 'utf8',
+ stdio: ['ignore', 'pipe', 'pipe'],
+ timeout: 180_000,
+ });
+
+ const result = JSON.parse(output.trim()) as {
+ packageName?: unknown;
+ packageVersion?: unknown;
+ installedFromTarball?: unknown;
+ consumerInstallCompleted?: unknown;
+ serverRenderedNamedField?: unknown;
+ autosaveObserverWired?: unknown;
+ esmMarkdownProjection?: unknown;
+ commonJsMarkdownProjection?: unknown;
+ publicAssetEntriesContained?: unknown;
+ sourceImportDetected?: unknown;
+ };
+
+ expect(result.packageName).toBe(packageMetadata.name);
+ expect(result.packageVersion).toBe(packageMetadata.version);
+ expect(result.installedFromTarball).toBe(true);
+ expect(result.consumerInstallCompleted).toBe(true);
+ expect(result.serverRenderedNamedField).toBe(true);
+ expect(result.autosaveObserverWired).toBe(true);
+ expect(result.esmMarkdownProjection).toBe(true);
+ expect(result.commonJsMarkdownProjection).toBe(true);
+ expect(result.publicAssetEntriesContained).toBe(true);
+ expect(result.sourceImportDetected).toBe(false);
+ },
+ 180_000,
+ );
+});
diff --git a/src/referenceHostPackedBrowserBinding.test.ts b/src/referenceHostPackedBrowserBinding.test.ts
new file mode 100644
index 00000000..ab4b5907
--- /dev/null
+++ b/src/referenceHostPackedBrowserBinding.test.ts
@@ -0,0 +1,70 @@
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+function repositoryFile(path: string): string {
+ return readFileSync(resolve(process.cwd(), path), 'utf8');
+}
+
+describe('reference-host packed browser binding', () => {
+ it('routes the buyer host public package imports through the exact packed release artifact', () => {
+ const viteConfig = repositoryFile('tests/browser/vite.config.ts');
+ const nativeFormHost = repositoryFile(
+ 'examples/reference-host/native-form-host.tsx',
+ );
+ const browserHost = repositoryFile(
+ 'examples/reference-host/browser-host.tsx',
+ );
+ const presentationFull = repositoryFile(
+ 'examples/reference-host/presentation-full.css',
+ );
+
+ expect(nativeFormHost).toContain(
+ "from '@contextualwisdomlab/cwl-editor'",
+ );
+ expect(browserHost).toContain("import './presentation-full.css'");
+ expect(presentationFull).toContain(
+ "@import '@contextualwisdomlab/cwl-editor/styles.css'",
+ );
+ expect(presentationFull).toContain(
+ "@import '@contextualwisdomlab/cwl-editor/fonts.css'",
+ );
+ expect(nativeFormHost).not.toContain('inkspan-browser-under-test');
+ expect(browserHost).not.toContain('inkspan-browser-under-test');
+
+ expect(viteConfig).toContain('INKSPAN_BROWSER_PACKAGE_ENTRY');
+ expect(viteConfig).toContain(
+ "find: '@contextualwisdomlab/cwl-editor/styles.css'",
+ );
+ expect(viteConfig).toContain(
+ "find: '@contextualwisdomlab/cwl-editor/fonts.css'",
+ );
+ expect(viteConfig).toContain(
+ "find: '@contextualwisdomlab/cwl-editor/fonts-latin.css'",
+ );
+ expect(viteConfig).toContain(
+ "find: '@contextualwisdomlab/cwl-editor'",
+ );
+ expect(viteConfig).toContain(
+ "resolve(packedPackageRoot, 'dist/cwl-editor.css')",
+ );
+ expect(viteConfig).toContain(
+ "resolve(packedPackageRoot, 'src/fonts/fonts.css')",
+ );
+ expect(viteConfig).toContain(
+ "resolve(packedPackageRoot, 'src/fonts/fonts-latin.css')",
+ );
+ expect(viteConfig).toContain('replacement: packageEntry');
+ });
+
+ it('pre-optimizes every browser application entry before parallel Playwright workers can invalidate shared Vite dependency chunks', () => {
+ const viteConfig = repositoryFile('tests/browser/vite.config.ts');
+
+ expect(viteConfig).toContain('optimizeDeps: {');
+ expect(viteConfig).toContain("'tests/browser/harness.html'");
+ expect(viteConfig).toContain(
+ "'examples/reference-host/browser-host.html'",
+ );
+ });
+});
diff --git a/src/referenceHostPresentationAssets.test.ts b/src/referenceHostPresentationAssets.test.ts
new file mode 100644
index 00000000..801d859d
--- /dev/null
+++ b/src/referenceHostPresentationAssets.test.ts
@@ -0,0 +1,47 @@
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+function repositoryFile(path: string): string {
+ return readFileSync(resolve(process.cwd(), path), 'utf8');
+}
+
+describe('reference-host public presentation assets', () => {
+ it('wires editor styles plus the complete multilingual font option through public package subpaths', () => {
+ const source = repositoryFile('examples/reference-host/presentation-full.css');
+
+ expect(source).toBe(
+ "@import '@contextualwisdomlab/cwl-editor/styles.css';\n" +
+ "@import '@contextualwisdomlab/cwl-editor/fonts.css';\n",
+ );
+ });
+
+ it('wires editor styles plus the smaller Latin font option through public package subpaths', () => {
+ const source = repositoryFile('examples/reference-host/presentation-latin.css');
+
+ expect(source).toBe(
+ "@import '@contextualwisdomlab/cwl-editor/styles.css';\n" +
+ "@import '@contextualwisdomlab/cwl-editor/fonts-latin.css';\n",
+ );
+ });
+
+ it('runs the real browser host through the complete multilingual presentation entrypoint', () => {
+ const source = repositoryFile('examples/reference-host/browser-host.tsx');
+
+ expect(source).toContain("import './presentation-full.css';");
+ expect(source).not.toContain(
+ "import '@contextualwisdomlab/cwl-editor/styles.css';",
+ );
+ });
+
+ it('keeps every referenced presentation entrypoint in the published package export map', () => {
+ const packageMetadata = JSON.parse(repositoryFile('package.json')) as {
+ exports: Record;
+ };
+
+ expect(packageMetadata.exports).toHaveProperty('./styles.css');
+ expect(packageMetadata.exports).toHaveProperty('./fonts.css');
+ expect(packageMetadata.exports).toHaveProperty('./fonts-latin.css');
+ });
+});
\ No newline at end of file
diff --git a/src/referenceHostReflectionContainment.test.ts b/src/referenceHostReflectionContainment.test.ts
new file mode 100644
index 00000000..54686ad6
--- /dev/null
+++ b/src/referenceHostReflectionContainment.test.ts
@@ -0,0 +1,146 @@
+import { execFileSync } from 'node:child_process';
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { pathToFileURL } from 'node:url';
+
+import { describe, expect, it } from 'vitest';
+
+const OFFICE_MARKDOWN_IMPORT =
+ "import { markdownToPlainText } from '@contextualwisdomlab/cwl-editor/markdown';";
+
+function fixtureModuleUrl(fixture: string): string {
+ const path = resolve(process.cwd(), fixture);
+ if (fixture !== 'examples/reference-host/office-handoff.mjs') {
+ return pathToFileURL(path).href;
+ }
+
+ const source = readFileSync(path, 'utf8');
+ const isolatedSource = source.replace(
+ OFFICE_MARKDOWN_IMPORT,
+ 'const markdownToPlainText = (markdown) => markdown;',
+ );
+ if (isolatedSource === source) {
+ throw new Error('Office handoff public Markdown import contract changed.');
+ }
+ return `data:text/javascript;base64,${Buffer.from(isolatedSource, 'utf8').toString('base64')}`;
+}
+
+function observeHostileReflectionFailure(
+ fixture: string,
+ sourceTrap: string,
+ call: string,
+): string {
+ const fixtureUrl = fixtureModuleUrl(fixture);
+ const script = `
+ const module = await import(${JSON.stringify(fixtureUrl)});
+ const privateSentinel = 'private-reflection-sentinel';
+ const hostileError = new Proxy({}, {
+ getPrototypeOf() {
+ throw privateSentinel;
+ },
+ });
+ const hostileSource = new Proxy({}, ${sourceTrap});
+ let observed = 'no-error';
+ try {
+ ${call}
+ } catch (error) {
+ if (typeof error === 'object' && error !== null) {
+ const descriptor = Object.getOwnPropertyDescriptor(error, 'message');
+ observed = descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value')
+ ? descriptor.value
+ : 'object-error';
+ } else {
+ observed = String(error);
+ }
+ }
+ process.stdout.write(JSON.stringify({ observed }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+ return (JSON.parse(output) as { observed: string }).observed;
+}
+
+describe('reference-host hostile reflection containment', () => {
+ it('redacts hostile meta-object failures across every reference boundary', () => {
+ const prototypeTrap = `{
+ getPrototypeOf() {
+ throw hostileError;
+ },
+ }`;
+ const descriptorTrap = `{
+ getOwnPropertyDescriptor() {
+ throw hostileError;
+ },
+ }`;
+
+ expect(
+ observeHostileReflectionFailure(
+ 'examples/reference-host/synthetic-document-repository.mjs',
+ prototypeTrap,
+ 'module.createSyntheticDocumentRepository(hostileSource);',
+ ),
+ ).toBe('Reference persistence invalid_options.');
+ expect(
+ observeHostileReflectionFailure(
+ 'examples/reference-host/delayed-proposal.mjs',
+ prototypeTrap,
+ 'await module.createDelayedProposal(hostileSource);',
+ ),
+ ).toBe('proposal creation is invalid.');
+ expect(
+ observeHostileReflectionFailure(
+ 'examples/reference-host/office-handoff.mjs',
+ `{
+ get(_target, property) {
+ if (property === 'title') throw privateSentinel;
+ return undefined;
+ },
+ }`,
+ 'module.createReferenceDocxRequest(hostileSource);',
+ ),
+ ).toBe('Office handoff input is invalid.');
+ expect(
+ observeHostileReflectionFailure(
+ 'examples/reference-host/autosave-view-model.mjs',
+ descriptorTrap,
+ 'module.createAutosaveViewModel().observe(hostileSource);',
+ ),
+ ).toBe('autosave snapshot is invalid.');
+ expect(
+ observeHostileReflectionFailure(
+ 'examples/reference-host/collaboration-provider-lifecycle.mjs',
+ descriptorTrap,
+ 'module.createHostCollaborationLifecycle(hostileSource);',
+ ),
+ ).toBe('collaboration options are invalid.');
+ expect(
+ observeHostileReflectionFailure(
+ 'examples/reference-host/collaboration-provider-lifecycle.mjs',
+ descriptorTrap,
+ `module.createHostCollaborationLifecycle({
+ documentFactory() { return hostileSource; },
+ providerFactory() {
+ return { connect() {}, disconnect() {}, destroy() {} };
+ },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });`,
+ ),
+ ).toBe('documentFactory returned an invalid document.');
+ expect(
+ observeHostileReflectionFailure(
+ 'examples/reference-host/collaboration-provider-lifecycle.mjs',
+ descriptorTrap,
+ `module.createHostCollaborationLifecycle({
+ documentFactory() { return { destroy() {} }; },
+ providerFactory() { throw hostileError; },
+ roomId: 'reference-room',
+ actorId: 'reference-actor',
+ });`,
+ ),
+ ).toBe('collaboration lifecycle initialization failed.');
+ });
+});
diff --git a/src/referenceHostSingleFlightSubmission.test.ts b/src/referenceHostSingleFlightSubmission.test.ts
new file mode 100644
index 00000000..0f40d5c9
--- /dev/null
+++ b/src/referenceHostSingleFlightSubmission.test.ts
@@ -0,0 +1,101 @@
+import { describe, expect, it, vi } from 'vitest';
+import { createSingleFlightSubmission } from '../examples/reference-host/single-flight-submission.js';
+
+describe('reference-host authorized submission single-flight boundary', () => {
+ it('admits at most one durable host submission at a time and permits a later submission after settlement', async () => {
+ let resolveFirst: (() => void) | undefined;
+ const firstResult = new Promise((resolve) => {
+ resolveFirst = resolve;
+ });
+ const onAuthorizedSubmit = vi
+ .fn<(messageBody: string) => Promise>()
+ .mockImplementationOnce(async () => firstResult)
+ .mockResolvedValue(undefined);
+ const states: string[] = [];
+ const submit = createSingleFlightSubmission(onAuthorizedSubmit, (state) => {
+ states.push(state);
+ });
+
+ const first = submit('# First');
+
+ // Admission is synchronous even when a React-style state observer has not
+ // committed a render yet. Native submit/reset handlers must be able to
+ // consult this gate directly rather than infer it from presentation state.
+ expect(submit.isInFlight()).toBe(true);
+
+ const overlapping = submit('# Overlapping');
+
+ expect(onAuthorizedSubmit).toHaveBeenCalledTimes(1);
+ expect(onAuthorizedSubmit).toHaveBeenLastCalledWith('# First');
+ await expect(overlapping).resolves.toBe(false);
+ expect(submit.isInFlight()).toBe(true);
+ expect(states).toEqual(['saving']);
+
+ resolveFirst?.();
+ await expect(first).resolves.toBe(true);
+ expect(submit.isInFlight()).toBe(false);
+ expect(states).toEqual(['saving', 'saved']);
+
+ await expect(submit('# Later')).resolves.toBe(true);
+ expect(submit.isInFlight()).toBe(false);
+ expect(onAuthorizedSubmit).toHaveBeenCalledTimes(2);
+ expect(onAuthorizedSubmit).toHaveBeenLastCalledWith('# Later');
+ expect(states).toEqual(['saving', 'saved', 'saving', 'saved']);
+ });
+
+ it('releases the gate after a failed host submission without exposing the failure value', async () => {
+ const privateFailure = new Error('private durable-store detail');
+ const onAuthorizedSubmit = vi
+ .fn<(messageBody: string) => Promise>()
+ .mockRejectedValueOnce(privateFailure)
+ .mockResolvedValue(undefined);
+ const states: string[] = [];
+ const submit = createSingleFlightSubmission(onAuthorizedSubmit, (state) => {
+ states.push(state);
+ });
+
+ await expect(submit('# Failing')).resolves.toBe(false);
+ expect(submit.isInFlight()).toBe(false);
+ await expect(submit('# Retry')).resolves.toBe(true);
+
+ expect(onAuthorizedSubmit).toHaveBeenCalledTimes(2);
+ expect(states).toEqual(['saving', 'failed', 'saving', 'saved']);
+ });
+
+ it('contains a saving-state observer failure without blocking durable host submission or later retries', async () => {
+ const privateObserverFailure = new Error('private presentation detail');
+ const onAuthorizedSubmit = vi
+ .fn<(messageBody: string) => Promise>()
+ .mockResolvedValue(undefined);
+ let savingNotifications = 0;
+ const submit = createSingleFlightSubmission(onAuthorizedSubmit, (state) => {
+ if (state === 'saving' && savingNotifications++ === 0) {
+ throw privateObserverFailure;
+ }
+ });
+
+ await expect(submit('# First')).resolves.toBe(true);
+ expect(submit.isInFlight()).toBe(false);
+ await expect(submit('# Retry')).resolves.toBe(true);
+ expect(onAuthorizedSubmit).toHaveBeenCalledTimes(2);
+ });
+
+ it('does not reclassify successful durable persistence when the saved-state observer fails', async () => {
+ const privateObserverFailure = new Error('private presentation detail');
+ const onAuthorizedSubmit = vi
+ .fn<(messageBody: string) => Promise>()
+ .mockResolvedValue(undefined);
+ const states: string[] = [];
+ const submit = createSingleFlightSubmission(onAuthorizedSubmit, (state) => {
+ states.push(state);
+ if (state === 'saved') {
+ throw privateObserverFailure;
+ }
+ });
+
+ await expect(submit('# Persisted')).resolves.toBe(true);
+ expect(submit.isInFlight()).toBe(false);
+ expect(onAuthorizedSubmit).toHaveBeenCalledOnce();
+ expect(states).toEqual(['saving', 'saved']);
+ });
+});
diff --git a/src/referenceHostSyntheticRepository.test.ts b/src/referenceHostSyntheticRepository.test.ts
new file mode 100644
index 00000000..60b642f0
--- /dev/null
+++ b/src/referenceHostSyntheticRepository.test.ts
@@ -0,0 +1,207 @@
+import { execFileSync } from 'node:child_process';
+import { existsSync, readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { pathToFileURL } from 'node:url';
+
+import { describe, expect, it } from 'vitest';
+
+const fixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/synthetic-document-repository.mjs',
+);
+const guidePath = resolve(process.cwd(), 'examples/reference-host/README.md');
+
+describe('reference-host synthetic durable repository contract', () => {
+ it('ships one executable reference-only repository fixture outside the published runtime', () => {
+ expect(existsSync(fixturePath)).toBe(true);
+ });
+
+ it('keeps the host persistence fixture source-independent and network-free', () => {
+ if (!existsSync(fixturePath)) return;
+ const source = readFileSync(fixturePath, 'utf8');
+
+ expect(source).not.toMatch(/(?:from|import\()\s*['"][^'"]*src\//u);
+ expect(source).not.toMatch(/\b(?:fetch|XMLHttpRequest|WebSocket)\b/u);
+ expect(source).toContain('REFERENCE_ONLY');
+ expect(source).toContain('If-Match');
+ });
+
+ it('proves failure-safe retry, restore, fork isolation, and stale-write conflict semantics', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(process.execPath, [fixturePath, '--self-test'], {
+ encoding: 'utf8',
+ });
+
+ expect(JSON.parse(output)).toEqual({
+ afterAmbiguousValidator: '"v1"',
+ afterFailureValidator: '"v2"',
+ conflictCurrentValidator: '"v2"',
+ forkDocument: 'Buyer draft v1',
+ forkFinalDocument: 'Fork-only edit',
+ forkInitialValidator: '"f1-v1"',
+ forkSavedValidator: '"f1-v2"',
+ initialValidator: '"v1"',
+ restoredValidator: '"v4"',
+ retrySavedValidator: '"v3"',
+ savedValidator: '"v2"',
+ sourceDocumentAfterFork: 'Buyer draft v1',
+ sourceValidatorAfterFork: '"v4"',
+ });
+ });
+
+ it('gives an immediate fork validator authority distinct from its unchanged source', () => {
+ if (!existsSync(fixturePath)) return;
+ const fixtureUrl = pathToFileURL(fixturePath).href;
+ const output = execFileSync(
+ process.execPath,
+ [
+ '--input-type=module',
+ '--eval',
+ `import { createSyntheticDocumentRepository } from ${JSON.stringify(fixtureUrl)};
+const source = createSyntheticDocumentRepository({
+ documentId: 'buyer-document',
+ initialDocument: 'Buyer draft v1',
+});
+const sourceInitial = source.read('buyer-document');
+const forked = source.fork({
+ documentId: 'buyer-document',
+ forkDocumentId: 'buyer-document-fork',
+ ifMatch: sourceInitial.validator,
+});
+const forkInitial = forked.repository.read('buyer-document-fork');
+process.stdout.write(JSON.stringify({
+ forkValidator: forkInitial.validator,
+ sourceValidator: sourceInitial.validator,
+}));`,
+ ],
+ { encoding: 'utf8' },
+ );
+ const evidence = JSON.parse(output) as {
+ forkValidator: string;
+ sourceValidator: string;
+ };
+
+ expect(evidence.forkValidator).not.toBe(evidence.sourceValidator);
+ });
+
+ it('keeps deeply nested fork-issued validators acceptable to their own save boundary', () => {
+ if (!existsSync(fixturePath)) return;
+ const fixtureUrl = pathToFileURL(fixturePath).href;
+ const output = execFileSync(
+ process.execPath,
+ [
+ '--input-type=module',
+ '--eval',
+ `import { createSyntheticDocumentRepository } from ${JSON.stringify(fixtureUrl)};
+let repository = createSyntheticDocumentRepository({
+ documentId: 'buyer-document-0',
+ initialDocument: 'Buyer draft v1',
+});
+let documentId = 'buyer-document-0';
+for (let depth = 1; depth <= 100; depth += 1) {
+ const current = repository.read(documentId);
+ const forkDocumentId = \`buyer-document-\${depth}\`;
+ const forked = repository.fork({
+ documentId,
+ forkDocumentId,
+ ifMatch: current.validator,
+ });
+ repository = forked.repository;
+ documentId = forkDocumentId;
+}
+const leaf = repository.read(documentId);
+const saved = repository.save({
+ documentId,
+ document: 'Deep fork edit',
+ ifMatch: leaf.validator,
+});
+process.stdout.write(JSON.stringify({
+ initialValidatorLength: leaf.validator.length,
+ savedStatus: saved.status,
+ savedValidatorLength: saved.validator.length,
+}));`,
+ ],
+ { encoding: 'utf8' },
+ );
+ const evidence = JSON.parse(output) as {
+ initialValidatorLength: number;
+ savedStatus: string;
+ savedValidatorLength: number;
+ };
+
+ expect(evidence.savedStatus).toBe('saved');
+ expect(evidence.initialValidatorLength).toBeLessThanOrEqual(256);
+ expect(evidence.savedValidatorLength).toBeLessThanOrEqual(256);
+ });
+
+ it('accepts an empty document body while keeping document identifiers non-empty', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(
+ process.execPath,
+ [fixturePath, '--empty-document-self-test'],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ clearedDocument: '',
+ clearedValidator: '"v2"',
+ emptyDocumentIdError: 'invalid_document_id',
+ initialEmptyDocument: '',
+ });
+ });
+
+ it('fails closed without invoking caller-owned option, save, or fork-request accessors', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(
+ process.execPath,
+ [fixturePath, '--hostile-accessor-self-test'],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ forkErrorCode: 'invalid_fork_request',
+ forkGetterCalls: 0,
+ optionErrorCode: 'invalid_options',
+ optionGetterCalls: 0,
+ requestErrorCode: 'invalid_request',
+ requestGetterCalls: 0,
+ });
+ });
+
+ it('fails closed on unknown option, save, and fork fields before durable state changes', () => {
+ if (!existsSync(fixturePath)) return;
+ const output = execFileSync(
+ process.execPath,
+ [fixturePath, '--unknown-field-self-test'],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ forkErrorCode: 'invalid_fork_request',
+ optionErrorCode: 'invalid_options',
+ saveErrorCode: 'invalid_request',
+ savedDocument: 'Buyer draft v1',
+ savedValidator: '"v1"',
+ });
+ });
+
+ it('keeps the buyer guide code-current for retry, ambiguity reconciliation, restore, and independent fork semantics', () => {
+ const guide = readFileSync(guidePath, 'utf8');
+
+ expect(guide).toContain(
+ 'A confirmed failure can be retried with the unchanged current validator.',
+ );
+ expect(guide).toContain(
+ '`ambiguous_failure` models a pre-commit failure, while `ambiguous_commit_failure` commits durable state but returns the same ambiguous error without a replacement validator.',
+ );
+ expect(guide).toContain(
+ 'After either ambiguous outcome, re-read durable state before retrying instead of advancing or blindly reusing the caller\'s last known validator.',
+ );
+ expect(guide).toContain(
+ 'A restore is a normal confirmed save against the current validator and advances it only after success.',
+ );
+ expect(guide).toContain(
+ 'A fork requires the current validator, copies the current document into an independent reference repository, and starts that fork at a fresh validator.',
+ );
+ });
+});
diff --git a/src/referenceHostSyntheticRepositoryAmbiguousCommit.test.ts b/src/referenceHostSyntheticRepositoryAmbiguousCommit.test.ts
new file mode 100644
index 00000000..c84e07ab
--- /dev/null
+++ b/src/referenceHostSyntheticRepositoryAmbiguousCommit.test.ts
@@ -0,0 +1,65 @@
+import { execFileSync } from 'node:child_process';
+import { pathToFileURL } from 'node:url';
+import { resolve } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+const fixturePath = resolve(
+ process.cwd(),
+ 'examples/reference-host/synthetic-document-repository.mjs',
+);
+
+describe('reference-host ambiguous persistence reconciliation', () => {
+ it('models a transport-ambiguous write as possibly committed and forces a durable read before retry', () => {
+ const fixtureUrl = JSON.stringify(pathToFileURL(fixturePath).href);
+ const script = `
+ import {
+ ReferencePersistenceError,
+ createSyntheticDocumentRepository,
+ } from ${fixtureUrl};
+ const repository = createSyntheticDocumentRepository({
+ documentId: 'buyer-document',
+ initialDocument: 'Buyer draft v1',
+ });
+ const initial = repository.read('buyer-document');
+ let ambiguousError = null;
+ try {
+ repository.save({
+ documentId: 'buyer-document',
+ document: 'Possibly committed draft',
+ ifMatch: initial.validator,
+ outcome: 'ambiguous_commit_failure',
+ });
+ } catch (error) {
+ ambiguousError =
+ error instanceof ReferencePersistenceError ? error.code : 'unexpected_error';
+ }
+ const reconciled = repository.read('buyer-document');
+ const staleRetry = repository.save({
+ documentId: 'buyer-document',
+ document: 'Blind retry must not overwrite',
+ ifMatch: initial.validator,
+ });
+ process.stdout.write(JSON.stringify({
+ ambiguousError,
+ initialValidator: initial.validator,
+ reconciledDocument: reconciled.document,
+ reconciledValidator: reconciled.validator,
+ staleRetry,
+ }));
+ `;
+ const output = execFileSync(
+ process.execPath,
+ ['--input-type=module', '--eval', script],
+ { encoding: 'utf8' },
+ );
+
+ expect(JSON.parse(output)).toEqual({
+ ambiguousError: 'ambiguous_failure',
+ initialValidator: '"v1"',
+ reconciledDocument: 'Possibly committed draft',
+ reconciledValidator: '"v2"',
+ staleRetry: { status: 'conflict', currentValidator: '"v2"' },
+ });
+ });
+});
diff --git a/tests/browser/playwright.config.ts b/tests/browser/playwright.config.ts
index 375056da..520c067e 100644
--- a/tests/browser/playwright.config.ts
+++ b/tests/browser/playwright.config.ts
@@ -2,7 +2,7 @@ import { defineConfig, devices } from '@playwright/test';
const HARNESS_ORIGIN = 'http://127.0.0.1:4173';
const HARNESS_URL = `${HARNESS_ORIGIN}/tests/browser/harness.html`;
-const ENGINE_BROWSER_SPECS = /(?:clipboard|print)\.browser\.spec\.ts/u;
+const ENGINE_BROWSER_SPECS = /\.browser\.spec\.ts$/u;
export default defineConfig({
testDir: './specs',
diff --git a/tests/browser/specs/reference-host-dirty-state.browser.spec.ts b/tests/browser/specs/reference-host-dirty-state.browser.spec.ts
new file mode 100644
index 00000000..f77d5124
--- /dev/null
+++ b/tests/browser/specs/reference-host-dirty-state.browser.spec.ts
@@ -0,0 +1,132 @@
+import { expect, test, type Page } from '@playwright/test';
+
+const REFERENCE_HOST_URL =
+ 'http://127.0.0.1:4173/examples/reference-host/browser-host.html';
+
+function isReferenceHostRequest(requestUrl: string): boolean {
+ const url = new URL(requestUrl);
+ return (
+ url.protocol === 'http:' &&
+ url.hostname === '127.0.0.1' &&
+ url.port === '4173'
+ );
+}
+
+async function failUnexpectedNetwork(page: Page) {
+ const rejectedRequests: string[] = [];
+ const pageErrors: string[] = [];
+
+ page.on('pageerror', (error) => {
+ pageErrors.push(error.message);
+ });
+ await page.route('**/*', async (route) => {
+ const requestUrl = route.request().url();
+ if (isReferenceHostRequest(requestUrl)) {
+ await route.continue();
+ return;
+ }
+ rejectedRequests.push(requestUrl);
+ await route.abort('blockedbyclient');
+ });
+
+ return { rejectedRequests, pageErrors };
+}
+
+test('invalidates a completed save claim after the buyer edits the packed editor again', async ({
+ page,
+}) => {
+ const { rejectedRequests, pageErrors } = await failUnexpectedNetwork(page);
+ const response = await page.goto(REFERENCE_HOST_URL);
+ expect(response?.ok()).toBe(true);
+
+ const editor = page.getByRole('textbox');
+ const field = page.locator(
+ '[data-inkspan-form-field][name="message_body"]',
+ );
+ await expect(editor).toBeVisible();
+ await expect(field).toHaveValue('# Draft');
+
+ await page.getByRole('button', { name: 'Save document' }).click();
+ await expect(page.getByText('Saved', { exact: true })).toBeVisible();
+
+ await editor.click();
+ await page.keyboard.press('Control+A');
+ await page.keyboard.type('Edited after save');
+
+ await expect(field).not.toHaveValue('# Draft');
+ await expect(page.getByText('Saved', { exact: true })).toHaveCount(0);
+ await expect(page.getByText('Not saved yet', { exact: true })).toBeVisible();
+ expect(rejectedRequests).toEqual([]);
+ expect(pageErrors).toEqual([]);
+});
+
+test('does not claim a newer document is saved when the submitted version settles later', async ({
+ page,
+}) => {
+ const { rejectedRequests, pageErrors } = await failUnexpectedNetwork(page);
+ const response = await page.goto(`${REFERENCE_HOST_URL}?deferSubmission=1`);
+ expect(response?.ok()).toBe(true);
+
+ const editor = page.getByRole('textbox');
+ const saveButton = page.getByRole('button', { name: 'Save document' });
+ await expect(editor).toBeVisible();
+
+ await editor.click();
+ await page.keyboard.press('Control+A');
+ await page.keyboard.type('Submitted version');
+ await saveButton.click();
+ await expect(page.getByText('Saving…', { exact: true })).toBeVisible();
+ await expect(saveButton).toBeDisabled();
+
+ await editor.click();
+ await page.keyboard.press('Control+A');
+ await page.keyboard.type('Newer unsaved version');
+ await page.evaluate(() => window.referenceHostResolveSubmission?.());
+
+ await expect(page.getByText('Saving…', { exact: true })).toHaveCount(0);
+ await expect(page.getByText('Saved', { exact: true })).toHaveCount(0);
+ await expect(page.getByText('Not saved yet', { exact: true })).toBeVisible();
+ expect(await page.evaluate(() => window.referenceHostSubmissions)).toEqual([
+ 'Submitted version',
+ ]);
+ expect(rejectedRequests).toEqual([]);
+ expect(pageErrors).toEqual([]);
+});
+
+test('blocks a same-turn native reset after durable submission admission before React presentation commits', async ({
+ page,
+}) => {
+ const { rejectedRequests, pageErrors } = await failUnexpectedNetwork(page);
+ const response = await page.goto(`${REFERENCE_HOST_URL}?deferSubmission=1`);
+ expect(response?.ok()).toBe(true);
+
+ const editor = page.getByRole('textbox');
+ const field = page.locator(
+ '[data-inkspan-form-field][name="message_body"]',
+ );
+ await expect(editor).toBeVisible();
+
+ await editor.click();
+ await page.keyboard.press('Control+A');
+ await page.keyboard.type('Durable gate payload');
+ await expect(field).toHaveValue('Durable gate payload');
+
+ await page.evaluate(() => {
+ const form = document.querySelector('form');
+ if (!(form instanceof HTMLFormElement)) {
+ throw new Error('Reference host form is missing.');
+ }
+ form.requestSubmit();
+ form.reset();
+ });
+
+ await expect(field).toHaveValue('Durable gate payload');
+ expect(await page.evaluate(() => window.referenceHostSubmissions)).toEqual([
+ 'Durable gate payload',
+ ]);
+
+ await page.evaluate(() => window.referenceHostResolveSubmission?.());
+ await expect(page.getByText('Saved', { exact: true })).toBeVisible();
+ expect(rejectedRequests).toEqual([]);
+ expect(pageErrors).toEqual([]);
+});
diff --git a/tests/browser/specs/reference-host-forced-colors.print.browser.spec.ts b/tests/browser/specs/reference-host-forced-colors.print.browser.spec.ts
new file mode 100644
index 00000000..b8a5f9fa
--- /dev/null
+++ b/tests/browser/specs/reference-host-forced-colors.print.browser.spec.ts
@@ -0,0 +1,81 @@
+import { expect, test } from '@playwright/test';
+
+const REFERENCE_HOST_URL =
+ 'http://127.0.0.1:4173/examples/reference-host/browser-host.html';
+
+function isReferenceHostRequest(requestUrl: string): boolean {
+ const url = new URL(requestUrl);
+ return (
+ url.protocol === 'http:' &&
+ url.hostname === '127.0.0.1' &&
+ url.port === '4173'
+ );
+}
+
+test.describe.configure({ mode: 'serial' });
+
+test('keeps the real buyer host operable in forced-colors mode without runtime network', async ({
+ page,
+}) => {
+ const rejectedRequests: string[] = [];
+ const pageErrors: string[] = [];
+
+ page.on('pageerror', (error) => {
+ pageErrors.push(error.message);
+ });
+ await page.route('**/*', async (route) => {
+ const requestUrl = route.request().url();
+ if (isReferenceHostRequest(requestUrl)) {
+ await route.continue();
+ return;
+ }
+ rejectedRequests.push(requestUrl);
+ await route.abort('blockedbyclient');
+ });
+
+ await page.emulateMedia({ forcedColors: 'active' });
+ const response = await page.goto(REFERENCE_HOST_URL);
+ expect(response?.ok()).toBe(true);
+
+ await expect(
+ page.getByRole('heading', { name: 'Inkspan reference host' }),
+ ).toBeVisible();
+ await expect(page.getByRole('textbox')).toBeVisible();
+ await expect(page.getByRole('button', { name: 'Save document' })).toBeVisible();
+ await expect(page.getByRole('button', { name: 'Reset draft' })).toBeVisible();
+ await expect(
+ page.locator('[data-inkspan-form-field][name="message_body"]'),
+ ).toHaveValue('# Draft');
+ await expect(page.getByText('Loading buyer editor')).toHaveCount(0);
+
+ const toolbarButton = page.locator('.cwl-tb-btn').first();
+ await expect(toolbarButton).toBeVisible();
+ await toolbarButton.focus();
+
+ const forcedColorsEvidence = await page.evaluate(() => {
+ const editor = document.querySelector('.cwl-editor');
+ const toolbarControl = document.querySelector('.cwl-tb-btn');
+ if (!editor || !toolbarControl) {
+ throw new Error('reference host forced-colors surface is incomplete');
+ }
+ const editorStyle = getComputedStyle(editor);
+ const toolbarStyle = getComputedStyle(toolbarControl);
+ return {
+ forcedColorsMatches: matchMedia('(forced-colors: active)').matches,
+ editorColor: editorStyle.color,
+ editorBackgroundColor: editorStyle.backgroundColor,
+ outlineStyle: toolbarStyle.outlineStyle,
+ outlineWidth: toolbarStyle.outlineWidth,
+ outlineColor: toolbarStyle.outlineColor,
+ };
+ });
+
+ expect(forcedColorsEvidence.forcedColorsMatches).toBe(true);
+ expect(forcedColorsEvidence.editorColor).not.toBe('rgba(0, 0, 0, 0)');
+ expect(forcedColorsEvidence.editorBackgroundColor).not.toBe('rgba(0, 0, 0, 0)');
+ expect(forcedColorsEvidence.outlineStyle).not.toBe('none');
+ expect(forcedColorsEvidence.outlineWidth).not.toBe('0px');
+ expect(forcedColorsEvidence.outlineColor).not.toBe('rgba(0, 0, 0, 0)');
+ expect(rejectedRequests).toEqual([]);
+ expect(pageErrors).toEqual([]);
+});
diff --git a/tests/browser/specs/reference-host-hydration.browser.spec.ts b/tests/browser/specs/reference-host-hydration.browser.spec.ts
new file mode 100644
index 00000000..664b3ab3
--- /dev/null
+++ b/tests/browser/specs/reference-host-hydration.browser.spec.ts
@@ -0,0 +1,127 @@
+import { expect, test, type Page } from '@playwright/test';
+
+const REFERENCE_HOST_URL =
+ 'http://127.0.0.1:4173/examples/reference-host/browser-host.html';
+
+function isReferenceHostRequest(requestUrl: string): boolean {
+ const url = new URL(requestUrl);
+ return (
+ url.protocol === 'http:' &&
+ url.hostname === '127.0.0.1' &&
+ url.port === '4173'
+ );
+}
+
+async function observeReferenceHost(page: Page) {
+ const rejectedRequests: string[] = [];
+ const pageErrors: string[] = [];
+ const consoleErrors: string[] = [];
+
+ page.on('pageerror', (error) => {
+ pageErrors.push(error.message);
+ });
+ page.on('console', (message) => {
+ if (message.type() === 'error') {
+ consoleErrors.push(message.text());
+ }
+ });
+ await page.route('**/*', async (route) => {
+ const requestUrl = route.request().url();
+ if (isReferenceHostRequest(requestUrl)) {
+ await route.continue();
+ return;
+ }
+ rejectedRequests.push(requestUrl);
+ await route.abort('blockedbyclient');
+ });
+
+ return { rejectedRequests, pageErrors, consoleErrors };
+}
+
+test('preserves an accessible pre-hydration shell and hydrates the packed reference host without mismatch or external network', async ({
+ browser,
+}) => {
+ const staticContext = await browser.newContext({ javaScriptEnabled: false });
+ const staticPage = await staticContext.newPage();
+ const staticEvidence = await observeReferenceHost(staticPage);
+
+ const staticResponse = await staticPage.goto(REFERENCE_HOST_URL);
+ expect(staticResponse?.ok()).toBe(true);
+ await expect(
+ staticPage.getByRole('heading', { name: 'Inkspan reference host' }),
+ ).toBeVisible();
+ await expect(
+ staticPage.getByText('Loading buyer editor', { exact: true }),
+ ).toBeVisible();
+ await expect(staticPage.locator('[aria-busy="true"]')).toHaveText(
+ 'Loading buyer editor',
+ );
+ await expect(staticPage.getByRole('textbox')).toHaveCount(0);
+ expect(staticEvidence.rejectedRequests).toEqual([]);
+ expect(staticEvidence.pageErrors).toEqual([]);
+ expect(staticEvidence.consoleErrors).toEqual([]);
+ await staticContext.close();
+
+ const hydratedContext = await browser.newContext();
+ const hydratedPage = await hydratedContext.newPage();
+ const hydratedEvidence = await observeReferenceHost(hydratedPage);
+
+ const hydratedResponse = await hydratedPage.goto(REFERENCE_HOST_URL);
+ expect(hydratedResponse?.ok()).toBe(true);
+ await expect(
+ hydratedPage.getByRole('heading', { name: 'Inkspan reference host' }),
+ ).toBeVisible();
+ await expect(hydratedPage.getByRole('textbox')).toBeVisible();
+ await expect(
+ hydratedPage.getByRole('button', { name: 'Save document' }),
+ ).toBeVisible();
+ await expect(
+ hydratedPage.getByText('Loading buyer editor', { exact: true }),
+ ).toHaveCount(0);
+ await expect(hydratedPage.locator('[aria-busy="true"]')).toHaveCount(0);
+ await expect(
+ hydratedPage.locator('[data-inkspan-form-field][name="message_body"]'),
+ ).toHaveValue('# Draft');
+ expect(hydratedEvidence.rejectedRequests).toEqual([]);
+ expect(hydratedEvidence.pageErrors).toEqual([]);
+ expect(hydratedEvidence.consoleErrors).toEqual([]);
+ await hydratedContext.close();
+});
+
+test('exercises the controlled public editor composition in the packed reference host without changing host authority', async ({
+ browser,
+}) => {
+ const context = await browser.newContext();
+ const page = await context.newPage();
+ const evidence = await observeReferenceHost(page);
+
+ const response = await page.goto(`${REFERENCE_HOST_URL}?controlMode=controlled`);
+ expect(response?.ok()).toBe(true);
+
+ const form = page.locator('form[data-reference-host-control-mode="controlled"]');
+ await expect(form).toBeVisible();
+ const textbox = page.getByRole('textbox');
+ await expect(textbox).toBeVisible();
+ await expect(textbox).toHaveText('Draft');
+ await textbox.fill('Controlled buyer draft');
+ await expect(
+ page.locator('[data-inkspan-form-field][name="message_body"]'),
+ ).toHaveValue('# Controlled buyer draft');
+
+ await page.getByRole('button', { name: 'Save document' }).click();
+ await expect(page.getByText('Saved', { exact: true })).toBeVisible();
+ expect(await page.evaluate(() => window.referenceHostSubmissions)).toEqual([
+ '# Controlled buyer draft',
+ ]);
+
+ await page.getByRole('button', { name: 'Reset draft' }).click();
+ await expect(textbox).toHaveText('Draft');
+ await expect(
+ page.locator('[data-inkspan-form-field][name="message_body"]'),
+ ).toHaveValue('# Draft');
+ await expect(page.getByText('Not saved yet', { exact: true })).toBeVisible();
+ expect(evidence.rejectedRequests).toEqual([]);
+ expect(evidence.pageErrors).toEqual([]);
+ expect(evidence.consoleErrors).toEqual([]);
+ await context.close();
+});
diff --git a/tests/browser/specs/reference-host-readonly.browser.spec.ts b/tests/browser/specs/reference-host-readonly.browser.spec.ts
new file mode 100644
index 00000000..6b36d98b
--- /dev/null
+++ b/tests/browser/specs/reference-host-readonly.browser.spec.ts
@@ -0,0 +1,68 @@
+import { expect, test, type Page } from '@playwright/test';
+
+const REFERENCE_HOST_URL =
+ 'http://127.0.0.1:4173/examples/reference-host/browser-host.html';
+
+function isReferenceHostRequest(requestUrl: string): boolean {
+ const url = new URL(requestUrl);
+ return (
+ url.protocol === 'http:' &&
+ url.hostname === '127.0.0.1' &&
+ url.port === '4173'
+ );
+}
+
+async function observeReferenceHost(page: Page) {
+ const rejectedRequests: string[] = [];
+ const pageErrors: string[] = [];
+
+ page.on('pageerror', (error) => {
+ pageErrors.push(error.message);
+ });
+ await page.route('**/*', async (route) => {
+ const requestUrl = route.request().url();
+ if (isReferenceHostRequest(requestUrl)) {
+ await route.continue();
+ return;
+ }
+ rejectedRequests.push(requestUrl);
+ await route.abort('blockedbyclient');
+ });
+
+ return { rejectedRequests, pageErrors };
+}
+
+test('keeps the read-only reference host inert at the native-form write boundary', async ({
+ page,
+}) => {
+ const evidence = await observeReferenceHost(page);
+ const response = await page.goto(`${REFERENCE_HOST_URL}?readOnly=1`);
+ expect(response?.ok()).toBe(true);
+
+ const textbox = page.getByRole('textbox');
+ const field = page.locator(
+ '[data-inkspan-form-field][name="message_body"]',
+ );
+ const saveButton = page.getByRole('button', { name: 'Save document' });
+ const resetButton = page.getByRole('button', { name: 'Reset draft' });
+ const form = page.locator('form');
+
+ await expect(textbox).toBeVisible();
+ await expect(textbox).toHaveAttribute('contenteditable', 'false');
+ await expect(textbox).toHaveText('Draft');
+ await expect(field).toBeDisabled();
+ await expect(field).toHaveValue('# Draft');
+ await expect(saveButton).toBeDisabled();
+ await expect(resetButton).toBeDisabled();
+
+ await form.evaluate((element) => {
+ element.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
+ element.dispatchEvent(new Event('reset', { bubbles: true, cancelable: true }));
+ });
+
+ await expect(textbox).toHaveText('Draft');
+ await expect(field).toHaveValue('# Draft');
+ expect(await page.evaluate(() => window.referenceHostSubmissions)).toEqual([]);
+ expect(evidence.rejectedRequests).toEqual([]);
+ expect(evidence.pageErrors).toEqual([]);
+});
diff --git a/tests/browser/specs/reference-host.print.browser.spec.ts b/tests/browser/specs/reference-host.print.browser.spec.ts
new file mode 100644
index 00000000..91c900c8
--- /dev/null
+++ b/tests/browser/specs/reference-host.print.browser.spec.ts
@@ -0,0 +1,300 @@
+import { expect, test } from '@playwright/test';
+
+const REFERENCE_HOST_URL =
+ 'http://127.0.0.1:4173/examples/reference-host/browser-host.html';
+
+function isReferenceHostRequest(requestUrl: string): boolean {
+ const url = new URL(requestUrl);
+ return url.protocol === 'http:' && url.hostname === '127.0.0.1' && url.port === '4173';
+}
+
+test.describe.configure({ mode: 'serial' });
+
+test('hydrates the real native-form reference host without external runtime requests', async ({
+ page,
+}) => {
+ const rejectedRequests: string[] = [];
+ const pageErrors: string[] = [];
+ const consoleErrors: string[] = [];
+
+ page.on('pageerror', (error) => {
+ pageErrors.push(error.message);
+ });
+ page.on('console', (message) => {
+ if (message.type() === 'error') {
+ consoleErrors.push(message.text());
+ }
+ });
+ await page.route('**/*', async (route) => {
+ const requestUrl = route.request().url();
+ if (isReferenceHostRequest(requestUrl)) {
+ await route.continue();
+ return;
+ }
+ rejectedRequests.push(requestUrl);
+ await route.abort('blockedbyclient');
+ });
+
+ const response = await page.goto(REFERENCE_HOST_URL);
+ expect(response?.ok()).toBe(true);
+
+ await expect(
+ page.getByRole('heading', { name: 'Inkspan reference host' }),
+ ).toBeVisible();
+ await expect(
+ page.locator('[data-inkspan-form-field][name="message_body"]'),
+ ).toHaveValue('# Draft');
+ await expect(page.getByText('Loading buyer editor')).toHaveCount(0);
+
+ await page.getByRole('button', { name: 'Save document' }).click();
+ await expect
+ .poll(() =>
+ page.evaluate(() => {
+ const hostWindow = window as typeof window & {
+ referenceHostSubmissions?: string[];
+ };
+ return hostWindow.referenceHostSubmissions ?? [];
+ }),
+ )
+ .toEqual(['# Draft']);
+
+ expect(rejectedRequests).toEqual([]);
+ expect(pageErrors).toEqual([]);
+ expect(
+ consoleErrors.filter((message) =>
+ /hydration|did not match|server html/iu.test(message),
+ ),
+ ).toEqual([]);
+});
+
+test('restores the packed native-form draft without creating a host submission', async ({
+ page,
+}) => {
+ const rejectedRequests: string[] = [];
+ const pageErrors: string[] = [];
+
+ page.on('pageerror', (error) => {
+ pageErrors.push(error.message);
+ });
+ await page.route('**/*', async (route) => {
+ const requestUrl = route.request().url();
+ if (isReferenceHostRequest(requestUrl)) {
+ await route.continue();
+ return;
+ }
+ rejectedRequests.push(requestUrl);
+ await route.abort('blockedbyclient');
+ });
+
+ const response = await page.goto(REFERENCE_HOST_URL);
+ expect(response?.ok()).toBe(true);
+
+ const editor = page.getByRole('textbox');
+ const field = page.locator(
+ '[data-inkspan-form-field][name="message_body"]',
+ );
+ await expect(editor).toBeVisible();
+ await expect(field).toHaveValue('# Draft');
+
+ await editor.click();
+ await page.keyboard.press('Control+A');
+ await page.keyboard.type('Buyer changed draft');
+ await expect(field).not.toHaveValue('# Draft');
+
+ await page.getByRole('button', { name: 'Reset draft' }).click();
+ await expect(field).toHaveValue('# Draft');
+ await expect(editor).toContainText('Draft');
+ await expect(page.getByText('Not saved yet', { exact: true })).toBeVisible();
+ await expect
+ .poll(() =>
+ page.evaluate(() => {
+ const hostWindow = window as typeof window & {
+ referenceHostSubmissions?: string[];
+ };
+ return hostWindow.referenceHostSubmissions ?? [];
+ }),
+ )
+ .toEqual([]);
+
+ expect(rejectedRequests).toEqual([]);
+ expect(pageErrors).toEqual([]);
+});
+
+test('keeps the buyer host readable while read-only mode fail-closes native writes', async ({
+ page,
+}) => {
+ const rejectedRequests: string[] = [];
+ const pageErrors: string[] = [];
+ const consoleErrors: string[] = [];
+
+ page.on('pageerror', (error) => {
+ pageErrors.push(error.message);
+ });
+ page.on('console', (message) => {
+ if (message.type() === 'error') {
+ consoleErrors.push(message.text());
+ }
+ });
+ await page.route('**/*', async (route) => {
+ const requestUrl = route.request().url();
+ if (isReferenceHostRequest(requestUrl)) {
+ await route.continue();
+ return;
+ }
+ rejectedRequests.push(requestUrl);
+ await route.abort('blockedbyclient');
+ });
+
+ const response = await page.goto(`${REFERENCE_HOST_URL}?readOnly=1`);
+ expect(response?.ok()).toBe(true);
+
+ await expect(
+ page.getByRole('heading', { name: 'Inkspan reference host' }),
+ ).toBeVisible();
+ await expect(page.getByRole('textbox')).toHaveAttribute('aria-readonly', 'true');
+ await expect(page.getByRole('textbox')).toContainText('Draft');
+ await expect(
+ page.locator('[data-inkspan-form-field][name="message_body"]'),
+ ).toBeDisabled();
+ await expect(
+ page.locator('[data-inkspan-form-field][name="message_body"]'),
+ ).toHaveValue('# Draft');
+ await expect(page.getByRole('button', { name: 'Save document' })).toBeDisabled();
+ await expect(page.getByRole('button', { name: 'Reset draft' })).toBeDisabled();
+ await expect(page.getByText('Loading buyer editor')).toHaveCount(0);
+
+ await expect
+ .poll(() =>
+ page.evaluate(() => {
+ const hostWindow = window as typeof window & {
+ referenceHostSubmissions?: string[];
+ };
+ return hostWindow.referenceHostSubmissions ?? [];
+ }),
+ )
+ .toEqual([]);
+
+ expect(rejectedRequests).toEqual([]);
+ expect(pageErrors).toEqual([]);
+ expect(
+ consoleErrors.filter((message) =>
+ /hydration|did not match|server html/iu.test(message),
+ ),
+ ).toEqual([]);
+});
+
+test('keeps the buyer host usable without horizontal overflow at a narrow viewport', async ({
+ page,
+}) => {
+ const rejectedRequests: string[] = [];
+ const pageErrors: string[] = [];
+
+ page.on('pageerror', (error) => {
+ pageErrors.push(error.message);
+ });
+ await page.route('**/*', async (route) => {
+ const requestUrl = route.request().url();
+ if (isReferenceHostRequest(requestUrl)) {
+ await route.continue();
+ return;
+ }
+ rejectedRequests.push(requestUrl);
+ await route.abort('blockedbyclient');
+ });
+
+ await page.setViewportSize({ width: 320, height: 640 });
+ const response = await page.goto(REFERENCE_HOST_URL);
+ expect(response?.ok()).toBe(true);
+
+ await expect(
+ page.getByRole('heading', { name: 'Inkspan reference host' }),
+ ).toBeVisible();
+ await expect(page.getByRole('textbox')).toBeVisible();
+ await expect(page.getByRole('button', { name: 'Save document' })).toBeVisible();
+ await expect(page.getByRole('button', { name: 'Reset draft' })).toBeVisible();
+ await expect(page.getByText('Loading buyer editor')).toHaveCount(0);
+
+ const layout = await page.evaluate(() => {
+ const editor = document.querySelector('.cwl-editor');
+ if (!editor) {
+ throw new Error('reference host editor is missing');
+ }
+ const rect = editor.getBoundingClientRect();
+ return {
+ viewportWidth: window.innerWidth,
+ documentScrollWidth: document.documentElement.scrollWidth,
+ bodyScrollWidth: document.body.scrollWidth,
+ editorLeft: rect.left,
+ editorRight: rect.right,
+ };
+ });
+
+ expect(layout.viewportWidth).toBe(320);
+ expect(layout.documentScrollWidth).toBeLessThanOrEqual(layout.viewportWidth);
+ expect(layout.bodyScrollWidth).toBeLessThanOrEqual(layout.viewportWidth);
+ expect(layout.editorLeft).toBeGreaterThanOrEqual(0);
+ expect(layout.editorRight).toBeLessThanOrEqual(layout.viewportWidth);
+ expect(rejectedRequests).toEqual([]);
+ expect(pageErrors).toEqual([]);
+});
+
+test('applies the package print contract inside the real buyer host without runtime network', async ({
+ page,
+}) => {
+ const rejectedRequests: string[] = [];
+ const pageErrors: string[] = [];
+
+ page.on('pageerror', (error) => {
+ pageErrors.push(error.message);
+ });
+ await page.route('**/*', async (route) => {
+ const requestUrl = route.request().url();
+ if (isReferenceHostRequest(requestUrl)) {
+ await route.continue();
+ return;
+ }
+ rejectedRequests.push(requestUrl);
+ await route.abort('blockedbyclient');
+ });
+
+ await page.emulateMedia({ media: 'print' });
+ const response = await page.goto(REFERENCE_HOST_URL);
+ expect(response?.ok()).toBe(true);
+
+ await expect(
+ page.getByRole('heading', { name: 'Inkspan reference host' }),
+ ).toBeVisible();
+ await expect(page.locator('.cwl-editor__content')).toContainText('Draft');
+ await expect(page.locator('.cwl-toolbar')).toBeHidden();
+
+ const printStyles = await page.evaluate(() => {
+ const editor = document.querySelector('.cwl-editor');
+ const surface = document.querySelector('.cwl-editor__surface');
+ const content = document.querySelector('.cwl-editor__content');
+ if (!editor || !surface || !content) {
+ throw new Error('reference host print surface is incomplete');
+ }
+ const editorStyle = getComputedStyle(editor);
+ const surfaceStyle = getComputedStyle(surface);
+ const contentStyle = getComputedStyle(content);
+ return {
+ printMediaMatches: matchMedia('print').matches,
+ editorOverflow: editorStyle.overflow,
+ editorBorderTopWidth: editorStyle.borderTopWidth,
+ surfaceOverflow: surfaceStyle.overflow,
+ surfaceMaxHeight: surfaceStyle.maxHeight,
+ contentMinHeight: contentStyle.minHeight,
+ contentPaddingTop: contentStyle.paddingTop,
+ };
+ });
+
+ expect(printStyles.printMediaMatches).toBe(true);
+ expect(printStyles.editorOverflow).toBe('visible');
+ expect(printStyles.editorBorderTopWidth).toBe('0px');
+ expect(printStyles.surfaceOverflow).toBe('visible');
+ expect(printStyles.surfaceMaxHeight).toBe('none');
+ expect(printStyles.contentMinHeight).toBe('0px');
+ expect(printStyles.contentPaddingTop).toBe('0px');
+ expect(rejectedRequests).toEqual([]);
+ expect(pageErrors).toEqual([]);
+});
diff --git a/tests/browser/vite.config.ts b/tests/browser/vite.config.ts
index 50c49dd1..1c1b7ea3 100644
--- a/tests/browser/vite.config.ts
+++ b/tests/browser/vite.config.ts
@@ -8,11 +8,44 @@ const configuredPackageEntry = process.env.INKSPAN_BROWSER_PACKAGE_ENTRY?.trim()
const packageEntry = configuredPackageEntry
? resolve(configuredPackageEntry)
: resolve(repositoryRoot, 'src/index.ts');
+const packedPackageRoot = configuredPackageEntry
+ ? resolve(dirname(packageEntry), '..')
+ : null;
+const alias = [
+ ...(packedPackageRoot
+ ? [
+ {
+ find: '@contextualwisdomlab/cwl-editor/styles.css',
+ replacement: resolve(packedPackageRoot, 'dist/cwl-editor.css'),
+ },
+ {
+ find: '@contextualwisdomlab/cwl-editor/fonts.css',
+ replacement: resolve(packedPackageRoot, 'src/fonts/fonts.css'),
+ },
+ {
+ find: '@contextualwisdomlab/cwl-editor/fonts-latin.css',
+ replacement: resolve(packedPackageRoot, 'src/fonts/fonts-latin.css'),
+ },
+ {
+ find: '@contextualwisdomlab/cwl-editor',
+ replacement: packageEntry,
+ },
+ ]
+ : []),
+ {
+ find: 'inkspan-browser-under-test',
+ replacement: packageEntry,
+ },
+];
export default defineConfig({
+ optimizeDeps: {
+ entries: [
+ 'tests/browser/harness.html',
+ 'examples/reference-host/browser-host.html',
+ ],
+ },
resolve: {
- alias: {
- 'inkspan-browser-under-test': packageEntry,
- },
+ alias,
},
});