@volter/world-core 3.0.37 → 3.0.39
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.
- package/app-route.cjs +26 -25
- package/app-route.d.cts +1 -0
- package/dist/app-route.cjs +26 -25
- package/dist/app-route.d.cts +1 -0
- package/dist/generated/pack-facts.json +409 -352
- package/dist/inject.cjs +474 -95
- package/dist/network-policy.cjs +108 -13
- package/dist/network-policy.d.cts +8 -3
- package/dist/src/actions.js +2 -1
- package/dist/src/anthropic-wire.d.ts +2 -0
- package/dist/src/anthropic-wire.js +5 -0
- package/dist/src/attestation.d.ts +6 -0
- package/dist/src/attestation.js +27 -0
- package/dist/src/bytes.d.ts +3 -0
- package/dist/src/bytes.js +17 -0
- package/dist/src/changeset.js +2 -1
- package/dist/src/clickhouse/sql.js +73 -14
- package/dist/src/derived-core.d.ts +210 -32
- package/dist/src/derived-core.js +510 -244
- package/dist/src/derived-real.d.ts +1 -1
- package/dist/src/derived-real.js +704 -93
- package/dist/src/derived.d.ts +18 -2
- package/dist/src/derived.js +21 -2
- package/dist/src/events.d.ts +28 -45
- package/dist/src/events.js +40 -58
- package/dist/src/exact-json.d.ts +10 -0
- package/dist/src/exact-json.js +98 -0
- package/dist/src/executor.d.ts +17 -0
- package/dist/src/executor.js +91 -30
- package/dist/src/file-response.js +1 -1
- package/dist/src/form.d.ts +8 -0
- package/dist/src/form.js +76 -0
- package/dist/src/git/format.d.ts +16 -0
- package/dist/src/git/format.js +110 -0
- package/dist/src/git/index.d.ts +1 -0
- package/dist/src/git/index.js +1 -0
- package/dist/src/git/lfs.d.ts +1 -1
- package/dist/src/git/lfs.js +4 -3
- package/dist/src/graphql-wire.d.ts +6 -0
- package/dist/src/graphql-wire.js +6 -3
- package/dist/src/grpc-wire.js +2 -1
- package/dist/src/head.d.ts +1 -0
- package/dist/src/head.js +15 -4
- package/dist/src/held-reads.d.ts +6 -0
- package/dist/src/held-reads.js +60 -0
- package/dist/src/history.js +5 -4
- package/dist/src/host-port.d.ts +5 -0
- package/dist/src/host-port.js +14 -0
- package/dist/src/index.d.ts +11 -3
- package/dist/src/index.js +9 -4
- package/dist/src/log.js +12 -7
- package/dist/src/machines.d.ts +81 -5
- package/dist/src/machines.js +125 -9
- package/dist/src/managed-database.d.ts +15 -11
- package/dist/src/managed-database.js +38 -37
- package/dist/src/multipart.d.ts +21 -3
- package/dist/src/multipart.js +112 -38
- package/dist/src/observe.d.ts +3 -0
- package/dist/src/observe.js +6 -2
- package/dist/src/openai-wire.d.ts +16 -0
- package/dist/src/openai-wire.js +43 -12
- package/dist/src/pack-fetch.d.ts +4 -6
- package/dist/src/pack-fetch.js +189 -57
- package/dist/src/packRegistry.d.ts +7 -1
- package/dist/src/packRegistry.js +20 -3
- package/dist/src/private-transport.d.ts +1 -0
- package/dist/src/private-transport.js +11 -0
- package/dist/src/protobuf.js +10 -4
- package/dist/src/rateBudget.d.ts +8 -1
- package/dist/src/rateBudget.js +1 -0
- package/dist/src/redis/engine.d.ts +14 -5
- package/dist/src/redis/engine.js +271 -72
- package/dist/src/redis/frames.d.ts +17 -0
- package/dist/src/redis/frames.js +123 -0
- package/dist/src/redis/index.d.ts +6 -0
- package/dist/src/redis/index.js +6 -0
- package/dist/src/redis/json.d.ts +2 -0
- package/dist/src/redis/json.js +14 -0
- package/dist/src/redis/lua-libs.d.ts +4 -0
- package/dist/src/redis/lua-libs.js +173 -0
- package/dist/src/redis/lua.d.ts +1 -0
- package/dist/src/redis/lua.js +36 -0
- package/dist/src/redis/member-storage.d.ts +5 -0
- package/dist/src/redis/member-storage.js +75 -0
- package/dist/src/redis/resp2.d.ts +4 -0
- package/dist/src/redis/resp2.js +29 -0
- package/dist/src/redis/stream.d.ts +14 -0
- package/dist/src/redis/stream.js +335 -0
- package/dist/src/remote-execute.d.ts +2 -2
- package/dist/src/request-body.d.ts +11 -0
- package/dist/src/request-body.js +11 -0
- package/dist/src/runtime.d.ts +6 -5
- package/dist/src/runtime.js +5 -5
- package/dist/src/s3/wire.d.ts +4 -4
- package/dist/src/s3/wire.js +5 -3
- package/dist/src/scenario.d.ts +22 -0
- package/dist/src/scenario.js +35 -31
- package/dist/src/serve-http.d.ts +2 -1
- package/dist/src/serve-http.js +10 -1
- package/dist/src/serve.js +7 -4
- package/dist/src/signing.d.ts +23 -5
- package/dist/src/signing.js +74 -11
- package/dist/src/sigv4.d.ts +3 -1
- package/dist/src/sigv4.js +4 -2
- package/dist/src/smtp.js +2 -1
- package/dist/src/sockets.js +8 -3
- package/dist/src/state-system.d.ts +3 -0
- package/dist/src/storage.js +5 -2
- package/dist/src/twin-fetch.d.ts +3 -1
- package/dist/src/twin-fetch.js +3 -2
- package/dist/src/vendor-call.d.ts +10 -3
- package/dist/src/vendor-call.js +58 -14
- package/dist/test-fixtures/attestation.SOURCE.md +1 -0
- package/dist/test-fixtures/attestation.json +50 -0
- package/generated/pack-facts.json +409 -352
- package/inject.cjs +474 -95
- package/network-policy.cjs +108 -13
- package/network-policy.d.cts +8 -3
- package/package.json +11 -4
- package/src/actions.ts +2 -1
- package/src/anthropic-wire.ts +6 -0
- package/src/attestation.ts +23 -0
- package/src/bytes.ts +21 -0
- package/src/changeset.ts +2 -1
- package/src/clickhouse/sql.ts +50 -16
- package/src/derived-core.ts +573 -249
- package/src/derived-real.ts +552 -42
- package/src/derived.ts +23 -4
- package/src/events.ts +54 -85
- package/src/exact-json.ts +71 -0
- package/src/executor.ts +86 -27
- package/src/file-response.ts +1 -1
- package/src/form.ts +66 -0
- package/src/git/format.ts +89 -0
- package/src/git/index.ts +1 -0
- package/src/git/lfs.ts +5 -4
- package/src/graphql-wire.ts +8 -3
- package/src/grpc-wire.ts +2 -1
- package/src/head.ts +15 -5
- package/src/held-reads.ts +54 -0
- package/src/history.ts +5 -4
- package/src/host-port.ts +9 -0
- package/src/index.ts +10 -4
- package/src/log.ts +11 -7
- package/src/machines.ts +135 -13
- package/src/managed-database.ts +48 -40
- package/src/multipart.ts +110 -35
- package/src/observe.ts +6 -3
- package/src/openai-wire.ts +45 -14
- package/src/pack-fetch.ts +173 -50
- package/src/packRegistry.ts +27 -4
- package/src/private-transport.ts +8 -0
- package/src/protobuf.ts +8 -4
- package/src/rateBudget.ts +6 -1
- package/src/redis/engine.ts +168 -63
- package/src/redis/frames.ts +53 -0
- package/src/redis/index.ts +6 -0
- package/src/redis/json.ts +13 -0
- package/src/redis/lua-libs.ts +62 -0
- package/src/redis/lua.ts +20 -0
- package/src/redis/member-storage.ts +58 -0
- package/src/redis/resp2.ts +25 -0
- package/src/redis/stream.ts +102 -0
- package/src/remote-execute.ts +2 -2
- package/src/request-body.ts +12 -0
- package/src/runtime.ts +6 -5
- package/src/s3/wire.ts +8 -5
- package/src/scenario.ts +34 -13
- package/src/serve-http.ts +11 -2
- package/src/serve.ts +4 -3
- package/src/signing.ts +71 -17
- package/src/sigv4.ts +5 -3
- package/src/smtp.ts +3 -2
- package/src/sockets.ts +8 -3
- package/src/state-system.ts +3 -0
- package/src/storage.ts +5 -2
- package/src/twin-fetch.ts +4 -3
- package/src/vendor-call.ts +59 -12
- package/test-fixtures/attestation.json +50 -0
package/dist/src/pack-fetch.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { parseExactJson } from "./exact-json.js";
|
|
2
|
+
import { readHostPort } from "./host-port.js";
|
|
1
3
|
// A Protocol 3 pack's whole HTTP surface, served by the kernel from the pack's declarations (docs/contributing/
|
|
2
4
|
// architecture.md, "The pack contract: the kernel's root"): the discovery door (`GET /twin`, the manifest's
|
|
3
5
|
// `discovery`), the vendor's own moves as time passes (`semantics/clock.ts`), the World's doors (the manifest's `doors`,
|
|
@@ -10,7 +12,7 @@ import { placedCookies, TWIN_PREFIX_HEADER } from "./twin-fetch.js";
|
|
|
10
12
|
import { PREFIX_PARAMS_HEADER } from "./derived.js";
|
|
11
13
|
import { withHeaders } from "./with-headers.js";
|
|
12
14
|
import { authRefusal, bindSemantics, contextFor, LANE_HEADER, SCENARIO_DECISION_HEADER, coreFor, crossCutting, derivedRequestScopes, vendorError } from "./derived-core.js";
|
|
13
|
-
import { compileSurface, createDerivedFetch, matchOperation } from "./derived.js";
|
|
15
|
+
import { compileSurface, createDerivedFetch, matchOperation, pathParams } from "./derived.js";
|
|
14
16
|
import { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, runAsVendorMove } from "./request-scope.js";
|
|
15
17
|
import { parseScenarioDocument, ScenarioEngine, ScenarioError, statefulTwinManifest, twinManifest } from "./scenario.js";
|
|
16
18
|
import { getActiveWorldStore } from "./world-store.js";
|
|
@@ -21,10 +23,12 @@ import { contentAddressOf } from "./content-route.js";
|
|
|
21
23
|
import { getPack } from "./packRegistry.js";
|
|
22
24
|
import { hostRules } from '../host-routing.cjs';
|
|
23
25
|
import { socketUpgrade } from "./sockets.js";
|
|
24
|
-
import { MACHINE_POOL, POOL_KINDS } from "./machines.js";
|
|
26
|
+
import { MACHINE_POOL, POOL_KINDS, resetFakePool } from "./machines.js";
|
|
25
27
|
import { applyTwinWrite } from "./serve.js";
|
|
26
28
|
import { graphqlEndpoint } from "./graphql-wire.js";
|
|
27
|
-
import {
|
|
29
|
+
import { gzipSync } from 'node:zlib';
|
|
30
|
+
import { decompress } from "./bytes.js";
|
|
31
|
+
import { decodedBodyLimit, oversizedBody, RequestBodyTooLargeError } from "./request-body.js";
|
|
28
32
|
/** A declared path (`/{account}/r2/api-tokens`, `/<room>`, a door's `/_twin/users/{email}`) as a matcher of a request's
|
|
29
33
|
* path: the path itself, or `prefix` a path under it too (a screen answers its page and the forms it posts). */
|
|
30
34
|
function pathMatcher(pattern, prefix) {
|
|
@@ -44,6 +48,9 @@ function pathMatcher(pattern, prefix) {
|
|
|
44
48
|
}
|
|
45
49
|
/** A declared host (`dash.cloudflare.com`, `<domain>.daily.co`) as a matcher of a request's host. */
|
|
46
50
|
function hostMatcher(pattern) {
|
|
51
|
+
const whole = /^(?:\{([^}]+)\}|<([^>]+)>)$/.exec(pattern);
|
|
52
|
+
if (whole)
|
|
53
|
+
return (host) => host ? { params: { [whole[1] ?? whole[2]]: host } } : undefined;
|
|
47
54
|
const names = [];
|
|
48
55
|
const re = new RegExp(`^${pattern.split('.').map((label) => {
|
|
49
56
|
const m = /^(?:\{([^}]+)\}|<([^>]+)>)$/.exec(label);
|
|
@@ -58,7 +65,7 @@ function hostMatcher(pattern) {
|
|
|
58
65
|
};
|
|
59
66
|
}
|
|
60
67
|
/** The host a request is for: the vendor host the World's injector names, else its own. */
|
|
61
|
-
const hostOf = (request) => (request.headers.get('x-volter-twin-original-host') ?? request.headers.get('host') ?? new URL(request.url).host)
|
|
68
|
+
const hostOf = (request) => readHostPort(request.headers.get('x-volter-twin-original-host') ?? request.headers.get('host') ?? new URL(request.url).host)?.host ?? '';
|
|
62
69
|
/** The answer for a path no operation, door or screen serves, and for an operation nothing models: the manifest's
|
|
63
70
|
* `gap`, its `{method}` and `{path}` filled, else the manifest's not-found refusal. */
|
|
64
71
|
function gapAnswer(m, request, otherMethod = false) {
|
|
@@ -68,31 +75,34 @@ function gapAnswer(m, request, otherMethod = false) {
|
|
|
68
75
|
return vendorError(m, { ...gap, message: gap.message.replace(/\{method\}/g, request.method).replace(/\{path\}/g, path) });
|
|
69
76
|
}
|
|
70
77
|
const METHODS = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
71
|
-
const DECODE = { gzip: gunzipSync, zstd: zstdDecompressSync, deflate: inflateSync, br: brotliDecompressSync };
|
|
72
78
|
/** A request with its body decoded as its Content-Encoding says, when the manifest's `encodings` reads that coding;
|
|
73
79
|
* undefined when the bytes are not what the header names. */
|
|
74
80
|
async function decoded(m, request) {
|
|
81
|
+
const codings = (request.headers.get('content-encoding') ?? '').split(',').map(c => c.trim().toLowerCase()).filter(Boolean);
|
|
82
|
+
if (codings.length > 1)
|
|
83
|
+
return new Response('Unsupported Content-Encoding', { status: 415 });
|
|
75
84
|
if (request.method === 'GET' || request.method === 'HEAD' || request.body === null)
|
|
76
85
|
return request;
|
|
77
86
|
// the body read once, here, and every part of the pack given a request made again from its bytes: a served request's
|
|
78
87
|
// body read through a clone and then read again (an `around` that checks a body before its handler does) tees a stream
|
|
79
88
|
// whose second branch a Bun server never finishes, and the request hung
|
|
80
89
|
const raw = new Uint8Array(await request.arrayBuffer());
|
|
81
|
-
const coding = (
|
|
90
|
+
const coding = (codings[0] ?? '');
|
|
82
91
|
if (!m.encodings?.request?.includes(coding))
|
|
83
92
|
return new Request(request.url, { method: request.method, headers: request.headers, body: raw });
|
|
84
93
|
let body;
|
|
85
94
|
try {
|
|
86
|
-
body =
|
|
95
|
+
body = await decompress(raw, coding, decodedBodyLimit(m.descriptor?.maxRequestBodySize));
|
|
87
96
|
}
|
|
88
|
-
catch {
|
|
89
|
-
return undefined;
|
|
97
|
+
catch (error) {
|
|
98
|
+
return error instanceof RequestBodyTooLargeError ? oversizedBody() : undefined;
|
|
90
99
|
}
|
|
91
100
|
const headers = new Headers(request.headers);
|
|
92
101
|
headers.delete('content-encoding');
|
|
93
102
|
headers.delete('content-length');
|
|
94
|
-
//
|
|
95
|
-
|
|
103
|
+
// A finite single-chunk stream holds the decoder's buffer; BufferSource extraction would copy the entire body.
|
|
104
|
+
const buffered = new ReadableStream({ start(controller) { controller.enqueue(body); controller.close(); } });
|
|
105
|
+
return new Request(request.url, { method: request.method, headers, body: buffered, duplex: 'half' });
|
|
96
106
|
}
|
|
97
107
|
/** An answer in the coding the request accepts, when the manifest's `encodings` answers in it. */
|
|
98
108
|
async function encoded(m, request, res) {
|
|
@@ -108,6 +118,8 @@ async function encoded(m, request, res) {
|
|
|
108
118
|
return new Response(gzipSync(new Uint8Array(await res.arrayBuffer())), { status: res.status, statusText: res.statusText, headers });
|
|
109
119
|
}
|
|
110
120
|
/** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
|
|
121
|
+
/** A door's path as it is served and listed: under `/_twin/`, whether the manifest writes the prefix or not. */
|
|
122
|
+
const doorPath = (path) => (path.startsWith('/_twin/') ? path : `/_twin${path.startsWith('/') ? '' : '/'}${path}`);
|
|
111
123
|
export function createPackFetch(parts, options = {}) {
|
|
112
124
|
const m = parts.manifest;
|
|
113
125
|
const scope = {
|
|
@@ -133,7 +145,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
133
145
|
if (parts.gap) {
|
|
134
146
|
const url = new URL(request.url);
|
|
135
147
|
const matched = operation ? matchOperation(routes, request.method, url.pathname, url.searchParams, request.headers) : undefined;
|
|
136
|
-
return parts.gap(await contextFor(m, { request, operation: operation ?? { id: 'unmatched', method: request.method, path: url.pathname, class: request.method === 'GET' ? 'retrieve' : 'action' }, params: matched?.params ??
|
|
148
|
+
return parts.gap(await contextFor(m, { request, operation: operation ?? { id: 'unmatched', method: request.method, path: url.pathname, class: request.method === 'GET' ? 'retrieve' : 'action' }, params: matched?.params ?? pathParams(routes, url.pathname) }, scope));
|
|
137
149
|
}
|
|
138
150
|
const url = new URL(request.url);
|
|
139
151
|
const other = !matchOperation(routes, request.method, url.pathname, url.searchParams, request.headers)
|
|
@@ -165,7 +177,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
165
177
|
if (raw === null)
|
|
166
178
|
throw new ScenarioError(`${m.vendor} scenario: no file at ${options.scenarioPath}`);
|
|
167
179
|
if (!engine || raw !== engineRaw) {
|
|
168
|
-
engine = new ScenarioEngine(parts.scenario.adapter, parseScenarioDocument(
|
|
180
|
+
engine = new ScenarioEngine(parts.scenario.adapter, parseScenarioDocument(parseExactJson(raw), parts.scenario.adapter));
|
|
169
181
|
engineRaw = raw;
|
|
170
182
|
}
|
|
171
183
|
return engine;
|
|
@@ -174,11 +186,13 @@ export function createPackFetch(parts, options = {}) {
|
|
|
174
186
|
const decided = async (arrived) => {
|
|
175
187
|
if (!parts.scenario)
|
|
176
188
|
return api(arrived);
|
|
177
|
-
// the body is read once and the request made again from its bytes
|
|
178
|
-
//
|
|
179
|
-
//
|
|
189
|
+
// the body is read once and the request made again from its bytes, so the turn's context and the API each read their
|
|
190
|
+
// own. Merge served with --scenario once answered no turn in 8 s (t_c0279b69), and this change came with the cure;
|
|
191
|
+
// putting the earlier read back (the served request cloned for the context, then forwarded) does not reproduce the
|
|
192
|
+
// hang on its own (2026-10-01: merge's CLI with --scenario, and its life walked over HTTP: every turn answered), so
|
|
193
|
+
// what hung is UNEXPLAINED, and this is no proof of its cause
|
|
180
194
|
const bytes = arrived.method === 'GET' || arrived.method === 'HEAD' ? undefined : await arrived.arrayBuffer();
|
|
181
|
-
const request = new Request(arrived.url, { method: arrived.method, headers: arrived.headers, ...(bytes ? { body: bytes } : {}) });
|
|
195
|
+
const request = new Request(arrived.url, { method: arrived.method, headers: arrived.headers, signal: arrived.signal, ...(bytes ? { body: bytes } : {}) });
|
|
182
196
|
const url = new URL(request.url);
|
|
183
197
|
const matched = matchOperation(routes, request.method, url.pathname, url.searchParams, request.headers);
|
|
184
198
|
if (!matched || !parts.scenario.operations.includes(matched.operation.id))
|
|
@@ -212,7 +226,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
212
226
|
const answer = parts.doors?.[d.id];
|
|
213
227
|
if (!answer)
|
|
214
228
|
throw new Error(`${m.vendor}: the manifest declares the door ${d.id}, and semantics/doors.ts exports no ${d.id}`);
|
|
215
|
-
return { decl: d, path: pathMatcher(
|
|
229
|
+
return { decl: d, path: pathMatcher(doorPath(d.path), false), answer };
|
|
216
230
|
});
|
|
217
231
|
const screens = (m.screens ?? []).filter((s) => s.status === 'done' && parts.screens?.[s.id]).map((s) => {
|
|
218
232
|
const hosts = [s.host, ...(s.hosts ?? [])].map(hostMatcher);
|
|
@@ -240,23 +254,52 @@ export function createPackFetch(parts, options = {}) {
|
|
|
240
254
|
};
|
|
241
255
|
// a context the kernel opens for what no surface operation names: a door, a screen, the clock, the board
|
|
242
256
|
const open = (request, id, method, path, params) => contextFor(m, { request, operation: { id, method, path, class: method === 'GET' ? 'retrieve' : 'action' }, params }, scope);
|
|
257
|
+
const advanceClock = async (request) => {
|
|
258
|
+
if (parts.clock)
|
|
259
|
+
await runAsVendorMove(async () => parts.clock(await open(new Request(request.url, m.tenant ? { headers: request.headers } : {}), 'clock', 'POST', '/_twin/clock', {})));
|
|
260
|
+
};
|
|
243
261
|
const refusedReadOnly = () => vendorError(m, m.readOnly);
|
|
244
262
|
const prefix = m.pathPrefix ? new RegExp(`^(?:${m.pathPrefix})(?=/)`) : undefined;
|
|
263
|
+
const base = surface.basePath ?? '';
|
|
264
|
+
const baseAlias = m.basePathAlias && base ? new RegExp(`^(?:${m.basePathAlias})(?=/)`) : undefined;
|
|
245
265
|
const hostPath = m.pathFromHost ? new RegExp(m.pathFromHost, 'i') : undefined;
|
|
246
266
|
const hostParams = m.hostParams ? new RegExp(m.hostParams, 'i') : undefined;
|
|
247
|
-
|
|
267
|
+
// each operation's spanning parameters, keyed by the operation and the parameter as the route compiles them (a whole
|
|
268
|
+
// segment's label whose name the surface or the manifest declares spanning), so one operation's spanning name never
|
|
269
|
+
// keeps another's slashes, nor a label that only part of a segment holds
|
|
270
|
+
const spanningNames = new Set(surface.spanning ?? []);
|
|
271
|
+
const spanningOf = new Set(surface.operations.flatMap((op) => ((op.basePath ?? base) + op.path).split('/').flatMap((seg) => {
|
|
272
|
+
const label = /^\{([^}]+)\}$/.exec(seg)?.[1];
|
|
273
|
+
return label !== undefined && spanningNames.has(label) ? [`${op.id}\u0000${label}`] : [];
|
|
274
|
+
})));
|
|
275
|
+
/** A path as the vendor routes it: the manifest's path prefix taken off, the spec's base in an alias's place, a host's
|
|
248
276
|
* path label (pathFromHost) put in front. */
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
let path = url.pathname.replace(/\/{2,}/g, '/');
|
|
252
|
-
// a prefix's named groups (Jira's `/ex/jira/(?<cloudId>[^/]+)`) and the host's (E2B's envd at
|
|
253
|
-
// `49983-(?<sandboxID>…).e2b.app`) join the operation's parameters
|
|
254
|
-
const groups = { ...(prefix?.exec(path)?.groups ?? {}), ...(hostParams?.exec(hostOf(request))?.groups ?? {}) };
|
|
277
|
+
const rewritten = (pathname, host) => {
|
|
278
|
+
let path = pathname;
|
|
255
279
|
if (prefix)
|
|
256
280
|
path = path.replace(prefix, '') || '/';
|
|
257
|
-
|
|
281
|
+
// a base the vendor serves as its own: the spec's base in its place (a path already under the spec's base stays)
|
|
282
|
+
if (baseAlias && !(path === base || path.startsWith(`${base}/`)))
|
|
283
|
+
path = path.replace(baseAlias, base);
|
|
284
|
+
const label = hostPath?.exec(host)?.[1];
|
|
258
285
|
if (label)
|
|
259
286
|
path = `/${label}${path === '/' ? '' : path}`;
|
|
287
|
+
return path;
|
|
288
|
+
};
|
|
289
|
+
/** The request as its vendor routes it: duplicate slashes collapsed, unless the rewritten path matches an operation
|
|
290
|
+
* through one of that operation's spanning parameters (the surface's and the manifest's), whose value keeps its
|
|
291
|
+
* slashes; then rewritten. */
|
|
292
|
+
const routed = (request) => {
|
|
293
|
+
const url = new URL(request.url);
|
|
294
|
+
const host = hostOf(request);
|
|
295
|
+
const kept = rewritten(url.pathname, host);
|
|
296
|
+
const matched = matchOperation(routes, request.method, kept);
|
|
297
|
+
const spanning = matched !== undefined && Object.keys(matched.params).some((name) => spanningOf.has(`${matched.operation.id}\u0000${name}`));
|
|
298
|
+
const source = spanning ? url.pathname : url.pathname.replace(/\/{2,}/g, '/');
|
|
299
|
+
// a prefix's named groups (Jira's `/ex/jira/(?<cloudId>[^/]+)`) and the host's (E2B's envd at
|
|
300
|
+
// `49983-(?<sandboxID>…).e2b.app`) join the operation's parameters
|
|
301
|
+
const groups = { ...(prefix?.exec(source)?.groups ?? {}), ...(hostParams?.exec(host)?.groups ?? {}), ...Object.fromEntries(Object.entries(m.headerParams ?? {}).flatMap(([param, header]) => { const value = request.headers.get(header); return value === null ? [] : [[param, value]]; })) };
|
|
302
|
+
const path = spanning ? kept : rewritten(source, host);
|
|
260
303
|
if (path === url.pathname && !Object.keys(groups).length)
|
|
261
304
|
return request;
|
|
262
305
|
// the path the client sent, which a signature over the request (SigV4's canonical URI) covers
|
|
@@ -271,6 +314,8 @@ export function createPackFetch(parts, options = {}) {
|
|
|
271
314
|
};
|
|
272
315
|
const rootServes = (method, path) => Boolean(matchOperation(routes, method, path));
|
|
273
316
|
const lane = m.lanes ? laneRouter(m.vendor, m.lanes, parts.lanes ?? {}, rootServes) : undefined;
|
|
317
|
+
// Inspect lane-owned World doors directly, without routing them back through the vendor gateway.
|
|
318
|
+
const laneDoor = (request, path) => laneDoors(parts.lanes ?? {}, request, path);
|
|
274
319
|
// at a World's place, a page's asset or upload on another of the pack's content hosts is `<base>/@<host>/<path>`
|
|
275
320
|
// (architecture, "A page at a World's place"): answered by the built content screen that names that host exactly,
|
|
276
321
|
// routed from it, and by nothing else; a request naming no such host is general routing's
|
|
@@ -341,7 +386,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
341
386
|
if (!address || !contentHosts.includes(address.host))
|
|
342
387
|
return undefined;
|
|
343
388
|
const readable = await prepared(arrived);
|
|
344
|
-
return readable ? atContentHost(readable) : undecodable(arrived);
|
|
389
|
+
return readable instanceof Response ? readable : readable ? atContentHost(readable) : undecodable(arrived);
|
|
345
390
|
};
|
|
346
391
|
const fetch = async (arrived) => {
|
|
347
392
|
// the discovery door is the pack's own, whichever lane would take its path
|
|
@@ -362,10 +407,14 @@ export function createPackFetch(parts, options = {}) {
|
|
|
362
407
|
const own = await answerContent(arrived);
|
|
363
408
|
if (own)
|
|
364
409
|
return own;
|
|
365
|
-
|
|
410
|
+
// Root World doors belong to the pack at every routed host, including a lane's host.
|
|
411
|
+
const rootDoor = doors.some((d) => d.decl.method.toUpperCase() === arrived.method && d.path(asked));
|
|
412
|
+
const toLane = discovery || rootDoor ? undefined : lane?.(arrived);
|
|
366
413
|
if (toLane)
|
|
367
414
|
return toLane(arrived);
|
|
368
415
|
const readable = await prepared(arrived);
|
|
416
|
+
if (readable instanceof Response)
|
|
417
|
+
return readable;
|
|
369
418
|
if (!readable)
|
|
370
419
|
return undecodable(arrived);
|
|
371
420
|
const incoming = routed(readable);
|
|
@@ -376,40 +425,48 @@ export function createPackFetch(parts, options = {}) {
|
|
|
376
425
|
const readOnly = (options.readOnly ?? false) || isReadOnlyRequest(request);
|
|
377
426
|
if (request.method === 'GET' && path === '/twin' && m.discovery) {
|
|
378
427
|
const d = m.discovery;
|
|
428
|
+
const discoveryDoors = [...(m.doors ?? []), ...(m.machines ? [{ id: 'machine-pool', method: 'POST', path: '/_twin/machine-pool', note: 'Enroll the World execution pool: { kind: light | docker | none | fake, images?: { image: { context, command?, runtime? } } }.' }] : [])];
|
|
379
429
|
// the workspaces a person can be shown (Viewing a World): each built workspace screen's id and path
|
|
380
|
-
const
|
|
430
|
+
const laneDiscovery = await laneDoor(request, '/twin');
|
|
431
|
+
const shown = { ...(workspaces().length ? { screens: workspaces() } : {}), ...(Object.keys(laneDiscovery).length ? { lanes: laneDiscovery } : {}) };
|
|
432
|
+
const identity = d.identity ?? (m.auth?.held ? 'Authenticate as the vendor does, using a credential issued in this World; unknown credentials are refused.' : undefined);
|
|
381
433
|
// a model vendor's: its state, and what its scenario scripts, with the scenario's live counts
|
|
382
434
|
if (parts.scenario && d.behavior) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
435
|
+
const body = twinManifest({
|
|
436
|
+
vendor: m.vendor, twinOf: d.twinOf,
|
|
437
|
+
stateSentence: `Stateful: it stores ${d.stores}. Create state through the vendor's OWN API with the real SDK or plain fetch pointed here${discoveryDoors.length ? "; what the vendor does on its own pages (not its API) is a door of the World, listed under doors" : ''}.`,
|
|
438
|
+
behaviorSentence: d.behavior, exampleHandler: d.exampleHandler ?? null, ...(engineOf() ? { engine: engineOf() } : {}),
|
|
439
|
+
});
|
|
440
|
+
const declared = Object.fromEntries(discoveryDoors.map((door) => [`${door.method} ${doorPath(door.path)}`, door.note ?? 'a door of the World']));
|
|
441
|
+
return Response.json({ ...shown, ...body, program: { ...body.program, ...(identity ? { identity } : {}) }, doors: { ...body.doors, ...declared } });
|
|
388
442
|
}
|
|
389
|
-
return Response.json({ ...statefulTwinManifest({ vendor: m.vendor, twinOf: d.twinOf, stores: d.stores, ...(
|
|
443
|
+
return Response.json({ ...statefulTwinManifest({ vendor: m.vendor, twinOf: d.twinOf, stores: d.stores, ...(identity ? { identity } : {}), ...(d.behavior ? { behavior: d.behavior } : {}), ...(d.notes ? { notes: d.notes } : {}), ...(discoveryDoors.length ? { doors: discoveryDoors.map((door) => ({ ...door, path: doorPath(door.path) })) } : {}) }), ...shown });
|
|
390
444
|
}
|
|
391
445
|
// the scenario's handlers with their match counts, and the recent misses
|
|
392
|
-
if (request.method === 'GET' && path === '/twin/scenario'
|
|
393
|
-
|
|
446
|
+
if (request.method === 'GET' && path === '/twin/scenario') {
|
|
447
|
+
const lanes = await laneDoor(request, '/twin/scenario');
|
|
448
|
+
if (parts.scenario || Object.keys(lanes).length)
|
|
449
|
+
return Response.json({ ...(engineOf()?.status() ?? { vendor: m.vendor, handlers: [], misses: 0, recentMisses: [] }), ...(Object.keys(lanes).length ? { lanes } : {}) });
|
|
450
|
+
}
|
|
394
451
|
// the vendor's own moves, not the caller's: its context carries none of the request's headers (a Stripe-Account
|
|
395
452
|
// header would make a payout time makes the caller's), but a tenant's, when the vendor keeps tenants apart
|
|
396
|
-
|
|
397
|
-
await runAsVendorMove(async () => parts.clock(await open(new Request(request.url, m.tenant ? { headers: request.headers } : {}), 'clock', 'POST', '/_twin/clock', {})));
|
|
453
|
+
await advanceClock(request);
|
|
398
454
|
if (path.startsWith('/_twin/')) {
|
|
399
455
|
// the World's machine pool, enrolled by the kernel for a vendor that runs images (machines.ts)
|
|
400
456
|
if (m.machines && path === '/_twin/machine-pool' && request.method === 'POST') {
|
|
401
457
|
if (readOnly)
|
|
402
458
|
return refusedReadOnly();
|
|
403
|
-
let
|
|
459
|
+
let enrollment = {};
|
|
404
460
|
try {
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
catch {
|
|
408
|
-
kind = undefined;
|
|
461
|
+
enrollment = parseExactJson(await request.clone().text());
|
|
409
462
|
}
|
|
463
|
+
catch { }
|
|
464
|
+
const kind = enrollment.kind;
|
|
410
465
|
if (!POOL_KINDS.includes(kind))
|
|
411
466
|
return Response.json({ message: `kind must be one of ${POOL_KINDS.join(', ')}` }, { status: 400 });
|
|
412
|
-
await applyTwinWrite(m.service, { operation: 'machine_pool.enroll', subjectType: MACHINE_POOL, subjectId: 'pool', fields: { kind }, occurredAt: (await open(request, 'machine-pool', 'POST', '/_twin/machine-pool', {})).occurredAt, actor: { kind: 'system' } }, options.root);
|
|
467
|
+
await applyTwinWrite(m.service, { operation: 'machine_pool.enroll', subjectType: MACHINE_POOL, subjectId: 'pool', fields: { kind, ...(kind === 'fake' && Array.isArray(enrollment.httpReplies) ? { httpReplies: enrollment.httpReplies } : {}), ...(kind === 'light' ? { images: enrollment.images ?? {} } : {}) }, occurredAt: (await open(request, 'machine-pool', 'POST', '/_twin/machine-pool', {})).occurredAt, actor: { kind: 'system' } }, options.root);
|
|
468
|
+
if (kind === 'fake')
|
|
469
|
+
resetFakePool(m.service, options.root);
|
|
413
470
|
return Response.json({ kind }, { status: 201 });
|
|
414
471
|
}
|
|
415
472
|
for (const d of doors) {
|
|
@@ -455,12 +512,34 @@ export function createPackFetch(parts, options = {}) {
|
|
|
455
512
|
return refusedReadOnly();
|
|
456
513
|
return byPath.s.answer(await open(request, byPath.s.decl.id, request.method, byPath.s.decl.path, { ...(byPath.onHost?.params ?? {}), ...byPath.hit.params }));
|
|
457
514
|
}
|
|
515
|
+
for (const route of m.machineRoutes ?? []) {
|
|
516
|
+
const host = request.headers.get('x-volter-twin-original-host') ?? new URL(request.url).hostname;
|
|
517
|
+
const match = new RegExp(route.host).exec(host);
|
|
518
|
+
if (!match)
|
|
519
|
+
continue;
|
|
520
|
+
const ctx = await open(request, 'machine.traffic', request.method, url.pathname, match.groups ?? {});
|
|
521
|
+
const field = (row, path) => path.split('.').reduce((value, key) => value && typeof value === 'object' ? value[key] : undefined, row);
|
|
522
|
+
const fill = (value, row = {}) => value.replace(/\{([^}]+)\}/g, (_, key) => String(match.groups?.[key] ?? field(row, key) ?? ''));
|
|
523
|
+
const row = ctx.rowsRaw(route.resource).find(row => Object.entries(route.where).every(([key, value]) => String(field(row, key)) === fill(value)));
|
|
524
|
+
if (!row)
|
|
525
|
+
return new Response('Guest is unavailable', { status: 503 });
|
|
526
|
+
try {
|
|
527
|
+
return await ctx.machines.request(String(field(row, route.name)), Number(typeof route.port === 'string' ? fill(route.port, row) : route.port), request);
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
return new Response('Guest is unavailable', { status: 503 });
|
|
531
|
+
}
|
|
532
|
+
}
|
|
458
533
|
if (!parts.around)
|
|
459
534
|
return encoded(m, request, await decided(request));
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const
|
|
463
|
-
|
|
535
|
+
// The front reads a context before forwarding (possibly with new headers). Materialize a transport body before
|
|
536
|
+
// those independent reads, rather than chaining tees of the incoming stream through the front and the scenario.
|
|
537
|
+
const bytes = request.method === 'GET' || request.method === 'HEAD' ? undefined : await request.arrayBuffer();
|
|
538
|
+
const buffered = new Request(request.url, { method: request.method, headers: request.headers, signal: request.signal, ...(bytes ? { body: bytes } : {}) });
|
|
539
|
+
const matched = matchOperation(routes, buffered.method, url.pathname, url.searchParams, buffered.headers);
|
|
540
|
+
const operation = matched?.operation ?? { id: 'unmatched', method: buffered.method, path: url.pathname, class: buffered.method === 'GET' ? 'retrieve' : 'action' };
|
|
541
|
+
const ctx = await contextFor(m, { request: buffered.clone(), operation, params: matched?.params ?? pathParams(routes, url.pathname) }, scope);
|
|
542
|
+
return encoded(m, buffered, await parts.around(ctx, (rewritten) => decided(rewritten ?? buffered)));
|
|
464
543
|
};
|
|
465
544
|
// the request's id is the correlation of every entry it appends (runtime contract D3), minted here, always: it is the
|
|
466
545
|
// group a perform sends as one call (the real-system adapters, "Perform"). Under `auto` the group is performed once
|
|
@@ -508,12 +587,20 @@ export function createPackFetch(parts, options = {}) {
|
|
|
508
587
|
for (const d of m.sockets ?? [])
|
|
509
588
|
if (!parts.sockets?.[d.id])
|
|
510
589
|
throw new Error(`${m.vendor}: the manifest declares the socket ${d.id}, and semantics/sockets.ts exports no ${d.id}`);
|
|
511
|
-
const
|
|
590
|
+
const ownUpgrade = m.sockets?.length ? socketUpgrade(m.service, options.root, m.sockets, parts.sockets, (request, d) => open(request, `socket:${d.id}`, 'GET', d.path, {}), hostOf, (request) => new URL(routed(request).url).pathname,
|
|
512
591
|
// a turn a session speaks (a voice agent's reply) is the World's scenario's, as a turn over HTTP is
|
|
513
592
|
async (asked) => engineOf()?.serve(asked)) : undefined;
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
593
|
+
const upgrade = laneUpgrades(parts.lanes ?? {}, ownUpgrade);
|
|
594
|
+
// what serves each operation: the pack's own, and each lane's, each unit's under its own name
|
|
595
|
+
const ownersByUnit = () => ({
|
|
596
|
+
'': {
|
|
597
|
+
...((surface.format === 'command-reply' || surface.format === 'command-table' && m.descriptor?.transport === 'raw-tcp') ? Object.fromEntries((surface.commands ?? []).map((c) => [c.id, parts.around && !m.unmodeled?.includes(c.id) ? 'handler' : 'gap'])) : api.owners()),
|
|
598
|
+
...Object.fromEntries((parts.graphql?.operations ?? []).map((op) => [op.id, `${op.kind === 'query' ? 'Query' : 'Mutation'}.${op.field}` in (parts.graphql?.resolvers ?? {}) ? 'handler' : 'gap'])),
|
|
599
|
+
},
|
|
600
|
+
...lanesOwners(parts.lanes ?? {}),
|
|
601
|
+
});
|
|
602
|
+
const owners = () => flatOwners(ownersByUnit());
|
|
603
|
+
return derivedRequestScopes(m, Object.assign(served, { owners, ownersByUnit, serves, advanceClock, workspaces, boards: Boolean(parts.board), maxRequestBodySize: m.descriptor?.maxRequestBodySize, draws: drawsUrl, contentHosts, ...(upgrade ? { upgrade } : {}) }), options.readOnly);
|
|
517
604
|
}
|
|
518
605
|
/** The vendor's gateway over its lanes: the lane a request goes to (a door to the lane that declares it, then the first
|
|
519
606
|
* route whose conditions all hold, then the default), behind the gateway's CORS; undefined when none takes it. */
|
|
@@ -568,13 +655,52 @@ function laneRouter(vendor, decl, lanes, rootServes = () => false) {
|
|
|
568
655
|
}
|
|
569
656
|
/** A vendor whose every API is a lane (Cloudflare's API v4 and R2, AWS's services), served from its manifest: the
|
|
570
657
|
* discovery door, then its lanes as its gateway sends them; a request no lane takes is the vendor's not-found. */
|
|
571
|
-
/**
|
|
572
|
-
|
|
658
|
+
/** Each lane's answer at one of its World doors (`/twin`, `/twin/scenario`), by lane, read from the lane directly rather
|
|
659
|
+
* than routed back through the vendor's front: a vendor's discovery holds its lanes' (their doors, their scripts). */
|
|
660
|
+
async function laneDoors(lanes, request, path) {
|
|
661
|
+
const entries = await Promise.all(Object.entries(lanes).map(async ([name, part]) => {
|
|
662
|
+
const url = new URL(request.url);
|
|
663
|
+
url.pathname = path;
|
|
664
|
+
url.search = '';
|
|
665
|
+
const answer = await part.fetch(new Request(url, { headers: request.headers, signal: request.signal }));
|
|
666
|
+
return answer.ok ? [name, await answer.json()] : undefined;
|
|
667
|
+
}));
|
|
668
|
+
return Object.fromEntries(entries.filter((entry) => entry !== undefined));
|
|
669
|
+
}
|
|
670
|
+
/** What serves each of a vendor's lanes' operations (a handler, the core, or the gap), by lane: a lane's operation is the
|
|
671
|
+
* lane's own, so an id it shares with its root or another lane (Clerk's Backend and Frontend APIs both name
|
|
672
|
+
* `ListOrganizationMemberships`) keeps the lane's own owner, and a lane's gap stays a gap whoever else serves the id. */
|
|
673
|
+
const lanesOwners = (lanes) => Object.fromEntries(Object.entries(lanes).map(([name, l]) => [name, l.fetch.owners?.() ?? {}]));
|
|
674
|
+
/** A vendor's owners as one map, for its breadth: its own API's operations by id, a lane's as `<lane>:<id>`. */
|
|
675
|
+
const flatOwners = (byUnit) => Object.fromEntries(Object.entries(byUnit).flatMap(([unit, map]) => Object.entries(map).map(([id, owner]) => [unit ? `${unit}:${id}` : id, owner])));
|
|
573
676
|
/** The workspaces a vendor's lanes draw, as one list. */
|
|
574
677
|
const lanesWorkspaces = (lanes) => Object.values(lanes).flatMap((l) => l.fetch.workspaces?.() ?? []);
|
|
678
|
+
/** Keep each upgraded peer on the vendor or lane engine that accepted its opening request. */
|
|
679
|
+
function laneUpgrades(lanes, own) {
|
|
680
|
+
const choices = [own, ...Object.values(lanes).map(lane => lane.fetch.upgrade)].filter((choice) => choice !== undefined);
|
|
681
|
+
if (choices.length === 0)
|
|
682
|
+
return undefined;
|
|
683
|
+
if (choices.length === 1)
|
|
684
|
+
return choices[0];
|
|
685
|
+
const held = new WeakMap();
|
|
686
|
+
const select = (request) => choices.find(choice => choice.accepts(request));
|
|
687
|
+
return {
|
|
688
|
+
accepts: request => select(request) !== undefined,
|
|
689
|
+
protocol: request => select(request)?.protocol?.(request),
|
|
690
|
+
open(peer, request) { const selected = select(request); if (selected) {
|
|
691
|
+
held.set(peer, selected);
|
|
692
|
+
selected.open(peer, request);
|
|
693
|
+
} },
|
|
694
|
+
message(peer, data) { return held.get(peer)?.message(peer, data); },
|
|
695
|
+
close(peer) { const selected = held.get(peer); held.delete(peer); selected?.close(peer); },
|
|
696
|
+
};
|
|
697
|
+
}
|
|
575
698
|
export function createVendorFetch(manifest, lanes) {
|
|
699
|
+
const upgrade = laneUpgrades(lanes);
|
|
576
700
|
const lane = laneRouter(manifest.vendor, manifest.lanes, lanes);
|
|
577
701
|
return Object.assign(async (request) => {
|
|
702
|
+
for (const part of Object.values(lanes))
|
|
703
|
+
await part.fetch.advanceClock?.(request);
|
|
578
704
|
const path = new URL(request.url).pathname.replace(/\/+$/, '') || '/';
|
|
579
705
|
// the vendor's board is the frames of the lanes that lay one out, each drawn when any lane's screen takes its url
|
|
580
706
|
if (request.method === 'GET' && path === '/twin/board') {
|
|
@@ -595,9 +721,15 @@ export function createVendorFetch(manifest, lanes) {
|
|
|
595
721
|
if (request.method === 'GET' && path === '/twin' && manifest.discovery) {
|
|
596
722
|
// the vendor's screens are its lanes' (a dashboard drawn by one lane is the vendor's)
|
|
597
723
|
const screens = lanesWorkspaces(lanes);
|
|
598
|
-
|
|
724
|
+
const laneDiscovery = await laneDoors(lanes, request, '/twin');
|
|
725
|
+
return Response.json({ ...statefulTwinManifest({ vendor: manifest.vendor, ...manifest.discovery }), ...(screens.length ? { screens } : {}), ...(Object.keys(laneDiscovery).length ? { lanes: laneDiscovery } : {}) });
|
|
726
|
+
}
|
|
727
|
+
if (request.method === 'GET' && path === '/twin/scenario') {
|
|
728
|
+
const laneScenarios = await laneDoors(lanes, request, '/twin/scenario');
|
|
729
|
+
if (Object.keys(laneScenarios).length)
|
|
730
|
+
return Response.json({ vendor: manifest.vendor, lanes: laneScenarios });
|
|
599
731
|
}
|
|
600
732
|
const to = lane(request);
|
|
601
733
|
return to ? to(request) : Response.json({ error: `${manifest.vendor}: no lane serves ${request.method} ${path}` }, { status: 404 });
|
|
602
|
-
}, { owners: () => lanesOwners(lanes), workspaces: () => lanesWorkspaces(lanes) });
|
|
734
|
+
}, { maxRequestBodySize: manifest.descriptor?.maxRequestBodySize, owners: () => flatOwners(lanesOwners(lanes)), ownersByUnit: () => lanesOwners(lanes), workspaces: () => lanesWorkspaces(lanes), ...(upgrade ? { upgrade } : {}) });
|
|
603
735
|
}
|
|
@@ -140,6 +140,7 @@ export type HostRule = ({
|
|
|
140
140
|
hostPattern: string;
|
|
141
141
|
}) & {
|
|
142
142
|
pathPattern?: string;
|
|
143
|
+
demandPathPattern?: string | false;
|
|
143
144
|
key?: string;
|
|
144
145
|
exclude?: true;
|
|
145
146
|
};
|
|
@@ -171,6 +172,8 @@ export type TwinPack = {
|
|
|
171
172
|
rateBudget?: RateBudgetDeclaration;
|
|
172
173
|
emitter?: TwinEmitter;
|
|
173
174
|
serveExport?: string;
|
|
175
|
+
/** Declared HTTP transport cap, in bytes; the serve seam and World fronts honor it. */
|
|
176
|
+
maxRequestBodySize?: number;
|
|
174
177
|
adoption?: {
|
|
175
178
|
sdks?: string[];
|
|
176
179
|
pypi?: string[];
|
|
@@ -187,6 +190,9 @@ export type TwinPack = {
|
|
|
187
190
|
usage: 'build' | 'deployment';
|
|
188
191
|
}>;
|
|
189
192
|
};
|
|
193
|
+
/** Why no client case is owed though `adoption.sdks` names npm clients: the vendor maintains no official SDK, or the
|
|
194
|
+
* demand's applications use none (architecture, "The descriptor"; `conform`'s client check reads it). */
|
|
195
|
+
clientCasesNone?: string;
|
|
190
196
|
hosts?: HostRule[];
|
|
191
197
|
hostsNone?: string;
|
|
192
198
|
hostsClaimed?: {
|
|
@@ -202,7 +208,7 @@ export type TwinPack = {
|
|
|
202
208
|
/** The door that issues the application the credentials the twin will accept (a key made on the vendor's dashboard,
|
|
203
209
|
* and whatever names it: a database's URL, a project's id), and the env names it fills, each from a field of the
|
|
204
210
|
* answer: `init` writes `$issue:<service>` for each, and `up`, once this twin is running, POSTs the body to the door
|
|
205
|
-
* and sets each name to its field. A twin that holds its keys (refusing any it
|
|
211
|
+
* and sets each name to its field (`file:<field>` materializes one file; `directory:<field>` a relative-file map). A twin that holds its keys (refusing any it
|
|
206
212
|
* did not issue) declares one, so a World's applications hold keys it issued rather than fixtures it refuses. */
|
|
207
213
|
credentialDoor?: {
|
|
208
214
|
path: string;
|
package/dist/src/packRegistry.js
CHANGED
|
@@ -117,12 +117,29 @@ beside = {}) {
|
|
|
117
117
|
if (hand.length)
|
|
118
118
|
throw new Error(`${manifest.vendor}: the descriptor declares ${hand.join(', ')} by hand; the kernel derives them from the manifest (architecture, "The real-system adapters")`);
|
|
119
119
|
const pack = { vendor: manifest.vendor, ...manifest.descriptor, ...(manifest.rateBudget ? { rateBudget: manifest.rateBudget } : {}) };
|
|
120
|
-
// a pack with no vendor-backed half says why, and derives no adapters
|
|
121
|
-
|
|
120
|
+
// a pack with no vendor-backed half says why, and derives no adapters; one whose own surface has none beside lanes
|
|
121
|
+
// that do derives its lanes' adapters, its own writes settled as the World's (`unsent`)
|
|
122
|
+
const live = Object.fromEntries(Object.entries(beside).filter(([, l]) => !l.manifest.vendorBacked?.none));
|
|
123
|
+
if (manifest.vendorBacked?.none && !Object.keys(live).length)
|
|
122
124
|
return { ...pack, vendorBacked: { none: manifest.vendorBacked.none } };
|
|
123
125
|
if (!surface)
|
|
124
126
|
return pack;
|
|
125
|
-
|
|
127
|
+
// the unit's own surface: its operations, or a command table (a vendor that carries Redis over HTTP: architecture,
|
|
128
|
+
// "Other wires"), which names no operation a derived adapter performs; anything else is a map of lanes
|
|
129
|
+
const ownSurface = 'operations' in surface || 'commands' in surface;
|
|
130
|
+
if (manifest.vendorBacked?.none && ownSurface) {
|
|
131
|
+
const own = new Set((surface.operations ?? []).map((o) => o.id));
|
|
132
|
+
// a command table's every write is the unit's own (no operation of it is performed)
|
|
133
|
+
const commandTable = !('operations' in surface);
|
|
134
|
+
const why = manifest.vendorBacked.none;
|
|
135
|
+
const references = [manifest, ...Object.values(beside).map((l) => l.manifest)].flatMap(derivedReferences);
|
|
136
|
+
return {
|
|
137
|
+
...pack,
|
|
138
|
+
stateSystem: { ...deriveVendorStateSystem(manifest, live), unsent: (input) => (input && typeof input.lane !== 'string' && (commandTable || own.has(String(input.operationId))) ? why : undefined) },
|
|
139
|
+
...(references.length ? { references } : {}),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const lanes = ownSurface ? undefined : surface;
|
|
126
143
|
const units = lanes ? Object.values(lanes).map((l) => l.manifest) : [manifest, ...Object.values(beside).map((l) => l.manifest)];
|
|
127
144
|
const references = units.flatMap(derivedReferences);
|
|
128
145
|
return {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function privateTransportKey(namespace: string): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Process-local ingress capabilities for byte-stream fronts using an internal request seam.
|
|
2
|
+
// Lazy: importing the pack contract creates no key and opens no process handle.
|
|
3
|
+
const keys = new Map();
|
|
4
|
+
export function privateTransportKey(namespace) {
|
|
5
|
+
let key = keys.get(namespace);
|
|
6
|
+
if (!key) {
|
|
7
|
+
key = crypto.randomUUID();
|
|
8
|
+
keys.set(namespace, key);
|
|
9
|
+
}
|
|
10
|
+
return key;
|
|
11
|
+
}
|
package/dist/src/protobuf.js
CHANGED
|
@@ -87,7 +87,7 @@ function writeScalar(w, type, v) {
|
|
|
87
87
|
break;
|
|
88
88
|
case 'fixed32':
|
|
89
89
|
case 'sfixed32':
|
|
90
|
-
w.fixed(
|
|
90
|
+
w.fixed(BigInt(v), 4);
|
|
91
91
|
break;
|
|
92
92
|
case 'float':
|
|
93
93
|
w.fixed(Number(v), 4, true);
|
|
@@ -228,7 +228,7 @@ function readScalar(r, type) {
|
|
|
228
228
|
case 'uint32': return Number(BigInt.asUintN(32, r.varint()));
|
|
229
229
|
case 'int64': return String(BigInt.asIntN(64, r.varint()));
|
|
230
230
|
case 'uint64': return String(BigInt.asUintN(64, r.varint()));
|
|
231
|
-
case 'sint32': return Number(unzigzag(r.varint()));
|
|
231
|
+
case 'sint32': return Number(BigInt.asIntN(32, unzigzag(r.varint())));
|
|
232
232
|
case 'sint64': return String(unzigzag(r.varint()));
|
|
233
233
|
case 'bool': return r.varint() !== 0n;
|
|
234
234
|
case 'fixed64': return String(new DataView(r.take(8).slice().buffer).getBigUint64(0, true));
|
|
@@ -291,7 +291,10 @@ export function decodeMessage(schema, type, bytes) {
|
|
|
291
291
|
const out = {};
|
|
292
292
|
const r = new Reader(bytes);
|
|
293
293
|
while (!r.done) {
|
|
294
|
-
const
|
|
294
|
+
const rawKey = r.varint();
|
|
295
|
+
if (rawKey > 0xffffffffn)
|
|
296
|
+
throw new Error('protobuf: field tag exceeds 32 bits');
|
|
297
|
+
const key = Number(rawKey);
|
|
295
298
|
const number = key >>> 3;
|
|
296
299
|
const wire = key & 7;
|
|
297
300
|
const f = byNumber.get(number);
|
|
@@ -304,7 +307,10 @@ export function decodeMessage(schema, type, bytes) {
|
|
|
304
307
|
let k;
|
|
305
308
|
let v;
|
|
306
309
|
while (!entry.done) {
|
|
307
|
-
const
|
|
310
|
+
const rawKey = entry.varint();
|
|
311
|
+
if (rawKey > 0xffffffffn)
|
|
312
|
+
throw new Error('protobuf: map field tag exceeds 32 bits');
|
|
313
|
+
const ek = Number(rawKey);
|
|
308
314
|
if ((ek >>> 3) === 1)
|
|
309
315
|
k = readValue(schema, entry, { type: f.map.key, kind: 'scalar' }, ek & 7);
|
|
310
316
|
else if ((ek >>> 3) === 2)
|
package/dist/src/rateBudget.d.ts
CHANGED
|
@@ -86,6 +86,13 @@ export type RateBudgetDeclaration = {
|
|
|
86
86
|
* review, and the ceiling is the one number that can cost days if it is wrong.
|
|
87
87
|
*/
|
|
88
88
|
reason: string;
|
|
89
|
+
/** The vendor's documented allowance, in calls a minute, and the page that documents it, which the pack cites on a
|
|
90
|
+
* `// source:` line (checked like every other vendor fact): required of a budget that admits more than the fallback
|
|
91
|
+
* (`scripts/rate-budget-check.ts`). */
|
|
92
|
+
allowance?: {
|
|
93
|
+
perMinute: number;
|
|
94
|
+
source: string;
|
|
95
|
+
};
|
|
89
96
|
};
|
|
90
97
|
/**
|
|
91
98
|
* A declaration bound to the vendor it governs, with every pattern already compiled. The `RegExp`s
|
|
@@ -94,7 +101,7 @@ export type RateBudgetDeclaration = {
|
|
|
94
101
|
* pattern. (Declarations are repo source, not user input — this is a performance fix, not a
|
|
95
102
|
* sanitizer.)
|
|
96
103
|
*/
|
|
97
|
-
export type RateBudgetPolicy = Required<Omit<RateBudgetDeclaration, 'rules'>> & {
|
|
104
|
+
export type RateBudgetPolicy = Required<Omit<RateBudgetDeclaration, 'rules' | 'allowance'>> & Pick<RateBudgetDeclaration, 'allowance'> & {
|
|
98
105
|
vendor: string;
|
|
99
106
|
rules: RateBudgetWeightRule[];
|
|
100
107
|
/** `compiled[i]` is `rules[i].match`, compiled. Same length, same order. */
|