@volter/twin-tunnel 0.1.36 → 1.0.0

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.
Files changed (63) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +11 -121
  3. package/dist/generated/pack-facts.json +54 -0
  4. package/dist/spec/SOURCE.md +14 -0
  5. package/dist/spec/openapi.json +18 -0
  6. package/dist/src/cli.js +15 -53
  7. package/dist/src/fetch.d.ts +2 -0
  8. package/dist/src/fetch.js +19 -0
  9. package/dist/src/generated/surface.gen.json +1 -0
  10. package/dist/src/generated/ui.gen.json +1 -0
  11. package/dist/src/index.d.ts +5 -7
  12. package/dist/src/index.js +8 -42
  13. package/dist/src/manifest.d.ts +2 -0
  14. package/dist/src/manifest.js +42 -0
  15. package/dist/src/semantics/around.d.ts +2 -0
  16. package/dist/src/semantics/around.js +39 -0
  17. package/dist/src/semantics/doors.d.ts +3 -0
  18. package/dist/src/semantics/doors.js +18 -0
  19. package/dist/src/semantics/index.d.ts +2 -0
  20. package/dist/src/semantics/index.js +1 -0
  21. package/dist/src/semantics/shared.d.ts +17 -0
  22. package/dist/src/semantics/shared.js +15 -0
  23. package/dist/src/semantics/sockets.d.ts +2 -0
  24. package/dist/src/semantics/sockets.js +80 -0
  25. package/dist/src/semantics/states.d.ts +1 -0
  26. package/dist/src/semantics/states.js +3 -0
  27. package/dist/src/server.d.ts +7 -0
  28. package/dist/src/server.js +9 -0
  29. package/generated/pack-facts.json +54 -0
  30. package/package.json +16 -32
  31. package/spec/SOURCE.md +14 -0
  32. package/spec/openapi.json +18 -0
  33. package/src/cli.ts +15 -41
  34. package/src/fetch.ts +20 -0
  35. package/src/generated/surface.gen.json +1 -0
  36. package/src/generated/ui.gen.json +1 -0
  37. package/src/index.ts +9 -52
  38. package/src/manifest.ts +47 -0
  39. package/src/semantics/around.ts +41 -0
  40. package/src/semantics/doors.ts +21 -0
  41. package/src/semantics/index.ts +6 -0
  42. package/src/semantics/shared.ts +26 -0
  43. package/src/semantics/sockets.ts +69 -0
  44. package/src/semantics/states.ts +3 -0
  45. package/src/server.ts +10 -0
  46. package/dist/src/tunnel-budget.d.ts +0 -21
  47. package/dist/src/tunnel-budget.js +0 -26
  48. package/dist/src/tunnel-capabilities.d.ts +0 -4
  49. package/dist/src/tunnel-capabilities.js +0 -2091
  50. package/dist/src/tunnel-conformance.d.ts +0 -10
  51. package/dist/src/tunnel-conformance.js +0 -63
  52. package/dist/src/tunnel-connector.d.ts +0 -21
  53. package/dist/src/tunnel-connector.js +0 -68
  54. package/dist/src/tunnel-server.d.ts +0 -109
  55. package/dist/src/tunnel-server.js +0 -1197
  56. package/dist/src/tunnel-twin.d.ts +0 -175
  57. package/dist/src/tunnel-twin.js +0 -236
  58. package/src/tunnel-budget.ts +0 -34
  59. package/src/tunnel-capabilities.ts +0 -1770
  60. package/src/tunnel-conformance.ts +0 -62
  61. package/src/tunnel-connector.ts +0 -64
  62. package/src/tunnel-server.ts +0 -1194
  63. package/src/tunnel-twin.ts +0 -339
