@volter/twin 0.1.0 → 0.1.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.
Files changed (84) hide show
  1. package/README.md +16 -2
  2. package/inject.cjs +453 -59
  3. package/package.json +12 -22
  4. package/src/actions.ts +234 -49
  5. package/src/blob-store.ts +136 -0
  6. package/src/changeset.ts +807 -0
  7. package/src/cli.ts +60 -10
  8. package/src/connector.ts +30 -7
  9. package/src/control-plane.ts +17 -1
  10. package/src/emit.ts +242 -0
  11. package/src/fork.ts +19 -7
  12. package/src/index.ts +139 -6
  13. package/src/lease.ts +4 -6
  14. package/src/lifecycle.ts +8 -0
  15. package/src/packRegistry.ts +248 -2
  16. package/src/plan.ts +131 -23
  17. package/src/proxy.ts +5 -2
  18. package/src/pushLedger.ts +116 -11
  19. package/src/queueLifecycle.ts +3 -4
  20. package/src/rateBudget.ts +1115 -0
  21. package/src/refs.ts +9 -10
  22. package/src/remote-execute.ts +16 -0
  23. package/src/scenario.ts +387 -0
  24. package/src/serve.ts +397 -15
  25. package/src/shadow.ts +86 -7
  26. package/src/storage.ts +76 -147
  27. package/src/sync.ts +63 -17
  28. package/src/twin-fetch.ts +115 -0
  29. package/src/validate.ts +6 -5
  30. package/src/world-clock.ts +33 -0
  31. package/src/world-store.ts +482 -0
  32. package/src/worldConfig.ts +4 -3
  33. package/dist/src/actions.d.ts +0 -138
  34. package/dist/src/actions.js +0 -201
  35. package/dist/src/args.d.ts +0 -3
  36. package/dist/src/args.js +0 -12
  37. package/dist/src/cli.d.ts +0 -2
  38. package/dist/src/cli.js +0 -425
  39. package/dist/src/connector.d.ts +0 -106
  40. package/dist/src/connector.js +0 -129
  41. package/dist/src/control-plane.d.ts +0 -21
  42. package/dist/src/control-plane.js +0 -40
  43. package/dist/src/egress.d.ts +0 -93
  44. package/dist/src/egress.js +0 -264
  45. package/dist/src/fork.d.ts +0 -126
  46. package/dist/src/fork.js +0 -206
  47. package/dist/src/index.d.ts +0 -42
  48. package/dist/src/index.js +0 -52
  49. package/dist/src/lease.d.ts +0 -50
  50. package/dist/src/lease.js +0 -80
  51. package/dist/src/packRegistry.d.ts +0 -34
  52. package/dist/src/packRegistry.js +0 -22
  53. package/dist/src/plan.d.ts +0 -97
  54. package/dist/src/plan.js +0 -151
  55. package/dist/src/proxy.d.ts +0 -25
  56. package/dist/src/proxy.js +0 -152
  57. package/dist/src/pushLedger.d.ts +0 -81
  58. package/dist/src/pushLedger.js +0 -130
  59. package/dist/src/queueLifecycle.d.ts +0 -62
  60. package/dist/src/queueLifecycle.js +0 -95
  61. package/dist/src/reconcile.d.ts +0 -58
  62. package/dist/src/reconcile.js +0 -137
  63. package/dist/src/refs.d.ts +0 -29
  64. package/dist/src/refs.js +0 -68
  65. package/dist/src/schemas.d.ts +0 -78
  66. package/dist/src/schemas.js +0 -50
  67. package/dist/src/serve.d.ts +0 -44
  68. package/dist/src/serve.js +0 -93
  69. package/dist/src/shadow.d.ts +0 -77
  70. package/dist/src/shadow.js +0 -138
  71. package/dist/src/status.d.ts +0 -31
  72. package/dist/src/status.js +0 -42
  73. package/dist/src/storage.d.ts +0 -119
  74. package/dist/src/storage.js +0 -535
  75. package/dist/src/sync.d.ts +0 -91
  76. package/dist/src/sync.js +0 -121
  77. package/dist/src/types.d.ts +0 -40
  78. package/dist/src/types.js +0 -1
  79. package/dist/src/validate.d.ts +0 -27
  80. package/dist/src/validate.js +0 -68
  81. package/dist/src/visualizer.d.ts +0 -13
  82. package/dist/src/visualizer.js +0 -133
  83. package/dist/src/worldConfig.d.ts +0 -9
  84. package/dist/src/worldConfig.js +0 -16
package/inject.cjs CHANGED
@@ -9,13 +9,12 @@
9
9
  // node --require @volter/twin/inject app.js
10
10
  // NODE_OPTIONS="--require @volter/twin/inject" <your start command>
11
11
  //
12
- // Twin URLs come from the environment (only configured vendors are redirected):
13
- // STRIPE_TWIN_URL=http://127.0.0.1:12111
14
- // SLACK_TWIN_URL GITHUB_TWIN_URL JIRA_TWIN_URL LINEAR_TWIN_URL S3_TWIN_URL
15
- // OPENROUTER_TWIN_URL CLERK_TWIN_URL WEBRISK_TWIN_URL RESEND_TWIN_URL SENTRY_TWIN_URL
16
- // POLAR_TWIN_URL ANTHROPIC_TWIN_URL ELEVENLABS_TWIN_URL GOOGLEFAVICON_TWIN_URL BROWSERASSETS_TWIN_URL
17
- // VITAL_TWIN_URL CALCOM_TWIN_URL OPENWEATHER_TWIN_URL GOOGLEMAPS_TWIN_URL MAPBOX_TWIN_URL STREAM_TWIN_URL
18
- // DYNAMODB_TWIN_URL TIMESTREAM_TWIN_URL (point these at the consolidated `aws` twin, same as S3_TWIN_URL)
12
+ // <VENDOR>_TWIN_URL for every pack in the catalog (uppercase vendor id, non-alnum → _),
13
+ // e.g. STRIPE_TWIN_URL=http://127.0.0.1:12111 or a hosted skin URL
14
+ // {twinsUrl}/{org}/{world}/{vendor} with VOLTER_TWINS_KEY set (R7c). Host claims come
15
+ // from pack-facts at runtime — this file carries no per-vendor roster to rot.
16
+ // AWS service-areas (DYNAMODB/TIMESTREAM/SESV2/SECRETSMANAGER/BEDROCK_TWIN_URL) point at
17
+ // the consolidated `aws` twin, same as S3_TWIN_URL.
19
18
  // …or combined: TWIN_INJECT="stripe=http://127.0.0.1:12111,github=http://127.0.0.1:12120"
