@volter/twin-standard 1.0.36 → 1.0.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -112,7 +112,7 @@ export async function assessPack(packDir, options = {}) {
112
112
  const transport = await chromiumServed(packDir, life.host ?? 'twin.local');
113
113
  try {
114
114
  const a = await replay(transport.served), b = await replay(transport.served);
115
- browser = { measured: true, target: 'Chromium fetch, cookie jar and HTTP response behavior; no DOM interaction coverage', version: transport.version, answered: a.trace.length, failures: [...a.failures, ...b.failures], replay: { equal: a.trace.length > 0 && json(a.trace) === json(b.trace), firstSha256: hash(a.trace), secondSha256: hash(b.trace), runs: 2 }, operationCoverage: coverageOf(a), domCoverage: null, clock: 'World and page JavaScript clocks frozen; native cookie expiration uses Chromium wall time' };
115
+ browser = { measured: true, target: 'Chromium fetch, cookie jar and HTTP response behavior; no DOM interaction coverage', version: transport.version, answered: a.trace.length, failures: [...a.failures, ...b.failures], replay: { equal: a.trace.length > 0 && json(a.trace) === json(b.trace), firstSha256: hash(a.trace), secondSha256: hash(b.trace), runs: 2 }, operationCoverage: coverageOf(a), domCoverage: null, clock: transport.clock };
116
116
  }
117
117
  finally {
118
118
  await transport.stop();
@@ -2,5 +2,11 @@ import type { ServedTwin } from './walk/behavior-journey.js';
2
2
  export declare function chromiumServed(packDir: string, host: string): Promise<{
3
3
  served: ServedTwin;
4
4
  version: string;
5
+ clock: {
6
+ native: string;
7
+ nativePrecisionMs: number;
8
+ javascriptPrecisionMs: number;
9
+ monotonic: string;
10
+ };
5
11
  stop: () => Promise<void>;
6
12
  }>;
@@ -8,16 +8,39 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
8
8
  };
9
9
  // Browser transport for the existing journey engine. A container supplies network isolation in catalog execution.
10
10
  import { join } from 'node:path';
11
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
12
+ import { tmpdir } from 'node:os';
13
+ import { worldNow } from '@volter/world-core/runtime';
11
14
  export async function chromiumServed(packDir, host) {
12
15
  const { chromium } = await import('playwright');
13
- const browser = await chromium.launch({ args: ['--no-sandbox'] });
14
16
  const mod = await import(__rewriteRelativeImportExtension(join(packDir, 'src/index.ts')));
15
17
  const factory = Object.entries(mod).find(([name, value]) => /^create\w+Fetch$/.test(name) && !/TwinFetch$/.test(name) && typeof value === 'function')?.[1];
16
- if (!factory) {
17
- await browser.close();
18
+ if (!factory)
18
19
  throw new Error('Browser assessment needs the pack HTTP fetch factory');
20
+ const library = ['/usr/lib/x86_64-linux-gnu/faketime/libfaketimeMT.so.1', '/usr/lib/aarch64-linux-gnu/faketime/libfaketimeMT.so.1'].find(existsSync);
21
+ if (process.platform !== 'linux' || !library)
22
+ throw new Error('Deterministic browser assessment needs Linux libfaketime installed for native cookie time');
23
+ const clockDir = mkdtempSync(join(tmpdir(), 'twin-browser-clock-'));
24
+ const clockFile = join(clockDir, 'clock');
25
+ const syncClock = () => {
26
+ const instant = new Date(worldNow());
27
+ // libfaketime's absolute format stops wall time, at HTTP-date (one-second) precision.
28
+ writeFileSync(clockFile, `${instant.toISOString().slice(0, 19).replace('T', ' ')}\n`);
29
+ return instant;
30
+ };
31
+ syncClock();
32
+ const env = Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined && !entry[0].startsWith('FAKETIME')));
33
+ Object.assign(env, { LD_PRELOAD: library, TZ: 'UTC', FAKETIME_TIMESTAMP_FILE: clockFile, FAKETIME_NO_CACHE: '1', FAKETIME_DISABLE_SHM: '1', FAKETIME_DONT_FAKE_MONOTONIC: '1', NO_FAKE_STAT: '1' });
34
+ let browser;
35
+ try {
36
+ browser = await chromium.launch({ args: ['--no-sandbox'], env });
37
+ }
38
+ catch (error) {
39
+ rmSync(clockDir, { recursive: true, force: true });
40
+ throw error;
19
41
  }
20
42
  const served = async (root, options) => {
43
+ syncClock();
21
44
  const fetchPack = factory({ root, ...options });
22
45
  const context = await browser.newContext({ serviceWorkers: 'block', locale: 'en-US', timezoneId: 'UTC' });
23
46
  const page = await context.newPage();
@@ -35,6 +58,9 @@ export async function chromiumServed(packDir, host) {
35
58
  try {
36
59
  const response = await fetchPack(new Request(request.url(), { method: request.method(), headers: await request.allHeaders(), ...(request.postDataBuffer() ? { body: new Uint8Array(request.postDataBuffer()).buffer } : {}) }));
37
60
  const headers = Object.fromEntries(response.headers);
61
+ // Playwright otherwise adds CORS permission to cross-origin fulfillments. Absence means denial on the vendor wire.
62
+ if (!Object.hasOwn(headers, 'access-control-allow-origin'))
63
+ headers['access-control-allow-origin'] = '';
38
64
  const cookies = response.headers.getSetCookie();
39
65
  if (cookies.length)
40
66
  headers['set-cookie'] = cookies.join('\n');
@@ -45,9 +71,10 @@ export async function chromiumServed(packDir, host) {
45
71
  }
46
72
  });
47
73
  await page.goto(harness);
48
- await page.clock.setFixedTime(new Date('2026-01-01T00:00:00.000Z'));
74
+ await page.clock.setFixedTime(syncClock());
49
75
  return {
50
76
  fetch: async (request) => {
77
+ await page.clock.setFixedTime(syncClock());
51
78
  const body = request.body ? Buffer.from(await request.arrayBuffer()).toString('base64') : null;
52
79
  const result = await page.evaluate(async ({ url, method, headers, body }) => {
53
80
  const bytes = body === null ? undefined : Uint8Array.from(atob(body), (char) => char.charCodeAt(0));
@@ -62,8 +89,13 @@ export async function chromiumServed(packDir, host) {
62
89
  throw new Error('Chromium returned an opaque response; this journey needs browser-specific navigation assertions');
63
90
  return new Response([101, 204, 205, 304].includes(result.status) || request.method === 'HEAD' ? null : Buffer.from(result.body, 'base64'), { status: result.status, headers: result.headers });
64
91
  },
65
- stop: () => { void context.close(); },
92
+ stop: () => context.close(),
66
93
  };
67
94
  };
68
- return { served, version: browser.version(), stop: () => browser.close() };
95
+ return { served, version: browser.version(), clock: { native: 'World time through libfaketime', nativePrecisionMs: 1000, javascriptPrecisionMs: 1, monotonic: 'elapsed machine time' }, stop: async () => { try {
96
+ await browser.close();
97
+ }
98
+ finally {
99
+ rmSync(clockDir, { recursive: true, force: true });
100
+ } } };
69
101
  }
@@ -0,0 +1,19 @@
1
+ export declare function verifyBrowserTransport(pack: string): Promise<{
2
+ purpose: string;
3
+ passed: boolean;
4
+ chromium: string;
5
+ replays: number;
6
+ exercisedOperations: number;
7
+ declaredOperations: number;
8
+ cookieJar: boolean;
9
+ cookieExpiry: string[];
10
+ clock: {
11
+ native: string;
12
+ nativePrecisionMs: number;
13
+ javascriptPrecisionMs: number;
14
+ monotonic: string;
15
+ };
16
+ binaryBytes: number;
17
+ corsEnforced: boolean;
18
+ domCoverage: null;
19
+ }>;
@@ -0,0 +1,86 @@
1
+ // Regression of the standard's own browser transport. This fixture is not a pack or an admission verdict.
2
+ import assert from 'node:assert/strict';
3
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
4
+ import { createRequire } from 'node:module';
5
+ import { join } from 'node:path';
6
+ import { pathToFileURL } from 'node:url';
7
+ import { getActiveWorldStore, observeDerivedDispatch } from '@volter/world-core/runtime';
8
+ import { inMemoryWorld } from "./walk/behavior-journey.js";
9
+ import { chromiumServed } from "./browser-assessment.js";
10
+ const require = createRequire(import.meta.url);
11
+ export async function verifyBrowserTransport(pack) {
12
+ assert.ok(process.env.VOLTER_WORLD, 'browser transport verification must run through a World');
13
+ assert.ok(!existsSync(pack), 'browser fixture directory must be new and task-owned');
14
+ mkdirSync(join(pack, 'src'), { recursive: true });
15
+ writeFileSync(join(pack, 'src/index.ts'), `
16
+ import { createDerivedFetch } from ${JSON.stringify(pathToFileURL(require.resolve('@volter/world-core')).href)};
17
+ export function createFixtureFetch() {
18
+ return createDerivedFetch({ surface: { version: '1', operations: [
19
+ { id: 'login', method: 'post', path: '/login', class: 'action' },
20
+ { id: 'identity', method: 'get', path: '/identity', class: 'get' },
21
+ { id: 'binary', method: 'get', path: '/binary', class: 'get' },
22
+ { id: 'head', method: 'head', path: '/head', class: 'get' },
23
+ { id: 'gap', method: 'get', path: '/gap', class: 'get' },
24
+ { id: 'unused', method: 'get', path: '/unused', class: 'get' },
25
+ ] }, handlers: {
26
+ login: async () => new Response(null, { status: 204, headers: [['set-cookie', 'session=fixture; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=3600'], ['set-cookie', 'second=fixture; Path=/; Secure; SameSite=Strict; Expires=Thu, 01 Jan 2026 01:00:00 GMT']] }),
27
+ identity: async ({ request }) => {
28
+ const session = request.headers.get('cookie')?.includes('session=fixture') || false;
29
+ const second = request.headers.get('cookie')?.includes('second=fixture') || false;
30
+ return Response.json({ authenticated: session && second, session, second });
31
+ },
32
+ binary: async () => new Response(new Uint8Array(100000).fill(197), { headers: { 'content-type': 'application/octet-stream' } }),
33
+ head: async () => new Response(null, { status: 204 }),
34
+ }, gap: async () => Response.json({ error: 'unsupported' }, { status: 404 }) });
35
+ }
36
+ `);
37
+ const world = inMemoryWorld('browser-transport-verification');
38
+ const setClock = (instant) => getActiveWorldStore().write(process.env.TWIN_WORLD_CLOCK_FILE, instant);
39
+ const browser = await chromiumServed(pack, 'browser-fixture.test').catch((error) => { world.restore(); throw error; });
40
+ const seen = new Set();
41
+ const unsubscribe = observeDerivedDispatch(({ operation }) => seen.add(operation.id));
42
+ const replays = [];
43
+ try {
44
+ for (let run = 0; run < 2; run++) {
45
+ setClock('2026-01-01T00:00:00.000Z');
46
+ const served = await browser.served(world.root(), {});
47
+ try {
48
+ const fetch = (path, method = 'GET') => served.fetch(new Request(`https://browser-fixture.test${path}`, { method }));
49
+ assert.equal((await (await fetch('/identity')).json()).authenticated, false, 'fresh contexts must not inherit cookies');
50
+ assert.equal((await fetch('/login', 'POST')).status, 204);
51
+ const identity = await (await fetch('/identity')).json();
52
+ assert.equal(identity.authenticated, true, 'the browser cookie jar must reach the twin');
53
+ setClock('2026-01-01T00:59:59.000Z');
54
+ assert.deepEqual(await (await fetch('/identity')).json(), identity, 'both cookie types must remain before their World-time expiry');
55
+ setClock('2026-01-01T01:00:01.000Z');
56
+ const expired = await (await fetch('/identity')).json();
57
+ assert.deepEqual(expired, { authenticated: false, session: false, second: false }, 'native Expires and Max-Age must follow World time');
58
+ const binary = new Uint8Array(await (await fetch('/binary')).arrayBuffer());
59
+ assert.equal(binary.byteLength, 100000);
60
+ assert.ok(binary.every((byte) => byte === 197));
61
+ const head = await fetch('/head', 'HEAD');
62
+ assert.equal(head.status, 204);
63
+ assert.equal(await head.text(), '');
64
+ assert.equal((await fetch('/gap')).status, 404);
65
+ assert.equal((await fetch('/unknown')).status, 404);
66
+ await assert.rejects(() => served.fetch(new Request('https://another-origin.test/identity')), /Failed to fetch/);
67
+ replays.push({ identity, expired, binaryBytes: binary.byteLength, headStatus: head.status, gapStatus: 404 });
68
+ }
69
+ finally {
70
+ await served.stop();
71
+ }
72
+ }
73
+ assert.deepEqual(replays[0], replays[1]);
74
+ assert.deepEqual([...seen].sort(), ['binary', 'gap', 'head', 'identity', 'login']);
75
+ return { purpose: 'browser-transport-fixture', passed: true, chromium: browser.version, replays: 2, exercisedOperations: seen.size, declaredOperations: 6, cookieJar: true, cookieExpiry: ['Expires', 'Max-Age'], clock: browser.clock, binaryBytes: 100000, corsEnforced: true, domCoverage: null };
76
+ }
77
+ finally {
78
+ unsubscribe();
79
+ try {
80
+ await browser.stop();
81
+ }
82
+ finally {
83
+ world.restore();
84
+ }
85
+ }
86
+ }
@@ -10,3 +10,4 @@ export type { Criterion, CriterionResult, GradeUnit, ScoreReport, Standard } fro
10
10
  export { compilePackFacts, renderPackFacts, type PackFacts, type PackFactsFile } from './pack-facts.js';
11
11
  export { assessPack, type AssessmentOptions } from './assessment.js';
12
12
  export { createPack } from './create-pack.js';
13
+ export { verifyBrowserTransport } from './browser-verification.js';
package/dist/src/index.js CHANGED
@@ -14,3 +14,4 @@ export { unitAt } from "./unit.js";
14
14
  export { compilePackFacts, renderPackFacts } from "./pack-facts.js";
15
15
  export { assessPack } from "./assessment.js";
16
16
  export { createPack } from "./create-pack.js";
17
+ export { verifyBrowserTransport } from "./browser-verification.js";
@@ -34,7 +34,7 @@ export type ServedTwin = (root: string, options: {
34
34
  application?: import('@volter/world-core').PackFetchOptions['application'];
35
35
  }) => Promise<{
36
36
  fetch: (request: Request) => Promise<Response>;
37
- stop: () => void;
37
+ stop: () => void | Promise<void>;
38
38
  socket?: (url: string, headers: Record<string, string>) => WebSocket;
39
39
  stream?: TwinStream;
40
40
  }>;
@@ -1094,7 +1094,7 @@ export async function walk(journey, root, collect, opts) {
1094
1094
  await Promise.allSettled([...pending.values()].map((request) => request.answer));
1095
1095
  setDeliveryObserver(previousObserver);
1096
1096
  setInProcessTwins(previousTwins);
1097
- served?.stop();
1097
+ await served?.stop();
1098
1098
  if (sqlSession)
1099
1099
  await sqlSession.then((session) => session.end(), () => { });
1100
1100
  // a World's managed database ends with its walk (vendor-fetch.ts), so the next World declares it at the same port
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-standard",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
4
4
  "description": "The Protocol 3 standard for Volter twin packs: the criteria a pack is graded by, the grader, the gate a pack repository and the twin catalog run on every change and submission, and the spec derivation the grade checks against. Pin a version: every place a pack is judged judges by that standard.",
5
5
  "keywords": [
6
6
  "twin",
@@ -75,7 +75,7 @@
75
75
  "yaml": "2.9.0"
76
76
  },
77
77
  "peerDependencies": {
78
- "@volter/world-core": "3.0.44",
78
+ "@volter/world-core": "3.0.46",
79
79
  "@volter/world-runtime": "*",
80
80
  "playwright": "^1.63.0"
81
81
  },
package/src/assessment.ts CHANGED
@@ -84,7 +84,7 @@ export async function assessPack(packDir: string, options: AssessmentOptions = {
84
84
  const transport = await chromiumServed(packDir, life.host ?? 'twin.local');
85
85
  try {
86
86
  const a = await replay(transport.served), b = await replay(transport.served);
87
- browser = { measured: true, target: 'Chromium fetch, cookie jar and HTTP response behavior; no DOM interaction coverage', version: transport.version, answered: a.trace.length, failures: [...a.failures, ...b.failures], replay: { equal: a.trace.length > 0 && json(a.trace) === json(b.trace), firstSha256: hash(a.trace), secondSha256: hash(b.trace), runs: 2 }, operationCoverage: coverageOf(a), domCoverage: null, clock: 'World and page JavaScript clocks frozen; native cookie expiration uses Chromium wall time' };
87
+ browser = { measured: true, target: 'Chromium fetch, cookie jar and HTTP response behavior; no DOM interaction coverage', version: transport.version, answered: a.trace.length, failures: [...a.failures, ...b.failures], replay: { equal: a.trace.length > 0 && json(a.trace) === json(b.trace), firstSha256: hash(a.trace), secondSha256: hash(b.trace), runs: 2 }, operationCoverage: coverageOf(a), domCoverage: null, clock: transport.clock };
88
88
  } finally { await transport.stop(); }
89
89
  }
90
90
  const conform = options.conformance === false ? null : await conformance(packDir);
@@ -1,13 +1,32 @@
1
1
  // Browser transport for the existing journey engine. A container supplies network isolation in catalog execution.
2
2
  import { join } from 'node:path';
3
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { worldNow } from '@volter/world-core/runtime';
3
6
  import type { ServedTwin } from './walk/behavior-journey.ts';
4
7
  export async function chromiumServed(packDir: string, host: string) {
5
8
  const { chromium } = await import('playwright');
6
- const browser = await chromium.launch({ args: ['--no-sandbox'] });
7
9
  const mod = await import(join(packDir, 'src/index.ts')) as Record<string, unknown>;
8
10
  const factory = Object.entries(mod).find(([name, value]) => /^create\w+Fetch$/.test(name) && !/TwinFetch$/.test(name) && typeof value === 'function')?.[1] as ((options: import('@volter/world-core').PackFetchOptions) => (request: Request) => Promise<Response>) | undefined;
9
- if (!factory) { await browser.close(); throw new Error('Browser assessment needs the pack HTTP fetch factory'); }
11
+ if (!factory) throw new Error('Browser assessment needs the pack HTTP fetch factory');
12
+ const library = ['/usr/lib/x86_64-linux-gnu/faketime/libfaketimeMT.so.1', '/usr/lib/aarch64-linux-gnu/faketime/libfaketimeMT.so.1'].find(existsSync);
13
+ if (process.platform !== 'linux' || !library) throw new Error('Deterministic browser assessment needs Linux libfaketime installed for native cookie time');
14
+ const clockDir = mkdtempSync(join(tmpdir(), 'twin-browser-clock-'));
15
+ const clockFile = join(clockDir, 'clock');
16
+ const syncClock = () => {
17
+ const instant = new Date(worldNow());
18
+ // libfaketime's absolute format stops wall time, at HTTP-date (one-second) precision.
19
+ writeFileSync(clockFile, `${instant.toISOString().slice(0, 19).replace('T', ' ')}\n`);
20
+ return instant;
21
+ };
22
+ syncClock();
23
+ const env: Record<string, string> = Object.fromEntries(Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined && !entry[0].startsWith('FAKETIME')));
24
+ Object.assign(env, { LD_PRELOAD: library, TZ: 'UTC', FAKETIME_TIMESTAMP_FILE: clockFile, FAKETIME_NO_CACHE: '1', FAKETIME_DISABLE_SHM: '1', FAKETIME_DONT_FAKE_MONOTONIC: '1', NO_FAKE_STAT: '1' });
25
+ let browser;
26
+ try { browser = await chromium.launch({ args: ['--no-sandbox'], env }); }
27
+ catch (error) { rmSync(clockDir, { recursive: true, force: true }); throw error; }
10
28
  const served: ServedTwin = async (root, options) => {
29
+ syncClock();
11
30
  const fetchPack = factory({ root, ...options });
12
31
  const context = await browser.newContext({ serviceWorkers: 'block', locale: 'en-US', timezoneId: 'UTC' });
13
32
  const page = await context.newPage();
@@ -19,15 +38,18 @@ export async function chromiumServed(packDir: string, host: string) {
19
38
  try {
20
39
  const response = await fetchPack(new Request(request.url(), { method: request.method(), headers: await request.allHeaders(), ...(request.postDataBuffer() ? { body: new Uint8Array(request.postDataBuffer()!).buffer } : {}) }));
21
40
  const headers = Object.fromEntries(response.headers);
41
+ // Playwright otherwise adds CORS permission to cross-origin fulfillments. Absence means denial on the vendor wire.
42
+ if (!Object.hasOwn(headers, 'access-control-allow-origin')) headers['access-control-allow-origin'] = '';
22
43
  const cookies = response.headers.getSetCookie();
23
44
  if (cookies.length) headers['set-cookie'] = cookies.join('\n');
24
45
  await route.fulfill({ status: response.status, headers, body: Buffer.from(await response.arrayBuffer()) });
25
46
  } catch { await route.abort('failed'); }
26
47
  });
27
48
  await page.goto(harness);
28
- await page.clock.setFixedTime(new Date('2026-01-01T00:00:00.000Z'));
49
+ await page.clock.setFixedTime(syncClock());
29
50
  return {
30
51
  fetch: async (request: Request) => {
52
+ await page.clock.setFixedTime(syncClock());
31
53
  const body = request.body ? Buffer.from(await request.arrayBuffer()).toString('base64') : null;
32
54
  const result = await page.evaluate(async ({ url, method, headers, body }) => {
33
55
  const bytes = body === null ? undefined : Uint8Array.from(atob(body), (char) => char.charCodeAt(0));
@@ -40,8 +62,8 @@ export async function chromiumServed(packDir: string, host: string) {
40
62
  if (!result.status) throw new Error('Chromium returned an opaque response; this journey needs browser-specific navigation assertions');
41
63
  return new Response([101, 204, 205, 304].includes(result.status) || request.method === 'HEAD' ? null : Buffer.from(result.body, 'base64'), { status: result.status, headers: result.headers });
42
64
  },
43
- stop: () => { void context.close(); },
65
+ stop: () => context.close(),
44
66
  };
45
67
  };
46
- return { served, version: browser.version(), stop: () => browser.close() };
68
+ return { served, version: browser.version(), clock: { native: 'World time through libfaketime', nativePrecisionMs: 1000, javascriptPrecisionMs: 1, monotonic: 'elapsed machine time' }, stop: async () => { try { await browser.close(); } finally { rmSync(clockDir, { recursive: true, force: true }); } } };
47
69
  }
@@ -0,0 +1,74 @@
1
+ // Regression of the standard's own browser transport. This fixture is not a pack or an admission verdict.
2
+ import assert from 'node:assert/strict';
3
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
4
+ import { createRequire } from 'node:module';
5
+ import { join } from 'node:path';
6
+ import { pathToFileURL } from 'node:url';
7
+ import { getActiveWorldStore, observeDerivedDispatch } from '@volter/world-core/runtime';
8
+ import { inMemoryWorld } from './walk/behavior-journey.ts';
9
+ import { chromiumServed } from './browser-assessment.ts';
10
+ const require = createRequire(import.meta.url);
11
+ export async function verifyBrowserTransport(pack: string) {
12
+ assert.ok(process.env.VOLTER_WORLD, 'browser transport verification must run through a World');
13
+ assert.ok(!existsSync(pack), 'browser fixture directory must be new and task-owned');
14
+
15
+ mkdirSync(join(pack, 'src'), { recursive: true });
16
+ writeFileSync(join(pack, 'src/index.ts'), `
17
+ import { createDerivedFetch } from ${JSON.stringify(pathToFileURL(require.resolve('@volter/world-core')).href)};
18
+ export function createFixtureFetch() {
19
+ return createDerivedFetch({ surface: { version: '1', operations: [
20
+ { id: 'login', method: 'post', path: '/login', class: 'action' },
21
+ { id: 'identity', method: 'get', path: '/identity', class: 'get' },
22
+ { id: 'binary', method: 'get', path: '/binary', class: 'get' },
23
+ { id: 'head', method: 'head', path: '/head', class: 'get' },
24
+ { id: 'gap', method: 'get', path: '/gap', class: 'get' },
25
+ { id: 'unused', method: 'get', path: '/unused', class: 'get' },
26
+ ] }, handlers: {
27
+ login: async () => new Response(null, { status: 204, headers: [['set-cookie', 'session=fixture; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=3600'], ['set-cookie', 'second=fixture; Path=/; Secure; SameSite=Strict; Expires=Thu, 01 Jan 2026 01:00:00 GMT']] }),
28
+ identity: async ({ request }) => {
29
+ const session = request.headers.get('cookie')?.includes('session=fixture') || false;
30
+ const second = request.headers.get('cookie')?.includes('second=fixture') || false;
31
+ return Response.json({ authenticated: session && second, session, second });
32
+ },
33
+ binary: async () => new Response(new Uint8Array(100000).fill(197), { headers: { 'content-type': 'application/octet-stream' } }),
34
+ head: async () => new Response(null, { status: 204 }),
35
+ }, gap: async () => Response.json({ error: 'unsupported' }, { status: 404 }) });
36
+ }
37
+ `);
38
+ const world = inMemoryWorld('browser-transport-verification');
39
+ const setClock = (instant: string) => getActiveWorldStore().write(process.env.TWIN_WORLD_CLOCK_FILE!, instant);
40
+ const browser = await chromiumServed(pack, 'browser-fixture.test').catch((error) => { world.restore(); throw error; });
41
+ const seen = new Set<string>();
42
+ const unsubscribe = observeDerivedDispatch(({ operation }) => seen.add(operation.id));
43
+ const replays = [];
44
+ try {
45
+ for (let run = 0; run < 2; run++) {
46
+ setClock('2026-01-01T00:00:00.000Z');
47
+ const served = await browser.served(world.root(), {});
48
+ try {
49
+ const fetch = (path: string, method = 'GET') => served.fetch(new Request(`https://browser-fixture.test${path}`, { method }));
50
+ assert.equal((await (await fetch('/identity')).json()).authenticated, false, 'fresh contexts must not inherit cookies');
51
+ assert.equal((await fetch('/login', 'POST')).status, 204);
52
+ const identity = await (await fetch('/identity')).json();
53
+ assert.equal(identity.authenticated, true, 'the browser cookie jar must reach the twin');
54
+ setClock('2026-01-01T00:59:59.000Z');
55
+ assert.deepEqual(await (await fetch('/identity')).json(), identity, 'both cookie types must remain before their World-time expiry');
56
+ setClock('2026-01-01T01:00:01.000Z');
57
+ const expired = await (await fetch('/identity')).json();
58
+ assert.deepEqual(expired, { authenticated: false, session: false, second: false }, 'native Expires and Max-Age must follow World time');
59
+ const binary = new Uint8Array(await (await fetch('/binary')).arrayBuffer());
60
+ assert.equal(binary.byteLength, 100000);
61
+ assert.ok(binary.every((byte) => byte === 197));
62
+ const head = await fetch('/head', 'HEAD');
63
+ assert.equal(head.status, 204); assert.equal(await head.text(), '');
64
+ assert.equal((await fetch('/gap')).status, 404);
65
+ assert.equal((await fetch('/unknown')).status, 404);
66
+ await assert.rejects(() => served.fetch(new Request('https://another-origin.test/identity')), /Failed to fetch/);
67
+ replays.push({ identity, expired, binaryBytes: binary.byteLength, headStatus: head.status, gapStatus: 404 });
68
+ } finally { await served.stop(); }
69
+ }
70
+ assert.deepEqual(replays[0], replays[1]);
71
+ assert.deepEqual([...seen].sort(), ['binary', 'gap', 'head', 'identity', 'login']);
72
+ return { purpose: 'browser-transport-fixture', passed: true, chromium: browser.version, replays: 2, exercisedOperations: seen.size, declaredOperations: 6, cookieJar: true, cookieExpiry: ['Expires', 'Max-Age'], clock: browser.clock, binaryBytes: 100000, corsEnforced: true, domCoverage: null };
73
+ } finally { unsubscribe(); try { await browser.stop(); } finally { world.restore(); } }
74
+ }
package/src/index.ts CHANGED
@@ -17,3 +17,4 @@ export { compilePackFacts, renderPackFacts, type PackFacts, type PackFactsFile }
17
17
  export { assessPack, type AssessmentOptions } from './assessment.ts';
18
18
 
19
19
  export { createPack } from './create-pack.ts';
20
+ export { verifyBrowserTransport } from './browser-verification.ts';
@@ -168,7 +168,7 @@ function takeReplies(c: Connection): LineReply[] {
168
168
  /** A pack served for a walk the way a World runs it (its server.ts on a port, the World's root and scenario its options):
169
169
  * the walk's requests go to it over HTTP, the vendor's host in their Host header, instead of to the fetch in process;
170
170
  * its sockets are WebSockets to that port, and a line connection (`stream`) is a TCP connection to its protocol port. */
171
- export type ServedTwin = (root: string, options: { scenarioPath?: string; application?: import('@volter/world-core').PackFetchOptions['application'] }) => Promise<{ fetch: (request: Request) => Promise<Response>; stop: () => void; socket?: (url: string, headers: Record<string, string>) => WebSocket; stream?: TwinStream }>;
171
+ export type ServedTwin = (root: string, options: { scenarioPath?: string; application?: import('@volter/world-core').PackFetchOptions['application'] }) => Promise<{ fetch: (request: Request) => Promise<Response>; stop: () => void | Promise<void>; socket?: (url: string, headers: Record<string, string>) => WebSocket; stream?: TwinStream }>;
172
172
 
173
173
  export async function walk(journey: Journey, root: string, collect: Failure[], opts: { beats: boolean; verbose?: boolean; hooks?: StepHooks; captured?: Captured; served?: ServedTwin }): Promise<Captured> {
174
174
  attributionName(journey.author);
@@ -769,7 +769,7 @@ export async function walk(journey: Journey, root: string, collect: Failure[], o
769
769
  await Promise.allSettled([...pending.values()].map((request) => request.answer));
770
770
  setDeliveryObserver(previousObserver);
771
771
  setInProcessTwins(previousTwins);
772
- served?.stop();
772
+ await served?.stop();
773
773
  if (sqlSession) await sqlSession.then((session) => session.end(), () => {});
774
774
  // a World's managed database ends with its walk (vendor-fetch.ts), so the next World declares it at the same port
775
775
  await (twin as { stopDatabase?: () => Promise<void> }).stopDatabase?.();