@@ -0,0 +1,42 @@
1
+ export const manifest = {
2
+ vendor: 'tunnel',
3
+ service: 'tunnel',
4
+ body: { json: 'always' },
5
+ ids: { template: '{letters:8}' },
6
+ time: 'iso',
7
+ error: '{message}',
8
+ readOnly: { status: 403, message: 'This twin was started read-only; writes are refused.' },
9
+ notFound: { status: 404, message: 'Not Found' },
10
+ gap: { status: 404, message: 'Not Found' },
11
+ list: { style: 'envelope', envelope: { data: '{data}' }, limit: { param: 'limit', default: 100, max: 100 } },
12
+ deleted: {},
13
+ resources: {
14
+ _account: { idPrefix: '' },
15
+ // a tunnel id an account holds (one minted when a client names none)
16
+ _tunnel: { idPrefix: '' },
17
+ },
18
+ sockets: [
19
+ { id: 'control', path: '/ws', note: "a client's control channel: register, then answer each request the relay sends", source: 'https://github.com/volter-ai/volter-tunnel/blob/main/packages/core/src/protocol.ts' },
20
+ ],
21
+ doors: [
22
+ { id: 'accounts', method: 'POST', path: '/_twin/accounts', note: '{ slug }: an account on the relay and its token (the World application\'s TUNNEL_SECRET)' },
23
+ ],
24
+ discovery: {
25
+ twinOf: "Volter's tunnel relay (volter-tunnel) as Volter Harness's remote access uses it: a client's control socket, and every request to its tunnel answered through it",
26
+ stores: 'accounts and their tokens (by their SHA-256), and the tunnel ids they hold',
27
+ identity: "the account's token in the control channel's register frame",
28
+ },
29
+ descriptor: {
30
+ protocol: '3',
31
+ transport: 'rest',
32
+ archetype: 'proxy',
33
+ bin: 'world-tunnel',
34
+ resources: [],
35
+ specSource: 'written from volter-ai/volter-tunnel (spec/SOURCE.md)',
36
+ description: "Tunnel relay twin — a client's control socket (register, then each public request sent down it and answered by the frames it sends back), as Volter Harness's remote access shares a local server.",
37
+ adoption: { sdks: ['@volter/tunnel'], envStems: ['TUNNEL', 'VOLTERTUNNEL'] },
38
+ hosts: [{ host: 'voltertest.xyz' }, { suffix: '.voltertest.xyz' }, { host: 'www.voltertest.xyz', exclude: true }, { host: 'twins.voltertest.xyz', exclude: true }],
39
+ credentialDoor: { path: '/_twin/accounts', body: { slug: 'world' }, fill: { TUNNEL_SECRET: 'token', TUNNEL_SERVER_URL: 'server', SUPERCODE_TUNNEL_SERVER_URL: 'server' } },
40
+ endpointEnvNone: 'the relay URL is the credential door\'s (TUNNEL_SERVER_URL, SUPERCODE_TUNNEL_SERVER_URL), the World claiming voltertest.xyz',
41
+ },
42
+ };
@@ -0,0 +1,2 @@
1
+ import type { HandlerContext } from '@volter/world-core';
2
+ export declare function around(ctx: HandlerContext, next: (request?: Request) => Promise<Response>): Promise<Response>;
@@ -0,0 +1,39 @@
1
+ import { b64encode, DOMAIN, holder, RESERVED } from "./shared.js";
2
+ const text = (status, body) => new Response(body, { status, headers: { 'content-type': 'text/plain; charset=utf-8' } });
3
+ const HOP = new Set(['host', 'connection', 'upgrade', 'keep-alive', 'transfer-encoding', 'content-length']);
4
+ /** The tunnel a request is for: its host's label, or `?__tunnel=` on a direct host (the relay's local testing). */
5
+ function tunnelOf(request) {
6
+ const url = new URL(request.url);
7
+ const host = (request.headers.get('host') ?? url.host).toLowerCase().replace(/:\d+$/, '');
8
+ const label = host.endsWith(`.${DOMAIN}`) ? host.slice(0, -DOMAIN.length - 1) : undefined;
9
+ if (label && !label.includes('.') && !RESERVED.has(label))
10
+ return label;
11
+ const direct = host === 'localhost' || host.startsWith('127.') || host === '0.0.0.0' || !host.endsWith(DOMAIN);
12
+ return direct ? url.searchParams.get('__tunnel') ?? undefined : undefined;
13
+ }
14
+ export async function around(ctx, next) {
15
+ const request = ctx.call.request;
16
+ const id = tunnelOf(request);
17
+ if (!id)
18
+ return next();
19
+ const client = holder(ctx, id);
20
+ if (!client)
21
+ return text(502, 'Tunnel not connected');
22
+ const url = new URL(request.url);
23
+ url.searchParams.delete('__tunnel');
24
+ const body = request.method === 'GET' || request.method === 'HEAD' ? null : new Uint8Array(await request.arrayBuffer());
25
+ // a body goes in one frame: over the relay's limit it is refused at once
26
+ if (body && body.length > 750_000)
27
+ return text(413, 'Request body too large to tunnel (limit ~750KB)');
28
+ const headers = Object.fromEntries([...request.headers].filter(([k]) => !HOP.has(k.toLowerCase()) && !k.toLowerCase().startsWith('x-volter')));
29
+ // the relay's correlation key for this request on this client's socket: the socket's own count, not World state (a
30
+ // request the relay forwards is no subject of the World)
31
+ const reqId = `${client.id}-${(client.state.requests = Number(client.state.requests ?? 0) + 1)}`;
32
+ client.send({ type: 'request', reqId, method: request.method, path: `${url.pathname}${url.search}`, headers, body: body?.length ? b64encode(body) : null });
33
+ try {
34
+ return (await client.expect(reqId, 30_000));
35
+ }
36
+ catch {
37
+ return text(504, 'Tunnel timeout');
38
+ }
39
+ }
@@ -0,0 +1,3 @@
1
+ import type { HandlerContext } from '@volter/world-core';
2
+ /** An account by its slug (answered again for the same slug) and its token, drawn from the World's secret. */
3
+ export declare function accounts(ctx: HandlerContext): Promise<Response>;
@@ -0,0 +1,18 @@
1
+ import { ACCOUNT, sha256 } from "./shared.js";
2
+ /** An account by its slug (answered again for the same slug) and its token, drawn from the World's secret. */
3
+ export async function accounts(ctx) {
4
+ const b = (ctx.body && typeof ctx.body === 'object' ? ctx.body : {});
5
+ const slug = String(b.slug ?? '');
6
+ // the relay's account slug (server-cf/src/credits.ts, SLUG_RE)
7
+ if (!/^[a-z0-9][a-z0-9-]{1,62}$/.test(slug))
8
+ return Response.json({ message: 'slug must be lowercase letters, digits and dashes' }, { status: 400 });
9
+ let account = ctx.rowsRaw(ACCOUNT).find((a) => a.deleted !== true && a.slug === slug);
10
+ // Where the documentation stops: a token's form is the relay's own; the twin's is `vt_` and 40 letters and digits
11
+ const tokenOf = async (id) => `vt_${(await ctx.secret(`tunnel-token:${id}`)).replace(/[^A-Za-z0-9]/g, '').slice(0, 40)}`;
12
+ if (!account) {
13
+ const id = ctx.mint(ACCOUNT);
14
+ await ctx.record(ACCOUNT, { slug, token_sha256: sha256(ctx, await tokenOf(id)) }, id);
15
+ account = ctx.row(ACCOUNT, id);
16
+ }
17
+ return Response.json({ slug, token: await tokenOf(String(account.id)), server: `https://voltertest.xyz` }, { status: 201 });
18
+ }
@@ -0,0 +1,2 @@
1
+ import type { Handler } from '@volter/world-core';
2
+ export declare const tunnelSemantics: Record<string, Handler>;
@@ -0,0 +1 @@
1
+ export const tunnelSemantics = {};
@@ -0,0 +1,17 @@
1
+ import type { HandlerContext, SocketSession } from '@volter/world-core';
2
+ export type Row = Record<string, unknown>;
3
+ export type { HandlerContext };
4
+ export declare const ACCOUNT = "_account";
5
+ export declare const TUNNEL = "_tunnel";
6
+ export declare const DOMAIN = "voltertest.xyz";
7
+ /** Labels of the domain that are not tunnels: the relay's own front (`www`, its RESERVED_HOSTS default) and the twins
8
+ * cloud Volter serves there. */
9
+ export declare const RESERVED: Set<string>;
10
+ export declare const sha256: (ctx: Pick<HandlerContext, "crypto">, v: string) => string;
11
+ export declare const tunnelUrl: (id: string) => string;
12
+ /** A tunnel id as the relay's registry takes one: lowercase letters, digits and inner dashes. */
13
+ export declare const validId: (id: string) => boolean;
14
+ /** The registered control session holding a tunnel, if a client holds it now. */
15
+ export declare const holder: (ctx: Pick<HandlerContext, "sockets">, id: string) => SocketSession | undefined;
16
+ export declare const b64encode: (bytes: Uint8Array) => string;
17
+ export declare const b64decode: (text: string) => Uint8Array;
@@ -0,0 +1,15 @@
1
+ export const ACCOUNT = '_account';
2
+ export const TUNNEL = '_tunnel';
3
+ // the relay's TUNNEL_DOMAIN (server-cf/wrangler.jsonc, spec/SOURCE.md)
4
+ export const DOMAIN = 'voltertest.xyz';
5
+ /** Labels of the domain that are not tunnels: the relay's own front (`www`, its RESERVED_HOSTS default) and the twins
6
+ * cloud Volter serves there. */
7
+ export const RESERVED = new Set(['www', 'twins']);
8
+ export const sha256 = (ctx, v) => ctx.crypto.digest('sha256', v);
9
+ export const tunnelUrl = (id) => `https://${id}.${DOMAIN}`;
10
+ /** A tunnel id as the relay's registry takes one: lowercase letters, digits and inner dashes. */
11
+ export const validId = (id) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(id);
12
+ /** The registered control session holding a tunnel, if a client holds it now. */
13
+ export const holder = (ctx, id) => ctx.sockets('control').find((s) => s.state.registered === true && s.state.tunnelId === id);
14
+ export const b64encode = (bytes) => btoa(Array.from(bytes, (b) => String.fromCharCode(b)).join(''));
15
+ export const b64decode = (text) => Uint8Array.from(atob(text), (c) => c.charCodeAt(0));
@@ -0,0 +1,2 @@
1
+ import type { HandlerContext, SocketEngine } from '@volter/world-core';
2
+ export declare const control: SocketEngine<HandlerContext>;
@@ -0,0 +1,80 @@
1
+ import { ACCOUNT, b64decode, holder, sha256, TUNNEL, tunnelUrl, validId } from "./shared.js";
2
+ const obj = (v) => (v && typeof v === 'object' && !Array.isArray(v) ? v : {});
3
+ const streams = (s) => (s.state.streams ??= new Map());
4
+ const headersOf = (v) => { const h = new Headers(); for (const [k, x] of Object.entries(obj(v)))
5
+ h.set(k, String(x)); return h; };
6
+ /** A rejected registration: the error frame, then the close (protocol.ts `ErrorMsg`; tunnel-do's rejection). */
7
+ function reject(s, message, code, reason) {
8
+ s.send({ type: 'error', message });
9
+ s.close(code, reason);
10
+ }
11
+ async function register(s, msg, ctx) {
12
+ const token = typeof msg.secret === 'string' ? msg.secret : '';
13
+ const account = token ? ctx.rowsRaw(ACCOUNT).find((a) => a.deleted !== true && a.token_sha256 === sha256(ctx, token)) : undefined;
14
+ if (!account)
15
+ return reject(s, 'Tunnel rejected: credential is invalid or revoked. On an owner-authenticated device, run "volter-tunnel tokens" and restore this host token, or sign in again on the host.', 4003, 'badToken');
16
+ // the control URL's ?id= names the tunnel (the routing relay's authority), else the register frame's, else one minted
17
+ // This id names a stored reservation: mint it, then record the reservation with its owner below. ctx.issue is for
18
+ // issued ids whose subjects the vendor does not store; its empty row would look like another account's reservation.
19
+ const id = String(s.state.tunnelId || msg.tunnelId || ctx.mint(TUNNEL));
20
+ if (!validId(id))
21
+ return reject(s, `Tunnel rejected: invalid tunnel id ${id}`, 4029, 'invalidId');
22
+ const held = ctx.row(TUNNEL, id);
23
+ // Where the documentation stops: a tunnel id another account holds is refused, as the relay's reservation refuses it
24
+ if (held && held._owner !== account.id)
25
+ return reject(s, `Tunnel rejected: ${id} is reserved by another account`, 4029, 'reserved');
26
+ const live = holder(ctx, id);
27
+ if (live && live !== s) {
28
+ // Where the documentation stops: a live registration of the id is refused unless this one replaces it
29
+ if (msg.replace !== true)
30
+ return reject(s, `Tunnel rejected: ${id} is already connected`, 4029, 'inUse');
31
+ live.state.registered = false;
32
+ live.close(4001, 'Replaced by new client');
33
+ }
34
+ if (!held)
35
+ await ctx.record(TUNNEL, { _owner: account.id, reservedAt: ctx.occurredAt }, id);
36
+ Object.assign(s.state, { registered: true, tunnelId: id, account: account.id, authRequired: msg.authRequired !== false });
37
+ s.send({ type: 'registered', tunnelId: id, url: tunnelUrl(id) });
38
+ }
39
+ export const control = {
40
+ open(s, ctx) {
41
+ s.state.tunnelId = new URL(ctx.call.request.url).searchParams.get('id') ?? '';
42
+ },
43
+ async message(s, data, ctx) {
44
+ let msg;
45
+ try {
46
+ msg = obj(JSON.parse(data));
47
+ }
48
+ catch {
49
+ return;
50
+ }
51
+ if (msg.type === 'register')
52
+ return register(s, msg, ctx);
53
+ if (s.state.registered !== true)
54
+ return;
55
+ const reqId = String(msg.reqId ?? '');
56
+ if (msg.type === 'response') {
57
+ s.fulfil(reqId, new Response(msg.body ? b64decode(String(msg.body)) : null, { status: Number(msg.status ?? 502), headers: headersOf(msg.headers) }));
58
+ }
59
+ else if (msg.type === 'response-start') {
60
+ const body = new ReadableStream({ start: (c) => { streams(s).set(reqId, c); } });
61
+ s.fulfil(reqId, new Response(body, { status: Number(msg.status ?? 502), headers: headersOf(msg.headers) }));
62
+ }
63
+ else if (msg.type === 'response-chunk') {
64
+ streams(s).get(reqId)?.enqueue(b64decode(String(msg.data ?? '')));
65
+ }
66
+ else if (msg.type === 'response-end') {
67
+ streams(s).get(reqId)?.close();
68
+ streams(s).delete(reqId);
69
+ }
70
+ },
71
+ close(s) {
72
+ for (const c of streams(s).values()) {
73
+ try {
74
+ c.error(new Error('Tunnel closed'));
75
+ }
76
+ catch { /* already ended */ }
77
+ }
78
+ streams(s).clear();
79
+ },
80
+ };
@@ -0,0 +1 @@
1
+ export declare const states: {};
@@ -0,0 +1,3 @@
1
+ // The relay's state rulings (architecture, "State"): a tunnel id is held by the account that registered it first; a
2
+ // session's registration is the connection's, never World state.
3
+ export const states = {};
@@ -0,0 +1,7 @@
1
+ import { type PackFetchOptions } from '@volter/world-core';
2
+ export declare function createTunnelTwinServer(options?: PackFetchOptions & {
3
+ port?: number;
4
+ }): Promise<{
5
+ port: number;
6
+ stop: () => void;
7
+ }>;
@@ -0,0 +1,9 @@
1
+ // FETCH-FIRST (runtime contract R12b): a fixed file, the kernel's serve seam around the pack's fetch (./fetch.ts).
2
+ import { serveHttp } from '@volter/world-core';
3
+ import { createTunnelFetch } from "./fetch.js";
4
+ export async function createTunnelTwinServer(options = {}) {
5
+ const fetch = createTunnelFetch(options);
6
+ // the vendor's sockets, when its manifest declares any (the kernel's sockets.ts)
7
+ const server = await serveHttp({ port: options.port ?? 0, idleTimeout: 60, fetch, ...(fetch.upgrade ? { upgrade: fetch.upgrade } : {}) });
8
+ return { port: server.port ?? options.port ?? 0, stop: () => server.stop(true) };
9
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "//": "GENERATED by scripts/pack-facts.ts — current pack descriptors and explicit world-runtime server backings; no prior-artifact entries are retained.",
3
+ "packs": {
4
+ "tunnel": {
5
+ "adoption": {
6
+ "sdks": [
7
+ "@volter/tunnel"
8
+ ],
9
+ "envStems": [
10
+ "TUNNEL",
11
+ "VOLTERTUNNEL"
12
+ ]
13
+ },
14
+ "hosts": [
15
+ {
16
+ "host": "voltertest.xyz"
17
+ },
18
+ {
19
+ "suffix": ".voltertest.xyz"
20
+ },
21
+ {
22
+ "host": "www.voltertest.xyz",
23
+ "exclude": true
24
+ },
25
+ {
26
+ "host": "twins.voltertest.xyz",
27
+ "exclude": true
28
+ }
29
+ ],
30
+ "archetype": "proxy",
31
+ "protocol": {
32
+ "declared": "3",
33
+ "major": 3,
34
+ "standing": "current"
35
+ },
36
+ "transport": "rest",
37
+ "resources": [],
38
+ "endpointEnvNone": "the relay URL is the credential door's (TUNNEL_SERVER_URL, SUPERCODE_TUNNEL_SERVER_URL), the World claiming voltertest.xyz",
39
+ "credentialDoor": {
40
+ "path": "/_twin/accounts",
41
+ "body": {
42
+ "slug": "world"
43
+ },
44
+ "fill": {
45
+ "TUNNEL_SECRET": "token",
46
+ "TUNNEL_SERVER_URL": "server",
47
+ "SUPERCODE_TUNNEL_SERVER_URL": "server"
48
+ }
49
+ },
50
+ "serveExport": "createTunnelTwinServer"
51
+ }
52
+ },
53
+ "undeclared": []
54
+ }
package/package.json CHANGED
@@ -1,20 +1,9 @@
1
1
  {
2
2
  "name": "@volter/twin-tunnel",
3
- "version": "0.1.36",
4
- "description": "Local Cloudflare Quick Tunnel control-plane and Volter relay twin with a genuine loopback HTTP data path.",
3
+ "version": "1.0.0",
4
+ "description": "The tunnel twin (Protocol 3).",
5
5
  "author": "Volter (https://github.com/volter-ai)",
6
6
  "license": "Apache-2.0",
7
- "files": [
8
- "src",
9
- "README.md",
10
- "!**/*.test.ts",
11
- "dist"
12
- ],
13
- "repository": {
14
- "type": "git",
15
- "url": "git+https://github.com/volter-ai/twin.git",
16
- "directory": "packages/twin/tunnel"
17
- },
18
7
  "type": "module",
19
8
  "exports": {
20
9
  ".": {
@@ -25,28 +14,23 @@
25
14
  "bin": {
26
15
  "world-tunnel": "dist/src/cli.js"
27
16
  },
28
- "scripts": {
29
- "test": "bun test src/*.test.ts",
30
- "typecheck": "tsc --noEmit",
31
- "build": "node ../../../scripts/publish/build.mjs",
32
- "prepack": "node ../../../scripts/publish/prepare-publish.mjs prepack",
33
- "postpack": "node ../../../scripts/publish/prepare-publish.mjs postpack"
34
- },
17
+ "files": [
18
+ "src",
19
+ "spec",
20
+ "README.md",
21
+ "!**/*.test.ts",
22
+ "dist",
23
+ "generated"
24
+ ],
35
25
  "peerDependencies": {
36
- "@volter/world-core": "2.0.36"
37
- },
38
- "devDependencies": {
39
- "@types/bun": "^1.2.20",
40
- "@types/node": "^24.0.0",
41
- "@types/ws": "^8.18.1",
42
- "@volter/tunnel": "^2.0.5",
43
- "@volter/tunnel-core": "0.1.3",
44
- "@volter/world-core": "2.0.36",
45
- "@volter/world-tooling": "0.1.0",
46
- "typescript": "^5.9.0"
26
+ "@volter/world-core": "^3.0.1"
47
27
  },
48
28
  "engines": {
49
29
  "node": ">=22.3"
50
30
  },
51
- "homepage": "https://world.volter.ai/twins/tunnel"
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/volter-ai/twin-packs-p3.git",
34
+ "directory": "tunnel"
35
+ }
52
36
  }
package/spec/SOURCE.md ADDED
@@ -0,0 +1,14 @@
1
+ # volter-tunnel relay spec — provenance
2
+
3
+ - **Why this document:** the relay publishes no OpenAPI document. Its contract is volter-ai/volter-tunnel at
4
+ `c14b6e3` (2026-09-28): `packages/core/src/protocol.ts` (the control channel's frames, "the single source of truth
5
+ for the messages exchanged over the control WebSocket between a client and the relay") and the Cloudflare relay,
6
+ `server-cf/src/worker.ts` (routing: the control channel at `/ws?id=<tunnelId>`; a tunnel at `<tunnelId>.<domain>`,
7
+ or `?__tunnel=<id>` on a direct host such as localhost; `/api/status` at the apex) and `server-cf/src/tunnel-do.ts`
8
+ (registration: `registered` with `url: https://<tunnelId>.<domain>`, a rejected credential `{type:"error"}` then
9
+ close 4003, a replaced client closed 4001 "Replaced by new client"; a public request: 502 "Tunnel not connected",
10
+ 504 "Tunnel timeout" after 30 s, 413 over 750000 bytes of body; the reply frames `response`, `response-start`,
11
+ `response-chunk`, `response-end`). The domain is `voltertest.xyz` (`server-cf/wrangler.jsonc`, `TUNNEL_DOMAIN`).
12
+ - **Corrections:** none.
13
+ - **Read by:** the twin orchestrator, 2026-09-30.
14
+ - **Vendored bytes (sha256):** `openapi.json` `b4fcdc3d599026bef687c946cea3c8837c63ff982e5d899df1425e585832aebd`.
@@ -0,0 +1,18 @@
1
+ {
2
+ "openapi": "3.0.2",
3
+ "info": {
4
+ "title": "volter-tunnel relay",
5
+ "version": "2",
6
+ "description": "The volter-tunnel relay's HTTP surface at its apex: its status. A client's control channel (a WebSocket at /ws?id=<tunnelId>) and the public requests it answers (every path of <tunnelId>.<domain>) are the relay's protocol, recorded in spec/SOURCE.md. Written from volter-ai/volter-tunnel (spec/SOURCE.md)."
7
+ },
8
+ "servers": [{ "url": "https://voltertest.xyz" }],
9
+ "paths": {
10
+ "/api/status": {
11
+ "get": {
12
+ "operationId": "getStatus",
13
+ "summary": "The relay's status.",
14
+ "responses": { "200": { "description": "The relay is up.", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "relay": { "type": "string", "example": "cloudflare-do" } } } } } } }
15
+ }
16
+ }
17
+ }
18
+ }
package/src/cli.ts CHANGED
@@ -1,48 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import { keepProcessAlive } from '@volter/world-core/lifecycle';
2
+ // world-tunnel: serve the tunnel twin.
3
3
  import { hasFlag, optionValue } from '@volter/world-core/args';