20
19
  //
21
20
  // It patches http/https `request`/`get` and global `fetch` at the process
@@ -29,27 +28,133 @@ const { Readable } = require('stream');
29
28
  const Module = require('module');
30
29
  const { createPublicKey, createVerify } = require('crypto');
31
30
 
32
- // vendor predicate(hostname). A vendor is only active if its twin URL is set.
31
+ // ── PATH-AWARE DISAMBIGUATION (the shared-host case) ─────────────────────────────────────────
32
+ // A predicate is called as `(hostname, pathname)`. `pathname` is OPTIONAL and is `undefined`
33
+ // wherever the caller genuinely does not have one yet — most importantly the proxy's CONNECT
34
+ // handler, which sees only the SNI host before TLS is terminated. So the contract is:
35
+ //
36
+ // • pathname === undefined ⇒ "could this vendor serve ANY path on this host?" (candidacy).
37
+ // The ambient proxy uses this to decide whether to MITM at all, then re-resolves WITH the
38
+ // path once the request is decrypted.
39
+ // • pathname supplied ⇒ "does this vendor serve THIS path on this host?" (routing).
40
+ //
41
+ // Nearly every predicate ignores the second argument, because nearly every vendor owns its host
42
+ // outright. `www.googleapis.com` is the exception: `googleauth` serves the OAuth2 token exchange
43
+ // there and `youtube` serves the Data API v3 at /youtube/v3/*. Since `resolveTwin` returns the
44
+ // FIRST matching vendor in insertion order, a host-only match would give whichever key is declared
45
+ // first the entire host — silently breaking the other twin whenever both are configured in one
46
+ // world. The two predicates below are therefore mutually exclusive on that host BY PATH.
47
+ //
48
+ // THE SHARED-HOST RULE (Cal.com incident, 2026-08): a shared-host predicate must claim ONLY the
49
+ // paths its twin actually serves — never "the rest of the host". Claiming the remainder is how
50
+ // every `@googleapis/calendar` call (37 methods, all defaulting to www.googleapis.com) was routed
51
+ // into the googleauth/gemini twin and answered with a plausible Google-shaped 404: a mis-route
52
+ // that fails OPEN and PLAUSIBLE, strictly worse than a coverage gap. An UNCLAIMED path on a
53
+ // claimed host is refused LOUDLY instead (see unclaimedTwinnedHostPathMessage below): the error
54
+ // names the host as twinned and the path as belonging to no pack in this world.
55
+ /**
56
+ * Does this path belong to the OAuth2 token-exchange surface the googleauth twin actually serves?
57
+ * Exactly the routes gemini-twin.ts §googleauth models: `POST /token` (the oauth2.googleapis.com
58
+ * path, also answered on the legacy host) and `POST /oauth2/v4/token` (the legacy
59
+ * www.googleapis.com token path older google-auth-library versions default to). NOTHING else:
60
+ * /calendar/v3/*, /drive/v3/*, … are other Google products with no twin in this repo, and
61
+ * claiming them would mis-route those SDKs into an auth twin that answers with vendor-shaped
62
+ * 404s (the Cal.com incident).
63
+ */
64
+ // (The `googleauth` matcher itself is DATA on gemini's descriptor — packages/twin/gemini/src/index.ts.)
65
+
66
+ /**
67
+ * Does this path belong to Google's user-facing OAuth 2.0 / OIDC surface — the one the
68
+ * `googleoauth` PACK serves?
69
+ *
70
+ * accounts.google.com is NOT an API host: it is Google's whole sign-in web property (the account
71
+ * chooser, Gmail SSO, security settings, the Workspace admin login). Claiming the host outright
72
+ * would be the Cal.com mis-route incident again in a much larger blast radius — every Google
73
+ * sign-in a browser in the world performs would land in a twin that models one protocol. So this
74
+ * claims EXACTLY the OAuth/OIDC paths the pack serves and refuses the rest LOUDLY
75
+ * (unclaimedTwinnedHostPathMessage).
76
+ */
77
+ const isGoogleOAuthConsentPath = (p) =>
78
+ typeof p === 'string'
79
+ && (/^\/(o\/oauth2\/v2\/auth(\/oauthchooseaccount)?|o\/oauth2\/auth|o\/oauth2\/token|signin\/oauth\/error|\.well-known\/openid-configuration)\/?$/.test(p)
80
+ || /^\/_twin\//.test(p));
81
+ // ANCHORED, and `/o/oauth2/auth/oauthchooseaccount` deliberately absent. The first version had no
82
+ // terminal anchor, so `/o/oauth2/tokeninfo`, `/signin/oauth/errorPage` and
83
+ // `/.well-known/openid-configuration.json` were all claimed and all 404 — and its alternation also
84
+ // claimed an UN-VERSIONED chooser path the router does not serve (§9 round two). The claim set is
85
+ // now exactly `AUTH_PATHS` + the three singles + the twin-only prefix.
86
+ // NB `/o/oauth2/revoke` (the legacy revocation alias) is NOT claimed: the pack files it as a todo
87
+ // and its handler 404s it. An injector entry claiming a path the twin then refuses routes real
88
+ // traffic into a twin that cannot serve it — the Cal.com mis-route in miniature, and the reason
89
+ // every claim here is checked against what the router actually answers.
90
+
91
+ /**
92
+ * The oauth2.googleapis.com paths the googleoauth PACK serves: the token endpoint (all three
93
+ * spellings), revocation, and token introspection.
94
+ */
95
+ const isGoogleOAuthTokenPath = (p) =>
96
+ typeof p === 'string' && /^\/(token|oauth2\/v4\/token|revoke|tokeninfo|device\/code)\/?$/.test(p);
97
+ // `/device/code` is a DELIBERATE exception to "never claim a path the twin refuses", and the two
98
+ // rules genuinely collide here (§9 round two). The twin's own discovery document advertises
99
+ // `device_authorization_endpoint` — because Google's does, and dropping it would be infidelity — so
100
+ // a discovery-driven client WILL dial it. Unclaimed, that client escapes a sealed world and reaches
101
+ // the real Google; claimed, it gets the twin's honest 404. Containment wins: an unmodelled
102
+ // operation must fail LOCALLY, not succeed remotely.
103
+
104
+ /**
105
+ * The www.googleapis.com paths it serves: the v1 (PEM) and v3 (JWK) cert endpoints, and the v3
106
+ * userinfo alias. NOT `/oauth2/v2/*` — Google publishes no v2 certs endpoint at all, and the v2
107
+ * userinfo alias returns a DIFFERENT field set the pack does not model
108
+ * (`googleoauth.endpoints.userinfo_v2`, todo), so its handler refuses it.
109
+ */
110
+ const isGoogleOAuthApisPath = (p) =>
111
+ typeof p === 'string' && /^\/oauth2\/(v1\/certs|v3\/(certs|userinfo))\/?$/.test(p);
112
+
113
+ // vendor → predicate(hostname, pathname?). A vendor is only active if its twin URL is set.
114
+ // THE HAND TABLE'S ONLY RESIDENTS: keys served by KERNEL packages (browser-assets serves
115
+ // `browserassets` and `googlefavicon`; kernel packages carry no pack descriptor) and
116
+ // `googleoauth`, which must precede gemini's `googleauth` key on the shared token host
117
+ // (`resolveTwin` is first-match in insertion order, and this table is inserted before the
118
+ // compiled descriptors below). Every vendor pack's hosts live on its descriptor as DATA —
119
+ // host | suffix | hostPattern, pathPattern, exclude, key — compiled from pack-facts.json.
33
120
  const VENDOR_HOSTS = {
34
- stripe: (h) => h === 'api.stripe.com',
35
- slack: (h) => h === 'slack.com' || h === 'www.slack.com' || h === 'api.slack.com',
36
- github: (h) => h === 'api.github.com',
37
- jira: (h) => h === 'atlassian.net' || h.endsWith('.atlassian.net'),
38
- linear: (h) => h === 'api.linear.app',
39
- openrouter: (h) => h === 'openrouter.ai',
40
- anthropic: (h) => h === 'api.anthropic.com',
41
- clerk: (h) => h === 'api.clerk.com',
42
- webrisk: (h) => h === 'webrisk.googleapis.com',
43
- resend: (h) => h === 'api.resend.com',
44
- sentry: (h) => h === 'sentry.io' || h.endsWith('.ingest.sentry.io'),
45
- polar: (h) => h === 'api.polar.sh' || h === 'sandbox-api.polar.sh',
46
- elevenlabs: (h) => h === 'api.elevenlabs.io',
47
- vital: (h) => h === 'api.tryvital.io' || h === 'api.sandbox.tryvital.io',
48
- calcom: (h) => h === 'api.cal.com',
49
- openweather: (h) => h === 'api.openweathermap.org',
50
- googlemaps: (h) => h === 'maps.googleapis.com',
51
- mapbox: (h) => h === 'api.mapbox.com',
52
- stream: (h) => h === 'chat.stream-io-api.com',
121
+ // google-auth-library exchanges a locally-signed JWT here before ANY
122
+ // Vertex/GCS call a sealed world must model the exchange or every
123
+ // service-account flow dies in auth before reaching a modeled API.
124
+ //
125
+ // SHARES www.googleapis.com with the `youtube` PACK (whose `hosts` declaration carries the
126
+ // matching pathPattern): this vendor claims
127
+ // ONLY the token-exchange paths its twin serves there (`/token`, `/oauth2/v4/token` —
128
+ // gemini-twin.ts §googleauth; the `googleauth` key on gemini's descriptor). It used to claim "everything that
129
+ // is not YouTube" on that host, which silently swallowed Google Calendar (`/calendar/v3/*` —
130
+ // `@googleapis/calendar` defaults every method to www.googleapis.com) and answered it with
131
+ // plausible Google-shaped 404s from the wrong pack: the Cal.com mis-route incident. Unclaimed
132
+ // paths on this host now refuse loudly instead (unclaimedTwinnedHostPathMessage). With no
133
+ // pathname it still claims the host so the ambient proxy MITMs it; the post-decrypt resolve
134
+ // then routes each request to the right twin — or to the loud refusal.
135
+ //
136
+ // GOOGLEOAUTH — the real PACK (@volter/twin-googleoauth), declared BEFORE `googleauth` on purpose.
137
+ // `resolveTwin` returns the FIRST matching vendor in insertion order, so in a world where BOTH are
138
+ // configured the token endpoint goes to the pack rather than to the pack-less plumbing key — which
139
+ // is what you want, because the pack's token endpoint is a strict SUPERSET: it serves
140
+ // authorization_code and refresh_token (which googleauth has no state for) AND the
141
+ // jwt-bearer service-account exchange googleauth exists for, with a REAL RS256 id_token instead of
142
+ // gemini's `alg: none` stub. A world running ONLY the gemini twin still resolves through
143
+ // `googleauth` below, unchanged.
144
+ //
145
+ // FOUR hosts, every one path-scoped, because Google spreads ONE product across them and shares
146
+ // three of them with other products:
147
+ // • accounts.google.com the consent screen, the error page, OIDC discovery. NOT claimed
148
+ // outright: this host is Google's entire sign-in web property.
149
+ // • oauth2.googleapis.com token / revoke / tokeninfo. SHARED with `googleauth`.
150
+ // • www.googleapis.com the JWKS + legacy userinfo. SHARED with `googleauth` AND
151
+ // `youtube`, and disambiguated from both BY PATH.
152
+ // • openidconnect.googleapis.com the OIDC userinfo endpoint. Not shared.
153
+ googleoauth: (h, p) =>
154
+ (h === 'accounts.google.com' && (p === undefined || isGoogleOAuthConsentPath(p)))
155
+ || (h === 'oauth2.googleapis.com' && (p === undefined || isGoogleOAuthTokenPath(p)))
156
+ || (h === 'www.googleapis.com' && (p === undefined || isGoogleOAuthApisPath(p)))
157
+ || h === 'openidconnect.googleapis.com',
53
158
  googlefavicon: (h) => h === 'www.google.com' || h === 'google.com' || h === 't2.gstatic.com',
54
159
  browserassets: (h) =>
55
160
  h === 'cdnjs.cloudflare.com' ||
@@ -66,22 +171,51 @@ const VENDOR_HOSTS = {
66
171
  h === 'www.youtube.com' ||
67
172
  h === 'www.youtube-nocookie.com' ||
68
173
  h === 'i.ytimg.com',
69
- s3: (h) =>
70
- h === 's3.amazonaws.com' ||
71
- h.endsWith('.s3.amazonaws.com') ||
72
- /^s3[.-][a-z0-9-]+\.amazonaws\.com$/.test(h) ||
73
- /\.s3[.-][a-z0-9-]+\.amazonaws\.com$/.test(h) ||
74
- /^s3\.dualstack\.[a-z0-9-]+\.amazonaws\.com$/.test(h) ||
75
- /\.s3\.dualstack\.[a-z0-9-]+\.amazonaws\.com$/.test(h) ||
76
- h.endsWith('.s3-accelerate.amazonaws.com') ||
77
- h.endsWith('.s3-accelerate.dualstack.amazonaws.com') ||
78
- h.endsWith('.r2.cloudflarestorage.com'),
79
- // DynamoDB + Timestream are served by the SAME consolidated `aws` twin as s3 (it routes by
80
- // X-Amz-Target). Point DYNAMODB_TWIN_URL / TIMESTREAM_TWIN_URL at the aws twin's URL.
81
- dynamodb: (h) => /^dynamodb\.[a-z0-9-]+\.amazonaws\.com$/.test(h),
82
- timestream: (h) => /(^|\.)(ingest|query)\.timestream\.[a-z0-9-]+\.amazonaws\.com$/.test(h),
83
174
  };
84
175
 
176
+ // TWIN-PACK-CONTRACT migration: packs now declare their hosts as DATA on their descriptor
177
+ // (`hosts` on TwinPack), compiled into the committed pack-facts artifact — a plain JSON this
178
+ // preloaded, dependency-free module can `require` natively. The hand table above shrinks toward
179
+ // empty as entries move; a vendor present in both homes throws (dual declaration is the drift
180
+ // this migration ends, and a broken checkout should refuse to inject rather than half-route).
181
+ {
182
+ const { packs } = require('./generated/pack-facts.json');
183
+ for (const vendor of Object.keys(packs)) {
184
+ const rules = packs[vendor].hosts;
185
+ if (!rules || rules.length === 0) continue;
186
+ if (VENDOR_HOSTS[vendor]) {
187
+ throw new Error(`inject: vendor "${vendor}" declares hosts on its pack descriptor AND in the hand VENDOR_HOSTS table — one home per fact; delete the hand entry.`);
188
+ }
189
+ // Rules group by `key` (default: the vendor id) — an aws descriptor declares the s3 /
190
+ // dynamodb / … routing identities its consolidated twin answers under; gemini declares
191
+ // `googleauth`. A key matches when ANY include rule matches AND NO exclude rule matches
192
+ // (upstashredis: `.upstash.io` minus qstash and the vector hosts).
193
+ const byKey = new Map();
194
+ for (const rule of rules) {
195
+ const key = rule.key === undefined ? vendor : rule.key;
196
+ if (!byKey.has(key)) byKey.set(key, []);
197
+ byKey.get(key).push(rule);
198
+ }
199
+ for (const [key, keyRules] of byKey) {
200
+ if (VENDOR_HOSTS[key]) {
201
+ throw new Error(`inject: key "${key}" (pack ${vendor}) is declared twice — on this descriptor and in the hand VENDOR_HOSTS table or another pack's descriptor; a key has one home.`);
202
+ }
203
+ const compile = (rule) => {
204
+ const selectors = ['host', 'suffix', 'hostPattern'].filter((k) => rule[k] !== undefined).length;
205
+ if (selectors !== 1) throw new Error(`inject: key "${key}" (pack ${vendor}) rule ${JSON.stringify(rule)} — exactly one of host | suffix | hostPattern (pack-facts.json is hand-edited or stale)`);
206
+ const hostRe = rule.hostPattern === undefined ? null : new RegExp(rule.hostPattern);
207
+ const pathRe = rule.pathPattern === undefined ? null : new RegExp(rule.pathPattern);
208
+ return (h, p) =>
209
+ (rule.host !== undefined ? h === rule.host : rule.suffix !== undefined ? h.endsWith(rule.suffix) : hostRe.test(h))
210
+ && (pathRe === null || p === undefined || pathRe.test(p));
211
+ };
212
+ const includes = keyRules.filter((r) => r.exclude !== true).map(compile);
213
+ const excludes = keyRules.filter((r) => r.exclude === true).map(compile);
214
+ VENDOR_HOSTS[key] = (h, p) => includes.some((m) => m(h, p)) && !excludes.some((m) => m(h, p));
215
+ }
216
+ }
217
+ }
218
+
85
219
  function decodeBase64UrlJson(part) {
86
220
  try {
87
221
  const padded = String(part).replace(/-/g, '+').replace(/_/g, '/');
@@ -155,10 +289,25 @@ function installClerkFastifyTwinShim() {
155
289
 
156
290
  installClerkFastifyTwinShim();
157
291
 
292
+ // The exact inverse of readMap's env lookup: which vendor (if any) would the injector read
293
+ // this env var for? `S3_TWIN_URL` → 's3'; `AWS_TWIN_URL` → null (the consolidated aws twin
294
+ // answers as s3/dynamodb/timestream/sesv2/secretsmanager/bedrock — there IS no 'aws' vendor key, which is exactly the
295
+ // silent-inert-env trap this helper exists to make checkable). Shared with world-runtime so
296
+ // its "this injectEnv var is inert" warning can never drift from what readMap actually reads.
297
+ function twinEnvStem(vendor) {
298
+ return String(vendor).toUpperCase().replace(/[^A-Z0-9]/g, '_');
299
+ }
300
+
301
+ function twinUrlVendor(name) {
302
+ const match = /^([A-Z][A-Z0-9_]*)_TWIN_URL$/.exec(String(name || ''));
303
+ if (!match) return null;
304
+ return Object.keys(VENDOR_HOSTS).find((vendor) => twinEnvStem(vendor) === match[1]) || null;
305
+ }
306
+
158
307
  function readMap(env) {
159
308
  const map = {};
160
309
  for (const vendor of Object.keys(VENDOR_HOSTS)) {
161
- const url = env[`${vendor.toUpperCase()}_TWIN_URL`];
310
+ const url = env[`${twinEnvStem(vendor)}_TWIN_URL`];
162
311
  if (url) map[vendor] = url.replace(/\/$/, '');
163
312
  }
164
313
  if (env.TWIN_INJECT) {
@@ -173,9 +322,18 @@ function readMap(env) {
173
322
  return map;
174
323
  }
175
324
 
176
- function resolveTwin(hostname, map) {
325
+ /**
326
+ * Which twin (if any) serves `hostname` — and, when the caller knows it, `pathname`.
327
+ *
328
+ * `pathname` is optional and backward-compatible: predicates that take one argument ignore it, so
329
+ * a two-argument call is identical to the old behavior for every vendor that owns its host. It
330
+ * matters only where two vendors SHARE a host (see the VENDOR_HOSTS header). Callers that have a
331
+ * URL should always pass it; callers that genuinely do not (the proxy's CONNECT handler, which
332
+ * sees only the SNI name) omit it and get host-level candidacy.
333
+ */
334
+ function resolveTwin(hostname, map, pathname) {
177
335
  for (const vendor of Object.keys(VENDOR_HOSTS)) {
178
- if (map[vendor] && VENDOR_HOSTS[vendor](hostname)) return { vendor, origin: map[vendor] };
336
+ if (map[vendor] && VENDOR_HOSTS[vendor](hostname, pathname)) return { vendor, origin: map[vendor] };
179
337
  }
180
338
  return null;
181
339
  }
@@ -201,7 +359,53 @@ function strictEgressEnabled() {
201
359
  }
202
360
 
203
361
  function shouldBlockUntwinned(url) {
204
- return strictEgressEnabled() && url && !isLocalOrPrivateHost(url.hostname) && !resolveTwin(url.hostname, MAP);
362
+ return strictEgressEnabled() && url && !isLocalOrPrivateHost(url.hostname) && !resolveTwin(url.hostname, MAP, url.pathname);
363
+ }
364
+
365
+ // --- untwinned-host TLS failure naming (local mode) -----------------------------------------
366
+ // A world's activate/shell env points the whole TLS trust story at the session CA:
367
+ // `SSL_CERT_FILE` — which Node honors and which REPLACES the default root store (verified on
368
+ // Node 22: with it set, both https.request and fetch fail EVERY real-vendor chain) — plus
369
+ // `NODE_EXTRA_CA_CERTS`. Twinned hosts never notice: the injector intercepts before TLS, and
370
+ // the ambient proxy serves session-CA leaves. But a Node request to an UNTWINNED host in
371
+ // local mode escapes the injector, reaches the real vendor, and dies with a bare
372
+ // "unable to get local issuer certificate" — while curl (whose CA story the world also
373
+ // scopes, but whose escape hatch differs) sails through to the real vendor. That bare
374
+ // OpenSSL message cost the LibreChat blind-adoption run real time: NAME the situation on the
375
+ // error, exactly where it surfaces (the app's request/fetch rejection).
376
+ const TLS_ISSUER_CODES = new Set([
377
+ 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
378
+ 'UNABLE_TO_GET_ISSUER_CERT',
379
+ 'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
380
+ 'SELF_SIGNED_CERT_IN_CHAIN',
381
+ 'DEPTH_ZERO_SELF_SIGNED_CERT',
382
+ ]);
383
+
384
+ // Only annotate when the world's CA env is actually in force — a TLS failure without it is a
385
+ // genuinely bad chain, and blaming the world would mislead. VOLTER_WORLD_CA is the world's
386
+ // definitive marker (proxyEnvFor sets it alongside the trust vars); SSL_CERT_FILE is the var
387
+ // that mechanically replaces Node's root store.
388
+ function worldCaSealsTls() {
389
+ return Boolean(process.env.VOLTER_WORLD_CA || process.env.SSL_CERT_FILE);
390
+ }
391
+
392
+ function untwinnedTlsExplanation(hostname) {
393
+ return `[twin-inject] host ${hostname} is not twinned in this world; local mode does not intercept it — this traffic goes to the REAL vendor. (Node TLS trusts only the world session CA here, so the real vendor's certificate fails verification. Use sealed mode for a strict refusal, or add a twin for ${hostname}.)`;
394
+ }
395
+
396
+ function annotateUntwinnedTlsError(error, hostname) {
397
+ let cursor = error;
398
+ for (let depth = 0; cursor && depth < 5; depth += 1) {
399
+ const code = cursor.code;
400
+ const message = String(cursor.message || '');
401
+ if ((code && TLS_ISSUER_CODES.has(code)) || message.includes('unable to get local issuer certificate')) {
402
+ if (error instanceof Error && !String(error.message).includes('[twin-inject]')) {
403
+ error.message = `${error.message}\n${untwinnedTlsExplanation(hostname)}`;
404
+ }
405
+ return;
406
+ }
407
+ cursor = cursor.cause;
408
+ }
205
409
  }
206
410
 
207
411
  // --- request-args → { url, method, headers, callback } across every overload ---
@@ -237,13 +441,45 @@ function describe(args, defaultProtocol) {
237
441
 
238
442
  let ORIGINALS = null; // captured once, used to forward to the twin without re-entering the patch
239
443
 
444
+ // R7c (runtime contract): when *_TWIN_URL points at a hosted SKIN
445
+ // ({twinsUrl}/{org}/{world}/{vendor}), the transport key rides x-twins-key on every
446
+ // REWRITTEN request — its own header, never Authorization (that stays vendor
447
+ // semantics). One key per world: VOLTER_TWINS_KEY. Absent = local loopback twins,
448
+ // no key attached — exactly today's behavior.
449
+ function twinsKey() { return process.env.VOLTER_TWINS_KEY || null; }
450
+ function attachTwinsKey(headers) {
451
+ const key = twinsKey();
452
+ // Case-insensitive caller check (S4 review F6): node header iteration is
453
+ // last-write-wins case-insensitively, so ANY caller casing must suppress the attach.
454
+ if (key && !Object.keys(headers).some((k) => k.toLowerCase() === 'x-twins-key')) headers['x-twins-key'] = key;
455
+ return headers;
456
+ }
457
+
458
+ let SESSION_CA = undefined; // lazily read once; NODE_EXTRA_CA_CERTS set after process start
459
+ function sessionCa() {
460
+ if (SESSION_CA === undefined) {
461
+ const caPath = process.env.NODE_EXTRA_CA_CERTS;
462
+ try { SESSION_CA = caPath ? require('fs').readFileSync(caPath) : null; } catch { SESSION_CA = null; }
463
+ }
464
+ return SESSION_CA;
465
+ }
466
+
240
467
  function forward(origin, method, fullPath, headers, body) {
241
468
  return new Promise((resolve, reject) => {
242
469
  let target;
243
470
  try { target = new URL(origin); } catch (error) { reject(new Error(`bad twin URL "${origin}": ${error.message}`)); return; }
244
471
  const mod = target.protocol === 'https:' ? ORIGINALS.httpsRequest : ORIGINALS.httpRequest;
245
- const fwdHeaders = Object.assign({}, headers);
472
+ const fwdHeaders = attachTwinsKey(Object.assign({}, headers));
246
473
  delete fwdHeaders['accept-encoding']; delete fwdHeaders['Accept-Encoding'];
474
+ // A SKIN origin carries its namespace as a PATH (/{org}/{world}/{vendor}) — prefix
475
+ // it, or the hosted twin is addressed at the wrong root (R7c). Loopback origins have
476
+ // pathname '/', which prefixes to nothing: local behavior unchanged.
477
+ const originPath = target.pathname.replace(/\/$/, '');
478
+ fullPath = `${originPath}${fullPath}`;
479
+ // an https twin origin (a served world's door) verifies against the session
480
+ // CA even when NODE_EXTRA_CA_CERTS was set after process start (the rung-2
481
+ // dormant attach does exactly that)
482
+ const ca = target.protocol === 'https:' ? sessionCa() : null;
247
483
  const req = mod({
248
484
  protocol: target.protocol,
249
485
  hostname: target.hostname,
@@ -251,6 +487,7 @@ function forward(origin, method, fullPath, headers, body) {
251
487
  method,
252
488
  path: fullPath,
253
489
  headers: fwdHeaders,
490
+ ...(ca ? { ca } : {}),
254
491
  }, (res) => {
255
492
  const chunks = [];
256
493
  res.on('data', (chunk) => chunks.push(chunk));
@@ -295,9 +532,20 @@ function fakeClientRequest(origin, method, fullPath, headers, callback) {
295
532
  .then(({ statusCode, headers: resHeaders, body }) => {
296
533
  const res = Readable.from([body]);
297
534
  res.statusCode = statusCode;
298
- res.statusMessage = statusCode >= 400 ? 'Error' : 'OK';
535
+ // Preserve Node IncomingMessage semantics. npm's pacote path classifies a missing
536
+ // packument from statusCode + the canonical statusMessage; the generic "Error" placeholder
537
+ // made an ordinary 404 fatal before npm publish could proceed to PUT.
538
+ res.statusMessage = http.STATUS_CODES[statusCode] || '';
299
539
  res.headers = Object.assign({}, resHeaders);
300
- res.rawHeaders = [];
540
+ res.rawHeaders = Object.entries(resHeaders).flatMap(([name, value]) => [name, String(value)]);
541
+ res.trailers = {};
542
+ res.rawTrailers = [];
543
+ res.complete = true;
544
+ res.socket = socket;
545
+ res.connection = socket;
546
+ res.httpVersion = '1.1';
547
+ res.httpVersionMajor = 1;
548
+ res.httpVersionMinor = 1;
301
549
  if (typeof callback === 'function') callback(res);
302
550
  request.emit('response', res);
303
551
  if (typeof cb === 'function') cb();
@@ -317,9 +565,29 @@ function fakeClientRequest(origin, method, fullPath, headers, callback) {
317
565
  return request;
318
566
  }
319
567
 
320
- function blockedClientRequest(hostname) {
568
+ // --- unclaimed path on a twinned shared host: refuse LOUDLY, never mis-route -----------------
569
+ // The Cal.com incident class: a host two vendors share (www.googleapis.com) is twinned, the
570
+ // request's PATH belongs to neither twin (`/calendar/v3/*` — Google Calendar has no pack), and
571
+ // the old catch-all matcher answered it from the WRONG pack with a vendor-shaped 404: fails open
572
+ // AND plausible. The operator declared this host virtualized, so the traffic must not leak to the
573
+ // real vendor either. The only honest outcome is a refusal that NAMES the situation.
574
+
575
+ /** The configured vendors that claim `hostname` at host level (candidacy, pathname undefined). */
576
+ function twinnedHostVendors(hostname, map) {
577
+ return Object.keys(VENDOR_HOSTS).filter((vendor) => map[vendor] && VENDOR_HOSTS[vendor](hostname, undefined));
578
+ }
579
+
580
+ function unclaimedTwinnedHostPathMessage(hostname, pathname, map) {
581
+ const vendors = twinnedHostVendors(hostname, map);
582
+ return `[twin-inject] host ${hostname} is twinned in this world (configured twin${vendors.length === 1 ? '' : 's'}: ${vendors.join(', ')}), `
583
+ + `but path ${pathname} belongs to no pack in this world — no twin serves that API yet. `
584
+ + `Refusing loudly rather than answering from the wrong twin or leaking to the real vendor. `
585
+ + `If a twin for this API exists, set its *_TWIN_URL; otherwise this API needs a new pack.`;
586
+ }
587
+
588
+ function blockedClientRequest(message) {
321
589
  const request = new EventEmitter();
322
- const error = new Error(`[twin-inject] blocked untwinned external request to ${hostname}`);
590
+ const error = new Error(message);
323
591
  const socket = new EventEmitter();
324
592
  socket.connecting = false;
325
593
  socket.setKeepAlive = () => socket;
@@ -355,14 +623,33 @@ let installed = false;
355
623
  function patchedFactory(originalRequest, defaultProtocol) {
356
624
  return function patchedRequest(...args) {
357
625
  const { url, method, headers, callback } = describe(args, defaultProtocol);
358
- const twin = url && resolveTwin(url.hostname, MAP);
626
+ const twin = url && resolveTwin(url.hostname, MAP, url.pathname);
359
627
  if (twin) {
360
628
  const fwdHeaders = Object.assign({}, headers);
361
629
  if (!fwdHeaders.host && !fwdHeaders.Host) fwdHeaders.host = url.host;
362
630
  return fakeClientRequest(twin.origin, method, url.pathname + url.search, fwdHeaders, callback);
363
631
  }
364
- if (shouldBlockUntwinned(url)) return blockedClientRequest(url.hostname);
365
- return originalRequest.apply(this, args);
632
+ // No twin claims this path — but if configured twins claim the HOST, the path is an unclaimed
633
+ // slice of a shared, virtualized host (the Cal.com /calendar/v3 case): refuse loudly, in every
634
+ // mode, before the strict-egress check gets a chance to phrase it as an ordinary untwinned host.
635
+ if (url && twinnedHostVendors(url.hostname, MAP).length > 0) {
636
+ return blockedClientRequest(unclaimedTwinnedHostPathMessage(url.hostname, url.pathname, MAP));
637
+ }
638
+ if (shouldBlockUntwinned(url)) return blockedClientRequest(`[twin-inject] blocked untwinned external request to ${url.hostname}`);
639
+ const request = originalRequest.apply(this, args);
640
+ // Local mode, untwinned external host, world CA in force: the TLS handshake against the
641
+ // real vendor is about to fail with a bare OpenSSL message — annotate it in-place via an
642
+ // emit wrapper (NOT an 'error' listener, which would swallow the crash-on-unhandled
643
+ // semantics callers rely on).
644
+ if (url && !isLocalOrPrivateHost(url.hostname) && worldCaSealsTls() && request && typeof request.emit === 'function') {
645
+ const originalEmit = request.emit;
646
+ const hostname = url.hostname;
647
+ request.emit = function annotatedEmit(event, ...rest) {
648
+ if (event === 'error' && rest[0]) annotateUntwinnedTlsError(rest[0], hostname);
649
+ return originalEmit.call(this, event, ...rest);
650
+ };
651
+ }
652
+ return request;
366
653
  };
367
654
  }
368
655
 
@@ -391,23 +678,130 @@ function install(mapOverride) {
391
678
  try { urlStr = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input && input.url; } catch { urlStr = undefined; }
392
679
  let url = null;
393
680
  if (urlStr) { try { url = new URL(urlStr); } catch { url = null; } }
394
- const twin = url && resolveTwin(url.hostname, MAP);
681
+ const twin = url && resolveTwin(url.hostname, MAP, url.pathname);
395
682
  if (!twin) {
683
+ // Same shared-host rule as patchedFactory: an unclaimed path on a twinned host refuses
684
+ // loudly in every mode — never mis-routed, never leaked to the real vendor.
685
+ if (url && twinnedHostVendors(url.hostname, MAP).length > 0) {
686
+ return Promise.reject(new Error(unclaimedTwinnedHostPathMessage(url.hostname, url.pathname, MAP)));
687
+ }
396
688
  if (shouldBlockUntwinned(url)) return Promise.reject(new Error(`[twin-inject] blocked untwinned external fetch to ${url.hostname}`));
397
- return originalFetch(input, init);
689
+ const passthrough = originalFetch(input, init);
690
+ // Same untwinned-host TLS naming as patchedFactory: fetch wraps the issuer error in
691
+ // "fetch failed" with the code down the `cause` chain — annotate the surfaced error.
692
+ if (url && !isLocalOrPrivateHost(url.hostname) && worldCaSealsTls() && passthrough && typeof passthrough.catch === 'function') {
693
+ const hostname = url.hostname;
694
+ return passthrough.catch((error) => {
695
+ annotateUntwinnedTlsError(error, hostname);
696
+ throw error;
697
+ });
698
+ }
699
+ return passthrough;
398
700
  }
399
- const target = `${twin.origin}${url.pathname}${url.search}`;
701
+ const target = `${twin.origin.replace(/\/$/, '')}${url.pathname}${url.search}`;
400
702
  const reqInit = Object.assign({}, init);
401
703
  if (!reqInit.method && typeof input === 'object' && input && input.method) reqInit.method = input.method;
402
704
  if (!reqInit.headers && typeof input === 'object' && input && input.headers) reqInit.headers = input.headers;
403
705
  if (reqInit.body === undefined && typeof input === 'object' && input && input.body != null) reqInit.body = input.body;
706
+ // Preserve the vendor destination for twins that serve more than one registry flavor from
707
+ // one local origin. Host is honored by node:http; this explicit internal header is the
708
+ // fetch-safe fallback when a runtime normalizes the forbidden Host header itself.
709
+ const redirectedHeaders = new Headers(reqInit.headers || {});
710
+ redirectedHeaders.set('x-volter-twin-original-host', url.host);
711
+ { const key = twinsKey(); if (key && !redirectedHeaders.has('x-twins-key')) redirectedHeaders.set('x-twins-key', key); }
712
+ reqInit.headers = redirectedHeaders;
713
+ // S4 review F3: undici's default redirect-follow forwards CUSTOM headers across
714
+ // origins — a twin answering 30x to an external URL would carry x-twins-key to a
715
+ // REAL host. Follow ourselves instead: a twin-resolvable hop re-enters this patch
716
+ // (key re-attached correctly); anything else is fetched WITHOUT the key. The node
717
+ // path already behaves this way (the SDK sees the 30x and retries through the
718
+ // patch), so the two rewrite paths now agree.
719
+ if (twinsKey() && (reqInit.redirect === undefined || reqInit.redirect === 'follow')) {
720
+ reqInit.redirect = 'manual';
721
+ const followedFetch = async () => {
722
+ let hop = await originalFetch(target, reqInit);
723
+ let hopUrl = target;
724
+ let hopMethod = String(reqInit.method || 'GET').toUpperCase();
725
+ let hopBody = reqInit.body;
726
+ for (let hops = 0; hops < 5; hops += 1) {
727
+ const location = hop.status >= 300 && hop.status < 400 ? hop.headers.get('location') : null;
728
+ if (!location) return hop;
729
+ // RFC semantics per hop (audit M7): 303 → GET; 301/302 demote POST to GET
730
+ // (the browser convention SDKs expect); 307/308 preserve method AND body.
731
+ if (hop.status === 303 || ((hop.status === 301 || hop.status === 302) && hopMethod === 'POST')) {
732
+ hopMethod = 'GET';
733
+ hopBody = undefined;
734
+ }
735
+ // Resolve against the CURRENT hop (a relative Location from hop 2 is
736
+ // relative to hop 2), cancel the interim body, and re-enter the PATCHED
737
+ // fetch with the ORIGINAL headers minus the twins key — a twin-resolvable
738
+ // hop gets the key re-attached by the patch; an external hop stays keyless.
739
+ const nextUrl = new URL(location, hopUrl);
740
+ try { hop.body && (await hop.body.cancel()); } catch { /* interim body */ }
741
+ const hopHeaders = new Headers(reqInit.headers || {});
742
+ hopHeaders.delete('x-twins-key');
743
+ hopHeaders.delete('x-volter-twin-original-host');
744
+ hop = await globalThis.fetch(nextUrl, {
745
+ method: hopMethod,
746
+ headers: hopHeaders,
747
+ ...(hopBody === undefined ? {} : { body: hopBody }),
748
+ redirect: 'manual',
749
+ });
750
+ hopUrl = nextUrl;
751
+ }
752
+ return hop;
753
+ };
754
+ return followedFetch();
755
+ }
756
+ // an https twin origin (a served world's door): undici cannot use a
757
+ // session CA set after process start, so route through forward() — the
758
+ // node-https path that honors it — and synthesize a Response. Falls
759
+ // back to the native fetch for streaming bodies (rare for SDK JSON).
760
+ if (/^https:/i.test(twin.origin) && sessionCa()) {
761
+ const bodyValue = reqInit.body;
762
+ if (bodyValue === undefined || typeof bodyValue === 'string' || Buffer.isBuffer(bodyValue)) {
763
+ const headerObj = {};
764
+ const rawHeaders = reqInit.headers;
765
+ if (rawHeaders && typeof rawHeaders.forEach === 'function') rawHeaders.forEach((value, key) => { headerObj[key] = value; });
766
+ else if (rawHeaders) Object.assign(headerObj, rawHeaders);
767
+ headerObj.host = url.host; // the vendor host, for the door's Host-routing fidelity
768
+ // forward() is the SINGLE owner of origin-path prefixing (S4 review F1: this
769
+ // branch used to prefix too, doubling the namespace on pathed https skins).
770
+ const fullPath = `${url.pathname}${url.search}`;
771
+ return forward(twin.origin, String(reqInit.method || 'GET').toUpperCase(), fullPath, headerObj, bodyValue === undefined ? undefined : Buffer.from(bodyValue))
772
+ .then((res) => new Response(res.body, { status: res.statusCode, headers: res.headers }));
773
+ }
774
+ }
404
775
  return originalFetch(target, reqInit);
405
776
  };
406
777
  }
407
778
 
779
+ // Bun replaces `node-fetch` imports with an internal shim bound to the
780
+ // NATIVE fetch, so a library that imports node-fetch bypasses the
781
+ // globalThis.fetch patch above entirely (gaxios v7 does exactly this when
782
+ // `window` is absent — every google-auth token exchange would leak). The
783
+ // shim's exports object is mutable and later ESM imports observe the
784
+ // mutation, so re-point `default` at whatever globalThis.fetch is at CALL
785
+ // time — which keeps redirect + strict-egress semantics, and stays correct
786
+ // if restore() later swaps the patched fetch back out.
787
+ if (typeof Bun !== 'undefined') {
788
+ try {
789
+ const nodeFetchShim = require('node-fetch');
790
+ if (nodeFetchShim && (typeof nodeFetchShim === 'object' || typeof nodeFetchShim === 'function')) {
791
+ nodeFetchShim.default = (input, init) => globalThis.fetch(input, init);
792
+ }
793
+ } catch { /* node-fetch not importable here — nothing to seal */ }
794
+ }
795
+
408
796
  installed = true;
409
- for (const vendor of active) {
410
- process.stderr.write(`[twin-inject] ${vendor}: redirecting to ${MAP[vendor]}\n`);
797
+ // Exact subprocess-output receipts still need the interception itself, but
798
+ // infrastructure diagnostics must not become application stderr. The
799
+ // attach command can opt those descendants into silence without weakening
800
+ // routing or the sealed-world boundary.
801
+ if (process.env.VOLTER_TWIN_INJECT_QUIET !== '1') {
802
+ for (const vendor of active) {
803
+ process.stderr.write(`[twin-inject] ${vendor}: redirecting to ${MAP[vendor]}\n`);
804
+ }
411
805
  }
412
806
  return { active };
413
807
  }
@@ -423,7 +817,7 @@ function restore() {
423
817
  MAP = {};
424
818
  }
425
819
 
426
- module.exports = { install, restore, readMap, resolveTwin, VENDOR_HOSTS };
820
+ module.exports = { install, restore, readMap, resolveTwin, twinUrlVendor, twinnedHostVendors, unclaimedTwinnedHostPathMessage, VENDOR_HOSTS };
427
821
 
428
822
  // Auto-install when loaded as a preload (`--require`). No-op if no twin URLs are set.
429
823
  install();