@volter/world-core 2.0.0 → 2.0.1

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 CHANGED
@@ -116,13 +116,17 @@ function appRequestOptions(route, url, method, headers, extra) {
116
116
  /** One request to the application for `value`, a URL on one of its hosts, as `route` (appDestination's answer) names
117
117
  * it, with appRequestOptions' headers. No redirect is followed: the caller decides, as a delivery reports what it got.
118
118
  * Answers { status, headers (lower-cased), body (decoded text) } within `timeoutMs` overall; a failure rejects. */
119
- function deliverToApp(route, value, { method = 'POST', headers = {}, body, timeoutMs = 30_000 } = {}) {
119
+ function deliverToApp(route, value, { method = 'POST', headers = {}, body, timeoutMs = 30_000, signal } = {}) {
120
120
  const url = new URL(String(value));
121
+ if (signal && signal.aborted) return Promise.reject(signal.reason);
121
122
  const options = appRequestOptions(route, url, method, headers);
122
123
  const make = (options.protocol === 'https:' ? require('https') : require('http')).request;
123
124
  return new Promise((resolve, reject) => {
124
125
  let settled = false;
125
- const fail = (error) => { if (!settled) { settled = true; clearTimeout(deadline); req.destroy(); reject(error); } };
126
+ // an abort (the caller's `signal`, as fetch's) ends the exchange with the signal's reason
127
+ const onAbort = () => fail(signal.reason);
128
+ const done = () => { settled = true; clearTimeout(deadline); if (signal) signal.removeEventListener('abort', onAbort); };
129
+ const fail = (error) => { if (!settled) { done(); req.destroy(); reject(error); } };
126
130
  const req = make(options, (res) => {
127
131
  // a reply cut off mid-body fails the delivery, decoded or not (pipe forwards no error to the decoder)
128
132
  res.on('error', fail);
@@ -136,19 +140,104 @@ function deliverToApp(route, value, { method = 'POST', headers = {}, body, timeo
136
140
  stream.on('error', fail);
137
141
  stream.on('end', () => {
138
142
  if (settled) return;
139
- settled = true;
140
- clearTimeout(deadline);
143
+ done();
141
144
  const answer = {};
142
145
  for (const [k, v] of Object.entries(res.headers)) if (v !== undefined && !(decoder && (k === 'content-encoding' || k === 'content-length'))) answer[k.toLowerCase()] = Array.isArray(v) ? v.join(', ') : String(v);
143
146
  resolve({ status: res.statusCode, headers: answer, body: Buffer.concat(chunks).toString('utf8') });
144
147
  });
145
148
  });
146
149
  // one deadline for the whole exchange, not an idle timeout: an answer that trickles is still cut off
147
- const deadline = setTimeout(() => fail(new Error(`the application did not answer ${url.origin}${url.pathname} within ${timeoutMs} ms`)), timeoutMs);
150
+ // (Infinity: none, the caller's signal governing, as fetch has no overall deadline)
151
+ const deadline = Number.isFinite(timeoutMs) ? setTimeout(() => fail(new Error(`the application did not answer ${url.origin}${url.pathname} within ${timeoutMs} ms`)), timeoutMs) : undefined;
148
152
  req.on('error', fail);
153
+ if (signal) signal.addEventListener('abort', onAbort, { once: true });
149
154
  if (body != null && method !== 'GET' && method !== 'HEAD') req.write(body);
150
155
  req.end();
151
156
  });
152
157
  }
153
158
 
154
- module.exports = { appRoute, appHostMatch, appForwardHeaders, appDestination, appRequestOptions, deliverToApp, sessionCa };
159
+ const REDIRECTS = [301, 302, 303, 307, 308];
160
+ const NULL_BODY = [204, 205, 304];
161
+ // what fetch drops from a request when a redirect crosses origins
162
+ const CROSS_ORIGIN_DROPPED = ['authorization', 'cookie', 'proxy-authorization'];
163
+
164
+ /** A request body as fetch takes one, as the bytes deliverToApp sends (a form's default content type set, as fetch sets it). */
165
+ function requestBody(body, headers) {
166
+ if (body == null) return null;
167
+ if (typeof body === 'string') return body;
168
+ if (body instanceof URLSearchParams) {
169
+ if (!Object.keys(headers).some((k) => k.toLowerCase() === 'content-type')) headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
170
+ return body.toString();
171
+ }
172
+ if (body instanceof ArrayBuffer) return Buffer.from(body);
173
+ if (ArrayBuffer.isView(body)) return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
174
+ throw new TypeError('appFetch sends a string, URLSearchParams or bytes body');
175
+ }
176
+
177
+ /** fetch, as a twin's own outbound delivery makes it from the colocated twin host (which runs without the injector):
178
+ * a URL on one of the application's hostnames (appDestination) reaches the application where it listens
179
+ * (deliverToApp), any other URL is fetch's own request. The caller applies worldEgressRefusal to the URL first when
180
+ * appDestination names no application for it, as before, and appFetch applies it again (a refusal rejects), so a
181
+ * host that left the record between the two is never let out ungated; the answer is a Response, read as fetch's is.
182
+ * From the application, `init.redirect` is honoured as fetch honours it: 'follow' (the default) follows at most 20, a
183
+ * 303, or a 301/302 after a POST, becoming a GET without its body, and drops the credentials crossing origins;
184
+ * 'manual' answers the 3xx itself; 'error' rejects. Every hop is routed again: onto the application's hosts it
185
+ * reaches the application, elsewhere it goes out only where worldEgressRefusal lets it (a refusal rejects as an
186
+ * unreachable host does). `init.signal` aborts, as fetch's does. */
187
+ async function appFetch(value, init = {}) {
188
+ // the route is read again here, so a host no longer recorded since the caller asked is gated as any other URL
189
+ if (!appDestination(value)) {
190
+ const refusal = require('./network-policy.cjs').worldEgressRefusal(value);
191
+ if (refusal !== null) throw new TypeError(refusal);
192
+ return fetch(value, init);
193
+ }
194
+ const mode = init.redirect || 'follow';
195
+ const signal = init.signal || undefined;
196
+ let at = new URL(String(value));
197
+ let method = String(init.method || 'GET').toUpperCase();
198
+ const headers = {};
199
+ new Headers(init.headers || {}).forEach((v, k) => { headers[k] = v; });
200
+ let body = requestBody(init.body, headers);
201
+ for (let hops = 0; ; hops++) {
202
+ const route = appDestination(at);
203
+ let res;
204
+ if (route) {
205
+ // the application's own origin is where the request goes: an application recorded outside the World (an app-url
206
+ // naming a deployment elsewhere) meets the World's egress rule as any other destination does
207
+ const originRefusal = require('./network-policy.cjs').worldEgressRefusal(route.origin);
208
+ if (originRefusal !== null) throw new TypeError(originRefusal);
209
+ // fetch's own bound (undici's 300 s headers and body timeouts), whatever signal the caller gives, so a hung
210
+ // application never holds a delivery, or the API request awaiting it, for good; a caller's signal may end it sooner
211
+ const answer = await deliverToApp(route, at, { method, headers, body, signal, timeoutMs: 300_000 });
212
+ const nullBody = NULL_BODY.includes(answer.status) || method === 'HEAD';
213
+ // a status fetch resolves with but Response refuses to construct (outside 200-599) is carried as fetch carries it
214
+ const inRange = answer.status >= 200 && answer.status <= 599;
215
+ res = new Response(nullBody ? null : answer.body, { status: inRange ? answer.status : 200, headers: answer.headers });
216
+ if (!inRange) Object.defineProperties(res, { status: { value: answer.status }, ok: { value: false } });
217
+ Object.defineProperty(res, 'url', { value: at.toString() });
218
+ } else {
219
+ const refusal = require('./network-policy.cjs').worldEgressRefusal(at);
220
+ if (refusal !== null) throw new TypeError(refusal);
221
+ // off the application, the World's own headers never go with the request
222
+ for (const k of Object.keys(headers)) if (['x-twins-key', 'x-volter-twin-original-host'].includes(k.toLowerCase())) delete headers[k];
223
+ res = await fetch(at, { method, headers, ...(body != null && method !== 'GET' && method !== 'HEAD' ? { body } : {}), redirect: 'manual', signal });
224
+ }
225
+ if (hops > 0) Object.defineProperty(res, 'redirected', { value: true });
226
+ const location = res.headers.get('location');
227
+ if (!REDIRECTS.includes(res.status) || location === null || mode === 'manual') return res;
228
+ if (mode === 'error') throw new TypeError(`${at.origin}${at.pathname} redirected, and the request's redirect mode is error`);
229
+ if (hops >= 20) throw new TypeError('redirect count exceeded');
230
+ if (res.body) await res.body.cancel();
231
+ const next = new URL(location, at);
232
+ if (next.protocol !== 'http:' && next.protocol !== 'https:') throw new TypeError(`redirect to ${next.protocol} is not followed`);
233
+ if ((res.status === 303 && method !== 'GET' && method !== 'HEAD') || ((res.status === 301 || res.status === 302) && method === 'POST')) {
234
+ method = 'GET';
235
+ body = null;
236
+ for (const k of Object.keys(headers)) if (/^content-/i.test(k)) delete headers[k];
237
+ }
238
+ if (next.origin !== at.origin) for (const k of Object.keys(headers)) if (CROSS_ORIGIN_DROPPED.includes(k.toLowerCase())) delete headers[k];
239
+ at = next;
240
+ }
241
+ }
242
+
243
+ module.exports = { appRoute, appHostMatch, appForwardHeaders, appDestination, appRequestOptions, deliverToApp, appFetch, sessionCa };
package/app-route.d.cts CHANGED
@@ -4,4 +4,5 @@ export function appRoute(instanceFile: string | undefined): AppRoute | null;
4
4
  export function appHostMatch(hosts: ReadonlySet<string>, host: string): boolean;
5
5
  export function appForwardHeaders(url: URL): Record<string, string>;
6
6
  export function appDestination(value: string | URL, env?: Readonly<Record<string, string | undefined>>): AppRoute | null;
7
- export function deliverToApp(route: AppRoute, value: string | URL, init?: { method?: string; headers?: Record<string, string>; body?: string | Uint8Array | null; timeoutMs?: number }): Promise<AppAnswer>;
7
+ export function deliverToApp(route: AppRoute, value: string | URL, init?: { method?: string; headers?: Record<string, string>; body?: string | Uint8Array | null; /** Infinity: no deadline of its own */ timeoutMs?: number; signal?: AbortSignal }): Promise<AppAnswer>;
8
+ export function appFetch(value: string | URL, init?: RequestInit): Promise<Response>;
@@ -116,13 +116,17 @@ function appRequestOptions(route, url, method, headers, extra) {
116
116
  /** One request to the application for `value`, a URL on one of its hosts, as `route` (appDestination's answer) names
117
117
  * it, with appRequestOptions' headers. No redirect is followed: the caller decides, as a delivery reports what it got.
118
118
  * Answers { status, headers (lower-cased), body (decoded text) } within `timeoutMs` overall; a failure rejects. */
119
- function deliverToApp(route, value, { method = 'POST', headers = {}, body, timeoutMs = 30_000 } = {}) {
119
+ function deliverToApp(route, value, { method = 'POST', headers = {}, body, timeoutMs = 30_000, signal } = {}) {
120
120
  const url = new URL(String(value));
121
+ if (signal && signal.aborted) return Promise.reject(signal.reason);
121
122
  const options = appRequestOptions(route, url, method, headers);
122
123
  const make = (options.protocol === 'https:' ? require('https') : require('http')).request;
123
124
  return new Promise((resolve, reject) => {
124
125
  let settled = false;
125
- const fail = (error) => { if (!settled) { settled = true; clearTimeout(deadline); req.destroy(); reject(error); } };
126
+ // an abort (the caller's `signal`, as fetch's) ends the exchange with the signal's reason
127
+ const onAbort = () => fail(signal.reason);
128
+ const done = () => { settled = true; clearTimeout(deadline); if (signal) signal.removeEventListener('abort', onAbort); };
129
+ const fail = (error) => { if (!settled) { done(); req.destroy(); reject(error); } };
126
130
  const req = make(options, (res) => {
127
131
  // a reply cut off mid-body fails the delivery, decoded or not (pipe forwards no error to the decoder)
128
132
  res.on('error', fail);
@@ -136,19 +140,104 @@ function deliverToApp(route, value, { method = 'POST', headers = {}, body, timeo
136
140
  stream.on('error', fail);
137
141
  stream.on('end', () => {
138
142
  if (settled) return;
139
- settled = true;
140
- clearTimeout(deadline);
143
+ done();
141
144
  const answer = {};
142
145
  for (const [k, v] of Object.entries(res.headers)) if (v !== undefined && !(decoder && (k === 'content-encoding' || k === 'content-length'))) answer[k.toLowerCase()] = Array.isArray(v) ? v.join(', ') : String(v);
143
146
  resolve({ status: res.statusCode, headers: answer, body: Buffer.concat(chunks).toString('utf8') });
144
147
  });
145
148
  });
146
149
  // one deadline for the whole exchange, not an idle timeout: an answer that trickles is still cut off
147
- const deadline = setTimeout(() => fail(new Error(`the application did not answer ${url.origin}${url.pathname} within ${timeoutMs} ms`)), timeoutMs);
150
+ // (Infinity: none, the caller's signal governing, as fetch has no overall deadline)
151
+ const deadline = Number.isFinite(timeoutMs) ? setTimeout(() => fail(new Error(`the application did not answer ${url.origin}${url.pathname} within ${timeoutMs} ms`)), timeoutMs) : undefined;
148
152
  req.on('error', fail);
153
+ if (signal) signal.addEventListener('abort', onAbort, { once: true });
149
154
  if (body != null && method !== 'GET' && method !== 'HEAD') req.write(body);
150
155
  req.end();
151
156
  });
152
157
  }
153
158
 
154
- module.exports = { appRoute, appHostMatch, appForwardHeaders, appDestination, appRequestOptions, deliverToApp, sessionCa };
159
+ const REDIRECTS = [301, 302, 303, 307, 308];
160
+ const NULL_BODY = [204, 205, 304];
161
+ // what fetch drops from a request when a redirect crosses origins
162
+ const CROSS_ORIGIN_DROPPED = ['authorization', 'cookie', 'proxy-authorization'];
163
+
164
+ /** A request body as fetch takes one, as the bytes deliverToApp sends (a form's default content type set, as fetch sets it). */
165
+ function requestBody(body, headers) {
166
+ if (body == null) return null;
167
+ if (typeof body === 'string') return body;
168
+ if (body instanceof URLSearchParams) {
169
+ if (!Object.keys(headers).some((k) => k.toLowerCase() === 'content-type')) headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
170
+ return body.toString();
171
+ }
172
+ if (body instanceof ArrayBuffer) return Buffer.from(body);
173
+ if (ArrayBuffer.isView(body)) return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
174
+ throw new TypeError('appFetch sends a string, URLSearchParams or bytes body');
175
+ }
176
+
177
+ /** fetch, as a twin's own outbound delivery makes it from the colocated twin host (which runs without the injector):
178
+ * a URL on one of the application's hostnames (appDestination) reaches the application where it listens
179
+ * (deliverToApp), any other URL is fetch's own request. The caller applies worldEgressRefusal to the URL first when
180
+ * appDestination names no application for it, as before, and appFetch applies it again (a refusal rejects), so a
181
+ * host that left the record between the two is never let out ungated; the answer is a Response, read as fetch's is.
182
+ * From the application, `init.redirect` is honoured as fetch honours it: 'follow' (the default) follows at most 20, a
183
+ * 303, or a 301/302 after a POST, becoming a GET without its body, and drops the credentials crossing origins;
184
+ * 'manual' answers the 3xx itself; 'error' rejects. Every hop is routed again: onto the application's hosts it
185
+ * reaches the application, elsewhere it goes out only where worldEgressRefusal lets it (a refusal rejects as an
186
+ * unreachable host does). `init.signal` aborts, as fetch's does. */
187
+ async function appFetch(value, init = {}) {
188
+ // the route is read again here, so a host no longer recorded since the caller asked is gated as any other URL
189
+ if (!appDestination(value)) {
190
+ const refusal = require('./network-policy.cjs').worldEgressRefusal(value);
191
+ if (refusal !== null) throw new TypeError(refusal);
192
+ return fetch(value, init);
193
+ }
194
+ const mode = init.redirect || 'follow';
195
+ const signal = init.signal || undefined;
196
+ let at = new URL(String(value));
197
+ let method = String(init.method || 'GET').toUpperCase();
198
+ const headers = {};
199
+ new Headers(init.headers || {}).forEach((v, k) => { headers[k] = v; });
200
+ let body = requestBody(init.body, headers);
201
+ for (let hops = 0; ; hops++) {
202
+ const route = appDestination(at);
203
+ let res;
204
+ if (route) {
205
+ // the application's own origin is where the request goes: an application recorded outside the World (an app-url
206
+ // naming a deployment elsewhere) meets the World's egress rule as any other destination does
207
+ const originRefusal = require('./network-policy.cjs').worldEgressRefusal(route.origin);
208
+ if (originRefusal !== null) throw new TypeError(originRefusal);
209
+ // fetch's own bound (undici's 300 s headers and body timeouts), whatever signal the caller gives, so a hung
210
+ // application never holds a delivery, or the API request awaiting it, for good; a caller's signal may end it sooner
211
+ const answer = await deliverToApp(route, at, { method, headers, body, signal, timeoutMs: 300_000 });
212
+ const nullBody = NULL_BODY.includes(answer.status) || method === 'HEAD';
213
+ // a status fetch resolves with but Response refuses to construct (outside 200-599) is carried as fetch carries it
214
+ const inRange = answer.status >= 200 && answer.status <= 599;
215
+ res = new Response(nullBody ? null : answer.body, { status: inRange ? answer.status : 200, headers: answer.headers });
216
+ if (!inRange) Object.defineProperties(res, { status: { value: answer.status }, ok: { value: false } });
217
+ Object.defineProperty(res, 'url', { value: at.toString() });
218
+ } else {
219
+ const refusal = require('./network-policy.cjs').worldEgressRefusal(at);
220
+ if (refusal !== null) throw new TypeError(refusal);
221
+ // off the application, the World's own headers never go with the request
222
+ for (const k of Object.keys(headers)) if (['x-twins-key', 'x-volter-twin-original-host'].includes(k.toLowerCase())) delete headers[k];
223
+ res = await fetch(at, { method, headers, ...(body != null && method !== 'GET' && method !== 'HEAD' ? { body } : {}), redirect: 'manual', signal });
224
+ }
225
+ if (hops > 0) Object.defineProperty(res, 'redirected', { value: true });
226
+ const location = res.headers.get('location');
227
+ if (!REDIRECTS.includes(res.status) || location === null || mode === 'manual') return res;
228
+ if (mode === 'error') throw new TypeError(`${at.origin}${at.pathname} redirected, and the request's redirect mode is error`);
229
+ if (hops >= 20) throw new TypeError('redirect count exceeded');
230
+ if (res.body) await res.body.cancel();
231
+ const next = new URL(location, at);
232
+ if (next.protocol !== 'http:' && next.protocol !== 'https:') throw new TypeError(`redirect to ${next.protocol} is not followed`);
233
+ if ((res.status === 303 && method !== 'GET' && method !== 'HEAD') || ((res.status === 301 || res.status === 302) && method === 'POST')) {
234
+ method = 'GET';
235
+ body = null;
236
+ for (const k of Object.keys(headers)) if (/^content-/i.test(k)) delete headers[k];
237
+ }
238
+ if (next.origin !== at.origin) for (const k of Object.keys(headers)) if (CROSS_ORIGIN_DROPPED.includes(k.toLowerCase())) delete headers[k];
239
+ at = next;
240
+ }
241
+ }
242
+
243
+ module.exports = { appRoute, appHostMatch, appForwardHeaders, appDestination, appRequestOptions, deliverToApp, appFetch, sessionCa };
@@ -4,4 +4,5 @@ export function appRoute(instanceFile: string | undefined): AppRoute | null;
4
4
  export function appHostMatch(hosts: ReadonlySet<string>, host: string): boolean;
5
5
  export function appForwardHeaders(url: URL): Record<string, string>;
6
6
  export function appDestination(value: string | URL, env?: Readonly<Record<string, string | undefined>>): AppRoute | null;
7
- export function deliverToApp(route: AppRoute, value: string | URL, init?: { method?: string; headers?: Record<string, string>; body?: string | Uint8Array | null; timeoutMs?: number }): Promise<AppAnswer>;
7
+ export function deliverToApp(route: AppRoute, value: string | URL, init?: { method?: string; headers?: Record<string, string>; body?: string | Uint8Array | null; /** Infinity: no deadline of its own */ timeoutMs?: number; signal?: AbortSignal }): Promise<AppAnswer>;
8
+ export function appFetch(value: string | URL, init?: RequestInit): Promise<Response>;
@@ -714,6 +714,9 @@
714
714
  "hosts": [
715
715
  {
716
716
  "host": "api.cohere.com"
717
+ },
718
+ {
719
+ "host": "api.cohere.ai"
717
720
  }
718
721
  ],
719
722
  "archetype": "generative",
@@ -728,7 +731,7 @@
728
731
  "embed_job",
729
732
  "token"
730
733
  ],
731
- "endpointEnvNone": "neither official SDK reads a base-URL env var: cohere-ai@8.1.0 takes the override as the `baseUrl`/`environment` CONSTRUCTOR option and reads only CO_API_KEY from the environment (auth/BearerAuthProvider.js), and @ai-sdk/cohere@4.0.35 takes `options.baseURL` and reads only COHERE_API_KEY. Interception is host-based on api.cohere.com; inventing a COHERE_BASE_URL nothing reads would make `covers` claim a world it does not cover.",
734
+ "endpointEnvNone": "neither official SDK reads a base-URL env var: cohere-ai@8.1.0 takes the override as the `baseUrl`/`environment` CONSTRUCTOR option and reads only CO_API_KEY from the environment (auth/BearerAuthProvider.js), and @ai-sdk/cohere@4.0.35 takes `options.baseURL` and reads only COHERE_API_KEY. Interception is host-based on api.cohere.com and api.cohere.ai; inventing a COHERE_BASE_URL nothing reads would make `covers` claim a world it does not cover.",
732
735
  "serveExport": "createCohereTwinServer"
733
736
  },
734
737
  "currencyapi": {
@@ -963,6 +966,10 @@
963
966
  "host": "discord.com",
964
967
  "pathPattern": "^/api/"
965
968
  },
969
+ {
970
+ "host": "discord.com",
971
+ "pathPattern": "^/(oauth2/authorize|login)/?$"
972
+ },
966
973
  {
967
974
  "host": "gateway.discord.gg"
968
975
  },
@@ -1399,6 +1406,10 @@
1399
1406
  "host": "github.com",
1400
1407
  "pathPattern": "^/login/oauth/(authorize|access_token)$"
1401
1408
  },
1409
+ {
1410
+ "host": "github.com",
1411
+ "pathPattern": "^/(login|session|sessions/two-factor(/app)?)$"
1412
+ },
1402
1413
  {
1403
1414
  "host": "github.com",
1404
1415
  "pathPattern": "^/apps/[^/]+/installations/new$"
@@ -1470,6 +1481,37 @@
1470
1481
  "endpointEnvNone": "no Google Ads client exposes an HTTP base-URL override: google-ads-api builds the host into the request literally, google-ads-python's `endpoint` is a gRPC host:port for its transport channel, and Dub hardcodes the host in a raw fetch. Interception is host-based.",
1471
1482
  "serveExport": "createGoogleAdsTwinServer"
1472
1483
  },
1484
+ "googlecalendar": {
1485
+ "adoption": {
1486
+ "sdks": [
1487
+ "@googleapis/calendar"
1488
+ ],
1489
+ "pypi": [],
1490
+ "envStems": [
1491
+ "GOOGLECALENDAR"
1492
+ ]
1493
+ },
1494
+ "hosts": [
1495
+ {
1496
+ "host": "www.googleapis.com",
1497
+ "pathPattern": "^/(batch/)?calendar/v3(/|$)"
1498
+ }
1499
+ ],
1500
+ "archetype": "crud",
1501
+ "protocol": {
1502
+ "declared": "2",
1503
+ "major": 2,
1504
+ "standing": "current"
1505
+ },
1506
+ "transport": "rest",
1507
+ "resources": [
1508
+ "Calendar",
1509
+ "CalendarListEntry",
1510
+ "Event"
1511
+ ],
1512
+ "endpointEnvNone": "@googleapis/calendar and googleapis read no base-URL variable: the root URL is the Discovery document's `https://www.googleapis.com/` unless the caller passes `rootUrl` (Inbox Zero reads its own GOOGLE_BASE_URL for that); interception is the host rule above.",
1513
+ "serveExport": "createGooglecalendarTwinServer"
1514
+ },
1473
1515
  "googlemaps": {
1474
1516
  "adoption": {
1475
1517
  "pypi": [
@@ -2148,6 +2190,27 @@
2148
2190
  ],
2149
2191
  "serveExport": "createMixpanelTwinServer"
2150
2192
  },
2193
+ "mongodb": {
2194
+ "adoption": {
2195
+ "pypi": [],
2196
+ "sdks": []
2197
+ },
2198
+ "hostsNone": "RAW-PROTOCOL pack — there is no host to map. MongoDB clients speak the wire protocol over a raw TCP socket to whatever host the connection string names, which the injector (http/fetch patching) cannot see; a World reaches this twin through the connection string itself (MONGO_URI and its kin), which World-managed infrastructure injects.",
2199
+ "archetype": "crud",
2200
+ "protocol": {
2201
+ "declared": "2",
2202
+ "major": 2,
2203
+ "standing": "current"
2204
+ },
2205
+ "transport": "raw-tcp",
2206
+ "resources": [
2207
+ "collection",
2208
+ "index",
2209
+ "document"
2210
+ ],
2211
+ "endpointEnvNone": "no vendor base-URL variable: the application reads its own connection-string variable (MONGO_URI, MONGODB_URL, DATABASE_URL, …), and World-managed infrastructure (`volter-world init` → `volter-world-infra`) injects that variable pointing at this twin's listener.",
2212
+ "serveExport": "createMongodbTwinServer"
2213
+ },
2151
2214
  "moonshot": {
2152
2215
  "adoption": {
2153
2216
  "pypi": [],
@@ -2816,6 +2879,26 @@
2816
2879
  "endpointEnvNone": "reddit clients take no base-URL environment variable (snoowrap and PRAW build https://oauth.reddit.com and https://www.reddit.com URLs from constants or explicit constructor options), so a World reaches them through the hosts above; inventing a REDDIT_BASE_URL the app never reads would report coverage the app does not have.",
2817
2880
  "serveExport": "createRedditTwinServer"
2818
2881
  },
2882
+ "redis": {
2883
+ "adoption": {},
2884
+ "hostsNone": "RAW-PROTOCOL pack — there is no host to map. Redis's protocol runs over a TCP socket the injector (which patches http/fetch) never sees, and a Redis has no vendor hostname: it is wherever the app's REDIS_URL points. The World points it here: init classifies REDIS_URL as managed infrastructure and the managed redis service is this twin.",
2885
+ "archetype": "crud",
2886
+ "protocol": {
2887
+ "major": 1,
2888
+ "standing": "deprecated"
2889
+ },
2890
+ "transport": "raw-tcp",
2891
+ "resources": [
2892
+ "key",
2893
+ "field",
2894
+ "member",
2895
+ "scored",
2896
+ "entry",
2897
+ "script"
2898
+ ],
2899
+ "endpointEnvNone": "Redis clients read no base-URL env of their own: the app passes its REDIS_URL (or host/port) to ioredis or node-redis itself, and init emits that REDIS_URL for the managed redis service (world-runtime/src/init.ts), not as a twin endpoint env.",
2900
+ "serveExport": "createRedisTwinServer"
2901
+ },
2819
2902
  "replicate": {
2820
2903
  "adoption": {
2821
2904
  "pypi": [
@@ -3358,11 +3441,11 @@
3358
3441
  },
3359
3442
  {
3360
3443
  "host": "connect.stripe.com",
3361
- "pathPattern": "^/setup/"
3444
+ "pathPattern": "^/setup/|^/oauth/(authorize|token|deauthorize)/?$"
3362
3445
  },
3363
3446
  {
3364
3447
  "host": "dashboard.stripe.com",
3365
- "pathPattern": "^/settings/public/?$"
3448
+ "pathPattern": "^/settings/public/?$|^/(test/)?settings/connect(/onboarding-options/oauth)?/?$"
3366
3449
  },
3367
3450
  {
3368
3451
  "host": "verify.stripe.com",
@@ -3561,6 +3644,38 @@
3561
3644
  "endpointEnvNone": "the @tavily/core SDK exposes no base-URL env var — it reads only TAVILY_API_KEY/TAVILY_HTTP(S)_PROXY/TAVILY_ORG_ID/TAVILY_PROJECT from process.env; base-URL override is the constructor option apiBaseURL (dist/index.mjs post()/get(): `apiBaseURL || BASE_URL`), so interception is the hosts entry above.",
3562
3645
  "serveExport": "createTavilyTwinServer"
3563
3646
  },
3647
+ "temporal": {
3648
+ "adoption": {
3649
+ "sdks": [
3650
+ "@temporalio/client",
3651
+ "@temporalio/worker",
3652
+ "@temporalio/workflow",
3653
+ "@temporalio/activity"
3654
+ ],
3655
+ "scopes": [
3656
+ "@temporalio/"
3657
+ ],
3658
+ "pypi": [
3659
+ "temporalio"
3660
+ ],
3661
+ "envStems": [
3662
+ "TEMPORAL"
3663
+ ]
3664
+ },
3665
+ "hostsNone": "RAW-PROTOCOL pack — there is no host to map. The Temporal SDKs dial the frontend address they are configured with (Connection.connect / NativeConnection.connect `address`, conventionally TEMPORAL_ADDRESS) over gRPC, which the http/fetch injector never sees. Interception is the app-read address: the World injects TEMPORAL_ADDRESS=<host>:<port> at this twin (APP_READ_ENDPOINT_ENV in world-runtime/src/init.ts).",
3666
+ "archetype": "engine-control",
3667
+ "protocol": {
3668
+ "declared": "2",
3669
+ "major": 2,
3670
+ "standing": "current"
3671
+ },
3672
+ "transport": "raw-tcp",
3673
+ "resources": [
3674
+ "workflow-execution",
3675
+ "namespace"
3676
+ ],
3677
+ "serveExport": "createTemporalTwinServer"
3678
+ },
3564
3679
  "tiktok": {
3565
3680
  "adoption": {
3566
3681
  "sdks": [],
@@ -4154,11 +4269,11 @@
4154
4269
  "hosts": [
4155
4270
  {
4156
4271
  "host": "api.x.com",
4157
- "pathPattern": "^(?:/2/media/upload/?|/2/media/upload/initialize/?|/2/media/upload/[0-9]+/(?:append|finalize)/?|/2/tweets/?|/2/tweets/[^/]+/?|/2/tweets/search/recent/?|/2/users/(?!me/?$)[^/]+/?|/2/users/by/username/[^/]+/?|/2/users/[^/]+/mentions/?|/2/users/[^/]+/tweets/?|/2/users/[^/]+/timelines/reverse_chronological/?)$|^/_twin/"
4272
+ "pathPattern": "^(?:/2/media/upload/?|/2/media/upload/initialize/?|/2/media/upload/[0-9]+/(?:append|finalize)/?|/2/tweets/?|/2/tweets/[^/]+/?|/2/tweets/search/recent/?|/2/users/(?!me/?$)[^/]+/?|/2/users/by/username/[^/]+/?|/2/users/[^/]+/mentions/?|/2/users/[^/]+/tweets/?|/2/users/[^/]+/timelines/reverse_chronological/?)$|^/_twin/(?!assets/consent\\.(?:js|css)$)"
4158
4273
  },
4159
4274
  {
4160
4275
  "host": "api.twitter.com",
4161
- "pathPattern": "^(?:/2/media/upload/?|/2/media/upload/initialize/?|/2/media/upload/[0-9]+/(?:append|finalize)/?|/2/tweets/?|/2/tweets/[^/]+/?|/2/tweets/search/recent/?|/2/users/(?!me/?$)[^/]+/?|/2/users/by/username/[^/]+/?|/2/users/[^/]+/mentions/?|/2/users/[^/]+/tweets/?|/2/users/[^/]+/timelines/reverse_chronological/?)$|^/_twin/"
4276
+ "pathPattern": "^(?:/2/media/upload/?|/2/media/upload/initialize/?|/2/media/upload/[0-9]+/(?:append|finalize)/?|/2/tweets/?|/2/tweets/[^/]+/?|/2/tweets/search/recent/?|/2/users/(?!me/?$)[^/]+/?|/2/users/by/username/[^/]+/?|/2/users/[^/]+/mentions/?|/2/users/[^/]+/tweets/?|/2/users/[^/]+/timelines/reverse_chronological/?)$|^/_twin/(?!assets/consent\\.(?:js|css)$)"
4162
4277
  }
4163
4278
  ],
4164
4279
  "archetype": "crud",
@@ -4241,11 +4356,11 @@
4241
4356
  },
4242
4357
  {
4243
4358
  "host": "api.x.com",
4244
- "pathPattern": "^/2/(?:oauth2/(?:token|revoke)|users/me)/?$"
4359
+ "pathPattern": "^/2/(?:oauth2/(?:token|revoke)|users/me)/?$|^/oauth/(?:request_token|authorize|authenticate|access_token)/?$|^/1\\.1/oauth/invalidate_token(?:\\.json)?/?$|^/_twin/assets/consent\\.(?:js|css)$"
4245
4360
  },
4246
4361
  {
4247
4362
  "host": "api.twitter.com",
4248
- "pathPattern": "^/2/(?:oauth2/(?:token|revoke)|users/me)/?$"
4363
+ "pathPattern": "^/2/(?:oauth2/(?:token|revoke)|users/me)/?$|^/oauth/(?:request_token|authorize|authenticate|access_token)/?$|^/1\\.1/oauth/invalidate_token(?:\\.json)?/?$|^/_twin/assets/consent\\.(?:js|css)$"
4249
4364
  }
4250
4365
  ],
4251
4366
  "archetype": "crud",
@@ -4263,7 +4378,11 @@
4263
4378
  "access_token",
4264
4379
  "refresh_token",
4265
4380
  "grant",
4266
- "rate_window"
4381
+ "rate_window",
4382
+ "oauth1_app",
4383
+ "oauth1_request_token",
4384
+ "oauth1_token",
4385
+ "oauth1_grant"
4267
4386
  ],
4268
4387
  "serveExport": "createXIdentityTwinServer"
4269
4388
  },
package/dist/inject.cjs CHANGED
@@ -678,7 +678,7 @@ function fakeClientRequest(origin, method, fullPath, headers, callback) {
678
678
 
679
679
  // --- unclaimed path on a twinned shared host: refuse LOUDLY, never mis-route -----------------
680
680
  // The Cal.com incident class: a host two vendors share (www.googleapis.com) is twinned, the
681
- // request's PATH belongs to neither twin (`/calendar/v3/*` — Google Calendar has no pack), and
681
+ // request's PATH belongs to neither twin (`/drive/v3/*` — Google Drive has no pack; `/calendar/v3/*` was the case until the googlecalendar pack), and
682
682
  // the old catch-all matcher answered it from the WRONG pack with a vendor-shaped 404: fails open
683
683
  // AND plausible. The operator declared this host virtualized, so the traffic must not leak to the
684
684
  // real vendor either. The only honest outcome is a refusal that NAMES the situation.
@@ -1092,6 +1092,51 @@ function restore() {
1092
1092
 
1093
1093
  module.exports = { install, restore, readMap, resolveTwin, twinUrlVendor, twinnedHostVendors, unclaimedTwinnedHostPathMessage, appForwardHeaders, VENDOR_HOSTS };
1094
1094
 
1095
+ // WORLD TIME: an application process keeps the World's time, as its twins do (worldNow(), world-clock.ts). A World's
1096
+ // clock (TWIN_WORLD_CLOCK_FILE, one of world-clock.cjs's two forms) is read at most once a second, so a clock moved
1097
+ // while the application runs applies within a second; with no clock set, the process keeps the machine's time.
1098
+ // `Date` is the machine's own, wrapped so its `now()` and argument-less construction read the World's time; every other use
1099
+ // (a parsed date, `Date.UTC`, arithmetic, timers, `performance.now()`) is unchanged. The machine's own `Date.now` is
1100
+ // left under a global symbol, so the kernel's worldNow() applies a running clock's offset once in a process like this.
1101
+ // Without this, time moved in a World reaches its twins and not its application: a token the twins sign by the
1102
+ // World's time is refused by the application's (dub-stress findings row 107).
1103
+ function installWorldTime() {
1104
+ const file = process.env.TWIN_WORLD_CLOCK_FILE;
1105
+ if (!file || globalThis[Symbol.for('volter.machineDateNow')]) return;
1106
+ const fs = require('fs');
1107
+ const { parseClock, clockNowMs } = require('./world-clock.cjs');
1108
+ const MachineDate = Date;
1109
+ const machineNow = MachineDate.now.bind(MachineDate);
1110
+ let held = { checkedAt: -Infinity, mtimeMs: -1, clock: null };
1111
+ const clock = () => {
1112
+ const wall = machineNow();
1113
+ if (wall - held.checkedAt < 1000) return held.clock;
1114
+ held.checkedAt = wall;
1115
+ try {
1116
+ const mtimeMs = fs.statSync(file).mtimeMs;
1117
+ if (mtimeMs !== held.mtimeMs) held = { checkedAt: wall, mtimeMs, clock: parseClock(fs.readFileSync(file, 'utf8')) };
1118
+ } catch (error) {
1119
+ // no clock: the machine's time; a clock caught mid-write keeps the last one read until the next look
1120
+ if (error && error.code === 'ENOENT') held = { checkedAt: wall, mtimeMs: -1, clock: null };
1121
+ }
1122
+ return held.clock;
1123
+ };
1124
+ const worldMs = () => { const c = clock(); return c ? clockNowMs(c, machineNow()) : machineNow(); };
1125
+ // the machine's own Date, wrapped rather than subclassed, so identity holds everywhere: dates Node makes itself
1126
+ // (a file's mtime, structuredClone, a worker's message) are `instanceof Date`, `d.constructor === Date` stays true,
1127
+ // and Date.prototype is the machine's; only now() and argument-less construction read the World's time
1128
+ const WorldDate = new Proxy(MachineDate, {
1129
+ construct: (target, args, newTarget) => Reflect.construct(target, args.length ? args : [worldMs()], newTarget === WorldDate ? target : newTarget),
1130
+ // called as a function, Date() answers the current time's string, as the machine's does
1131
+ apply: () => new MachineDate(worldMs()).toString(),
1132
+ });
1133
+ Object.defineProperty(globalThis, Symbol.for('volter.machineDateNow'), { value: machineNow, enumerable: false });
1134
+ MachineDate.now = worldMs;
1135
+ Object.defineProperty(MachineDate.prototype, 'constructor', { value: WorldDate, writable: true, configurable: true, enumerable: false });
1136
+ globalThis.Date = WorldDate;
1137
+ }
1138
+
1095
1139
  // Auto-install as a preload (`--require`); strict refusal also applies without twin URLs.
1096
1140
  install();
1141
+ installWorldTime();
1097
1142
  installPrismaAdapterShim();
@@ -192,6 +192,30 @@ export declare function projectedPreconditionValue(precondition: TwinActionPreco
192
192
  export declare function projectResources(service: string, root?: string, opts?: {
193
193
  until?: string;
194
194
  }): TwinResource[];
195
+ /** A read of another pack's store that cannot choose: the subject it looks for (or, with none named, the store itself)
196
+ * is held by more than one service of the World. A reader answers it as the vendor answers a credential it cannot
197
+ * resolve; it is never a server error. */
198
+ export declare class OwnerStoreAmbiguousError extends Error {
199
+ constructor(owner: string, roots: string[], subject?: {
200
+ type: string;
201
+ id: string;
202
+ });
203
+ }
204
+ /**
205
+ * ANOTHER pack's rows, read by contract (architecture A3: one vendor's store split across two packs, xidentity's tokens
206
+ * read by x, googleoauth's by googlecalendar; scripts/architecture.test.ts holds the declared reader-owner pairs): the
207
+ * owner's tree, from wherever the World keeps the owner's store (`ownerStoreRoots`). Read only: the rows are frozen and
208
+ * shared. It claims no journal identity (the twin answering is the reader).
209
+ *
210
+ * With `subject`, the store that holds that subject (the token a request presents): `[]` when none does, and
211
+ * `OwnerStoreAmbiguousError` only when more than one does, so a second service running the owning pack never breaks a
212
+ * lookup of what only one of them issued. Without it, the one store there is, and `OwnerStoreAmbiguousError` when the
213
+ * World holds two.
214
+ */
215
+ export declare function projectOwnerResources(owner: string, root?: string, subject?: {
216
+ type: string;
217
+ id: string;
218
+ }): readonly TwinResource[];
195
219
  /** The local → vendor id aliases the landed copies carry: a read by the id a caller was handed before
196
220
  * its write was performed resolves to the row the vendor now owns. */
197
221
  export declare function subjectAliases(service: string, root?: string): Map<string, string>;