4
- import { createTunnelTwinServer, parseTunnelLoopbackOrigin } from './tunnel-server.ts';
4
+ import { keepProcessAlive } from '@volter/world-core/lifecycle';
5
+ import { createTunnelTwinServer } from './server.ts';
5
6
 
6
- const [command, ...args] = process.argv.slice(2);
7
- const rawPort = optionValue(args, '--port');
8
- const port = rawPort === undefined ? undefined : Number(rawPort);
9
- if (port !== undefined && (!Number.isInteger(port) || port < 0 || port > 65535)) {
10
- process.stderr.write(`world-tunnel: --port must be an integer 0-65535 (got ${JSON.stringify(rawPort)})\n`);
11
- process.exit(2);
12
- }
13
- const root = optionValue(args, '--root') || undefined;
7
+ const [cmd, ...rest] = process.argv.slice(2);
8
+ const port = Number(optionValue(rest, '--port', '0')) || undefined;
9
+ const root = optionValue(rest, '--root') || undefined;
10
+ const readOnly = hasFlag(rest, '--read-only');
11
+ // the World's managed Postgres, which the runtime binds a pack declaring managedDatabase to
12
+ const database = optionValue(rest, '--database') || undefined;
13
+ // the World's scenario file, for a model vendor's turns
14
+ const scenarioPath = optionValue(rest, '--scenario') || undefined;
14
15
 
15
- if (command === 'serve') {
16
- const server = createTunnelTwinServer({
17
- ...(port ? { port } : {}),
18
- ...(root ? { root } : {}),
19
- readOnly: hasFlag(args, '--read-only'),
20
- requireTid: hasFlag(args, '--require-tid'),
21
- });
22
- process.stdout.write(`tunnel twin listening on ${server.url}\n`);
23
- process.stdout.write(`Volter SDK host=${server.url}; Cloudflare quick service=${server.url}\n`);
24
- await keepProcessAlive();
25
- } else if (command === 'quick' || command === 'tunnel') {
26
- const origin = optionValue(args, '--url');
27
- if (!origin) { process.stderr.write('world-tunnel quick requires --url http://127.0.0.1:<port>\n'); process.exit(2); }
28
- try { parseTunnelLoopbackOrigin(origin); }
29
- catch (error) { process.stderr.write(`world-tunnel quick: ${error instanceof Error ? error.message : String(error)}\n`); process.exit(2); }
30
- const server = createTunnelTwinServer({ ...(port ? { port } : {}), ...(root ? { root } : {}) });
31
- const allocation = await fetch(`${server.url}/tunnel`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' });
32
- const body = await allocation.json() as { success: boolean; result?: { id: string } };
33
- if (!allocation.ok || !body.success || !body.result) throw new Error('quick-tunnel allocation failed');
34
- server.registerQuickOrigin(body.result.id, origin);
35
- // The world runtime discovers the first URL in stdout. This is deliberately a loopback URL:
36
- // the twin proves the control plane and genuine HTTP forwarding, not Cloudflare edge/DNS/TLS.
37
- process.stdout.write(`VOLTER_SHARE_URL=${server.url}\n`);
16
+ if (cmd === 'serve' || cmd === undefined) {
17
+ const s = await createTunnelTwinServer({ readOnly, ...(root ? { root } : {}), ...(port ? { port } : {}), ...(database ? { database } : {}), ...(scenarioPath ? { scenarioPath } : {}) });
18
+ process.stdout.write(`tunnel twin${readOnly ? ' [read-only]' : ''} at http://127.0.0.1:${s.port}\n`);
38
19
  await keepProcessAlive();
39
- } else if (command === 'conformance') {
40
- // Dev-only and lazy: the runtime entrypoint does not pull conformance into normal serve/share.
41
- const { checkTunnelConformance } = await import('./tunnel-conformance.ts');
42
- const report = await checkTunnelConformance({ ...(root ? { root } : {}) });
43
- process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
44
- if (!report.ok) process.exitCode = 1;
45
20
  } else {
46
- process.stdout.write('Usage: world-tunnel serve|conformance [--port N] [--root DIR] [--read-only] [--require-tid]\n');
47
- process.stdout.write(' world-tunnel quick --url http://127.0.0.1:<port> [--port N] [--root DIR]\n');
21
+ process.stdout.write('Usage: world-tunnel serve [--port N] [--root DIR] [--read-only] [--database URL] [--scenario FILE]\n');
48
22
  }
package/src/fetch.ts ADDED
@@ -0,0 +1,20 @@
1
+ // GENERATED by scripts/create-pack.ts (--init, --index): a fixed file. The kernel serves the discovery door, the clock,
2
+ // the World's doors, the vendor's screens and the API from the pack's parts (architecture, "The pack contract").
3
+ import { createPackFetch, type DerivedFetch, type DerivedSurface, type PackFetchOptions } from '@volter/world-core';
4
+ import surface from './generated/surface.gen.json' with { type: 'json' };
5
+ import { manifest } from './manifest.ts';
6
+ import { around } from './semantics/around.ts';
7
+ import * as sockets from './semantics/sockets.ts';
8
+ import * as doors from './semantics/doors.ts';
9
+ import { tunnelSemantics } from './semantics/index.ts';
10
+
11
+ export function createTunnelFetch(options: PackFetchOptions = {}): DerivedFetch {
12
+ return createPackFetch({
13
+ manifest,
14
+ surface: surface as DerivedSurface,
15
+ handlers: tunnelSemantics,
16
+ doors,
17
+ around,
18
+ sockets,
19
+ }, options);
20
+ }
@@ -0,0 +1 @@
1
+ {"generatedBy":"scripts/derive-pack.ts","spec":"openapi.json","format":"openapi","version":"2","resources":[],"operations":[{"id":"getStatus","method":"get","path":"/api/status","pathParams":[],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"computed"}]}
@@ -0,0 +1 @@
1
+ {"generatedBy":"scripts/derive-pack.ts","resources":{"_account":{"actions":[],"states":{},"embeds":{}},"_tunnel":{"actions":[],"states":{},"embeds":{}}}}
package/src/index.ts CHANGED
@@ -1,53 +1,10 @@
1
- export { createTunnelTwinFetch, createTunnelTwinHandler, createTunnelTwinServer } from './tunnel-server.ts';
2
- export { handleTunnelRelayMessage, handleTunnelTwinRequest } from './tunnel-twin.ts';
3
- export { liveTunnelExecute, mapTunnelRelay, pullTunnelRelay, syncTunnelFromReal } from './tunnel-connector.ts';
4
- export {
5
- TunnelBudget, TunnelBudgetError, TUNNEL_RATE_BUDGET, TUNNEL_BUDGET_CEILING,
6
- TUNNEL_BUDGET_WINDOW_MS, TUNNEL_CALL_WEIGHTS,
7
- } from './tunnel-budget.ts';
8
- export type { RelayControlMessage, RelaySession, TunnelRequest, TunnelResponse } from './tunnel-twin.ts';
1
+ // @volter/twin-tunnel — the tunnel twin (Protocol 3). A fixed file: the descriptor is the manifest's.
2
+ import { packOf, registerPack } from '@volter/world-core';
3
+ import { manifest } from './manifest.ts';
9
4
 
10
- // Registry descriptor (#1): the pack self-describes so tooling can discover it.
11
- // A consumer does `registerPack(pack)` after importing this package.
12
- import type { TwinPack } from '@volter/world-core';
13
- import { TUNNEL_RATE_BUDGET as RATE_BUDGET } from './tunnel-budget.ts';
14
- export const pack: TwinPack = {
15
- vendor: 'tunnel',
16
- // The SAME object tunnel-budget.ts declares at module load — one source of truth, so
17
- // registering the pack and importing the connector can never arm two different ceilings.
18
- rateBudget: RATE_BUDGET,
19
- // The CONTROL plane is HTTP/REST (POST /tunnel, GET /api/status). The relay's control link is a
20
- // WebSocket and its data path is real loopback HTTP — neither is a raw line protocol, so this is
21
- // not the `raw-tcp` class; the split is described in the README and spec-sources.json.
22
- transport: 'rest',
23
- archetype: 'proxy',
24
- bin: 'world-tunnel',
25
- // The subject types the twin stores: `quick_tunnel`/`relay_tunnel` written by tunnel-twin.ts,
26
- // and `relay` folded by the connector's status pull (mapTunnelRelay).
27
- resources: ['quick_tunnel', 'relay_tunnel', 'relay'],
28
- specSource:
29
- 'No single first-party machine-readable spec spans this combined pack. The Cloudflare Quick Tunnel ' +
30
- 'half is pinned to first-party cloudflared commit 733bfb939963e150dcf5c4faddb1603f744fbc98 (POST ' +
31
- '/tunnel and its {success,result,errors} envelope) plus Cloudflare\'s own Quick Tunnels docs; the ' +
32
- 'owned Volter relay half is pinned to volter-ai/volter-tunnel commit ' +
33
- 'a5abc6287384429d32cde200d8ecd042814e6619 (@volter/tunnel 2.0.5, @volter/tunnel-core\'s 15 control ' +
34
- 'frames). See spec-sources.json.',
35
- description:
36
- 'Cloudflare Quick Tunnel control-plane and owned Volter relay twin — POST /tunnel allocation and the ' +
37
- '@volter/tunnel WebSocket control protocol, with a genuine loopback HTTP data path (no Cloudflare ' +
38
- 'edge, DNS, TLS or SLA claimed).',
39
- // INTERCEPTION RULING — hostsNone, the pack's own home for it: no injector entry by design:
40
- // native cloudflared is an external binary, while @volter/tunnel opens a configurable
41
- // WebSocket control channel and the CLI reads TUNNEL_SERVER_URL. Worlds wire that app-read
42
- // endpoint directly; claiming api.trycloudflare.com in the Node injector would not redirect
43
- // cloudflared and would falsely imply the Cloudflare edge data plane was twinned.
44
- hostsNone:
45
- "no injector entry by design: native cloudflared is an external binary, while @volter/tunnel opens a configurable WebSocket control channel and the CLI reads TUNNEL_SERVER_URL. Worlds wire that app-read endpoint directly; claiming api.trycloudflare.com in the Node injector would not redirect cloudflared and would falsely imply the Cloudflare edge data plane was twinned",
46
- // Adoption, moved off the central maps unchanged (descriptor-first back-migration, adding-a-twin.md §3,
47
- // 2026-08-31).
48
- adoption: {
49
- // Internal Volter tunnel; the client is `@volter/tunnel` and there is no Python distribution.
50
- pypi: [],
51
- sdks: ['@volter/tunnel'], envStems: ['TUNNEL', 'VOLTERTUNNEL'],
52
- },
53
- };
5
+ export { createTunnelFetch } from './fetch.ts';
6
+ export { createTunnelTwinServer } from './server.ts';
7
+ export { manifest };
8
+
9
+ // The descriptor: twin-world's architecture, "The descriptor"; declared in manifest.ts.
10
+ export const pack = registerPack(packOf(manifest));