archal 0.9.19 → 0.9.20

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 (92) hide show
  1. package/README.md +9 -1
  2. package/agents/github-octokit/.archal.json +8 -0
  3. package/agents/github-octokit/Dockerfile +8 -0
  4. package/agents/github-octokit/README.md +113 -0
  5. package/agents/github-octokit/agent.mjs +54 -0
  6. package/agents/github-octokit/package.json +9 -0
  7. package/agents/github-octokit/scenarios/test-repo-access.md +27 -0
  8. package/agents/google-workspace-local-tools/Dockerfile +6 -0
  9. package/agents/google-workspace-local-tools/README.md +58 -0
  10. package/agents/google-workspace-local-tools/agent.mjs +196 -0
  11. package/agents/google-workspace-local-tools/archal-harness.json +7 -0
  12. package/agents/google-workspace-local-tools/run-input.yaml +16 -0
  13. package/agents/google-workspace-local-tools/scenario.md +29 -0
  14. package/agents/hermes/.archal.json +8 -0
  15. package/agents/hermes/Dockerfile +46 -0
  16. package/agents/hermes/README.md +87 -0
  17. package/agents/hermes/SOUL.md +27 -0
  18. package/agents/hermes/config.yaml +34 -0
  19. package/agents/hermes/drive.mjs +113 -0
  20. package/agents/hermes/scenarios/stripe-customers-read-only.md +32 -0
  21. package/agents/openclaw/.archal.json +8 -0
  22. package/agents/openclaw/Dockerfile +96 -0
  23. package/agents/openclaw/README.md +120 -0
  24. package/agents/openclaw/drive.mjs +311 -0
  25. package/agents/openclaw/package.json +9 -0
  26. package/agents/openclaw/scenarios/github-issue-triage-read-only.md +44 -0
  27. package/agents/openclaw/workspace/AGENTS.md +23 -0
  28. package/agents/openclaw/workspace/IDENTITY.md +8 -0
  29. package/agents/openclaw/workspace/SOUL.md +14 -0
  30. package/agents/openclaw/workspace/TOOLS.md +35 -0
  31. package/agents/pagination-test/README.md +24 -0
  32. package/agents/pagination-test/scenario.md +24 -0
  33. package/agents/replay-capsule-harness/README.md +29 -0
  34. package/agents/replay-capsule-harness/observability-install-offline-e2e.mts +1517 -0
  35. package/agents/replay-capsule-harness/replay-capsule-e2e.mjs +104 -0
  36. package/clone-assets/apify/tools.json +256 -22
  37. package/clone-assets/calcom/tools.json +510 -0
  38. package/clone-assets/clickup/tools.json +1258 -0
  39. package/clone-assets/customerio/tools.json +386 -0
  40. package/clone-assets/datadog/tools.json +734 -0
  41. package/clone-assets/github/tools.json +306 -25
  42. package/clone-assets/gitlab/tools.json +999 -0
  43. package/clone-assets/google-workspace/tools.json +18 -6
  44. package/clone-assets/hubspot/tools.json +1406 -0
  45. package/clone-assets/jira/fidelity.json +1 -1
  46. package/clone-assets/jira/tools.json +266 -543
  47. package/clone-assets/linear/tools.json +238 -40
  48. package/clone-assets/ownerrez/tools.json +548 -0
  49. package/clone-assets/pricelabs/tools.json +343 -0
  50. package/clone-assets/sentry/tools.json +745 -0
  51. package/clone-assets/slack/tools.json +1 -2
  52. package/clone-assets/stripe/tools.json +185 -46
  53. package/clone-assets/supabase/tools.json +437 -0
  54. package/clone-assets/unipile/tools.json +408 -0
  55. package/clone-assets/webflow/tools.json +415 -0
  56. package/dist/autoloop-worker-types-BEb_E44z.d.cts +196 -0
  57. package/dist/cli.cjs +150299 -87430
  58. package/dist/commands/autoloop-hosted-worker.cjs +43942 -0
  59. package/dist/commands/autoloop-hosted-worker.d.cts +143 -0
  60. package/dist/commands/autoloop-pr-verification.cjs +4227 -0
  61. package/dist/commands/autoloop-pr-verification.d.cts +17 -0
  62. package/dist/{vitest/chunk-L36NXAU6.js → commands/autoloop-result-parser.cjs} +16445 -18852
  63. package/dist/commands/autoloop-result-parser.d.cts +39 -0
  64. package/dist/commands/autoloop-worker.cjs +36163 -0
  65. package/dist/commands/autoloop-worker.d.cts +97 -0
  66. package/dist/harness.cjs +1 -0
  67. package/dist/index.cjs +1 -1
  68. package/dist/replay.cjs +49624 -0
  69. package/dist/replay.d.cts +4625 -0
  70. package/dist/scenarios.cjs +80343 -0
  71. package/dist/scenarios.d.cts +562 -0
  72. package/dist/vitest/chunk-6CBYFCFK.js +4667 -0
  73. package/dist/vitest/chunk-ARVS45PP.js +2764 -0
  74. package/dist/vitest/index.cjs +6011 -75261
  75. package/dist/vitest/index.d.ts +7 -6
  76. package/dist/vitest/index.js +8 -8
  77. package/dist/vitest/runtime/hosted-session-reaper.cjs +792 -34359
  78. package/dist/vitest/runtime/hosted-session-reaper.js +1 -1
  79. package/dist/vitest/runtime/setup-files.js +2 -2
  80. package/package.json +8 -3
  81. package/skills/archal-agent/SKILL.md +87 -0
  82. package/skills/{attach → autoloop}/SKILL.md +94 -120
  83. package/skills/autoloop/references/hosted-sources.md +62 -0
  84. package/skills/autoloop/references/trace-schema-mapping.md +73 -0
  85. package/skills/eval/SKILL.md +35 -1
  86. package/skills/install-agent/SKILL.md +221 -0
  87. package/skills/onboard/SKILL.md +73 -5
  88. package/skills/scenario/SKILL.md +19 -4
  89. package/skills/seed/SKILL.md +237 -0
  90. package/dist/seed/dynamic-generator.cjs +0 -45687
  91. package/dist/seed/dynamic-generator.d.cts +0 -106
  92. package/dist/vitest/chunk-WZ7SA4CK.js +0 -47369
@@ -0,0 +1,4667 @@
1
+ import {
2
+ DEFAULT_HOSTED_API_BASE_URL,
3
+ DEFAULT_READY_TIMEOUT_MS,
4
+ DEFAULT_RENEW_INTERVAL_MS,
5
+ DEFAULT_SESSION_TTL_SECONDS,
6
+ HostedSessionClient,
7
+ MIN_READY_TIMEOUT_MS,
8
+ MIN_RENEW_INTERVAL_MS,
9
+ createHostedAuthLease,
10
+ decodeConfig,
11
+ fileExists,
12
+ getSessionIdFilePath,
13
+ isProcessAlive,
14
+ loadFileSeedsIntoClones,
15
+ normalizeApiBaseUrl,
16
+ parsePositiveInteger,
17
+ redactSessionSnapshot,
18
+ requestedSeedsMatchSession,
19
+ sleep,
20
+ trimEnv
21
+ } from "./chunk-ARVS45PP.js";
22
+
23
+ // src/runtime-module-resolution.ts
24
+ import { dirname, extname, isAbsolute, resolve } from "path";
25
+ import { fileURLToPath } from "url";
26
+ function resolveRuntimeModuleExtension(currentExtension, buildExtension) {
27
+ if (currentExtension === ".ts" || currentExtension === ".mts" || currentExtension === ".cts") {
28
+ return currentExtension;
29
+ }
30
+ return buildExtension ?? currentExtension ?? ".js";
31
+ }
32
+ function resolveRuntimeModuleFromFile(currentModuleFile, relativePath, buildExtension) {
33
+ const currentExtension = extname(currentModuleFile) || ".js";
34
+ const resolvedExtension = resolveRuntimeModuleExtension(currentExtension, buildExtension);
35
+ return resolve(dirname(currentModuleFile), relativePath.replace(/\.js$/, resolvedExtension));
36
+ }
37
+ function resolveRuntimeModule(relativePath, buildExtension) {
38
+ const currentModuleFile = typeof __filename === "string" && isAbsolute(__filename) ? __filename : fileURLToPath(import.meta.url);
39
+ return resolveRuntimeModuleFromFile(currentModuleFile, relativePath, buildExtension);
40
+ }
41
+
42
+ // src/runtime/session-snapshot.ts
43
+ var ARCHAL_VITEST_CONFIG_ENV = "ARCHAL_VITEST_CONFIG";
44
+ function readArchalVitestConfigFromEnv() {
45
+ const encoded = process.env[ARCHAL_VITEST_CONFIG_ENV];
46
+ if (!encoded) {
47
+ throw new Error(`Missing ${ARCHAL_VITEST_CONFIG_ENV}. archalVitestProject() must set worker config before bootstrap.`);
48
+ }
49
+ return readArchalVitestConfig(encoded);
50
+ }
51
+ function readArchalVitestConfig(encoded) {
52
+ if (!encoded) {
53
+ throw new Error(`Missing ${ARCHAL_VITEST_CONFIG_ENV}. archalVitestProject() must set worker config before bootstrap.`);
54
+ }
55
+ return decodeConfig(encoded);
56
+ }
57
+
58
+ // ../route-runtime-core/src/errors.ts
59
+ var RouteRuntimePolicyError = class extends Error {
60
+ code;
61
+ service;
62
+ url;
63
+ constructor(input) {
64
+ super("Network request failed");
65
+ this.name = "TypeError";
66
+ Object.defineProperties(this, {
67
+ code: { value: input.code, enumerable: false },
68
+ service: { value: input.service, enumerable: false },
69
+ url: { value: input.url, enumerable: false },
70
+ policyMessage: { value: input.message, enumerable: false }
71
+ });
72
+ }
73
+ };
74
+
75
+ // ../route-runtime-core/src/runtime.ts
76
+ import { createRequire } from "module";
77
+ import { isAbsolute as isAbsolute2 } from "path";
78
+
79
+ // ../../clones/core/src/errors.ts
80
+ function errorMessage(error) {
81
+ return error instanceof Error ? error.message : String(error);
82
+ }
83
+
84
+ // ../route-runtime-core/src/runtime-request.ts
85
+ var DEFAULT_HTTP_PROTOCOL = "http:";
86
+ var DEFAULT_HTTPS_PROTOCOL = "https:";
87
+ function normalizeProtocol(protocol, fallback) {
88
+ if (!protocol) return fallback;
89
+ return protocol.endsWith(":") ? protocol : `${protocol}:`;
90
+ }
91
+ function normalizeHost(value) {
92
+ if (value.includes("://")) {
93
+ const parsed = new URL(value);
94
+ return {
95
+ hostname: parsed.hostname,
96
+ port: parsed.port || void 0
97
+ };
98
+ }
99
+ if (value.startsWith("[")) {
100
+ const closingBracketIndex = value.indexOf("]");
101
+ if (closingBracketIndex !== -1) {
102
+ const hostname = value.slice(1, closingBracketIndex);
103
+ const port = value.slice(closingBracketIndex + 1).replace(/^:/, "") || void 0;
104
+ return { hostname, port };
105
+ }
106
+ }
107
+ const lastColonIndex = value.lastIndexOf(":");
108
+ if (lastColonIndex === -1 || value.indexOf(":") !== lastColonIndex) {
109
+ return { hostname: value };
110
+ }
111
+ return {
112
+ hostname: value.slice(0, lastColonIndex),
113
+ port: value.slice(lastColonIndex + 1) || void 0
114
+ };
115
+ }
116
+ function buildUrlFromOptions(options, defaultProtocol) {
117
+ const protocol = normalizeProtocol(
118
+ typeof options.protocol === "string" ? options.protocol : void 0,
119
+ defaultProtocol
120
+ );
121
+ const rawHost = typeof options.hostname === "string" ? options.hostname : typeof options.host === "string" ? normalizeHost(options.host).hostname : "localhost";
122
+ const rawPort = options.port == null ? typeof options.host === "string" ? normalizeHost(options.host).port : void 0 : String(options.port);
123
+ const base = `${protocol}//${rawHost}${rawPort ? `:${rawPort}` : ""}`;
124
+ const path = typeof options.path === "string" && options.path.length > 0 ? options.path : "/";
125
+ return new URL(path, base);
126
+ }
127
+ function applyRequestOptionsToUrl(requestUrl, options, defaultProtocol) {
128
+ const effectiveUrl = new URL(requestUrl.toString());
129
+ const protocol = normalizeProtocol(
130
+ typeof options.protocol === "string" ? options.protocol : void 0,
131
+ effectiveUrl.protocol || defaultProtocol
132
+ );
133
+ effectiveUrl.protocol = protocol;
134
+ const normalizedHost = typeof options.hostname === "string" ? normalizeHost(options.hostname) : typeof options.host === "string" ? normalizeHost(options.host) : null;
135
+ if (normalizedHost) {
136
+ effectiveUrl.hostname = normalizedHost.hostname;
137
+ effectiveUrl.port = normalizedHost.port ?? "";
138
+ }
139
+ if (options.port != null) {
140
+ effectiveUrl.port = String(options.port);
141
+ }
142
+ if (typeof options.path === "string" && options.path.length > 0) {
143
+ const pathUrl = new URL(options.path, `${effectiveUrl.protocol}//${effectiveUrl.host}`);
144
+ effectiveUrl.pathname = pathUrl.pathname;
145
+ effectiveUrl.search = pathUrl.search;
146
+ effectiveUrl.hash = pathUrl.hash;
147
+ }
148
+ return effectiveUrl;
149
+ }
150
+ function normalizeHeaders(headers) {
151
+ if (!headers || Array.isArray(headers)) {
152
+ return headers;
153
+ }
154
+ const copy = {};
155
+ for (const [key, value] of Object.entries(headers)) {
156
+ if (value === void 0) continue;
157
+ copy[key] = value;
158
+ }
159
+ delete copy["host"];
160
+ delete copy["Host"];
161
+ return copy;
162
+ }
163
+ var ROUTE_CONTROL_AUTHORIZATION_HEADER = "x-route-authorization";
164
+ var ROUTE_SERVICE_ORIGIN_HEADER = "x-route-service-origin";
165
+ var ROUTED_CONTROL_HEADERS = /* @__PURE__ */ new Set([
166
+ ROUTE_CONTROL_AUTHORIZATION_HEADER,
167
+ ROUTE_SERVICE_ORIGIN_HEADER,
168
+ "proxy-authorization"
169
+ ]);
170
+ function isArchalInternalHeaderName(name) {
171
+ const normalized = name.toLowerCase();
172
+ return normalized === "x-archal" || normalized.startsWith("x-archal-");
173
+ }
174
+ function sanitizeRoutedHeaders(headers, service) {
175
+ const originalAuthorization = headers.get("authorization");
176
+ for (const header of [...headers.keys()]) {
177
+ const normalized = header.toLowerCase();
178
+ if (isArchalInternalHeaderName(normalized) || ROUTED_CONTROL_HEADERS.has(normalized)) {
179
+ headers.delete(header);
180
+ }
181
+ }
182
+ if (service.forwardedAuthorizationHeader) {
183
+ headers.delete(service.forwardedAuthorizationHeader);
184
+ }
185
+ return originalAuthorization;
186
+ }
187
+ function applyRoutedHeaders(headers, service) {
188
+ const originalAuthorization = sanitizeRoutedHeaders(headers, service);
189
+ const routedRequestHeaders = typeof service.routedRequestHeaders === "function" ? service.routedRequestHeaders() : service.routedRequestHeaders;
190
+ for (const [name, value] of Object.entries(routedRequestHeaders ?? {})) {
191
+ headers.set(name, value);
192
+ }
193
+ if (!service.forwardedAuthorizationHeader) {
194
+ return;
195
+ }
196
+ if (originalAuthorization) {
197
+ headers.set(service.forwardedAuthorizationHeader, originalAuthorization);
198
+ }
199
+ }
200
+ function buildPatchedHeaders(headers, service) {
201
+ const normalizedHeaders = new Headers(normalizeHeaders(headers));
202
+ applyRoutedHeaders(normalizedHeaders, service);
203
+ const patchedHeaders = {};
204
+ for (const [name, value] of normalizedHeaders.entries()) {
205
+ patchedHeaders[name] = value;
206
+ }
207
+ return patchedHeaders;
208
+ }
209
+ function parseRequestArgs(defaultProtocol, args) {
210
+ let callback;
211
+ for (let index = args.length - 1; index >= 0; index -= 1) {
212
+ const candidate = args[index];
213
+ if (typeof candidate === "function") {
214
+ callback = candidate;
215
+ break;
216
+ }
217
+ }
218
+ const first = args[0];
219
+ const second = args[1];
220
+ if (first instanceof URL || typeof first === "string") {
221
+ const baseRequestUrl = first instanceof URL ? new URL(first.toString()) : new URL(first, `${defaultProtocol}//localhost`);
222
+ const options = second && typeof second === "object" && !(second instanceof URL) ? { ...second } : {};
223
+ return {
224
+ requestUrl: applyRequestOptionsToUrl(baseRequestUrl, options, defaultProtocol),
225
+ options,
226
+ callback
227
+ };
228
+ }
229
+ if (first && typeof first === "object") {
230
+ const options = { ...first };
231
+ return {
232
+ requestUrl: buildUrlFromOptions(options, defaultProtocol),
233
+ options,
234
+ callback
235
+ };
236
+ }
237
+ return {
238
+ requestUrl: new URL(`${defaultProtocol}//localhost/`),
239
+ options: {},
240
+ callback
241
+ };
242
+ }
243
+ function buildPatchedArgs(rewrittenUrl, options, service, sourceProtocol, callback) {
244
+ const patchedOptions = {
245
+ ...options,
246
+ protocol: rewrittenUrl.protocol,
247
+ hostname: rewrittenUrl.hostname,
248
+ port: rewrittenUrl.port ? Number(rewrittenUrl.port) : void 0,
249
+ path: `${rewrittenUrl.pathname}${rewrittenUrl.search}`,
250
+ headers: buildPatchedHeaders(options.headers, service)
251
+ };
252
+ delete patchedOptions.host;
253
+ if (sourceProtocol !== rewrittenUrl.protocol) {
254
+ delete patchedOptions.agent;
255
+ delete patchedOptions.createConnection;
256
+ }
257
+ if (callback) {
258
+ return [rewrittenUrl, patchedOptions, callback];
259
+ }
260
+ return [rewrittenUrl, patchedOptions];
261
+ }
262
+ function buildRoutedFetchRequest(request, targetUrl, service, init) {
263
+ const rewrittenRequest = new Request(targetUrl, request);
264
+ const headers = new Headers(rewrittenRequest.headers);
265
+ if (init?.headers) {
266
+ for (const [name, value] of new Headers(init.headers).entries()) {
267
+ headers.set(name, value);
268
+ }
269
+ }
270
+ applyRoutedHeaders(headers, service);
271
+ return new Request(rewrittenRequest, {
272
+ ...init,
273
+ headers
274
+ });
275
+ }
276
+
277
+ // ../route-runtime-core/src/service-profiles.ts
278
+ function exactDomain(value) {
279
+ return { kind: "exact", value: value.toLowerCase() };
280
+ }
281
+ function suffixDomain(value) {
282
+ return { kind: "suffix", value: value.toLowerCase() };
283
+ }
284
+ function matchesDomainPattern(hostname, pattern) {
285
+ const normalizedHostname = hostname.toLowerCase();
286
+ if (pattern.kind === "exact") {
287
+ return normalizedHostname === pattern.value;
288
+ }
289
+ return normalizedHostname === pattern.value || normalizedHostname.endsWith(`.${pattern.value}`);
290
+ }
291
+ function classifyHostname(profile, hostname) {
292
+ if (profile.routeDomains.some((pattern) => matchesDomainPattern(hostname, pattern))) {
293
+ return "route";
294
+ }
295
+ if (profile.hardFailDomains.some((pattern) => matchesDomainPattern(hostname, pattern))) {
296
+ return "hard-fail";
297
+ }
298
+ if (profile.warningDomains.some((pattern) => matchesDomainPattern(hostname, pattern))) {
299
+ return "warn";
300
+ }
301
+ return "ignore";
302
+ }
303
+
304
+ // ../route-runtime-core/src/manifests/discord.ts
305
+ var discordRouteManifest = {
306
+ service: "discord",
307
+ manifestVersion: "2026-04-12.discord.v1",
308
+ baselineSeed: "small-server",
309
+ upstreamBasePath: "/api/v10",
310
+ routeDomains: [
311
+ exactDomain("discord.com"),
312
+ exactDomain("discordapp.com")
313
+ ],
314
+ // Discord's CDN and gateway surfaces are outside the approved Vitest route
315
+ // contract. Leaving them warning-only would create obvious exfil and
316
+ // network-escape channels once a test declares Discord route mode.
317
+ hardFailDomains: [
318
+ exactDomain("cdn.discordapp.com"),
319
+ exactDomain("media.discordapp.net"),
320
+ exactDomain("gateway.discord.gg")
321
+ ],
322
+ warningDomains: [
323
+ suffixDomain("discord.com"),
324
+ suffixDomain("discordapp.com"),
325
+ exactDomain("discord.gg")
326
+ ],
327
+ diagnostics: {
328
+ undeclared: ({ url }) => `Blocked Discord traffic to ${url}. Declare discord in archalVitestProject({ services: { discord: { mode: "route" } } }) before calling the Discord REST API in Vitest.`,
329
+ declaredEscape: ({ url }) => `Blocked Discord escape to ${url}. Discord route mode is configured, but this domain is outside the approved Discord REST route surface.`,
330
+ warning: ({ url }) => `Observed adjacent Discord domain ${url}. Archal did not reroute this request because it is outside the approved Discord REST route surface.`
331
+ },
332
+ sdkIdentifiers: ["discord.js", "@discordjs/rest"],
333
+ conformanceSuiteId: "discord-hosted-route"
334
+ };
335
+
336
+ // ../route-runtime-core/src/manifests/datadog.ts
337
+ var datadogRouteManifest = {
338
+ service: "datadog",
339
+ manifestVersion: "2026-06-07.datadog.v1",
340
+ baselineSeed: "empty",
341
+ routeDomains: [exactDomain("api.datadoghq.com")],
342
+ hardFailDomains: [],
343
+ warningDomains: [suffixDomain("datadoghq.com")],
344
+ diagnostics: {
345
+ undeclared: ({ url }) => `Blocked Datadog traffic to ${url}. Declare datadog before calling the Datadog API.`,
346
+ declaredEscape: ({ url }) => `Blocked Datadog escape to ${url}. Datadog route mode is configured, but this domain is outside the routed Datadog API surface.`,
347
+ warning: ({ url }) => `Observed adjacent Datadog domain ${url}. Archal did not reroute this request because it is outside the routed Datadog API surface.`
348
+ },
349
+ sdkIdentifiers: ["@datadog/datadog-api-client", "datadog-api-client"],
350
+ conformanceSuiteId: "datadog-hosted-route"
351
+ };
352
+
353
+ // ../route-runtime-core/src/manifests/github.ts
354
+ function rewriteGitHubRawContentPath(sourceUrl) {
355
+ if (sourceUrl.hostname.toLowerCase() !== "raw.githubusercontent.com") {
356
+ return null;
357
+ }
358
+ const [owner, repo, branch, ...pathSegments] = sourceUrl.pathname.split("/").filter(Boolean);
359
+ if (!owner || !repo || !branch || pathSegments.length === 0) {
360
+ return null;
361
+ }
362
+ const searchParams = new URLSearchParams(sourceUrl.search);
363
+ searchParams.set("ref", decodeURIComponent(branch));
364
+ return {
365
+ pathname: `/raw/${owner}/${repo}/${branch}/${pathSegments.join("/")}`,
366
+ search: `?${searchParams.toString()}`
367
+ };
368
+ }
369
+ var githubRouteManifest = {
370
+ service: "github",
371
+ manifestVersion: "2026-04-02.github.v1",
372
+ baselineSeed: "small-project",
373
+ routeDomains: [
374
+ exactDomain("api.github.com"),
375
+ exactDomain("uploads.github.com"),
376
+ exactDomain("github.com"),
377
+ exactDomain("raw.githubusercontent.com")
378
+ ],
379
+ // Block adjacent GitHub-owned domains that tests don't need. Keep raw
380
+ // content routed because GitHub's contents API returns download_url values
381
+ // on raw.githubusercontent.com, and normal clients often follow them.
382
+ hardFailDomains: [
383
+ exactDomain("gist.github.com"),
384
+ exactDomain("objects.githubusercontent.com"),
385
+ exactDomain("codeload.github.com")
386
+ ],
387
+ routePathRewrite: rewriteGitHubRawContentPath,
388
+ warningDomains: [
389
+ suffixDomain("github.com"),
390
+ suffixDomain("githubusercontent.com")
391
+ ],
392
+ diagnostics: {
393
+ undeclared: ({ url }) => `Blocked GitHub traffic to ${url}. Declare github in archalVitestProject({ services: { github: { mode: "route" } } }) before using the official GitHub SDK.`,
394
+ declaredEscape: ({ url }) => `Blocked GitHub escape to ${url}. GitHub route mode is configured, but this domain is outside the primary routed GitHub surface.`,
395
+ warning: ({ url }) => `Observed adjacent GitHub domain ${url}. Archal did not reroute this request because it is outside the primary GitHub API route surface.`
396
+ },
397
+ sdkIdentifiers: ["@octokit/rest"],
398
+ conformanceSuiteId: "github-hosted-route"
399
+ };
400
+
401
+ // ../route-runtime-core/src/manifests/gitlab.ts
402
+ var gitlabRouteManifest = {
403
+ service: "gitlab",
404
+ manifestVersion: "2026-06-08.gitlab.v1",
405
+ baselineSeed: "empty",
406
+ routeDomains: [
407
+ // GitLab SaaS serves its REST API from the gitlab.com apex (/api/v4).
408
+ exactDomain("gitlab.com")
409
+ ],
410
+ hardFailDomains: [],
411
+ warningDomains: [
412
+ // docs.gitlab.com / about.gitlab.com are marketing/docs hosts, not the API
413
+ // surface — warn rather than reroute so a stray call stays observable.
414
+ suffixDomain("gitlab.com")
415
+ ],
416
+ diagnostics: {
417
+ undeclared: ({ url }) => `Blocked GitLab traffic to ${url}. Declare gitlab in archalVitestProject({ services: { gitlab: { mode: "route" } } }) before calling the GitLab API.`,
418
+ declaredEscape: ({ url }) => `Blocked GitLab escape to ${url}. GitLab route mode is configured, but this domain is outside the routed GitLab API surface.`,
419
+ warning: ({ url }) => `Observed adjacent GitLab domain ${url}. Archal did not reroute this request because it is outside the routed GitLab API surface.`
420
+ },
421
+ sdkIdentifiers: ["@gitbeaker/rest"],
422
+ conformanceSuiteId: "gitlab-hosted-route"
423
+ };
424
+
425
+ // ../route-runtime-core/src/manifests/google-workspace.ts
426
+ var googleWorkspaceRouteManifest = {
427
+ service: "google-workspace",
428
+ manifestVersion: "2026-04-01.google-workspace.v1",
429
+ baselineSeed: "assistant-baseline",
430
+ routeDomains: [
431
+ exactDomain("gmail.googleapis.com"),
432
+ exactDomain("drive.googleapis.com"),
433
+ exactDomain("calendar.googleapis.com"),
434
+ exactDomain("people.googleapis.com"),
435
+ exactDomain("sheets.googleapis.com"),
436
+ exactDomain("oauth2.googleapis.com"),
437
+ exactDomain("www.googleapis.com")
438
+ ],
439
+ hardFailDomains: [],
440
+ warningDomains: [
441
+ suffixDomain("google.com"),
442
+ suffixDomain("googleusercontent.com")
443
+ ],
444
+ diagnostics: {
445
+ undeclared: ({ url }) => `Blocked Google Workspace traffic to ${url}. Declare google-workspace in archalVitestProject({ services: { 'google-workspace': { mode: "route" } } }) before using the official Google client.`,
446
+ declaredEscape: ({ url }) => `Blocked Google Workspace escape to ${url}. Google Workspace route mode is configured, but this domain is outside the primary routed surface.`,
447
+ warning: ({ url }) => `Observed adjacent Google Workspace domain ${url}. Archal did not reroute this request because it is outside the primary Google Workspace API route surface.`
448
+ },
449
+ sdkIdentifiers: ["googleapis"],
450
+ conformanceSuiteId: "google-workspace-hosted-route"
451
+ };
452
+
453
+ // ../route-runtime-core/src/manifests/apify.ts
454
+ var apifyRouteManifest = {
455
+ service: "apify",
456
+ manifestVersion: "2026-05-14.apify.v1",
457
+ baselineSeed: "empty",
458
+ routeDomains: [
459
+ exactDomain("api.apify.com"),
460
+ exactDomain("apify.com")
461
+ ],
462
+ hardFailDomains: [],
463
+ warningDomains: [
464
+ suffixDomain("apify.com")
465
+ ],
466
+ diagnostics: {
467
+ undeclared: ({ url }) => `Blocked Apify traffic to ${url}. Declare apify before using the official Apify client.`,
468
+ declaredEscape: ({ url }) => `Blocked Apify escape to ${url}. Apify route mode is configured, but this domain is outside the primary routed Apify API surface.`,
469
+ warning: ({ url }) => `Observed adjacent Apify domain ${url}. Archal did not reroute this request because it is outside the primary Apify API route surface.`
470
+ },
471
+ sdkIdentifiers: ["apify-client", "apify"],
472
+ conformanceSuiteId: "apify-hosted-route"
473
+ };
474
+
475
+ // ../route-runtime-core/src/manifests/calcom.ts
476
+ var calcomRouteManifest = {
477
+ service: "calcom",
478
+ manifestVersion: "2026-06-08.calcom.v1",
479
+ baselineSeed: "empty",
480
+ routeDomains: [
481
+ exactDomain("api.cal.com")
482
+ ],
483
+ hardFailDomains: [],
484
+ warningDomains: [
485
+ // The bare cal.com is the marketing/app host, not the API surface — warn
486
+ // rather than reroute so a stray call is observable but not silently served.
487
+ suffixDomain("cal.com")
488
+ ],
489
+ diagnostics: {
490
+ undeclared: ({ url }) => `Blocked Cal.com traffic to ${url}. Declare calcom in archalVitestProject({ services: { calcom: { mode: "route" } } }) before calling the Cal.com API.`,
491
+ declaredEscape: ({ url }) => `Blocked Cal.com escape to ${url}. Cal.com route mode is configured, but this domain is outside the primary routed Cal.com API surface.`,
492
+ warning: ({ url }) => `Observed adjacent Cal.com domain ${url}. Archal did not reroute this request because it is outside the primary Cal.com API route surface.`
493
+ },
494
+ sdkIdentifiers: ["@calcom/api"],
495
+ conformanceSuiteId: "calcom-hosted-route"
496
+ };
497
+
498
+ // ../route-runtime-core/src/manifests/clickup.ts
499
+ var clickupRouteManifest = {
500
+ service: "clickup",
501
+ manifestVersion: "2026-06-08.clickup.v1",
502
+ baselineSeed: "empty",
503
+ routeDomains: [
504
+ exactDomain("api.clickup.com")
505
+ ],
506
+ hardFailDomains: [],
507
+ warningDomains: [
508
+ // app.clickup.com is the web app host, not the API surface — warn rather
509
+ // than reroute so a stray call is observable but not silently served.
510
+ suffixDomain("clickup.com")
511
+ ],
512
+ diagnostics: {
513
+ undeclared: ({ url }) => `Blocked ClickUp traffic to ${url}. Declare clickup in archalVitestProject({ services: { clickup: { mode: "route" } } }) before calling the ClickUp API.`,
514
+ declaredEscape: ({ url }) => `Blocked ClickUp escape to ${url}. ClickUp route mode is configured, but this domain is outside the routed ClickUp API surface.`,
515
+ warning: ({ url }) => `Observed adjacent ClickUp domain ${url}. Archal did not reroute this request because it is outside the routed ClickUp API surface.`
516
+ },
517
+ sdkIdentifiers: ["clickup"],
518
+ conformanceSuiteId: "clickup-hosted-route"
519
+ };
520
+
521
+ // ../route-runtime-core/src/manifests/customerio.ts
522
+ var customerioRouteManifest = {
523
+ service: "customerio",
524
+ manifestVersion: "2026-06-08.customerio.v1",
525
+ baselineSeed: "empty",
526
+ routeDomains: [
527
+ exactDomain("api.customer.io")
528
+ ],
529
+ hardFailDomains: [],
530
+ warningDomains: [
531
+ // The bare customer.io is the marketing site and track.customer.io is the
532
+ // separate CDP/track ingest surface — neither is the App API this clone
533
+ // models. Warn rather than reroute so a stray call is observable.
534
+ suffixDomain("customer.io")
535
+ ],
536
+ diagnostics: {
537
+ undeclared: ({ url }) => `Blocked Customer.io traffic to ${url}. Declare customerio before calling the Customer.io API.`,
538
+ declaredEscape: ({ url }) => `Blocked Customer.io escape to ${url}. Customer.io route mode is configured, but this domain is outside the routed Customer.io App API surface.`,
539
+ warning: ({ url }) => `Observed adjacent Customer.io domain ${url}. Archal did not reroute this request because it is outside the routed Customer.io App API surface.`
540
+ },
541
+ sdkIdentifiers: ["customerio-node"],
542
+ conformanceSuiteId: "customerio-hosted-route"
543
+ };
544
+
545
+ // ../route-runtime-core/src/manifests/hubspot.ts
546
+ var hubspotRouteManifest = {
547
+ service: "hubspot",
548
+ manifestVersion: "2026-06-10.hubspot.v1",
549
+ baselineSeed: "empty",
550
+ routeDomains: [
551
+ exactDomain("api.hubapi.com")
552
+ ],
553
+ hardFailDomains: [],
554
+ warningDomains: [
555
+ // Non-API hubapi.com hosts (forms.hubapi.com, track ingest) and the
556
+ // hubspot.com web UI (app.hubspot.com — twin payload URLs point at
557
+ // app-na2.hubspot.com) are adjacent surfaces, not the CRM API this clone
558
+ // models. Warn rather than reroute so a stray call is observable.
559
+ suffixDomain("hubapi.com"),
560
+ suffixDomain("hubspot.com")
561
+ ],
562
+ diagnostics: {
563
+ undeclared: ({ url }) => `Blocked HubSpot traffic to ${url}. Declare hubspot before calling the HubSpot API.`,
564
+ declaredEscape: ({ url }) => `Blocked HubSpot escape to ${url}. HubSpot route mode is configured, but this domain is outside the routed HubSpot CRM API surface.`,
565
+ warning: ({ url }) => `Observed adjacent HubSpot domain ${url}. Archal did not reroute this request because it is outside the routed HubSpot CRM API surface.`
566
+ },
567
+ sdkIdentifiers: ["@hubspot/api-client"],
568
+ conformanceSuiteId: "hubspot-hosted-route"
569
+ };
570
+
571
+ // ../route-runtime-core/src/manifests/jira.ts
572
+ var jiraRouteManifest = {
573
+ service: "jira",
574
+ manifestVersion: "2026-04-01.jira.v1",
575
+ baselineSeed: "small-project",
576
+ upstreamBasePathAliases: ["/ex/jira/:cloudId"],
577
+ routeDomains: [
578
+ suffixDomain("atlassian.net"),
579
+ exactDomain("api.atlassian.com"),
580
+ exactDomain("jira.atlassian.com")
581
+ ],
582
+ hardFailDomains: [],
583
+ warningDomains: [
584
+ suffixDomain("atlassian.com"),
585
+ exactDomain("jira.com")
586
+ ],
587
+ diagnostics: {
588
+ undeclared: ({ url }) => `Blocked Jira traffic to ${url}. Declare jira in archalVitestProject({ services: { jira: { mode: "route" } } }) before using the official Jira SDK.`,
589
+ declaredEscape: ({ url }) => `Blocked Jira escape to ${url}. Jira route mode is configured, but this domain is outside the primary routed Jira surface.`,
590
+ warning: ({ url }) => `Observed adjacent Jira domain ${url}. Archal did not reroute this request because it is outside the primary Jira route surface.`
591
+ },
592
+ sdkIdentifiers: ["jira.js"],
593
+ conformanceSuiteId: "jira-hosted-route"
594
+ };
595
+
596
+ // ../route-runtime-core/src/manifests/linear.ts
597
+ var linearRouteManifest = {
598
+ service: "linear",
599
+ manifestVersion: "2026-04-19.linear.v1",
600
+ baselineSeed: "small-team",
601
+ routeDomains: [
602
+ exactDomain("api.linear.app")
603
+ ],
604
+ hardFailDomains: [],
605
+ warningDomains: [
606
+ suffixDomain("linear.app")
607
+ ],
608
+ diagnostics: {
609
+ undeclared: ({ url }) => `Blocked Linear traffic to ${url}. Declare linear in archalVitestProject({ services: { linear: { mode: "route" } } }) before using the official Linear SDK.`,
610
+ declaredEscape: ({ url }) => `Blocked Linear escape to ${url}. Linear route mode is configured, but this domain is outside the primary routed Linear API surface.`,
611
+ warning: ({ url }) => `Observed adjacent Linear domain ${url}. Archal did not reroute this request because it is outside the primary Linear API route surface.`
612
+ },
613
+ sdkIdentifiers: ["@linear/sdk"],
614
+ conformanceSuiteId: "linear-hosted-route"
615
+ };
616
+
617
+ // ../route-runtime-core/src/manifests/ownerrez.ts
618
+ var ownerrezRouteManifest = {
619
+ service: "ownerrez",
620
+ manifestVersion: "2026-06-07.ownerrez.v1",
621
+ baselineSeed: "empty",
622
+ routeDomains: [
623
+ exactDomain("api.ownerrez.com"),
624
+ exactDomain("ownerrez.com")
625
+ ],
626
+ hardFailDomains: [],
627
+ warningDomains: [
628
+ suffixDomain("ownerrez.com")
629
+ ],
630
+ diagnostics: {
631
+ undeclared: ({ url }) => `Blocked OwnerRez traffic to ${url}. Declare ownerrez before calling the OwnerRez API.`,
632
+ declaredEscape: ({ url }) => `Blocked OwnerRez escape to ${url}. OwnerRez route mode is configured, but this domain is outside the routed OwnerRez API surface.`,
633
+ warning: ({ url }) => `Observed adjacent OwnerRez domain ${url}. Archal did not reroute this request because it is outside the routed OwnerRez API surface.`
634
+ },
635
+ sdkIdentifiers: ["ownerrez"],
636
+ conformanceSuiteId: "ownerrez-hosted-route"
637
+ };
638
+
639
+ // ../route-runtime-core/src/manifests/pricelabs.ts
640
+ var pricelabsRouteManifest = {
641
+ service: "pricelabs",
642
+ manifestVersion: "2026-06-07.pricelabs.v1",
643
+ baselineSeed: "empty",
644
+ routeDomains: [
645
+ exactDomain("api.pricelabs.co"),
646
+ exactDomain("pricelabs.co")
647
+ ],
648
+ hardFailDomains: [],
649
+ warningDomains: [
650
+ suffixDomain("pricelabs.co")
651
+ ],
652
+ diagnostics: {
653
+ undeclared: ({ url }) => `Blocked PriceLabs traffic to ${url}. Declare pricelabs before calling the PriceLabs API.`,
654
+ declaredEscape: ({ url }) => `Blocked PriceLabs escape to ${url}. PriceLabs route mode is configured, but this domain is outside the routed PriceLabs API surface.`,
655
+ warning: ({ url }) => `Observed adjacent PriceLabs domain ${url}. Archal did not reroute this request because it is outside the routed PriceLabs API surface.`
656
+ },
657
+ sdkIdentifiers: ["pricelabs"],
658
+ conformanceSuiteId: "pricelabs-hosted-route"
659
+ };
660
+
661
+ // ../route-runtime-core/src/manifests/ramp.ts
662
+ var rampRouteManifest = {
663
+ service: "ramp",
664
+ manifestVersion: "2026-04-19.ramp.v1",
665
+ baselineSeed: "default",
666
+ routeDomains: [
667
+ exactDomain("api.ramp.com"),
668
+ exactDomain("app.ramp.com")
669
+ ],
670
+ hardFailDomains: [],
671
+ warningDomains: [
672
+ suffixDomain("ramp.com")
673
+ ],
674
+ diagnostics: {
675
+ undeclared: ({ url }) => `Blocked Ramp traffic to ${url}. Declare ramp in archalVitestProject({ services: { ramp: { mode: "route" } } }) before using the official Ramp client.`,
676
+ declaredEscape: ({ url }) => `Blocked Ramp escape to ${url}. Ramp route mode is configured, but this domain is outside the primary routed Ramp API surface.`,
677
+ warning: ({ url }) => `Observed adjacent Ramp domain ${url}. Archal did not reroute this request because it is outside the primary Ramp API route surface.`
678
+ },
679
+ sdkIdentifiers: ["ramp"],
680
+ conformanceSuiteId: "ramp-hosted-route"
681
+ };
682
+
683
+ // ../route-runtime-core/src/manifests/sendgrid.ts
684
+ var sendgridRouteManifest = {
685
+ service: "sendgrid",
686
+ manifestVersion: "2026-06-08.sendgrid.v1",
687
+ baselineSeed: "empty",
688
+ routeDomains: [
689
+ exactDomain("api.sendgrid.com")
690
+ ],
691
+ hardFailDomains: [],
692
+ warningDomains: [
693
+ // The bare sendgrid.com is the marketing site, not the API surface — warn
694
+ // rather than reroute so a stray call is observable but not silently served.
695
+ suffixDomain("sendgrid.com")
696
+ ],
697
+ diagnostics: {
698
+ undeclared: ({ url }) => `Blocked SendGrid traffic to ${url}. Declare sendgrid before calling the SendGrid API.`,
699
+ declaredEscape: ({ url }) => `Blocked SendGrid escape to ${url}. SendGrid route mode is configured, but this domain is outside the routed SendGrid API surface.`,
700
+ warning: ({ url }) => `Observed adjacent SendGrid domain ${url}. Archal did not reroute this request because it is outside the routed SendGrid API surface.`
701
+ },
702
+ sdkIdentifiers: ["sendgrid", "@sendgrid/mail", "@sendgrid/client"],
703
+ conformanceSuiteId: "sendgrid-hosted-route"
704
+ };
705
+
706
+ // ../route-runtime-core/src/manifests/sentry.ts
707
+ var sentryRouteManifest = {
708
+ service: "sentry",
709
+ manifestVersion: "2026-06-08.sentry.v1",
710
+ baselineSeed: "empty",
711
+ routeDomains: [
712
+ exactDomain("sentry.io")
713
+ ],
714
+ hardFailDomains: [],
715
+ warningDomains: [
716
+ // *.sentry.io subdomains (docs/blog/app/regional) sit adjacent to the routed
717
+ // sentry.io API apex — warn rather than reroute. The exact apex above wins
718
+ // for sentry.io itself, so only subdomains fall through to this warning.
719
+ suffixDomain("sentry.io")
720
+ ],
721
+ diagnostics: {
722
+ undeclared: ({ url }) => `Blocked Sentry traffic to ${url}. Declare sentry in archalVitestProject({ services: { sentry: { mode: "route" } } }) before calling the Sentry API.`,
723
+ declaredEscape: ({ url }) => `Blocked Sentry escape to ${url}. Sentry route mode is configured, but this domain is outside the primary routed Sentry API surface.`,
724
+ warning: ({ url }) => `Observed adjacent Sentry domain ${url}. Archal did not reroute this request because it is outside the primary Sentry API route surface.`
725
+ },
726
+ sdkIdentifiers: ["@sentry/node", "@sentry/browser", "@sentry/core"],
727
+ conformanceSuiteId: "sentry-hosted-route"
728
+ };
729
+
730
+ // ../route-runtime-core/src/manifests/slack.ts
731
+ var slackRouteManifest = {
732
+ service: "slack",
733
+ manifestVersion: "2026-04-02.slack.v1",
734
+ baselineSeed: "engineering-team",
735
+ routeDomains: [
736
+ exactDomain("slack.com"),
737
+ exactDomain("api.slack.com")
738
+ ],
739
+ upstreamBasePath: "/api",
740
+ // Block adjacent Slack-owned domains that tests don't need and that can be
741
+ // abused as data-exfiltration channels. hooks.slack.com and files.slack.com
742
+ // in particular are ideal POST targets for a malicious dependency trying to
743
+ // smuggle stolen tokens out through a domain the developer has already
744
+ // implicitly allow-listed.
745
+ hardFailDomains: [
746
+ exactDomain("hooks.slack.com"),
747
+ exactDomain("files.slack.com"),
748
+ exactDomain("edgeapi.slack.com"),
749
+ exactDomain("wss-primary.slack.com"),
750
+ exactDomain("wss-backup.slack.com")
751
+ ],
752
+ warningDomains: [
753
+ suffixDomain("slack.com")
754
+ ],
755
+ diagnostics: {
756
+ undeclared: ({ url }) => `Blocked Slack traffic to ${url}. Declare slack in archalVitestProject({ services: { slack: { mode: "route" } } }) before using the official Slack SDK.`,
757
+ declaredEscape: ({ url }) => `Blocked Slack escape to ${url}. Slack route mode is configured, but this domain is outside the primary routed Slack surface.`,
758
+ warning: ({ url }) => `Observed adjacent Slack domain ${url}. Archal did not reroute this request because it is outside the primary Slack API route surface.`
759
+ },
760
+ sdkIdentifiers: ["@slack/web-api"],
761
+ conformanceSuiteId: "slack-hosted-route"
762
+ };
763
+
764
+ // ../route-runtime-core/src/manifests/stripe.ts
765
+ var stripeRuntimeResourcePolicy = [
766
+ {
767
+ resource: "customers",
768
+ commands: ["customers.create", "customers.update"],
769
+ events: ["customer.created", "customer.updated"]
770
+ },
771
+ {
772
+ resource: "payment_intents",
773
+ commands: ["paymentIntents.create", "paymentIntents.confirm"],
774
+ events: ["payment_intent.succeeded"]
775
+ },
776
+ { resource: "charges", events: ["charge.refunded"] },
777
+ { resource: "refunds", commands: ["refunds.create"] },
778
+ { resource: "products" },
779
+ { resource: "prices" },
780
+ {
781
+ resource: "subscriptions",
782
+ commands: ["subscriptions.create"],
783
+ events: ["customer.subscription.created", "customer.subscription.updated"]
784
+ },
785
+ {
786
+ resource: "invoices",
787
+ commands: ["invoices.create", "invoices.finalizeInvoice"],
788
+ events: ["invoice.finalized"]
789
+ },
790
+ { resource: "webhook_endpoints", commands: ["webhookEndpoints.create"] },
791
+ { resource: "events" }
792
+ ];
793
+ function deriveStripeRuntimeSurface(policy) {
794
+ return {
795
+ resources: policy.map((entry) => entry.resource),
796
+ commands: policy.flatMap((entry) => entry.commands ?? []),
797
+ events: policy.flatMap((entry) => entry.events ?? [])
798
+ };
799
+ }
800
+ var stripeRuntimeSurface = deriveStripeRuntimeSurface(stripeRuntimeResourcePolicy);
801
+ var stripeRouteManifest = {
802
+ service: "stripe",
803
+ manifestVersion: "2026-04-01.stripe.v1",
804
+ baselineSeed: "small-business",
805
+ routeDomains: [
806
+ exactDomain("api.stripe.com")
807
+ ],
808
+ hardFailDomains: [
809
+ exactDomain("files.stripe.com"),
810
+ exactDomain("uploads.stripe.com")
811
+ ],
812
+ warningDomains: [
813
+ exactDomain("billing.stripe.com"),
814
+ exactDomain("buy.stripe.com"),
815
+ exactDomain("checkout.stripe.com"),
816
+ exactDomain("connect.stripe.com"),
817
+ exactDomain("dashboard.stripe.com"),
818
+ exactDomain("invoice.stripe.com"),
819
+ exactDomain("pay.stripe.com"),
820
+ exactDomain("verify.stripe.com"),
821
+ suffixDomain("stripe.com")
822
+ ],
823
+ diagnostics: {
824
+ undeclared: ({ url }) => `Blocked Stripe traffic to ${url}. Declare stripe in archalVitestProject({ services: { stripe: { mode: "route" } } }) before using the official Stripe SDK.`,
825
+ declaredEscape: ({ url }) => `Blocked Stripe escape to ${url}. Stripe route mode is configured, but this Stripe-owned domain is outside the current routed surface.`,
826
+ warning: ({ url }) => `Observed adjacent Stripe domain ${url}. Archal did not reroute this request because it is outside the primary Stripe API route surface.`
827
+ },
828
+ runtimeSurface: stripeRuntimeSurface,
829
+ sdkIdentifiers: ["stripe"],
830
+ conformanceSuiteId: "stripe-hosted-route"
831
+ };
832
+
833
+ // ../route-runtime-core/src/manifests/supabase.ts
834
+ var supabaseRouteManifest = {
835
+ service: "supabase",
836
+ manifestVersion: "2026-04-01.supabase.v1",
837
+ baselineSeed: "small-project",
838
+ routeDomains: [
839
+ suffixDomain("supabase.co"),
840
+ exactDomain("api.supabase.com")
841
+ ],
842
+ hardFailDomains: [],
843
+ warningDomains: [
844
+ suffixDomain("supabase.com")
845
+ ],
846
+ diagnostics: {
847
+ undeclared: ({ url }) => `Blocked Supabase traffic to ${url}. Declare supabase in archalVitestProject({ services: { supabase: { mode: "route" } } }) before using the official Supabase SDK.`,
848
+ declaredEscape: ({ url }) => `Blocked Supabase escape to ${url}. Supabase route mode is configured, but this domain is outside the primary routed Supabase surface.`,
849
+ warning: ({ url }) => `Observed adjacent Supabase domain ${url}. Archal did not reroute this request because it is outside the primary Supabase route surface.`
850
+ },
851
+ sdkIdentifiers: ["@supabase/supabase-js"],
852
+ conformanceSuiteId: "supabase-hosted-route"
853
+ };
854
+
855
+ // ../route-runtime-core/src/manifests/tavily.ts
856
+ var tavilyRouteManifest = {
857
+ service: "tavily",
858
+ manifestVersion: "2026-05-14.tavily.v1",
859
+ baselineSeed: "empty",
860
+ routeDomains: [
861
+ exactDomain("api.tavily.com"),
862
+ exactDomain("tavily.com")
863
+ ],
864
+ hardFailDomains: [],
865
+ warningDomains: [
866
+ suffixDomain("tavily.com")
867
+ ],
868
+ diagnostics: {
869
+ undeclared: ({ url }) => `Blocked Tavily traffic to ${url}. Declare tavily before using the official Tavily client.`,
870
+ declaredEscape: ({ url }) => `Blocked Tavily escape to ${url}. Tavily route mode is configured, but this domain is outside the primary routed Tavily API surface.`,
871
+ warning: ({ url }) => `Observed adjacent Tavily domain ${url}. Archal did not reroute this request because it is outside the primary Tavily API route surface.`
872
+ },
873
+ sdkIdentifiers: ["@tavily/core", "tavily"],
874
+ conformanceSuiteId: "tavily-hosted-route"
875
+ };
876
+
877
+ // ../route-runtime-core/src/manifests/unipile.ts
878
+ var unipileRouteManifest = {
879
+ service: "unipile",
880
+ manifestVersion: "2026-06-05.unipile.v1",
881
+ baselineSeed: "empty",
882
+ routeDomains: [
883
+ exactDomain("api.unipile.com"),
884
+ suffixDomain("unipile.com")
885
+ ],
886
+ hardFailDomains: [],
887
+ warningDomains: [],
888
+ diagnostics: {
889
+ undeclared: ({ url }) => `Blocked Unipile traffic to ${url}. Declare unipile before calling the Unipile API.`,
890
+ declaredEscape: ({ url }) => `Blocked Unipile escape to ${url}. Unipile route mode is configured, but this domain is outside the routed Unipile API surface.`,
891
+ warning: ({ url }) => `Observed adjacent Unipile domain ${url}. Archal did not reroute this request because it is outside the routed Unipile API surface.`
892
+ },
893
+ sdkIdentifiers: ["unipile"],
894
+ conformanceSuiteId: "unipile-hosted-route"
895
+ };
896
+
897
+ // ../route-runtime-core/src/manifests/webflow.ts
898
+ var webflowRouteManifest = {
899
+ service: "webflow",
900
+ manifestVersion: "2026-06-07.webflow.v1",
901
+ baselineSeed: "empty",
902
+ routeDomains: [
903
+ exactDomain("api.webflow.com"),
904
+ exactDomain("webflow.com")
905
+ ],
906
+ hardFailDomains: [],
907
+ warningDomains: [
908
+ suffixDomain("webflow.com")
909
+ ],
910
+ diagnostics: {
911
+ undeclared: ({ url }) => `Blocked Webflow traffic to ${url}. Declare webflow before calling the Webflow API.`,
912
+ declaredEscape: ({ url }) => `Blocked Webflow escape to ${url}. Webflow route mode is configured, but this domain is outside the routed Webflow API surface.`,
913
+ warning: ({ url }) => `Observed adjacent Webflow domain ${url}. Archal did not reroute this request because it is outside the routed Webflow API surface.`
914
+ },
915
+ sdkIdentifiers: ["webflow-api", "webflow"],
916
+ conformanceSuiteId: "webflow-hosted-route"
917
+ };
918
+
919
+ // ../route-runtime-core/src/manifests.ts
920
+ var SHARED_ROUTE_MANIFESTS = [
921
+ discordRouteManifest,
922
+ datadogRouteManifest,
923
+ githubRouteManifest,
924
+ gitlabRouteManifest,
925
+ googleWorkspaceRouteManifest,
926
+ apifyRouteManifest,
927
+ calcomRouteManifest,
928
+ clickupRouteManifest,
929
+ customerioRouteManifest,
930
+ hubspotRouteManifest,
931
+ jiraRouteManifest,
932
+ linearRouteManifest,
933
+ ownerrezRouteManifest,
934
+ pricelabsRouteManifest,
935
+ rampRouteManifest,
936
+ sendgridRouteManifest,
937
+ sentryRouteManifest,
938
+ slackRouteManifest,
939
+ stripeRouteManifest,
940
+ supabaseRouteManifest,
941
+ tavilyRouteManifest,
942
+ unipileRouteManifest,
943
+ webflowRouteManifest
944
+ ];
945
+ var SHARED_ROUTE_MANIFESTS_BY_SERVICE = new Map(
946
+ SHARED_ROUTE_MANIFESTS.map((manifest) => [manifest.service, manifest])
947
+ );
948
+ function listSharedRouteManifests() {
949
+ return [...SHARED_ROUTE_MANIFESTS];
950
+ }
951
+ function getSharedRouteManifest(service) {
952
+ return SHARED_ROUTE_MANIFESTS_BY_SERVICE.get(service);
953
+ }
954
+ function buildServiceCompatibilityProfile(manifest) {
955
+ return {
956
+ service: manifest.service,
957
+ upstreamBasePath: manifest.upstreamBasePath,
958
+ upstreamBasePathAliases: manifest.upstreamBasePathAliases,
959
+ routePathRewrite: manifest.routePathRewrite,
960
+ routeDomains: manifest.routeDomains,
961
+ hardFailDomains: manifest.hardFailDomains,
962
+ warningDomains: manifest.warningDomains,
963
+ diagnostics: manifest.diagnostics
964
+ };
965
+ }
966
+
967
+ // ../route-runtime-core/src/runtime.ts
968
+ var require2 = createRequire(
969
+ typeof __filename === "string" && isAbsolute2(__filename) ? __filename : import.meta.url
970
+ );
971
+ var httpModule = require2("node:http");
972
+ var httpsModule = require2("node:https");
973
+ var SHARED_SERVICE_PROFILES = listSharedRouteManifests().map(buildServiceCompatibilityProfile);
974
+ var CLIENT_VISIBLE_BLOCKED_REQUEST_MESSAGE = "Network request failed";
975
+ function createClientVisibleNetworkError() {
976
+ return new TypeError(CLIENT_VISIBLE_BLOCKED_REQUEST_MESSAGE);
977
+ }
978
+ function sanitizeClientVisibleNetworkError(error) {
979
+ const visibleError = error;
980
+ for (const key of Reflect.ownKeys(error)) {
981
+ delete visibleError[key];
982
+ }
983
+ error.name = "TypeError";
984
+ error.message = CLIENT_VISIBLE_BLOCKED_REQUEST_MESSAGE;
985
+ error.stack = `${error.name}: ${error.message}`;
986
+ }
987
+ function now() {
988
+ return (/* @__PURE__ */ new Date()).toISOString();
989
+ }
990
+ function normalizePathPrefix(pathPrefix) {
991
+ const withLeadingSlash = pathPrefix.startsWith("/") ? pathPrefix : `/${pathPrefix}`;
992
+ return withLeadingSlash.replace(/\/+$/, "") || "/";
993
+ }
994
+ function matchPathPrefix(pathPrefix, pathname) {
995
+ const normalizedPrefix = normalizePathPrefix(pathPrefix);
996
+ const normalizedPathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
997
+ if (normalizedPrefix === "/") {
998
+ return normalizedPathname;
999
+ }
1000
+ const prefixSegments = normalizedPrefix.split("/").filter(Boolean);
1001
+ const pathnameSegments = normalizedPathname.split("/").filter(Boolean);
1002
+ if (prefixSegments.length > pathnameSegments.length) {
1003
+ return null;
1004
+ }
1005
+ for (let index = 0; index < prefixSegments.length; index += 1) {
1006
+ const expected = prefixSegments[index];
1007
+ if (expected.startsWith(":")) {
1008
+ continue;
1009
+ }
1010
+ if (expected !== pathnameSegments[index]) {
1011
+ return null;
1012
+ }
1013
+ }
1014
+ const remainingSegments = pathnameSegments.slice(prefixSegments.length);
1015
+ return remainingSegments.length === 0 ? "/" : `/${remainingSegments.join("/")}`;
1016
+ }
1017
+ function resolveRoutedPathname(sourceUrl, service, profile, rewritten) {
1018
+ if (rewritten) {
1019
+ return rewritten.pathname;
1020
+ }
1021
+ const sourcePathname = sourceUrl.pathname || "/";
1022
+ const pathPrefixes = [
1023
+ service.upstreamBasePath,
1024
+ profile.upstreamBasePath,
1025
+ ...profile.upstreamBasePathAliases ?? []
1026
+ ].filter((prefix) => typeof prefix === "string" && prefix.length > 0);
1027
+ for (const pathPrefix of pathPrefixes) {
1028
+ const normalizedPathname = matchPathPrefix(pathPrefix, sourcePathname);
1029
+ if (normalizedPathname) {
1030
+ return normalizedPathname;
1031
+ }
1032
+ }
1033
+ return sourcePathname;
1034
+ }
1035
+ function mergeUrl(service, sourceUrl, profile) {
1036
+ const rewritten = profile.routePathRewrite?.(sourceUrl);
1037
+ const targetBaseUrl = new URL(service.baseUrl);
1038
+ const targetPathname = targetBaseUrl.pathname.endsWith("/") ? targetBaseUrl.pathname.slice(0, -1) : targetBaseUrl.pathname;
1039
+ targetBaseUrl.pathname = `${targetPathname}${resolveRoutedPathname(sourceUrl, service, profile, rewritten)}` || "/";
1040
+ targetBaseUrl.search = rewritten?.search ?? sourceUrl.search;
1041
+ targetBaseUrl.hash = sourceUrl.hash;
1042
+ return targetBaseUrl;
1043
+ }
1044
+ function bridgeSecureConnectIfNeeded(request, sourceProtocol, targetProtocol) {
1045
+ if (sourceProtocol !== DEFAULT_HTTPS_PROTOCOL || targetProtocol !== DEFAULT_HTTP_PROTOCOL) {
1046
+ return request;
1047
+ }
1048
+ request.once("socket", (socket) => {
1049
+ if (socket.connecting) {
1050
+ socket.once("connect", () => {
1051
+ socket.emit("secureConnect");
1052
+ });
1053
+ return;
1054
+ }
1055
+ queueMicrotask(() => {
1056
+ socket.emit("secureConnect");
1057
+ });
1058
+ });
1059
+ return request;
1060
+ }
1061
+ function blockedWarningCode(decision, servicesByName) {
1062
+ return servicesByName.has(decision.service) ? "ARCHAL_DECLARED_SERVICE_ESCAPE" : "ARCHAL_UNDECLARED_SERVICE";
1063
+ }
1064
+ var CLIENT_VISIBLE_INTERNAL_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
1065
+ "proxy-authorization",
1066
+ "x-route-authorization",
1067
+ "x-route-service-origin",
1068
+ "x-service-routing-scope"
1069
+ ]);
1070
+ function isClientVisibleInternalHeaderName(name) {
1071
+ const normalized = name.toLowerCase();
1072
+ return normalized === "x-archal" || normalized.startsWith("x-archal-") || normalized.startsWith("x-route-") || CLIENT_VISIBLE_INTERNAL_RESPONSE_HEADERS.has(normalized);
1073
+ }
1074
+ function sanitizedFetchResponse(response) {
1075
+ const headers = new Headers(response.headers);
1076
+ let changed = false;
1077
+ for (const header of [...headers.keys()]) {
1078
+ if (!isClientVisibleInternalHeaderName(header)) {
1079
+ continue;
1080
+ }
1081
+ headers.delete(header);
1082
+ changed = true;
1083
+ }
1084
+ if (!changed) {
1085
+ return response;
1086
+ }
1087
+ return new Response(response.body, {
1088
+ status: response.status,
1089
+ statusText: response.statusText,
1090
+ headers
1091
+ });
1092
+ }
1093
+ function sanitizedRawHeaders(rawHeaders) {
1094
+ const result = [];
1095
+ for (let index = 0; index < rawHeaders.length; index += 2) {
1096
+ const name = rawHeaders[index];
1097
+ const value = rawHeaders[index + 1];
1098
+ if (!name || isClientVisibleInternalHeaderName(name)) {
1099
+ continue;
1100
+ }
1101
+ result.push(name);
1102
+ if (value !== void 0) {
1103
+ result.push(value);
1104
+ }
1105
+ }
1106
+ return result;
1107
+ }
1108
+ function sanitizeIncomingResponseHeaders(response) {
1109
+ for (const header of Object.keys(response.headers)) {
1110
+ if (isClientVisibleInternalHeaderName(header)) {
1111
+ delete response.headers[header];
1112
+ }
1113
+ }
1114
+ if (Array.isArray(response.rawHeaders)) {
1115
+ response.rawHeaders = sanitizedRawHeaders(response.rawHeaders);
1116
+ }
1117
+ }
1118
+ function wrapRoutedResponseCallback(callback) {
1119
+ if (!callback) {
1120
+ return void 0;
1121
+ }
1122
+ return (response) => {
1123
+ sanitizeIncomingResponseHeaders(response);
1124
+ callback(response);
1125
+ };
1126
+ }
1127
+ var NodeRouteRuntime = class {
1128
+ profiles;
1129
+ servicesByName;
1130
+ events = [];
1131
+ onEvent;
1132
+ traceRecords = [];
1133
+ traceEnabled;
1134
+ onTrace;
1135
+ started = false;
1136
+ routingInstalled = false;
1137
+ originalHttpRequest = httpModule.request;
1138
+ originalHttpGet = httpModule.get;
1139
+ originalHttpsRequest = httpsModule.request;
1140
+ originalHttpsGet = httpsModule.get;
1141
+ originalFetch = globalThis.fetch;
1142
+ constructor(config) {
1143
+ this.profiles = config.profiles;
1144
+ this.servicesByName = new Map(config.services.map((service) => [service.name, service]));
1145
+ this.onEvent = config.onEvent;
1146
+ this.traceEnabled = config.trace?.enabled ?? false;
1147
+ this.onTrace = config.trace?.onTrace;
1148
+ }
1149
+ async start() {
1150
+ if (this.started) return;
1151
+ this.started = true;
1152
+ this.emit({ type: "runtime_started", timestamp: now() });
1153
+ }
1154
+ async reset() {
1155
+ this.events.length = 0;
1156
+ this.emit({ type: "runtime_reset", timestamp: now() });
1157
+ }
1158
+ async stop() {
1159
+ this.uninstallRouting();
1160
+ if (!this.started) return;
1161
+ this.started = false;
1162
+ this.emit({ type: "runtime_stopped", timestamp: now() });
1163
+ }
1164
+ installRouting() {
1165
+ if (this.routingInstalled) return;
1166
+ httpModule.request = ((...args) => this.dispatchRequest(this.originalHttpRequest, this.originalHttpsRequest, DEFAULT_HTTP_PROTOCOL, args));
1167
+ httpModule.get = ((...args) => this.dispatchGet(this.originalHttpGet, this.originalHttpsGet, DEFAULT_HTTP_PROTOCOL, args));
1168
+ httpsModule.request = ((...args) => this.dispatchRequest(this.originalHttpRequest, this.originalHttpsRequest, DEFAULT_HTTPS_PROTOCOL, args));
1169
+ httpsModule.get = ((...args) => this.dispatchGet(this.originalHttpGet, this.originalHttpsGet, DEFAULT_HTTPS_PROTOCOL, args));
1170
+ if (this.originalFetch) {
1171
+ globalThis.fetch = (async (input, init) => {
1172
+ const originalRequest = input instanceof Request ? input : void 0;
1173
+ const sourceUrl = new URL(
1174
+ originalRequest?.url ?? (input instanceof URL ? input.toString() : String(input))
1175
+ );
1176
+ const decision = this.evaluateRequest(sourceUrl);
1177
+ const method = (originalRequest?.method ?? init?.method ?? "GET").toUpperCase();
1178
+ if (decision.kind === "route") {
1179
+ const service = this.servicesByName.get(decision.service);
1180
+ if (!service) {
1181
+ throw new Error(`Missing routed service config for ${decision.service}.`);
1182
+ }
1183
+ const targetUrl = decision.targetUrl.toString();
1184
+ if (originalRequest) {
1185
+ let response3;
1186
+ try {
1187
+ response3 = await this.originalFetch(
1188
+ buildRoutedFetchRequest(originalRequest, decision.targetUrl, service, init)
1189
+ );
1190
+ } catch {
1191
+ this.traceRequest({
1192
+ method,
1193
+ sourceUrl: sourceUrl.toString(),
1194
+ manifestMatched: true,
1195
+ service: decision.service,
1196
+ target: "twin",
1197
+ outcome: "failed",
1198
+ reason: "network_error",
1199
+ targetUrl,
1200
+ error: CLIENT_VISIBLE_BLOCKED_REQUEST_MESSAGE
1201
+ });
1202
+ throw createClientVisibleNetworkError();
1203
+ }
1204
+ this.traceRequest({
1205
+ method,
1206
+ sourceUrl: sourceUrl.toString(),
1207
+ manifestMatched: true,
1208
+ service: decision.service,
1209
+ target: "twin",
1210
+ outcome: "routed",
1211
+ reason: "route",
1212
+ targetUrl,
1213
+ statusCode: response3.status,
1214
+ statusText: response3.statusText
1215
+ });
1216
+ return sanitizedFetchResponse(response3);
1217
+ }
1218
+ const headers = new Headers(init?.headers);
1219
+ applyRoutedHeaders(headers, service);
1220
+ let response2;
1221
+ try {
1222
+ response2 = await this.originalFetch(new URL(targetUrl), {
1223
+ ...init,
1224
+ headers
1225
+ });
1226
+ } catch {
1227
+ this.traceRequest({
1228
+ method,
1229
+ sourceUrl: sourceUrl.toString(),
1230
+ manifestMatched: true,
1231
+ service: decision.service,
1232
+ target: "twin",
1233
+ outcome: "failed",
1234
+ reason: "network_error",
1235
+ targetUrl,
1236
+ error: CLIENT_VISIBLE_BLOCKED_REQUEST_MESSAGE
1237
+ });
1238
+ throw createClientVisibleNetworkError();
1239
+ }
1240
+ this.traceRequest({
1241
+ method,
1242
+ sourceUrl: sourceUrl.toString(),
1243
+ manifestMatched: true,
1244
+ service: decision.service,
1245
+ target: "twin",
1246
+ outcome: "routed",
1247
+ reason: "route",
1248
+ targetUrl,
1249
+ statusCode: response2.status,
1250
+ statusText: response2.statusText
1251
+ });
1252
+ return sanitizedFetchResponse(response2);
1253
+ }
1254
+ if (decision.kind === "block") {
1255
+ this.traceRequest({
1256
+ method,
1257
+ sourceUrl: sourceUrl.toString(),
1258
+ manifestMatched: true,
1259
+ service: decision.service,
1260
+ target: "none",
1261
+ outcome: "blocked",
1262
+ reason: decision.code === "ARCHAL_UNDECLARED_SERVICE" ? "blocked_undeclared" : "blocked_escape"
1263
+ });
1264
+ throw new RouteRuntimePolicyError({
1265
+ code: decision.code,
1266
+ service: decision.service,
1267
+ url: sourceUrl.toString(),
1268
+ message: decision.message
1269
+ });
1270
+ }
1271
+ if (decision.kind === "warn") {
1272
+ const code = blockedWarningCode(decision, this.servicesByName);
1273
+ this.traceRequest({
1274
+ method,
1275
+ sourceUrl: sourceUrl.toString(),
1276
+ manifestMatched: true,
1277
+ service: decision.service,
1278
+ target: "none",
1279
+ outcome: "blocked",
1280
+ reason: code === "ARCHAL_UNDECLARED_SERVICE" ? "blocked_undeclared" : "blocked_escape"
1281
+ });
1282
+ throw new RouteRuntimePolicyError({
1283
+ code,
1284
+ service: decision.service,
1285
+ url: sourceUrl.toString(),
1286
+ message: decision.message
1287
+ });
1288
+ }
1289
+ let response;
1290
+ try {
1291
+ response = await this.originalFetch(input, init);
1292
+ } catch (error) {
1293
+ this.traceRequest({
1294
+ method,
1295
+ sourceUrl: sourceUrl.toString(),
1296
+ manifestMatched: false,
1297
+ target: "upstream",
1298
+ outcome: "failed",
1299
+ reason: "network_error",
1300
+ error: errorMessage(error)
1301
+ });
1302
+ throw error;
1303
+ }
1304
+ this.traceRequest({
1305
+ method,
1306
+ sourceUrl: sourceUrl.toString(),
1307
+ manifestMatched: false,
1308
+ target: "upstream",
1309
+ outcome: "bypassed",
1310
+ reason: "no_match",
1311
+ statusCode: response.status,
1312
+ statusText: response.statusText
1313
+ });
1314
+ return response;
1315
+ });
1316
+ }
1317
+ this.routingInstalled = true;
1318
+ this.emit({ type: "routing_installed", timestamp: now() });
1319
+ }
1320
+ uninstallRouting() {
1321
+ if (!this.routingInstalled) return;
1322
+ httpModule.request = this.originalHttpRequest;
1323
+ httpModule.get = this.originalHttpGet;
1324
+ httpsModule.request = this.originalHttpsRequest;
1325
+ httpsModule.get = this.originalHttpsGet;
1326
+ if (this.originalFetch) {
1327
+ globalThis.fetch = this.originalFetch;
1328
+ }
1329
+ this.routingInstalled = false;
1330
+ this.emit({ type: "routing_uninstalled", timestamp: now() });
1331
+ }
1332
+ getEvents() {
1333
+ return [...this.events];
1334
+ }
1335
+ getTraceRecords() {
1336
+ return [...this.traceRecords];
1337
+ }
1338
+ evaluateRequest(input) {
1339
+ const sourceUrl = input instanceof URL ? input : new URL(input);
1340
+ const hostname = sourceUrl.hostname.toLowerCase();
1341
+ for (const profile of this.profiles) {
1342
+ const classification = classifyHostname(profile, hostname);
1343
+ if (classification === "ignore") {
1344
+ continue;
1345
+ }
1346
+ const configuredService = this.servicesByName.get(profile.service);
1347
+ const diagnosticsContext = {
1348
+ service: profile.service,
1349
+ hostname,
1350
+ url: sourceUrl.toString(),
1351
+ configuredBaseUrl: configuredService?.baseUrl
1352
+ };
1353
+ if (classification === "route") {
1354
+ if (!configuredService) {
1355
+ const message2 = profile.diagnostics.undeclared(diagnosticsContext);
1356
+ this.emit({
1357
+ type: "request_blocked",
1358
+ timestamp: now(),
1359
+ service: profile.service,
1360
+ code: "ARCHAL_UNDECLARED_SERVICE",
1361
+ url: sourceUrl.toString(),
1362
+ message: message2
1363
+ });
1364
+ return {
1365
+ kind: "block",
1366
+ service: profile.service,
1367
+ code: "ARCHAL_UNDECLARED_SERVICE",
1368
+ message: message2
1369
+ };
1370
+ }
1371
+ const targetUrl = mergeUrl(configuredService, sourceUrl, profile);
1372
+ this.emit({
1373
+ type: "request_routed",
1374
+ timestamp: now(),
1375
+ service: profile.service,
1376
+ sourceUrl: sourceUrl.toString(),
1377
+ targetUrl: targetUrl.toString()
1378
+ });
1379
+ return {
1380
+ kind: "route",
1381
+ service: profile.service,
1382
+ targetUrl
1383
+ };
1384
+ }
1385
+ if (classification === "hard-fail") {
1386
+ const code = configuredService ? "ARCHAL_DECLARED_SERVICE_ESCAPE" : "ARCHAL_UNDECLARED_SERVICE";
1387
+ const message2 = configuredService ? profile.diagnostics.declaredEscape(diagnosticsContext) : profile.diagnostics.undeclared(diagnosticsContext);
1388
+ this.emit({
1389
+ type: "request_blocked",
1390
+ timestamp: now(),
1391
+ service: profile.service,
1392
+ code,
1393
+ url: sourceUrl.toString(),
1394
+ message: message2
1395
+ });
1396
+ return {
1397
+ kind: "block",
1398
+ service: profile.service,
1399
+ code,
1400
+ message: message2
1401
+ };
1402
+ }
1403
+ const message = profile.diagnostics.warning(diagnosticsContext);
1404
+ this.emit({
1405
+ type: "request_warning",
1406
+ timestamp: now(),
1407
+ service: profile.service,
1408
+ url: sourceUrl.toString(),
1409
+ message
1410
+ });
1411
+ return {
1412
+ kind: "warn",
1413
+ service: profile.service,
1414
+ message
1415
+ };
1416
+ }
1417
+ for (const profile of SHARED_SERVICE_PROFILES) {
1418
+ const classification = classifyHostname(profile, hostname);
1419
+ if (classification === "ignore") {
1420
+ continue;
1421
+ }
1422
+ const configuredService = this.servicesByName.get(profile.service);
1423
+ const diagnosticsContext = {
1424
+ service: profile.service,
1425
+ hostname,
1426
+ url: sourceUrl.toString(),
1427
+ configuredBaseUrl: configuredService?.baseUrl
1428
+ };
1429
+ const code = configuredService ? "ARCHAL_DECLARED_SERVICE_ESCAPE" : "ARCHAL_UNDECLARED_SERVICE";
1430
+ const message = configuredService ? profile.diagnostics.declaredEscape(diagnosticsContext) : profile.diagnostics.undeclared(diagnosticsContext);
1431
+ this.emit({
1432
+ type: "request_blocked",
1433
+ timestamp: now(),
1434
+ service: profile.service,
1435
+ code,
1436
+ url: sourceUrl.toString(),
1437
+ message
1438
+ });
1439
+ return {
1440
+ kind: "block",
1441
+ service: profile.service,
1442
+ code,
1443
+ message
1444
+ };
1445
+ }
1446
+ return { kind: "allow" };
1447
+ }
1448
+ dispatchRequest(originalHttpRequest, originalHttpsRequest, defaultProtocol, args) {
1449
+ const { requestUrl, options, callback } = parseRequestArgs(defaultProtocol, args);
1450
+ const decision = this.evaluateRequest(requestUrl);
1451
+ const method = typeof options.method === "string" && options.method.trim().length > 0 ? options.method.trim().toUpperCase() : "GET";
1452
+ if (decision.kind === "block") {
1453
+ this.traceRequest({
1454
+ method,
1455
+ sourceUrl: requestUrl.toString(),
1456
+ manifestMatched: true,
1457
+ service: decision.service,
1458
+ target: "none",
1459
+ outcome: "blocked",
1460
+ reason: decision.code === "ARCHAL_UNDECLARED_SERVICE" ? "blocked_undeclared" : "blocked_escape"
1461
+ });
1462
+ throw new RouteRuntimePolicyError({
1463
+ code: decision.code,
1464
+ service: decision.service,
1465
+ url: requestUrl.toString(),
1466
+ message: decision.message
1467
+ });
1468
+ }
1469
+ if (decision.kind === "warn") {
1470
+ const code = blockedWarningCode(decision, this.servicesByName);
1471
+ this.traceRequest({
1472
+ method,
1473
+ sourceUrl: requestUrl.toString(),
1474
+ manifestMatched: true,
1475
+ service: decision.service,
1476
+ target: "none",
1477
+ outcome: "blocked",
1478
+ reason: code === "ARCHAL_UNDECLARED_SERVICE" ? "blocked_undeclared" : "blocked_escape"
1479
+ });
1480
+ throw new RouteRuntimePolicyError({
1481
+ code,
1482
+ service: decision.service,
1483
+ url: requestUrl.toString(),
1484
+ message: decision.message
1485
+ });
1486
+ }
1487
+ if (decision.kind !== "route") {
1488
+ const request = Reflect.apply(
1489
+ defaultProtocol === DEFAULT_HTTPS_PROTOCOL ? originalHttpsRequest : originalHttpRequest,
1490
+ defaultProtocol === DEFAULT_HTTPS_PROTOCOL ? httpsModule : httpModule,
1491
+ args
1492
+ );
1493
+ return this.addTraceListeners(request, {
1494
+ method,
1495
+ sourceUrl: requestUrl.toString(),
1496
+ manifestMatched: false,
1497
+ target: "upstream",
1498
+ outcome: "bypassed",
1499
+ reason: "no_match"
1500
+ });
1501
+ }
1502
+ const service = this.servicesByName.get(decision.service);
1503
+ if (!service) {
1504
+ throw new Error(`Missing routed service config for ${decision.service}.`);
1505
+ }
1506
+ const rewrittenArgs = buildPatchedArgs(
1507
+ decision.targetUrl,
1508
+ options,
1509
+ service,
1510
+ defaultProtocol,
1511
+ wrapRoutedResponseCallback(callback)
1512
+ );
1513
+ if (decision.targetUrl.protocol === DEFAULT_HTTP_PROTOCOL) {
1514
+ const request = bridgeSecureConnectIfNeeded(
1515
+ Reflect.apply(originalHttpRequest, httpModule, rewrittenArgs),
1516
+ defaultProtocol,
1517
+ decision.targetUrl.protocol
1518
+ );
1519
+ return this.addTraceListeners(request, {
1520
+ method,
1521
+ sourceUrl: requestUrl.toString(),
1522
+ manifestMatched: true,
1523
+ service: decision.service,
1524
+ target: "twin",
1525
+ outcome: "routed",
1526
+ reason: "route",
1527
+ targetUrl: decision.targetUrl.toString()
1528
+ });
1529
+ }
1530
+ return this.addTraceListeners(
1531
+ Reflect.apply(originalHttpsRequest, httpsModule, rewrittenArgs),
1532
+ {
1533
+ method,
1534
+ sourceUrl: requestUrl.toString(),
1535
+ manifestMatched: true,
1536
+ service: decision.service,
1537
+ target: "twin",
1538
+ outcome: "routed",
1539
+ reason: "route",
1540
+ targetUrl: decision.targetUrl.toString()
1541
+ }
1542
+ );
1543
+ }
1544
+ dispatchGet(originalHttpGet, originalHttpsGet, defaultProtocol, args) {
1545
+ const { requestUrl, options, callback } = parseRequestArgs(defaultProtocol, args);
1546
+ const decision = this.evaluateRequest(requestUrl);
1547
+ const method = typeof options.method === "string" && options.method.trim().length > 0 ? options.method.trim().toUpperCase() : "GET";
1548
+ if (decision.kind === "block") {
1549
+ this.traceRequest({
1550
+ method,
1551
+ sourceUrl: requestUrl.toString(),
1552
+ manifestMatched: true,
1553
+ service: decision.service,
1554
+ target: "none",
1555
+ outcome: "blocked",
1556
+ reason: decision.code === "ARCHAL_UNDECLARED_SERVICE" ? "blocked_undeclared" : "blocked_escape"
1557
+ });
1558
+ throw new RouteRuntimePolicyError({
1559
+ code: decision.code,
1560
+ service: decision.service,
1561
+ url: requestUrl.toString(),
1562
+ message: decision.message
1563
+ });
1564
+ }
1565
+ if (decision.kind === "warn") {
1566
+ const code = blockedWarningCode(decision, this.servicesByName);
1567
+ this.traceRequest({
1568
+ method,
1569
+ sourceUrl: requestUrl.toString(),
1570
+ manifestMatched: true,
1571
+ service: decision.service,
1572
+ target: "none",
1573
+ outcome: "blocked",
1574
+ reason: code === "ARCHAL_UNDECLARED_SERVICE" ? "blocked_undeclared" : "blocked_escape"
1575
+ });
1576
+ throw new RouteRuntimePolicyError({
1577
+ code,
1578
+ service: decision.service,
1579
+ url: requestUrl.toString(),
1580
+ message: decision.message
1581
+ });
1582
+ }
1583
+ if (decision.kind !== "route") {
1584
+ const request = Reflect.apply(
1585
+ defaultProtocol === DEFAULT_HTTPS_PROTOCOL ? originalHttpsGet : originalHttpGet,
1586
+ defaultProtocol === DEFAULT_HTTPS_PROTOCOL ? httpsModule : httpModule,
1587
+ args
1588
+ );
1589
+ return this.addTraceListeners(request, {
1590
+ method,
1591
+ sourceUrl: requestUrl.toString(),
1592
+ manifestMatched: false,
1593
+ target: "upstream",
1594
+ outcome: "bypassed",
1595
+ reason: "no_match"
1596
+ });
1597
+ }
1598
+ const service = this.servicesByName.get(decision.service);
1599
+ if (!service) {
1600
+ throw new Error(`Missing routed service config for ${decision.service}.`);
1601
+ }
1602
+ const rewrittenArgs = buildPatchedArgs(
1603
+ decision.targetUrl,
1604
+ options,
1605
+ service,
1606
+ defaultProtocol,
1607
+ wrapRoutedResponseCallback(callback)
1608
+ );
1609
+ if (decision.targetUrl.protocol === DEFAULT_HTTP_PROTOCOL) {
1610
+ const request = bridgeSecureConnectIfNeeded(
1611
+ Reflect.apply(originalHttpGet, httpModule, rewrittenArgs),
1612
+ defaultProtocol,
1613
+ decision.targetUrl.protocol
1614
+ );
1615
+ return this.addTraceListeners(request, {
1616
+ method,
1617
+ sourceUrl: requestUrl.toString(),
1618
+ manifestMatched: true,
1619
+ service: decision.service,
1620
+ target: "twin",
1621
+ outcome: "routed",
1622
+ reason: "route",
1623
+ targetUrl: decision.targetUrl.toString()
1624
+ });
1625
+ }
1626
+ return this.addTraceListeners(
1627
+ Reflect.apply(originalHttpsGet, httpsModule, rewrittenArgs),
1628
+ {
1629
+ method,
1630
+ sourceUrl: requestUrl.toString(),
1631
+ manifestMatched: true,
1632
+ service: decision.service,
1633
+ target: "twin",
1634
+ outcome: "routed",
1635
+ reason: "route",
1636
+ targetUrl: decision.targetUrl.toString()
1637
+ }
1638
+ );
1639
+ }
1640
+ emit(event) {
1641
+ this.events.push(event);
1642
+ this.onEvent?.(event);
1643
+ }
1644
+ addTraceListeners(request, record) {
1645
+ if (record.target === "twin") {
1646
+ request.once("response", sanitizeIncomingResponseHeaders);
1647
+ }
1648
+ let settled = false;
1649
+ if (this.traceEnabled) {
1650
+ request.once("response", (response) => {
1651
+ settled = true;
1652
+ this.traceRequest({
1653
+ ...record,
1654
+ statusCode: response.statusCode,
1655
+ statusText: response.statusMessage
1656
+ });
1657
+ });
1658
+ }
1659
+ request.once("error", (error) => {
1660
+ if (this.traceEnabled && !settled) {
1661
+ this.traceRequest({
1662
+ ...record,
1663
+ outcome: "failed",
1664
+ reason: "network_error",
1665
+ error: record.target === "twin" ? CLIENT_VISIBLE_BLOCKED_REQUEST_MESSAGE : error.message
1666
+ });
1667
+ }
1668
+ if (record.target === "twin") {
1669
+ sanitizeClientVisibleNetworkError(error);
1670
+ }
1671
+ });
1672
+ return request;
1673
+ }
1674
+ traceRequest(record) {
1675
+ if (!this.traceEnabled) {
1676
+ return;
1677
+ }
1678
+ const traceRecord = {
1679
+ timestamp: now(),
1680
+ ...record,
1681
+ method: record.method.toUpperCase()
1682
+ };
1683
+ this.traceRecords.push(traceRecord);
1684
+ this.onTrace?.(traceRecord);
1685
+ }
1686
+ };
1687
+
1688
+ // ../route-runtime-core/src/routing-policy.ts
1689
+ import { isIP } from "net";
1690
+ var ROUTE_RUNTIME_PROXY_ROUTE_KINDS = [
1691
+ "api",
1692
+ "mcp",
1693
+ "rest",
1694
+ "state",
1695
+ "trace",
1696
+ "tools",
1697
+ "health",
1698
+ "events",
1699
+ "webhooks",
1700
+ "graphql"
1701
+ ];
1702
+ var ROUTE_RUNTIME_PUBLIC_ROUTE_KINDS = [
1703
+ "api",
1704
+ "mcp",
1705
+ "rest",
1706
+ "graphql"
1707
+ ];
1708
+ var ROUTE_RUNTIME_CONTROL_ROUTE_KINDS = [
1709
+ "state",
1710
+ "trace",
1711
+ "tools",
1712
+ "health",
1713
+ "events",
1714
+ "webhooks"
1715
+ ];
1716
+ var ROUTE_RUNTIME_PROXY_ROUTE_KIND_SET = new Set(ROUTE_RUNTIME_PROXY_ROUTE_KINDS);
1717
+ var ROUTE_RUNTIME_PUBLIC_ROUTE_KIND_SET = new Set(ROUTE_RUNTIME_PUBLIC_ROUTE_KINDS);
1718
+ var ROUTE_RUNTIME_CONTROL_ROUTE_KIND_SET = new Set(ROUTE_RUNTIME_CONTROL_ROUTE_KINDS);
1719
+ var ROUTE_RUNTIME_DISPATCHING_ROUTE_KINDS = new Set(ROUTE_RUNTIME_PUBLIC_ROUTE_KINDS);
1720
+
1721
+ // ../route-runtime-core/src/service-profiles/stripe.ts
1722
+ var stripeCompatibilityProfile = buildServiceCompatibilityProfile(stripeRouteManifest);
1723
+
1724
+ // ../../clones/core/dist/chunk-FBW6QFHJ.js
1725
+ var manifest_default = [
1726
+ {
1727
+ name: "apify",
1728
+ package: "@archal/clone-apify",
1729
+ path: "clones/apify",
1730
+ stage: "preview",
1731
+ display: {
1732
+ icon: "AP",
1733
+ name: "Apify",
1734
+ description: "Actors, runs, datasets, key-value stores, and request queues.",
1735
+ toolCount: 59
1736
+ },
1737
+ transport: "rest",
1738
+ hostedRuntime: {
1739
+ enabled: true,
1740
+ requiresDist: true,
1741
+ requiresEmptySeed: true
1742
+ },
1743
+ cli: {
1744
+ bundleAssets: true,
1745
+ bundleToolSnapshot: true
1746
+ },
1747
+ routeProxy: {
1748
+ enabled: true,
1749
+ domains: [
1750
+ {
1751
+ domain: "api.apify.com"
1752
+ },
1753
+ {
1754
+ domain: "apify.com"
1755
+ }
1756
+ ]
1757
+ },
1758
+ seeds: {
1759
+ generatedCatalog: true,
1760
+ default: "empty"
1761
+ },
1762
+ smoke: {
1763
+ production: "clone-surface"
1764
+ },
1765
+ architecture: {
1766
+ class: "domain-simulator",
1767
+ summary: "Storage/admin state uses StateEngine, but REST behavior still depends on Apify-specific upstream envelopes, fixture fallbacks, and stateful storage projections owned by upstream.ts.",
1768
+ domainPrimitives: [
1769
+ "Apify upstream error and pagination envelopes",
1770
+ "dataset, key-value store, and request-queue stateful projections",
1771
+ "fixture-backed fallback semantics for uncovered REST paths"
1772
+ ],
1773
+ behaviorOwner: "src/upstream.ts"
1774
+ }
1775
+ },
1776
+ {
1777
+ name: "box",
1778
+ package: "@archal/clone-box",
1779
+ path: "clones/box",
1780
+ stage: "internal",
1781
+ transport: "rest",
1782
+ hostedRuntime: {
1783
+ enabled: true,
1784
+ requiresDist: true,
1785
+ requiresEmptySeed: true
1786
+ },
1787
+ cli: {
1788
+ bundleAssets: false,
1789
+ bundleToolSnapshot: false
1790
+ },
1791
+ routeProxy: {
1792
+ enabled: false,
1793
+ domains: []
1794
+ },
1795
+ seeds: {
1796
+ generatedCatalog: false,
1797
+ default: "empty"
1798
+ },
1799
+ smoke: {
1800
+ production: "none"
1801
+ },
1802
+ architecture: {
1803
+ class: "replay-shell",
1804
+ summary: "Current internal Box package is a bootstrap scaffold with no registered tools or REST behavior; promote to standard-core only after file/folder/admin handlers mutate StateEngine and sequence invariants land.",
1805
+ domainPrimitives: [
1806
+ "file and folder hierarchy state",
1807
+ "shared-link and collaboration state",
1808
+ "future change-stream behavior"
1809
+ ],
1810
+ promotionEvidence: [
1811
+ "clones/box/SPEC.md tracks file/folder/admin handlers mutating StateEngine",
1812
+ "clones/box/SPEC.md tracks create/read/update/delete/reset sequence invariants"
1813
+ ]
1814
+ }
1815
+ },
1816
+ {
1817
+ name: "calcom",
1818
+ package: "@archal/clone-calcom",
1819
+ path: "clones/calcom",
1820
+ stage: "preview",
1821
+ display: {
1822
+ icon: "CC",
1823
+ name: "Cal.com",
1824
+ description: "Event types, schedules, availability slots, bookings, calendars, and webhooks.",
1825
+ toolCount: 18
1826
+ },
1827
+ transport: "both",
1828
+ hostedRuntime: {
1829
+ enabled: true,
1830
+ requiresDist: true,
1831
+ requiresEmptySeed: true
1832
+ },
1833
+ cli: {
1834
+ bundleAssets: true,
1835
+ bundleToolSnapshot: true
1836
+ },
1837
+ routeProxy: {
1838
+ enabled: true,
1839
+ domains: [
1840
+ {
1841
+ domain: "api.cal.com"
1842
+ }
1843
+ ]
1844
+ },
1845
+ seeds: {
1846
+ generatedCatalog: true,
1847
+ default: "empty"
1848
+ },
1849
+ smoke: {
1850
+ production: "health-and-seed"
1851
+ },
1852
+ architecture: {
1853
+ class: "domain-simulator",
1854
+ summary: "Cal.com relies on availability computation, booking lifecycle, calendar projection, and webhook lifecycle semantics beyond generic CRUD.",
1855
+ domainPrimitives: [
1856
+ "schedule and event-type availability projection",
1857
+ "booking create, cancel, and reschedule lifecycle",
1858
+ "host-wide busy interval and slot computation",
1859
+ "webhook summary/detail lifecycle projection"
1860
+ ],
1861
+ behaviorOwner: "src/state/relationships.ts"
1862
+ }
1863
+ },
1864
+ {
1865
+ name: "clickup",
1866
+ package: "@archal/clone-clickup",
1867
+ path: "clones/clickup",
1868
+ stage: "preview",
1869
+ display: {
1870
+ icon: "CU",
1871
+ name: "ClickUp",
1872
+ description: "Teams, spaces, folders, lists, tasks, comments, tags, time tracking, and checklists.",
1873
+ toolCount: 40
1874
+ },
1875
+ transport: "both",
1876
+ hostedRuntime: {
1877
+ enabled: true,
1878
+ requiresDist: true,
1879
+ requiresEmptySeed: true
1880
+ },
1881
+ cli: {
1882
+ bundleAssets: true,
1883
+ bundleToolSnapshot: true
1884
+ },
1885
+ routeProxy: {
1886
+ enabled: true,
1887
+ domains: [
1888
+ {
1889
+ domain: "api.clickup.com"
1890
+ }
1891
+ ]
1892
+ },
1893
+ seeds: {
1894
+ generatedCatalog: true,
1895
+ default: "empty"
1896
+ },
1897
+ smoke: {
1898
+ production: "health-and-seed"
1899
+ },
1900
+ architecture: {
1901
+ class: "standard-core",
1902
+ summary: "Current task/project/comment workflows are modeled as mutable records plus relationships."
1903
+ }
1904
+ },
1905
+ {
1906
+ name: "customerio",
1907
+ package: "@archal/clone-customerio",
1908
+ path: "clones/customerio",
1909
+ stage: "preview",
1910
+ display: {
1911
+ icon: "CI",
1912
+ name: "Customer.io",
1913
+ description: "Messaging automation across campaigns, broadcasts, transactional email, segments, customers, and message delivery.",
1914
+ toolCount: 22
1915
+ },
1916
+ transport: "rest",
1917
+ hostedRuntime: {
1918
+ enabled: true,
1919
+ requiresDist: true,
1920
+ requiresEmptySeed: true
1921
+ },
1922
+ cli: {
1923
+ bundleAssets: true,
1924
+ bundleToolSnapshot: true
1925
+ },
1926
+ routeProxy: {
1927
+ enabled: true,
1928
+ domains: [
1929
+ {
1930
+ domain: "api.customer.io"
1931
+ }
1932
+ ]
1933
+ },
1934
+ seeds: {
1935
+ generatedCatalog: true,
1936
+ default: "empty"
1937
+ },
1938
+ smoke: {
1939
+ production: "clone-surface"
1940
+ },
1941
+ architecture: {
1942
+ class: "standard-core",
1943
+ summary: "Current preview slice models campaigns, broadcasts, segments, customers, triggers, and message delivery as deterministic StateEngine records; Track API, webhook delivery, and real audience projection are not claimed."
1944
+ }
1945
+ },
1946
+ {
1947
+ name: "datadog",
1948
+ package: "@archal/clone-datadog",
1949
+ path: "clones/datadog",
1950
+ stage: "preview",
1951
+ display: {
1952
+ icon: "DD",
1953
+ name: "Datadog",
1954
+ description: "Observability REST API \u2014 metrics, logs, monitors, dashboards, SLOs, incidents, teams, users, and service definitions.",
1955
+ toolCount: 63
1956
+ },
1957
+ transport: "rest",
1958
+ hostedRuntime: {
1959
+ enabled: true,
1960
+ requiresDist: true,
1961
+ requiresEmptySeed: true
1962
+ },
1963
+ cli: {
1964
+ bundleAssets: true,
1965
+ bundleToolSnapshot: true
1966
+ },
1967
+ routeProxy: {
1968
+ enabled: true,
1969
+ domains: [
1970
+ {
1971
+ domain: "api.datadoghq.com"
1972
+ }
1973
+ ]
1974
+ },
1975
+ seeds: {
1976
+ generatedCatalog: true,
1977
+ default: "empty"
1978
+ },
1979
+ smoke: {
1980
+ production: "clone-surface"
1981
+ },
1982
+ architecture: {
1983
+ class: "domain-simulator",
1984
+ summary: "Observability behavior depends on time-windowed queries and monitor state, so REST routes must stay thin over DatadogSimulator.",
1985
+ domainPrimitives: [
1986
+ "time-series query windows",
1987
+ "log query filtering",
1988
+ "monitor evaluation and alert state"
1989
+ ],
1990
+ behaviorOwner: "src/domain/simulator.ts"
1991
+ }
1992
+ },
1993
+ {
1994
+ name: "discord",
1995
+ package: "@archal/clone-discord",
1996
+ path: "clones/discord",
1997
+ stage: "preview",
1998
+ display: {
1999
+ icon: "DC",
2000
+ name: "Discord",
2001
+ description: "Guilds, channels, messages, webhooks, threads, commands, and interaction responses.",
2002
+ toolCount: 67
2003
+ },
2004
+ transport: "both",
2005
+ hostedRuntime: {
2006
+ enabled: true,
2007
+ requiresDist: true,
2008
+ requiresEmptySeed: true
2009
+ },
2010
+ cli: {
2011
+ bundleAssets: true,
2012
+ bundleToolSnapshot: true
2013
+ },
2014
+ routeProxy: {
2015
+ enabled: true,
2016
+ domains: [
2017
+ {
2018
+ domain: "discord.com"
2019
+ },
2020
+ {
2021
+ domain: "discordapp.com"
2022
+ }
2023
+ ]
2024
+ },
2025
+ seeds: {
2026
+ generatedCatalog: true,
2027
+ default: "small-server"
2028
+ },
2029
+ smoke: {
2030
+ production: "health-and-seed"
2031
+ },
2032
+ architecture: {
2033
+ class: "domain-simulator",
2034
+ summary: "Chat correctness depends on channel/message/thread ordering, membership visibility, and event delivery.",
2035
+ domainPrimitives: [
2036
+ "channel membership",
2037
+ "message timeline ordering",
2038
+ "thread and event delivery semantics"
2039
+ ],
2040
+ behaviorOwner: "src/domain/discord-simulator.ts"
2041
+ },
2042
+ inferenceKeywords: [
2043
+ "discord",
2044
+ "guild",
2045
+ "text channel",
2046
+ "thread",
2047
+ "webhook",
2048
+ "slash command"
2049
+ ]
2050
+ },
2051
+ {
2052
+ name: "dropbox",
2053
+ package: "@archal/clone-dropbox",
2054
+ path: "clones/dropbox",
2055
+ stage: "internal",
2056
+ transport: "mcp",
2057
+ hostedRuntime: {
2058
+ enabled: true,
2059
+ requiresDist: true,
2060
+ requiresEmptySeed: true
2061
+ },
2062
+ cli: {
2063
+ bundleAssets: false,
2064
+ bundleToolSnapshot: false
2065
+ },
2066
+ routeProxy: {
2067
+ enabled: false,
2068
+ domains: []
2069
+ },
2070
+ seeds: {
2071
+ generatedCatalog: false,
2072
+ default: "empty"
2073
+ },
2074
+ smoke: {
2075
+ production: "none"
2076
+ },
2077
+ architecture: {
2078
+ class: "domain-simulator",
2079
+ summary: "A faithful Dropbox surface depends on file tree, revision, sharing, and delta/list-folder cursor semantics.",
2080
+ domainPrimitives: [
2081
+ "file tree and revisions",
2082
+ "sharing permissions",
2083
+ "delta/list-folder cursors"
2084
+ ],
2085
+ behaviorOwner: "src/domain/files.ts"
2086
+ }
2087
+ },
2088
+ {
2089
+ name: "figma",
2090
+ package: "@archal/clone-figma",
2091
+ path: "clones/figma",
2092
+ stage: "internal",
2093
+ transport: "mcp",
2094
+ hostedRuntime: {
2095
+ enabled: true,
2096
+ requiresDist: true,
2097
+ requiresEmptySeed: true
2098
+ },
2099
+ cli: {
2100
+ bundleAssets: false,
2101
+ bundleToolSnapshot: false
2102
+ },
2103
+ routeProxy: {
2104
+ enabled: false,
2105
+ domains: []
2106
+ },
2107
+ seeds: {
2108
+ generatedCatalog: false,
2109
+ default: "empty"
2110
+ },
2111
+ smoke: {
2112
+ production: "none"
2113
+ },
2114
+ architecture: {
2115
+ class: "standard-core",
2116
+ summary: "Current internal Figma slice is treated as deterministic file/project records; live collaboration semantics are not claimed."
2117
+ }
2118
+ },
2119
+ {
2120
+ name: "firecrawl",
2121
+ package: "@archal/clone-firecrawl",
2122
+ path: "clones/firecrawl",
2123
+ stage: "internal",
2124
+ display: {
2125
+ icon: "FC",
2126
+ name: "Firecrawl",
2127
+ description: "Scraping, crawling, mapping, search, and extraction.",
2128
+ toolCount: 6
2129
+ },
2130
+ transport: "rest",
2131
+ hostedRuntime: {
2132
+ enabled: true,
2133
+ requiresDist: true,
2134
+ requiresEmptySeed: true
2135
+ },
2136
+ cli: {
2137
+ bundleAssets: false,
2138
+ bundleToolSnapshot: false
2139
+ },
2140
+ routeProxy: {
2141
+ enabled: false,
2142
+ domains: []
2143
+ },
2144
+ seeds: {
2145
+ generatedCatalog: false,
2146
+ default: "empty"
2147
+ },
2148
+ smoke: {
2149
+ production: "none"
2150
+ },
2151
+ architecture: {
2152
+ class: "replay-shell",
2153
+ summary: "Current Firecrawl surface is recorded-response replay with request-key matching; crawling engine behavior is not modeled yet.",
2154
+ domainPrimitives: [
2155
+ "recording-backed scrape/search/map response replay",
2156
+ "request key canonicalization",
2157
+ "future crawl/job lifecycle simulator"
2158
+ ],
2159
+ promotionEvidence: [
2160
+ "clones/firecrawl/SPEC.md tracks crawl/job lifecycle state model",
2161
+ "clones/firecrawl/SPEC.md tracks runtime recording-backed handlers replaced by state-derived crawl/job responses",
2162
+ "clones/firecrawl/SPEC.md tracks status/progress/replay determinism invariants"
2163
+ ]
2164
+ }
2165
+ },
2166
+ {
2167
+ name: "freshdesk",
2168
+ package: "@archal/clone-freshdesk",
2169
+ path: "clones/freshdesk",
2170
+ stage: "internal",
2171
+ transport: "rest",
2172
+ hostedRuntime: {
2173
+ enabled: true,
2174
+ requiresDist: true,
2175
+ requiresEmptySeed: true
2176
+ },
2177
+ cli: {
2178
+ bundleAssets: false,
2179
+ bundleToolSnapshot: false
2180
+ },
2181
+ routeProxy: {
2182
+ enabled: false,
2183
+ domains: []
2184
+ },
2185
+ seeds: {
2186
+ generatedCatalog: false,
2187
+ default: "empty"
2188
+ },
2189
+ smoke: {
2190
+ production: "none"
2191
+ },
2192
+ architecture: {
2193
+ class: "replay-shell",
2194
+ summary: "Current internal Freshdesk package is a bootstrap scaffold with no registered tools or REST behavior; promote to standard-core only after ticket/contact/conversation handlers mutate StateEngine and sequence invariants land.",
2195
+ domainPrimitives: [
2196
+ "ticket status transitions",
2197
+ "contact and company records",
2198
+ "conversation and note history"
2199
+ ],
2200
+ promotionEvidence: [
2201
+ "clones/freshdesk/SPEC.md tracks runtime recording-backed handlers replaced by state-derived ticket/contact/conversation handlers",
2202
+ "clones/freshdesk/SPEC.md tracks ticket/contact/conversation handlers mutating StateEngine",
2203
+ "clones/freshdesk/SPEC.md tracks comment/status/read-after-write/reset sequence invariants"
2204
+ ]
2205
+ }
2206
+ },
2207
+ {
2208
+ name: "github",
2209
+ package: "@archal/clone-github",
2210
+ path: "clones/github",
2211
+ stage: "public",
2212
+ display: {
2213
+ icon: "GH",
2214
+ name: "GitHub",
2215
+ description: "Repos, issues, pull requests, branches, and commits.",
2216
+ toolCount: 26
2217
+ },
2218
+ transport: "both",
2219
+ hostedRuntime: {
2220
+ enabled: true,
2221
+ requiresDist: true,
2222
+ requiresEmptySeed: true
2223
+ },
2224
+ cli: {
2225
+ bundleAssets: true,
2226
+ bundleToolSnapshot: true
2227
+ },
2228
+ routeProxy: {
2229
+ enabled: true,
2230
+ domains: [
2231
+ {
2232
+ domain: "api.github.com"
2233
+ },
2234
+ {
2235
+ domain: "uploads.github.com"
2236
+ },
2237
+ {
2238
+ domain: "github.com"
2239
+ },
2240
+ {
2241
+ domain: "raw.githubusercontent.com"
2242
+ }
2243
+ ]
2244
+ },
2245
+ seeds: {
2246
+ generatedCatalog: true,
2247
+ default: "small-project"
2248
+ },
2249
+ smoke: {
2250
+ production: "health-and-seed"
2251
+ },
2252
+ architecture: {
2253
+ class: "domain-simulator",
2254
+ summary: "GitHub relies on repository, issue, pull-request, review, workflow, permission, and webhook semantics beyond generic CRUD.",
2255
+ domainPrimitives: [
2256
+ "repository branch, commit, tree, and diff projection",
2257
+ "issue and pull-request lifecycle projection",
2258
+ "review, comment, and status/check aggregation",
2259
+ "permission-scoped reads and webhook event delivery"
2260
+ ],
2261
+ behaviorOwner: "src/state/transition-rules.ts"
2262
+ },
2263
+ inferenceKeywords: [
2264
+ "github",
2265
+ "repository",
2266
+ "pull request",
2267
+ "issue",
2268
+ "create_issue",
2269
+ "create_pull_request",
2270
+ "merge_pull_request"
2271
+ ]
2272
+ },
2273
+ {
2274
+ name: "gitlab",
2275
+ package: "@archal/clone-gitlab",
2276
+ path: "clones/gitlab",
2277
+ stage: "preview",
2278
+ display: {
2279
+ icon: "GL",
2280
+ name: "GitLab",
2281
+ description: "Projects, branches, commits, issues, merge requests, pipelines, labels, milestones, releases, and webhooks.",
2282
+ toolCount: 46
2283
+ },
2284
+ transport: "both",
2285
+ hostedRuntime: {
2286
+ enabled: true,
2287
+ requiresDist: true,
2288
+ requiresEmptySeed: true
2289
+ },
2290
+ cli: {
2291
+ bundleAssets: true,
2292
+ bundleToolSnapshot: true
2293
+ },
2294
+ routeProxy: {
2295
+ enabled: true,
2296
+ domains: [
2297
+ {
2298
+ domain: "gitlab.com"
2299
+ }
2300
+ ]
2301
+ },
2302
+ seeds: {
2303
+ generatedCatalog: true,
2304
+ default: "empty"
2305
+ },
2306
+ smoke: {
2307
+ production: "health-and-seed"
2308
+ },
2309
+ architecture: {
2310
+ class: "domain-simulator",
2311
+ summary: "Current GitLab workflows combine StateEngine records with GitLab-specific repository, issue, merge-request, note, webhook, and event-delivery semantics.",
2312
+ domainPrimitives: [
2313
+ "issue and merge-request state transitions",
2314
+ "note/comment ordering and count projection",
2315
+ "repository branch, commit, merge, and diff projection",
2316
+ "GitLab webhook subscription, payload, and header emission"
2317
+ ],
2318
+ behaviorOwner: "src/state/webhook-delivery-config.ts"
2319
+ }
2320
+ },
2321
+ {
2322
+ name: "google-workspace",
2323
+ package: "@archal/clone-google-workspace",
2324
+ path: "clones/google-workspace",
2325
+ stage: "preview",
2326
+ display: {
2327
+ icon: "GW",
2328
+ name: "Google Workspace",
2329
+ description: "Gmail, Calendar, Drive, Sheets, and Contacts.",
2330
+ toolCount: 249
2331
+ },
2332
+ transport: "both",
2333
+ hostedRuntime: {
2334
+ enabled: true,
2335
+ requiresDist: true,
2336
+ requiresEmptySeed: true
2337
+ },
2338
+ cli: {
2339
+ bundleAssets: true,
2340
+ bundleToolSnapshot: true
2341
+ },
2342
+ routeProxy: {
2343
+ enabled: true,
2344
+ domains: [
2345
+ {
2346
+ domain: "gmail.googleapis.com"
2347
+ },
2348
+ {
2349
+ domain: "drive.googleapis.com"
2350
+ },
2351
+ {
2352
+ domain: "calendar.googleapis.com"
2353
+ },
2354
+ {
2355
+ domain: "people.googleapis.com"
2356
+ },
2357
+ {
2358
+ domain: "sheets.googleapis.com"
2359
+ },
2360
+ {
2361
+ domain: "oauth2.googleapis.com"
2362
+ },
2363
+ {
2364
+ domain: "www.googleapis.com"
2365
+ }
2366
+ ]
2367
+ },
2368
+ seeds: {
2369
+ generatedCatalog: true,
2370
+ default: "assistant-baseline"
2371
+ },
2372
+ smoke: {
2373
+ production: "health-and-seed"
2374
+ },
2375
+ architecture: {
2376
+ class: "domain-simulator",
2377
+ summary: "Gmail, Drive, Calendar, and watches rely on histories, change feeds, recurrence, sync tokens, and permission projection.",
2378
+ domainPrimitives: [
2379
+ "Gmail thread/history engine",
2380
+ "Drive changes and permissions",
2381
+ "Calendar recurrence and watch sync"
2382
+ ],
2383
+ behaviorOwner: "src/domain/google-workspace-simulator.ts"
2384
+ },
2385
+ inferenceKeywords: [
2386
+ "google workspace",
2387
+ "gmail",
2388
+ "google calendar",
2389
+ "calendar event",
2390
+ "inbox",
2391
+ "email draft",
2392
+ "google drive",
2393
+ "google sheet",
2394
+ "google sheets",
2395
+ "contacts"
2396
+ ]
2397
+ },
2398
+ {
2399
+ name: "hubspot",
2400
+ package: "@archal/clone-hubspot",
2401
+ path: "clones/hubspot",
2402
+ stage: "preview",
2403
+ display: {
2404
+ icon: "HS",
2405
+ name: "HubSpot",
2406
+ description: "CRM contacts, companies, deals, tickets, and engagements.",
2407
+ toolCount: 41
2408
+ },
2409
+ transport: "rest",
2410
+ hostedRuntime: {
2411
+ enabled: true,
2412
+ requiresDist: true,
2413
+ requiresEmptySeed: true
2414
+ },
2415
+ cli: {
2416
+ bundleAssets: true,
2417
+ bundleToolSnapshot: true
2418
+ },
2419
+ routeProxy: {
2420
+ enabled: true,
2421
+ domains: [
2422
+ {
2423
+ domain: "api.hubapi.com"
2424
+ }
2425
+ ]
2426
+ },
2427
+ seeds: {
2428
+ generatedCatalog: true,
2429
+ default: "empty"
2430
+ },
2431
+ smoke: {
2432
+ production: "clone-surface"
2433
+ },
2434
+ architecture: {
2435
+ class: "replay-shell",
2436
+ summary: "HubSpot has a stateful CRM overlay, but the exported REST/MCP surface still includes broad runtime recording replay.",
2437
+ domainPrimitives: [
2438
+ "CRM object state and associations",
2439
+ "Pipeline and property schema state",
2440
+ "Recording-backed REST/MCP aliases awaiting stateful lift"
2441
+ ],
2442
+ promotionEvidence: [
2443
+ "clones/hubspot/SPEC.md tracks runtime recording reads removed from exported handlers",
2444
+ "clones/hubspot/SPEC.md tracks CRM alias and association sequence invariants"
2445
+ ]
2446
+ }
2447
+ },
2448
+ {
2449
+ name: "jira",
2450
+ package: "@archal/clone-jira",
2451
+ path: "clones/jira",
2452
+ stage: "preview",
2453
+ display: {
2454
+ icon: "JR",
2455
+ name: "Jira",
2456
+ description: "Issues, projects, boards, sprints, and versions.",
2457
+ toolCount: 49
2458
+ },
2459
+ transport: "both",
2460
+ hostedRuntime: {
2461
+ enabled: true,
2462
+ requiresDist: true,
2463
+ requiresEmptySeed: true
2464
+ },
2465
+ cli: {
2466
+ bundleAssets: true,
2467
+ bundleToolSnapshot: true
2468
+ },
2469
+ routeProxy: {
2470
+ enabled: true,
2471
+ domains: [
2472
+ {
2473
+ domain: "atlassian.net",
2474
+ matchSubdomains: true
2475
+ },
2476
+ {
2477
+ domain: "api.atlassian.com"
2478
+ },
2479
+ {
2480
+ domain: "jira.atlassian.com"
2481
+ }
2482
+ ]
2483
+ },
2484
+ seeds: {
2485
+ generatedCatalog: true,
2486
+ default: "small-project"
2487
+ },
2488
+ smoke: {
2489
+ production: "health-and-seed"
2490
+ },
2491
+ architecture: {
2492
+ class: "domain-simulator",
2493
+ summary: "Jira relies on issue workflow, JQL/search, sprint/version timelines, permission projection, history, and webhook semantics beyond generic CRUD.",
2494
+ domainPrimitives: [
2495
+ "issue workflow transitions and mutation history",
2496
+ "JQL/search and issue field projection",
2497
+ "sprint, board, version, and SLA time-based transitions",
2498
+ "permission-scoped reads and Jira webhook event delivery"
2499
+ ],
2500
+ behaviorOwner: "src/state/transition-rules.ts"
2501
+ },
2502
+ inferenceKeywords: [
2503
+ "jira",
2504
+ "jira sprint",
2505
+ "jira epic",
2506
+ "jira board",
2507
+ "sprint",
2508
+ "epic"
2509
+ ]
2510
+ },
2511
+ {
2512
+ name: "linear",
2513
+ package: "@archal/clone-linear",
2514
+ path: "clones/linear",
2515
+ stage: "public",
2516
+ display: {
2517
+ icon: "LN",
2518
+ name: "Linear",
2519
+ description: "Issues, projects, teams, cycles, and workflows.",
2520
+ toolCount: 50
2521
+ },
2522
+ transport: "both",
2523
+ hostedRuntime: {
2524
+ enabled: true,
2525
+ requiresDist: true,
2526
+ requiresEmptySeed: true
2527
+ },
2528
+ cli: {
2529
+ bundleAssets: true,
2530
+ bundleToolSnapshot: true
2531
+ },
2532
+ routeProxy: {
2533
+ enabled: true,
2534
+ domains: [
2535
+ {
2536
+ domain: "api.linear.app"
2537
+ }
2538
+ ]
2539
+ },
2540
+ seeds: {
2541
+ generatedCatalog: true,
2542
+ default: "small-team"
2543
+ },
2544
+ smoke: {
2545
+ production: "health-and-seed"
2546
+ },
2547
+ architecture: {
2548
+ class: "domain-simulator",
2549
+ summary: "Current Linear workflows combine deterministic records with Linear-specific GraphQL dispatch, validation, issue workflow transitions, comments, cycles, and derived projections.",
2550
+ domainPrimitives: [
2551
+ "GraphQL operation dispatch",
2552
+ "issue workflow transitions",
2553
+ "comment and history projection",
2554
+ "cycle and project progress projection"
2555
+ ],
2556
+ behaviorOwner: "src/rest/graphql-dispatch.ts"
2557
+ },
2558
+ inferenceKeywords: [
2559
+ "linear",
2560
+ "linear ticket",
2561
+ "linear project",
2562
+ "linear cycle",
2563
+ "cycle"
2564
+ ]
2565
+ },
2566
+ {
2567
+ name: "mailchimp",
2568
+ package: "@archal/clone-mailchimp",
2569
+ path: "clones/mailchimp",
2570
+ stage: "internal",
2571
+ transport: "mcp",
2572
+ hostedRuntime: {
2573
+ enabled: true,
2574
+ requiresDist: true,
2575
+ requiresEmptySeed: true
2576
+ },
2577
+ cli: {
2578
+ bundleAssets: false,
2579
+ bundleToolSnapshot: false
2580
+ },
2581
+ routeProxy: {
2582
+ enabled: false,
2583
+ domains: []
2584
+ },
2585
+ seeds: {
2586
+ generatedCatalog: false,
2587
+ default: "empty"
2588
+ },
2589
+ smoke: {
2590
+ production: "none"
2591
+ },
2592
+ architecture: {
2593
+ class: "standard-core",
2594
+ summary: "Current internal Mailchimp slice models lists, members, merge fields, segments, campaign content, and campaign status as deterministic StateEngine records; send delivery, reports, and real audience activity history are not claimed."
2595
+ }
2596
+ },
2597
+ {
2598
+ name: "ownerrez",
2599
+ package: "@archal/clone-ownerrez",
2600
+ path: "clones/ownerrez",
2601
+ stage: "preview",
2602
+ display: {
2603
+ icon: "OR",
2604
+ name: "OwnerRez",
2605
+ description: "Vacation-rental PMS \u2014 properties, bookings, guests, quotes, financial reads, tags, fields, and webhook subscriptions.",
2606
+ toolCount: 25
2607
+ },
2608
+ transport: "rest",
2609
+ hostedRuntime: {
2610
+ enabled: true,
2611
+ requiresDist: true,
2612
+ requiresEmptySeed: true
2613
+ },
2614
+ cli: {
2615
+ bundleAssets: true,
2616
+ bundleToolSnapshot: true
2617
+ },
2618
+ routeProxy: {
2619
+ enabled: true,
2620
+ domains: [
2621
+ {
2622
+ domain: "api.ownerrez.com"
2623
+ },
2624
+ {
2625
+ domain: "ownerrez.com"
2626
+ }
2627
+ ]
2628
+ },
2629
+ seeds: {
2630
+ generatedCatalog: true,
2631
+ default: "empty"
2632
+ },
2633
+ smoke: {
2634
+ production: "clone-surface"
2635
+ },
2636
+ architecture: {
2637
+ class: "replay-shell",
2638
+ summary: "OwnerRez is primarily recording-replay backed with focused stateful overlays; it should not be treated as a general PMS simulator.",
2639
+ domainPrimitives: [
2640
+ "recording replay cursor",
2641
+ "tag and guest stateful overlays",
2642
+ "booking/payment read projections"
2643
+ ],
2644
+ promotionEvidence: [
2645
+ "clones/ownerrez/SPEC.md tracks reservation or payment lifecycle state model",
2646
+ "clones/ownerrez/SPEC.md tracks runtime recording-backed handlers replaced for the promoted reservation/payment slice",
2647
+ "clones/ownerrez/SPEC.md tracks availability/payment invariants"
2648
+ ]
2649
+ }
2650
+ },
2651
+ {
2652
+ name: "posthog",
2653
+ package: "@archal/clone-posthog",
2654
+ path: "clones/posthog",
2655
+ stage: "internal",
2656
+ transport: "rest",
2657
+ hostedRuntime: {
2658
+ enabled: true,
2659
+ requiresDist: true,
2660
+ requiresEmptySeed: true
2661
+ },
2662
+ cli: {
2663
+ bundleAssets: false,
2664
+ bundleToolSnapshot: false
2665
+ },
2666
+ routeProxy: {
2667
+ enabled: false,
2668
+ domains: []
2669
+ },
2670
+ seeds: {
2671
+ generatedCatalog: false,
2672
+ default: "empty"
2673
+ },
2674
+ smoke: {
2675
+ production: "none"
2676
+ },
2677
+ architecture: {
2678
+ class: "redesign-before-expansion",
2679
+ summary: "PostHog currently models admin resources and frozen insight/dashboard projections; event ingestion, query execution, and feature flag evaluation need a simulator contract before expansion.",
2680
+ domainPrimitives: ["event ingestion", "query execution", "feature flag evaluation"],
2681
+ redesignContract: "docs/internal/dev/clones/redesign-contracts/posthog.md"
2682
+ }
2683
+ },
2684
+ {
2685
+ name: "pricelabs",
2686
+ package: "@archal/clone-pricelabs",
2687
+ path: "clones/pricelabs",
2688
+ stage: "preview",
2689
+ display: {
2690
+ icon: "PL",
2691
+ name: "PriceLabs",
2692
+ description: "Dynamic pricing \u2014 listings, overrides, neighborhood data, rate plans, and reservations.",
2693
+ toolCount: 11
2694
+ },
2695
+ transport: "rest",
2696
+ hostedRuntime: {
2697
+ enabled: true,
2698
+ requiresDist: true,
2699
+ requiresEmptySeed: true
2700
+ },
2701
+ cli: {
2702
+ bundleAssets: true,
2703
+ bundleToolSnapshot: true
2704
+ },
2705
+ routeProxy: {
2706
+ enabled: true,
2707
+ domains: [
2708
+ {
2709
+ domain: "api.pricelabs.co"
2710
+ },
2711
+ {
2712
+ domain: "pricelabs.co"
2713
+ }
2714
+ ]
2715
+ },
2716
+ seeds: {
2717
+ generatedCatalog: true,
2718
+ default: "empty"
2719
+ },
2720
+ smoke: {
2721
+ production: "clone-surface"
2722
+ },
2723
+ architecture: {
2724
+ class: "replay-shell",
2725
+ summary: "PriceLabs remains a replay-shell-plus-overlay surface until pricing/listing/reservation state is fully modeled.",
2726
+ domainPrimitives: [
2727
+ "recording replay cursor",
2728
+ "listing settings overlay",
2729
+ "pricing and reservation projections"
2730
+ ],
2731
+ promotionEvidence: [
2732
+ "clones/pricelabs/SPEC.md tracks pricing calendar or reservation lifecycle state model",
2733
+ "clones/pricelabs/SPEC.md tracks runtime recording-backed handlers replaced for the promoted pricing/reservation slice",
2734
+ "clones/pricelabs/SPEC.md tracks occupancy/rate/replay determinism invariants"
2735
+ ]
2736
+ }
2737
+ },
2738
+ {
2739
+ name: "quickbooks",
2740
+ package: "@archal/clone-quickbooks",
2741
+ path: "clones/quickbooks",
2742
+ stage: "internal",
2743
+ transport: "rest",
2744
+ hostedRuntime: {
2745
+ enabled: true,
2746
+ requiresDist: true,
2747
+ requiresEmptySeed: true
2748
+ },
2749
+ cli: {
2750
+ bundleAssets: false,
2751
+ bundleToolSnapshot: false
2752
+ },
2753
+ routeProxy: {
2754
+ enabled: false,
2755
+ domains: []
2756
+ },
2757
+ seeds: {
2758
+ generatedCatalog: false,
2759
+ default: "empty"
2760
+ },
2761
+ smoke: {
2762
+ production: "none"
2763
+ },
2764
+ architecture: {
2765
+ class: "replay-shell",
2766
+ summary: "QuickBooks serves a recording-backed REST/MCP surface; it cannot safely expand as CRUD because accounting correctness depends on ledgers, balances, and immutable audit history.",
2767
+ domainPrimitives: [
2768
+ "ledger entries",
2769
+ "balance reconciliation",
2770
+ "invoice/payment audit history",
2771
+ "recording-backed REST/MCP surface awaiting stateful lift"
2772
+ ],
2773
+ promotionEvidence: [
2774
+ "clones/quickbooks/SPEC.md tracks runtime recording reads removed from exported handlers",
2775
+ "clones/quickbooks/SPEC.md tracks ledger -> balance and payment/refund -> reversal sequence invariants"
2776
+ ]
2777
+ }
2778
+ },
2779
+ {
2780
+ name: "ramp",
2781
+ package: "@archal/clone-ramp",
2782
+ path: "clones/ramp",
2783
+ stage: "preview",
2784
+ display: {
2785
+ icon: "RP",
2786
+ name: "Ramp",
2787
+ description: "Cards, funds, expenses, reimbursements, bills, and travel.",
2788
+ toolCount: 46
2789
+ },
2790
+ transport: "mcp",
2791
+ hostedRuntime: {
2792
+ enabled: true,
2793
+ requiresDist: true,
2794
+ requiresEmptySeed: true
2795
+ },
2796
+ cli: {
2797
+ bundleAssets: true,
2798
+ bundleToolSnapshot: true
2799
+ },
2800
+ routeProxy: {
2801
+ enabled: true,
2802
+ domains: [
2803
+ {
2804
+ domain: "api.ramp.com"
2805
+ },
2806
+ {
2807
+ domain: "app.ramp.com"
2808
+ }
2809
+ ]
2810
+ },
2811
+ seeds: {
2812
+ generatedCatalog: true,
2813
+ default: "default"
2814
+ },
2815
+ smoke: {
2816
+ production: "health-and-seed"
2817
+ },
2818
+ architecture: {
2819
+ class: "replay-shell",
2820
+ summary: "Ramp is CLI-native and fixture-scoped with seeded state overlays; it is not a broad state-derived Ramp or finance simulator.",
2821
+ domainPrimitives: [
2822
+ "checked-in Ramp CLI fixture corpus",
2823
+ "CLI-native command routing",
2824
+ "seeded approval and comment overlays"
2825
+ ],
2826
+ promotionEvidence: [
2827
+ "clones/ramp/SPEC.md tracks state-derived Ramp vertical slice beyond fixture replay",
2828
+ "clones/ramp/SPEC.md tracks approval/comment/CLI reset sequence invariants"
2829
+ ]
2830
+ },
2831
+ inferenceKeywords: [
2832
+ "ramp",
2833
+ "bill",
2834
+ "expense",
2835
+ "reimbursement",
2836
+ "fund",
2837
+ "card spend"
2838
+ ]
2839
+ },
2840
+ {
2841
+ name: "sendgrid",
2842
+ package: "@archal/clone-sendgrid",
2843
+ path: "clones/sendgrid",
2844
+ stage: "internal",
2845
+ display: {
2846
+ icon: "SG",
2847
+ name: "SendGrid",
2848
+ description: "Internal replay evidence for account, scopes, templates, suppressions, tracking, mail settings, alerts, and sender authentication.",
2849
+ toolCount: 26
2850
+ },
2851
+ transport: "rest",
2852
+ hostedRuntime: {
2853
+ enabled: true,
2854
+ requiresDist: true,
2855
+ requiresEmptySeed: true
2856
+ },
2857
+ cli: {
2858
+ bundleAssets: false,
2859
+ bundleToolSnapshot: false
2860
+ },
2861
+ routeProxy: {
2862
+ enabled: true,
2863
+ domains: [
2864
+ {
2865
+ domain: "api.sendgrid.com"
2866
+ }
2867
+ ]
2868
+ },
2869
+ seeds: {
2870
+ generatedCatalog: true,
2871
+ default: "empty"
2872
+ },
2873
+ smoke: {
2874
+ production: "none"
2875
+ },
2876
+ architecture: {
2877
+ class: "replay-shell",
2878
+ summary: "SendGrid is internal replay evidence only; send/event/suppression behavior and stateful account-setting invariants are not modeled yet.",
2879
+ domainPrimitives: [
2880
+ "recorded account and settings reads",
2881
+ "mail send state",
2882
+ "event and suppression history"
2883
+ ],
2884
+ promotionEvidence: [
2885
+ "clones/sendgrid/SPEC.md tracks send/event/suppression state model",
2886
+ "clones/sendgrid/SPEC.md tracks runtime recording-backed handlers replaced for send/event/suppression behavior",
2887
+ "clones/sendgrid/SPEC.md tracks send-to-event and suppression replay invariants"
2888
+ ]
2889
+ }
2890
+ },
2891
+ {
2892
+ name: "sentry",
2893
+ package: "@archal/clone-sentry",
2894
+ path: "clones/sentry",
2895
+ stage: "preview",
2896
+ display: {
2897
+ icon: "SN",
2898
+ name: "Sentry",
2899
+ description: "Error monitoring across organizations, projects, teams, issues, events, releases, and DSN keys.",
2900
+ toolCount: 25
2901
+ },
2902
+ transport: "rest",
2903
+ hostedRuntime: {
2904
+ enabled: true,
2905
+ requiresDist: true,
2906
+ requiresEmptySeed: true
2907
+ },
2908
+ cli: {
2909
+ bundleAssets: true,
2910
+ bundleToolSnapshot: true
2911
+ },
2912
+ routeProxy: {
2913
+ enabled: true,
2914
+ domains: [
2915
+ {
2916
+ domain: "sentry.io"
2917
+ }
2918
+ ]
2919
+ },
2920
+ seeds: {
2921
+ generatedCatalog: true,
2922
+ default: "empty"
2923
+ },
2924
+ smoke: {
2925
+ production: "clone-surface"
2926
+ },
2927
+ architecture: {
2928
+ class: "domain-simulator",
2929
+ summary: "Error-monitoring behavior depends on event grouping, issue state, releases, and time-windowed queries.",
2930
+ domainPrimitives: [
2931
+ "event grouping",
2932
+ "issue state transitions",
2933
+ "release and time-window projections"
2934
+ ],
2935
+ behaviorOwner: "src/domain/sentry-simulator.ts"
2936
+ }
2937
+ },
2938
+ {
2939
+ name: "slack",
2940
+ package: "@archal/clone-slack",
2941
+ path: "clones/slack",
2942
+ stage: "public",
2943
+ display: {
2944
+ icon: "SL",
2945
+ name: "Slack",
2946
+ description: "Channels, messages, threads, users, and reactions.",
2947
+ toolCount: 9
2948
+ },
2949
+ transport: "both",
2950
+ hostedRuntime: {
2951
+ enabled: true,
2952
+ requiresDist: true,
2953
+ requiresEmptySeed: true
2954
+ },
2955
+ cli: {
2956
+ bundleAssets: true,
2957
+ bundleToolSnapshot: true
2958
+ },
2959
+ routeProxy: {
2960
+ enabled: true,
2961
+ domains: [
2962
+ {
2963
+ domain: "slack.com"
2964
+ },
2965
+ {
2966
+ domain: "api.slack.com"
2967
+ }
2968
+ ]
2969
+ },
2970
+ seeds: {
2971
+ generatedCatalog: true,
2972
+ default: "engineering-team"
2973
+ },
2974
+ smoke: {
2975
+ production: "health-and-seed"
2976
+ },
2977
+ architecture: {
2978
+ class: "domain-simulator",
2979
+ summary: "Slack behavior depends on message timelines, threads, channel membership, permissions, and delivery semantics.",
2980
+ domainPrimitives: [
2981
+ "channel membership and visibility",
2982
+ "message/thread ordering",
2983
+ "event delivery semantics"
2984
+ ],
2985
+ behaviorOwner: "src/domain/slack-simulator.ts"
2986
+ },
2987
+ inferenceKeywords: [
2988
+ "slack",
2989
+ "slack channel",
2990
+ "send_message",
2991
+ "slack message",
2992
+ "direct message"
2993
+ ]
2994
+ },
2995
+ {
2996
+ name: "stripe",
2997
+ package: "@archal/clone-stripe",
2998
+ path: "clones/stripe",
2999
+ stage: "preview",
3000
+ display: {
3001
+ icon: "ST",
3002
+ name: "Stripe",
3003
+ description: "Customers, payments, subscriptions, invoices, and refunds.",
3004
+ toolCount: 28
3005
+ },
3006
+ transport: "both",
3007
+ hostedRuntime: {
3008
+ enabled: true,
3009
+ requiresDist: true,
3010
+ requiresEmptySeed: true
3011
+ },
3012
+ cli: {
3013
+ bundleAssets: true,
3014
+ bundleToolSnapshot: true
3015
+ },
3016
+ routeProxy: {
3017
+ enabled: true,
3018
+ domains: [
3019
+ {
3020
+ domain: "api.stripe.com"
3021
+ }
3022
+ ]
3023
+ },
3024
+ seeds: {
3025
+ generatedCatalog: true,
3026
+ default: "small-business"
3027
+ },
3028
+ smoke: {
3029
+ production: "health-and-seed"
3030
+ },
3031
+ architecture: {
3032
+ class: "domain-simulator",
3033
+ summary: "Payment behavior depends on ledgers, object lifecycle, webhooks, balances, and idempotency semantics.",
3034
+ domainPrimitives: [
3035
+ "payment object lifecycle",
3036
+ "balance and ledger projection",
3037
+ "webhook/idempotency semantics"
3038
+ ],
3039
+ behaviorOwner: "src/domain/stripe-simulator.ts"
3040
+ },
3041
+ inferenceKeywords: [
3042
+ "stripe",
3043
+ "payment",
3044
+ "refund",
3045
+ "subscription",
3046
+ "invoice",
3047
+ "charge"
3048
+ ]
3049
+ },
3050
+ {
3051
+ name: "woocommerce",
3052
+ package: "@archal/clone-woocommerce",
3053
+ path: "clones/woocommerce",
3054
+ stage: "internal",
3055
+ display: {
3056
+ icon: "WC",
3057
+ name: "WooCommerce",
3058
+ description: "Preview, coverage pending: products, customers, orders, coupons, categories, payment gateways, shipping zones, and taxes.",
3059
+ toolCount: 24
3060
+ },
3061
+ transport: "rest",
3062
+ hostedRuntime: {
3063
+ enabled: true,
3064
+ requiresDist: true,
3065
+ requiresEmptySeed: true
3066
+ },
3067
+ cli: {
3068
+ bundleAssets: false,
3069
+ bundleToolSnapshot: false
3070
+ },
3071
+ routeProxy: {
3072
+ enabled: false,
3073
+ domains: []
3074
+ },
3075
+ seeds: {
3076
+ generatedCatalog: false,
3077
+ default: "demo"
3078
+ },
3079
+ smoke: {
3080
+ production: "none"
3081
+ },
3082
+ architecture: {
3083
+ class: "standard-core",
3084
+ summary: "Current internal commerce slice is product/order/customer records; payment/fulfillment ledgers are not claimed."
3085
+ }
3086
+ },
3087
+ {
3088
+ name: "supabase",
3089
+ package: "@archal/clone-supabase",
3090
+ path: "clones/supabase",
3091
+ stage: "public",
3092
+ display: {
3093
+ icon: "SB",
3094
+ name: "Supabase",
3095
+ description: "SQL, migrations, logs, branches, and project metadata.",
3096
+ toolCount: 29
3097
+ },
3098
+ transport: "both",
3099
+ hostedRuntime: {
3100
+ enabled: true,
3101
+ requiresDist: true,
3102
+ requiresEmptySeed: true
3103
+ },
3104
+ cli: {
3105
+ bundleAssets: true,
3106
+ bundleToolSnapshot: true
3107
+ },
3108
+ routeProxy: {
3109
+ enabled: true,
3110
+ domains: [
3111
+ {
3112
+ domain: "supabase.co",
3113
+ matchSubdomains: true
3114
+ },
3115
+ {
3116
+ domain: "api.supabase.com"
3117
+ }
3118
+ ]
3119
+ },
3120
+ seeds: {
3121
+ generatedCatalog: true,
3122
+ default: "small-project"
3123
+ },
3124
+ smoke: {
3125
+ production: "health-and-seed"
3126
+ },
3127
+ architecture: {
3128
+ class: "domain-simulator",
3129
+ summary: "Supabase already requires and uses a Postgres/query/realtime simulator beside clone-core.",
3130
+ domainPrimitives: [
3131
+ "Postgres schema and SQL execution",
3132
+ "RLS and API projections",
3133
+ "realtime row changes"
3134
+ ],
3135
+ behaviorOwner: "src/pg-engine.ts"
3136
+ },
3137
+ inferenceKeywords: [
3138
+ "supabase",
3139
+ "database",
3140
+ "sql query",
3141
+ "database table",
3142
+ "sql"
3143
+ ]
3144
+ },
3145
+ {
3146
+ name: "tavily",
3147
+ package: "@archal/clone-tavily",
3148
+ path: "clones/tavily",
3149
+ stage: "preview",
3150
+ display: {
3151
+ icon: "TV",
3152
+ name: "Tavily",
3153
+ description: "Search, extract, crawl, map, research, usage, and API key operations.",
3154
+ toolCount: 11
3155
+ },
3156
+ transport: "rest",
3157
+ hostedRuntime: {
3158
+ enabled: true,
3159
+ requiresDist: true,
3160
+ requiresEmptySeed: true
3161
+ },
3162
+ cli: {
3163
+ bundleAssets: true,
3164
+ bundleToolSnapshot: true
3165
+ },
3166
+ routeProxy: {
3167
+ enabled: true,
3168
+ domains: [
3169
+ {
3170
+ domain: "api.tavily.com"
3171
+ },
3172
+ {
3173
+ domain: "tavily.com"
3174
+ }
3175
+ ]
3176
+ },
3177
+ seeds: {
3178
+ generatedCatalog: true,
3179
+ default: "empty"
3180
+ },
3181
+ smoke: {
3182
+ production: "clone-surface"
3183
+ },
3184
+ architecture: {
3185
+ class: "replay-shell",
3186
+ summary: "Tavily mixes deterministic search/extract helpers with runtime recording replay for REST/tool parity; treat it as replay-backed until all supported responses are state-derived.",
3187
+ domainPrimitives: [
3188
+ "recording-backed REST/tool response replay",
3189
+ "deterministic search/extract response helpers",
3190
+ "research request state overlay"
3191
+ ],
3192
+ promotionEvidence: [
3193
+ "clones/tavily/SPEC.md tracks state-derived response fixtures or query/result simulator",
3194
+ "clones/tavily/SPEC.md tracks runtime recording-backed handlers replaced for the promoted search/extract/research surface",
3195
+ "clones/tavily/SPEC.md tracks search/extract/research replay determinism invariants"
3196
+ ]
3197
+ }
3198
+ },
3199
+ {
3200
+ name: "typeform",
3201
+ package: "@archal/clone-typeform",
3202
+ path: "clones/typeform",
3203
+ stage: "internal",
3204
+ display: {
3205
+ icon: "TF",
3206
+ name: "Typeform",
3207
+ description: "Preview, coverage pending: forms, responses, workspaces, themes, and webhooks.",
3208
+ toolCount: 22
3209
+ },
3210
+ transport: "mcp",
3211
+ hostedRuntime: {
3212
+ enabled: true,
3213
+ requiresDist: true,
3214
+ requiresEmptySeed: true
3215
+ },
3216
+ cli: {
3217
+ bundleAssets: false,
3218
+ bundleToolSnapshot: false
3219
+ },
3220
+ routeProxy: {
3221
+ enabled: false,
3222
+ domains: []
3223
+ },
3224
+ seeds: {
3225
+ generatedCatalog: false,
3226
+ default: "empty"
3227
+ },
3228
+ smoke: {
3229
+ production: "none"
3230
+ },
3231
+ architecture: {
3232
+ class: "domain-simulator",
3233
+ summary: "Typeform relies on form response projection, webhook subscription scoping, and signed delivery semantics beyond generic CRUD.",
3234
+ domainPrimitives: [
3235
+ "form response submission projection",
3236
+ "webhook subscription scoping and signing",
3237
+ "responses API and webhook payload consistency"
3238
+ ],
3239
+ behaviorOwner: "src/state/webhook-delivery-config.ts"
3240
+ }
3241
+ },
3242
+ {
3243
+ name: "telegram",
3244
+ package: "@archal/clone-telegram",
3245
+ path: "clones/telegram",
3246
+ stage: "internal",
3247
+ display: {
3248
+ icon: "TG",
3249
+ name: "Telegram",
3250
+ description: "Bot messages, chats, updates, and webhooks.",
3251
+ toolCount: 32
3252
+ },
3253
+ transport: "both",
3254
+ hostedRuntime: {
3255
+ enabled: true,
3256
+ requiresDist: true,
3257
+ requiresEmptySeed: true
3258
+ },
3259
+ cli: {
3260
+ bundleAssets: false,
3261
+ bundleToolSnapshot: false
3262
+ },
3263
+ routeProxy: {
3264
+ enabled: false,
3265
+ domains: []
3266
+ },
3267
+ seeds: {
3268
+ generatedCatalog: false,
3269
+ default: "empty"
3270
+ },
3271
+ smoke: {
3272
+ production: "none"
3273
+ },
3274
+ architecture: {
3275
+ class: "domain-simulator",
3276
+ summary: "Telegram behavior depends on outbound message timelines, getUpdates ordering/filtering, bot configuration, and webhook registration state.",
3277
+ domainPrimitives: [
3278
+ "outbound message and update ordering",
3279
+ "bot configuration state",
3280
+ "webhook registration state"
3281
+ ],
3282
+ behaviorOwner: "src/domain/telegram-simulator.ts"
3283
+ }
3284
+ },
3285
+ {
3286
+ name: "twilio",
3287
+ package: "@archal/clone-twilio",
3288
+ path: "clones/twilio",
3289
+ stage: "internal",
3290
+ display: {
3291
+ icon: "TW",
3292
+ name: "Twilio",
3293
+ description: "Messages, calls, phone numbers, and verifications.",
3294
+ toolCount: 35
3295
+ },
3296
+ transport: "rest",
3297
+ hostedRuntime: {
3298
+ enabled: true,
3299
+ requiresDist: true,
3300
+ requiresEmptySeed: true
3301
+ },
3302
+ cli: {
3303
+ bundleAssets: false,
3304
+ bundleToolSnapshot: false
3305
+ },
3306
+ routeProxy: {
3307
+ enabled: false,
3308
+ domains: []
3309
+ },
3310
+ seeds: {
3311
+ generatedCatalog: false,
3312
+ default: "empty"
3313
+ },
3314
+ smoke: {
3315
+ production: "none"
3316
+ },
3317
+ architecture: {
3318
+ class: "replay-shell",
3319
+ summary: "Twilio is still a recording-backed REST shell with a narrow stateful free-resource overlay, not a broad communications simulator.",
3320
+ domainPrimitives: [
3321
+ "recording-backed REST route replay",
3322
+ "Keys, SigningKeys, Applications, and Queues overlay",
3323
+ "future message/call/conversation lifecycle simulator"
3324
+ ],
3325
+ promotionEvidence: [
3326
+ "clones/twilio/SPEC.md tracks message or call lifecycle state model",
3327
+ "clones/twilio/SPEC.md tracks runtime recording-backed handlers replaced for the promoted message/call slice",
3328
+ "clones/twilio/SPEC.md tracks delivery/status/webhook sequence invariants"
3329
+ ]
3330
+ }
3331
+ },
3332
+ {
3333
+ name: "unipile",
3334
+ package: "@archal/clone-unipile",
3335
+ path: "clones/unipile",
3336
+ stage: "preview",
3337
+ display: {
3338
+ icon: "UP",
3339
+ name: "Unipile",
3340
+ description: "LinkedIn and email messaging, accounts, and chats.",
3341
+ toolCount: 31
3342
+ },
3343
+ transport: "rest",
3344
+ hostedRuntime: {
3345
+ enabled: true,
3346
+ requiresDist: true,
3347
+ requiresEmptySeed: true
3348
+ },
3349
+ cli: {
3350
+ bundleAssets: true,
3351
+ bundleToolSnapshot: true
3352
+ },
3353
+ routeProxy: {
3354
+ enabled: true,
3355
+ domains: [
3356
+ {
3357
+ domain: "api.unipile.com"
3358
+ },
3359
+ {
3360
+ domain: "unipile.com",
3361
+ matchSubdomains: true
3362
+ }
3363
+ ]
3364
+ },
3365
+ seeds: {
3366
+ generatedCatalog: true,
3367
+ default: "empty"
3368
+ },
3369
+ smoke: {
3370
+ production: "clone-surface"
3371
+ },
3372
+ architecture: {
3373
+ class: "replay-shell",
3374
+ summary: "Unipile is currently an authenticated REST replay surface with one approved stateful WhatsApp send mutation, not a broad provider messaging simulator.",
3375
+ domainPrimitives: [
3376
+ "authenticated account and chat reads",
3377
+ "single WhatsApp send mutation",
3378
+ "future account, chat, and webhook simulator"
3379
+ ],
3380
+ promotionEvidence: [
3381
+ "clones/unipile/SPEC.md tracks chat timeline state model",
3382
+ "clones/unipile/SPEC.md tracks runtime recording-backed handlers replaced for the promoted chat/provider surface",
3383
+ "clones/unipile/SPEC.md tracks send/delivery/read/webhook sequence invariants"
3384
+ ]
3385
+ }
3386
+ },
3387
+ {
3388
+ name: "webflow",
3389
+ package: "@archal/clone-webflow",
3390
+ path: "clones/webflow",
3391
+ stage: "preview",
3392
+ display: {
3393
+ icon: "WF",
3394
+ name: "Webflow",
3395
+ description: "Sites, pages, CMS collections, items, assets, forms, and webhooks.",
3396
+ toolCount: 19
3397
+ },
3398
+ transport: "rest",
3399
+ hostedRuntime: {
3400
+ enabled: true,
3401
+ requiresDist: true,
3402
+ requiresEmptySeed: true
3403
+ },
3404
+ cli: {
3405
+ bundleAssets: true,
3406
+ bundleToolSnapshot: true
3407
+ },
3408
+ routeProxy: {
3409
+ enabled: true,
3410
+ domains: [
3411
+ {
3412
+ domain: "api.webflow.com"
3413
+ },
3414
+ {
3415
+ domain: "webflow.com"
3416
+ }
3417
+ ]
3418
+ },
3419
+ seeds: {
3420
+ generatedCatalog: true,
3421
+ default: "empty"
3422
+ },
3423
+ smoke: {
3424
+ production: "clone-surface"
3425
+ },
3426
+ architecture: {
3427
+ class: "replay-shell",
3428
+ summary: "Webflow is recording-replay backed with focused CMS stateful overlays; it should not be treated as a full Webflow behavior engine.",
3429
+ domainPrimitives: [
3430
+ "recording replay cursor",
3431
+ "CMS collection/item overlays",
3432
+ "publish lifecycle captures"
3433
+ ],
3434
+ promotionEvidence: [
3435
+ "clones/webflow/SPEC.md tracks staged/live publish projection state model",
3436
+ "clones/webflow/SPEC.md tracks runtime recording-backed handlers replaced for the promoted publish/CMS surface",
3437
+ "clones/webflow/SPEC.md tracks CMS publish/reset sequence invariants"
3438
+ ]
3439
+ }
3440
+ }
3441
+ ];
3442
+ var CLONE_MANIFEST = manifest_default;
3443
+ var CLONE_NAMES = CLONE_MANIFEST.map((t) => t.name);
3444
+ var CLONE_PACKAGES = CLONE_MANIFEST.map((t) => t.package);
3445
+
3446
+ // ../../clones/core/dist/chunk-SXNWBK3S.js
3447
+ var CLONE_PREFIX_PATTERN = new RegExp(`^(${CLONE_NAMES.join("|")})_`, "i");
3448
+
3449
+ // ../../clones/core/dist/chunk-EZKTFYZI.js
3450
+ var CLONE_STAGES = ["wip", "internal", "preview", "public", "retired"];
3451
+ var CLONE_TRANSPORTS = ["mcp", "rest", "both"];
3452
+ var CLONE_SMOKE_PROFILES = ["none", "health-and-seed", "clone-surface"];
3453
+ var STARTABLE_CLONE_STAGES = ["public", "preview"];
3454
+ var CATALOG_VISIBLE_CLONE_STAGES = ["public", "preview"];
3455
+ var CLONE_STAGE_SET = new Set(CLONE_STAGES);
3456
+ var CLONE_TRANSPORT_SET = new Set(CLONE_TRANSPORTS);
3457
+ var CLONE_SMOKE_PROFILE_SET = new Set(CLONE_SMOKE_PROFILES);
3458
+ var STARTABLE_CLONE_STAGE_SET = new Set(STARTABLE_CLONE_STAGES);
3459
+ var CATALOG_VISIBLE_CLONE_STAGE_SET = new Set(CATALOG_VISIBLE_CLONE_STAGES);
3460
+
3461
+ // ../../clones/core/dist/chunk-ENO6SYMV.js
3462
+ var CLONE_ARCHITECTURE_CLASSES = [
3463
+ "standard-core",
3464
+ "domain-simulator",
3465
+ "replay-shell",
3466
+ "redesign-before-expansion"
3467
+ ];
3468
+ var CLONE_ARCHITECTURE_PREVIEW_CLASSES = [
3469
+ "replay-shell",
3470
+ "redesign-before-expansion"
3471
+ ];
3472
+ var CLONE_ARCHITECTURE_CLASS_SET = new Set(CLONE_ARCHITECTURE_CLASSES);
3473
+ var CLONE_ARCHITECTURE_PREVIEW_CLASS_SET = new Set(CLONE_ARCHITECTURE_PREVIEW_CLASSES);
3474
+
3475
+ // ../../clones/core/dist/chunk-4B2Y764J.js
3476
+ var CLONE_RUNTIME_SOURCE_EXTENSIONS = Object.freeze([
3477
+ ".cjs",
3478
+ ".cts",
3479
+ ".js",
3480
+ ".jsx",
3481
+ ".mjs",
3482
+ ".mts",
3483
+ ".ts",
3484
+ ".tsx"
3485
+ ]);
3486
+ var RUNTIME_SOURCE_EXTENSIONS = new Set(CLONE_RUNTIME_SOURCE_EXTENSIONS);
3487
+
3488
+ // ../../clones/core/dist/chunk-CDEMQFER.js
3489
+ var FIELD_POLICY = {
3490
+ behaviorOwner: {
3491
+ required: /* @__PURE__ */ new Set(["domain-simulator"]),
3492
+ allowed: /* @__PURE__ */ new Set(["domain-simulator"])
3493
+ },
3494
+ domainPrimitives: {
3495
+ required: /* @__PURE__ */ new Set(["domain-simulator", "replay-shell", "redesign-before-expansion"]),
3496
+ allowed: /* @__PURE__ */ new Set(["domain-simulator", "replay-shell", "redesign-before-expansion"])
3497
+ },
3498
+ promotionEvidence: {
3499
+ required: /* @__PURE__ */ new Set(["replay-shell"]),
3500
+ allowed: /* @__PURE__ */ new Set(["replay-shell"])
3501
+ },
3502
+ redesignContract: {
3503
+ required: /* @__PURE__ */ new Set(["redesign-before-expansion"]),
3504
+ allowed: /* @__PURE__ */ new Set(["redesign-before-expansion"])
3505
+ }
3506
+ };
3507
+ var CLONE_ARCHITECTURE_POLICY_FIELDS = Object.freeze(Object.keys(FIELD_POLICY));
3508
+ var CLONE_ARCHITECTURE_MANIFEST_FIELDS = Object.freeze([
3509
+ "class",
3510
+ "summary",
3511
+ ...CLONE_ARCHITECTURE_POLICY_FIELDS
3512
+ ]);
3513
+ var CLONE_ARCHITECTURE_MANIFEST_FIELD_SET = new Set(CLONE_ARCHITECTURE_MANIFEST_FIELDS);
3514
+
3515
+ // src/runtime/service-profiles.ts
3516
+ function unsupportedServiceMessage(unsupportedServices) {
3517
+ const supportedServices = listSharedRouteManifests().map((manifest) => manifest.service).sort();
3518
+ return [
3519
+ `Unsupported route-mode service${unsupportedServices.length === 1 ? "" : "s"}: ${unsupportedServices.sort().join(", ")}.`,
3520
+ `Supported services in this Vitest adapter: ${supportedServices.join(", ")}.`
3521
+ ].join(" ");
3522
+ }
3523
+ function assertSupportedArchalVitestServices(services) {
3524
+ const unsupportedServices = Object.entries(services).filter(([, serviceConfig]) => serviceConfig.mode === "route").map(([serviceName]) => serviceName).filter((serviceName) => !getSharedRouteManifest(serviceName));
3525
+ if (unsupportedServices.length === 0) {
3526
+ return;
3527
+ }
3528
+ throw new Error(unsupportedServiceMessage(unsupportedServices));
3529
+ }
3530
+ function resolveArchalVitestServiceProfiles(services) {
3531
+ const declaredRouteServices = new Set(
3532
+ Object.entries(services).filter(([, serviceConfig]) => serviceConfig.mode === "route").map(([serviceName]) => serviceName)
3533
+ );
3534
+ const profiles = listSharedRouteManifests().map((manifest) => ({
3535
+ serviceName: manifest.service,
3536
+ profile: buildServiceCompatibilityProfile(manifest)
3537
+ }));
3538
+ const declaredProfiles = profiles.filter(({ serviceName }) => declaredRouteServices.has(serviceName)).map(({ profile }) => profile);
3539
+ const undeclaredEnforcementProfiles = profiles.filter(({ serviceName }) => !declaredRouteServices.has(serviceName)).map(({ profile }) => profile);
3540
+ return [...declaredProfiles, ...undeclaredEnforcementProfiles];
3541
+ }
3542
+
3543
+ // src/runtime/hosted-session-provider.ts
3544
+ import { createHash, randomUUID } from "crypto";
3545
+ import { spawn } from "child_process";
3546
+ import { promises as fs } from "fs";
3547
+ import { join, resolve as resolve2 } from "path";
3548
+ import { tmpdir } from "os";
3549
+
3550
+ // src/url-resolution.ts
3551
+ function readFirstConfiguredApiBaseUrl(envVars) {
3552
+ for (const envVar of envVars) {
3553
+ const configured = trimEnv(envVar);
3554
+ if (!configured) {
3555
+ continue;
3556
+ }
3557
+ return normalizeApiBaseUrl(configured);
3558
+ }
3559
+ return null;
3560
+ }
3561
+ function resolveArchalVitestApiBaseUrl(defaultBaseUrl) {
3562
+ return readFirstConfiguredApiBaseUrl([
3563
+ "ARCHAL_VITEST_API_URL",
3564
+ "ARCHAL_API_URL",
3565
+ "ARCHAL_API_BASE_URL",
3566
+ "ARCHAL_AUTH_URL",
3567
+ "ARCHAL_AUTH_BASE_URL"
3568
+ ]) ?? defaultBaseUrl;
3569
+ }
3570
+
3571
+ // src/runtime/hosted-session-provider.ts
3572
+ var COORDINATOR_LOCK_TIMEOUT_MS = 3e4;
3573
+ var COORDINATOR_STALE_LOCK_MS = COORDINATOR_LOCK_TIMEOUT_MS;
3574
+ var COORDINATOR_POLL_INTERVAL_MS = 50;
3575
+ var ROUTE_CONTROL_AUTHORIZATION_HEADER2 = "x-route-authorization";
3576
+ var ROUTE_SERVICE_ORIGIN_HEADER2 = "x-route-service-origin";
3577
+ var WORKER_ID_HEADER = "x-archal-worker-id";
3578
+ function currentVitestWorkerId() {
3579
+ const raw = process.env["VITEST_WORKER_ID"];
3580
+ if (!raw || typeof raw !== "string") return void 0;
3581
+ const trimmed = raw.trim();
3582
+ if (trimmed.length === 0) return void 0;
3583
+ return `vitest-${trimmed}`;
3584
+ }
3585
+ function resolveWorkerId() {
3586
+ const vitestWorkerId = trimEnv("VITEST_WORKER_ID");
3587
+ return vitestWorkerId ? `vitest-worker-${vitestWorkerId}` : `pid-${process.pid}`;
3588
+ }
3589
+ function resolveRunnerPid(config) {
3590
+ return typeof config.runnerPid === "number" && Number.isInteger(config.runnerPid) && config.runnerPid > 0 ? config.runnerPid : process.pid;
3591
+ }
3592
+ function toSafeCoordinatorDirectoryName(sessionKey) {
3593
+ if (sessionKey !== "." && sessionKey !== ".." && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(sessionKey)) {
3594
+ return sessionKey;
3595
+ }
3596
+ const slug = sessionKey.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^[.-]+|[.-]+$/g, "").slice(0, 48);
3597
+ const hash = createHash("sha256").update(sessionKey).digest("hex").slice(0, 12);
3598
+ return `${slug || "session"}-${hash}`;
3599
+ }
3600
+ function resolveCoordinatorPaths(config) {
3601
+ const baseDirectory = resolve2(
3602
+ trimEnv("ARCHAL_VITEST_COORDINATOR_DIR") ?? join(tmpdir(), "archal-vitest")
3603
+ );
3604
+ const sessionKey = config.sessionKey ?? `${config.projectName}-${randomUUID()}`;
3605
+ const directory = resolve2(baseDirectory, toSafeCoordinatorDirectoryName(sessionKey));
3606
+ return {
3607
+ directory,
3608
+ lockDirectory: join(directory, "lock"),
3609
+ statePath: join(directory, "state.json"),
3610
+ reaperPidPath: join(directory, "reaper.pid")
3611
+ };
3612
+ }
3613
+ function shouldStopSessionOnRelease() {
3614
+ const explicitSetting = trimEnv("ARCHAL_VITEST_STOP_SESSION_ON_RELEASE");
3615
+ if (explicitSetting) {
3616
+ return !/^(0|false)$/i.test(explicitSetting);
3617
+ }
3618
+ return trimEnv("CI") !== void 0 || trimEnv("GITHUB_ACTIONS") !== void 0;
3619
+ }
3620
+ async function resolveHostedSessionEnvironment() {
3621
+ const apiBaseUrl = resolveArchalVitestApiBaseUrl(DEFAULT_HOSTED_API_BASE_URL);
3622
+ const auth = await createHostedAuthLease(VITEST_AUTH_LEASE_OPTIONS);
3623
+ const ttlFromMinutes = parsePositiveInteger(trimEnv("ARCHAL_SESSION_TTL_MINUTES"), 30, 1) * 60;
3624
+ const ttlSeconds = parsePositiveInteger(
3625
+ trimEnv("ARCHAL_VITEST_SESSION_TTL_SECONDS"),
3626
+ ttlFromMinutes,
3627
+ 1
3628
+ );
3629
+ const readyTimeoutMs = Math.max(
3630
+ MIN_READY_TIMEOUT_MS,
3631
+ parsePositiveInteger(
3632
+ trimEnv("ARCHAL_VITEST_SESSION_READY_TIMEOUT_MS") ?? trimEnv("ARCHAL_SESSION_READY_TIMEOUT_MS"),
3633
+ DEFAULT_READY_TIMEOUT_MS,
3634
+ 1
3635
+ )
3636
+ );
3637
+ const renewIntervalMs = Math.max(
3638
+ MIN_RENEW_INTERVAL_MS,
3639
+ parsePositiveInteger(
3640
+ trimEnv("ARCHAL_VITEST_SESSION_RENEW_INTERVAL_MS"),
3641
+ Math.min(Math.floor((ttlSeconds || DEFAULT_SESSION_TTL_SECONDS) * 500), DEFAULT_RENEW_INTERVAL_MS),
3642
+ 1
3643
+ )
3644
+ );
3645
+ return {
3646
+ auth,
3647
+ apiBaseUrl,
3648
+ ttlSeconds: ttlSeconds || DEFAULT_SESSION_TTL_SECONDS,
3649
+ readyTimeoutMs,
3650
+ renewIntervalMs
3651
+ };
3652
+ }
3653
+ async function withCoordinatorLock(paths, lockTimeoutMs, action) {
3654
+ await fs.mkdir(paths.directory, { recursive: true });
3655
+ const deadline = Date.now() + resolveCoordinatorLockWaitTimeoutMs(lockTimeoutMs);
3656
+ while (true) {
3657
+ try {
3658
+ await fs.mkdir(paths.lockDirectory);
3659
+ break;
3660
+ } catch (error) {
3661
+ const code = error instanceof Error && "code" in error ? String(error.code) : "";
3662
+ if (code !== "EEXIST") {
3663
+ throw error;
3664
+ }
3665
+ const stale = await fs.stat(paths.lockDirectory).then((stats) => Date.now() - stats.mtimeMs >= COORDINATOR_STALE_LOCK_MS).catch(() => false);
3666
+ if (stale) {
3667
+ await fs.rm(paths.lockDirectory, { recursive: true, force: true }).catch(() => {
3668
+ });
3669
+ continue;
3670
+ }
3671
+ if (Date.now() >= deadline) {
3672
+ throw new Error(
3673
+ `Timed out waiting for Archal Vitest session coordinator lock in ${paths.directory}.`
3674
+ );
3675
+ }
3676
+ await sleep(COORDINATOR_POLL_INTERVAL_MS);
3677
+ }
3678
+ }
3679
+ try {
3680
+ const keepalive = setInterval(() => {
3681
+ void fs.utimes(paths.lockDirectory, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date()).catch(() => void 0);
3682
+ }, 1e3);
3683
+ keepalive.unref();
3684
+ try {
3685
+ return await action();
3686
+ } finally {
3687
+ clearInterval(keepalive);
3688
+ }
3689
+ } finally {
3690
+ await fs.rm(paths.lockDirectory, { recursive: true, force: true });
3691
+ }
3692
+ }
3693
+ function resolveCoordinatorLockWaitTimeoutMs(readyTimeoutMs) {
3694
+ return Math.max(COORDINATOR_LOCK_TIMEOUT_MS, readyTimeoutMs);
3695
+ }
3696
+ async function readCoordinatorState(paths) {
3697
+ if (!await fileExists(paths.statePath)) {
3698
+ return { kind: "missing" };
3699
+ }
3700
+ const raw = await fs.readFile(paths.statePath, "utf8");
3701
+ try {
3702
+ const normalizedState = normalizeCoordinatorState(JSON.parse(raw));
3703
+ if (!normalizedState) {
3704
+ await fs.rm(paths.statePath, { force: true }).catch(() => void 0);
3705
+ return { kind: "corrupt" };
3706
+ }
3707
+ return {
3708
+ kind: "ready",
3709
+ state: normalizedState
3710
+ };
3711
+ } catch {
3712
+ await fs.rm(paths.statePath, { force: true }).catch(() => void 0);
3713
+ return { kind: "corrupt" };
3714
+ }
3715
+ }
3716
+ function normalizeCoordinatorState(input) {
3717
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
3718
+ return null;
3719
+ }
3720
+ const record = input;
3721
+ const session = normalizeHostedSessionSnapshot(record["session"]);
3722
+ if (!session) {
3723
+ return null;
3724
+ }
3725
+ const leases = normalizeWorkerLeases(record["leases"]);
3726
+ if (!leases) {
3727
+ return null;
3728
+ }
3729
+ return { session, leases };
3730
+ }
3731
+ function normalizeHostedSessionSnapshot(input) {
3732
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
3733
+ return null;
3734
+ }
3735
+ const record = input;
3736
+ const sessionId = typeof record["sessionId"] === "string" ? record["sessionId"].trim() : "";
3737
+ const apiBaseUrls = normalizeStringRecord(record["apiBaseUrls"]);
3738
+ const resolvedRuntime = normalizeResolvedRuntime(record["resolvedRuntime"]);
3739
+ if (!sessionId || !apiBaseUrls || !resolvedRuntime) {
3740
+ return null;
3741
+ }
3742
+ return {
3743
+ sessionId,
3744
+ apiBaseUrls,
3745
+ resolvedRuntime
3746
+ };
3747
+ }
3748
+ function normalizeResolvedRuntime(input) {
3749
+ if (input === void 0 || input === null) {
3750
+ return null;
3751
+ }
3752
+ if (typeof input !== "object" || Array.isArray(input)) {
3753
+ return null;
3754
+ }
3755
+ const record = input;
3756
+ const resolvedServices = normalizeStringArray(record["resolvedServices"] ?? []);
3757
+ const resolvedSeeds = normalizeStringRecord(record["resolvedSeeds"] ?? {});
3758
+ const manifestVersions = normalizeStringRecord(record["manifestVersions"] ?? {});
3759
+ if (!resolvedServices || !resolvedSeeds || !manifestVersions) {
3760
+ return null;
3761
+ }
3762
+ const capabilityVersion = typeof record["capabilityVersion"] === "string" ? record["capabilityVersion"] : void 0;
3763
+ const runtimeVersion = typeof record["runtimeVersion"] === "string" ? record["runtimeVersion"] : void 0;
3764
+ if (resolvedServices.length === 0 && Object.keys(resolvedSeeds).length === 0 && Object.keys(manifestVersions).length === 0 && !capabilityVersion && !runtimeVersion) {
3765
+ return null;
3766
+ }
3767
+ return {
3768
+ resolvedServices,
3769
+ resolvedSeeds,
3770
+ manifestVersions,
3771
+ ...capabilityVersion ? { capabilityVersion } : {},
3772
+ ...runtimeVersion ? { runtimeVersion } : {}
3773
+ };
3774
+ }
3775
+ function normalizeWorkerLeases(input) {
3776
+ if (input === void 0) {
3777
+ return [];
3778
+ }
3779
+ if (!Array.isArray(input)) {
3780
+ return null;
3781
+ }
3782
+ return input.flatMap((lease) => {
3783
+ if (!lease || typeof lease !== "object" || Array.isArray(lease)) {
3784
+ return [];
3785
+ }
3786
+ const record = lease;
3787
+ const workerId = typeof record["workerId"] === "string" ? record["workerId"].trim() : "";
3788
+ const updatedAt = typeof record["updatedAt"] === "string" ? record["updatedAt"].trim() : "";
3789
+ const pid = typeof record["pid"] === "number" ? record["pid"] : Number.NaN;
3790
+ if (!workerId || !updatedAt || !Number.isInteger(pid) || pid <= 0) {
3791
+ return [];
3792
+ }
3793
+ return [{
3794
+ workerId,
3795
+ pid,
3796
+ updatedAt
3797
+ }];
3798
+ });
3799
+ }
3800
+ function normalizeStringArray(input) {
3801
+ if (!Array.isArray(input)) {
3802
+ return null;
3803
+ }
3804
+ const values = input.flatMap((value) => {
3805
+ if (typeof value !== "string") {
3806
+ return [];
3807
+ }
3808
+ const trimmed = value.trim();
3809
+ return trimmed ? [trimmed] : [];
3810
+ });
3811
+ return values.length === input.length ? values : null;
3812
+ }
3813
+ function normalizeStringRecord(input) {
3814
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
3815
+ return null;
3816
+ }
3817
+ const record = input;
3818
+ const entries = Object.entries(record).flatMap(([key, value]) => {
3819
+ if (typeof value !== "string") {
3820
+ return [];
3821
+ }
3822
+ return [[key, value]];
3823
+ });
3824
+ return entries.length === Object.keys(record).length ? Object.fromEntries(entries) : null;
3825
+ }
3826
+ async function writeCoordinatorState(paths, state) {
3827
+ await fs.writeFile(paths.statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
3828
+ }
3829
+ async function clearCoordinatorState(paths) {
3830
+ await Promise.all([
3831
+ fs.rm(paths.statePath, { force: true }),
3832
+ fs.rm(paths.reaperPidPath, { force: true })
3833
+ ]);
3834
+ }
3835
+ async function readRunnerReaperState(paths) {
3836
+ const raw = await fs.readFile(paths.reaperPidPath, "utf8").catch(() => null);
3837
+ if (!raw) {
3838
+ return null;
3839
+ }
3840
+ try {
3841
+ const parsed = JSON.parse(raw);
3842
+ if (typeof parsed === "number") {
3843
+ if (!Number.isInteger(parsed) || parsed <= 0) {
3844
+ return null;
3845
+ }
3846
+ return {
3847
+ pid: parsed,
3848
+ sessionId: ""
3849
+ };
3850
+ }
3851
+ if (!parsed || typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid) || parsed.pid <= 0 || typeof parsed.sessionId !== "string" || parsed.sessionId.trim().length === 0) {
3852
+ return null;
3853
+ }
3854
+ return {
3855
+ pid: parsed.pid,
3856
+ sessionId: parsed.sessionId
3857
+ };
3858
+ } catch {
3859
+ const pid = Number.parseInt(raw.trim(), 10);
3860
+ if (!Number.isInteger(pid) || pid <= 0) {
3861
+ return null;
3862
+ }
3863
+ return {
3864
+ pid,
3865
+ sessionId: ""
3866
+ };
3867
+ }
3868
+ }
3869
+ async function writeRunnerReaperState(paths, state) {
3870
+ await fs.writeFile(paths.reaperPidPath, JSON.stringify(state) + "\n", "utf8");
3871
+ }
3872
+ async function stopRunnerReaper(paths) {
3873
+ const reaperState = await readRunnerReaperState(paths);
3874
+ if (!reaperState) {
3875
+ await fs.rm(paths.reaperPidPath, { force: true }).catch(() => void 0);
3876
+ return;
3877
+ }
3878
+ if (isProcessAlive(reaperState.pid)) {
3879
+ try {
3880
+ process.kill(reaperState.pid, "SIGTERM");
3881
+ } catch {
3882
+ }
3883
+ }
3884
+ await fs.rm(paths.reaperPidPath, { force: true }).catch(() => void 0);
3885
+ }
3886
+ function pruneLeases(leases) {
3887
+ return leases.filter((lease) => isProcessAlive(lease.pid));
3888
+ }
3889
+ function runnerIsAlive(runnerPid) {
3890
+ return isProcessAlive(runnerPid);
3891
+ }
3892
+ function resolveRequestedSeeds(config, serviceNames) {
3893
+ const fileSeedServices = new Set(Object.keys(config.fileSeedContents ?? {}));
3894
+ const requestedSeeds = Object.fromEntries(
3895
+ serviceNames.flatMap((serviceName) => {
3896
+ if (fileSeedServices.has(serviceName)) return [];
3897
+ const seed = config.services[serviceName]?.seed?.trim();
3898
+ return seed ? [[serviceName, seed]] : [];
3899
+ })
3900
+ );
3901
+ return Object.keys(requestedSeeds).length > 0 ? requestedSeeds : void 0;
3902
+ }
3903
+ function retainedSessionWithoutLeases(state) {
3904
+ if (!state?.session) {
3905
+ return null;
3906
+ }
3907
+ return pruneLeases(state.leases).length === 0 ? state.session : null;
3908
+ }
3909
+ function activeWorkerSession(state) {
3910
+ if (!state?.session) {
3911
+ return null;
3912
+ }
3913
+ const leases = pruneLeases(state.leases);
3914
+ if (leases.length === 0) {
3915
+ return null;
3916
+ }
3917
+ return {
3918
+ session: state.session,
3919
+ leases
3920
+ };
3921
+ }
3922
+ var CoordinatedHostedSessionManager = class {
3923
+ constructor(paths, client, environment, config) {
3924
+ this.paths = paths;
3925
+ this.client = client;
3926
+ this.environment = environment;
3927
+ this.config = config;
3928
+ }
3929
+ paths;
3930
+ client;
3931
+ environment;
3932
+ config;
3933
+ async acquire(serviceNames, requestedSeeds) {
3934
+ const workerId = resolveWorkerId();
3935
+ const runnerPid = resolveRunnerPid(this.config);
3936
+ return await withCoordinatorLock(this.paths, this.environment.readyTimeoutMs, async () => {
3937
+ const stateResult = await readCoordinatorState(this.paths);
3938
+ const existingState = stateResult.kind === "ready" ? stateResult.state : null;
3939
+ if (stateResult.kind === "corrupt") {
3940
+ const recoveredSession = await this.recoverCorruptCoordinatorState(
3941
+ serviceNames,
3942
+ requestedSeeds,
3943
+ workerId,
3944
+ runnerPid
3945
+ );
3946
+ if (recoveredSession) {
3947
+ return { session: recoveredSession, acquisition: "reused" };
3948
+ }
3949
+ }
3950
+ const activeSession = activeWorkerSession(existingState);
3951
+ if (activeSession && requestedSeedsMatchSession(requestedSeeds, activeSession.session)) {
3952
+ const reusableSession = await this.tryReuseSession(
3953
+ activeSession.session,
3954
+ serviceNames
3955
+ );
3956
+ if (!reusableSession) {
3957
+ await this.client.stop(activeSession.session.sessionId).catch(() => void 0);
3958
+ await stopRunnerReaper(this.paths);
3959
+ await clearCoordinatorState(this.paths);
3960
+ } else {
3961
+ await this.ensureRunnerReaper(reusableSession.sessionId, runnerPid);
3962
+ await writeCoordinatorState(this.paths, {
3963
+ session: reusableSession,
3964
+ leases: this.upsertLease(activeSession.leases, workerId)
3965
+ });
3966
+ return { session: reusableSession, acquisition: "reused" };
3967
+ }
3968
+ }
3969
+ if (activeSession && !requestedSeedsMatchSession(requestedSeeds, activeSession.session)) {
3970
+ await this.client.stop(activeSession.session.sessionId).catch(() => void 0);
3971
+ await stopRunnerReaper(this.paths);
3972
+ await clearCoordinatorState(this.paths);
3973
+ }
3974
+ const retainedSession = retainedSessionWithoutLeases(existingState);
3975
+ if (retainedSession) {
3976
+ if (!runnerIsAlive(runnerPid)) {
3977
+ await this.client.stop(retainedSession.sessionId).catch(() => void 0);
3978
+ await stopRunnerReaper(this.paths);
3979
+ await clearCoordinatorState(this.paths);
3980
+ } else if (!requestedSeedsMatchSession(requestedSeeds, retainedSession)) {
3981
+ await this.client.stop(retainedSession.sessionId).catch(() => void 0);
3982
+ await stopRunnerReaper(this.paths);
3983
+ await clearCoordinatorState(this.paths);
3984
+ } else {
3985
+ const reusableSession = await this.tryReuseSession(retainedSession, serviceNames);
3986
+ if (reusableSession) {
3987
+ await this.ensureRunnerReaper(reusableSession.sessionId, runnerPid);
3988
+ await writeCoordinatorState(this.paths, {
3989
+ session: reusableSession,
3990
+ leases: this.upsertLease([], workerId)
3991
+ });
3992
+ return { session: reusableSession, acquisition: "reused" };
3993
+ }
3994
+ await this.client.stop(retainedSession.sessionId).catch(() => void 0);
3995
+ await stopRunnerReaper(this.paths);
3996
+ await clearCoordinatorState(this.paths);
3997
+ }
3998
+ }
3999
+ const startedSession = await this.client.startRouteSession(serviceNames, requestedSeeds, {
4000
+ source: "test"
4001
+ });
4002
+ await this.ensureRunnerReaper(startedSession.sessionId, runnerPid);
4003
+ await writeCoordinatorState(this.paths, {
4004
+ session: startedSession,
4005
+ leases: this.upsertLease([], workerId)
4006
+ });
4007
+ return { session: startedSession, acquisition: "fresh" };
4008
+ });
4009
+ }
4010
+ async release() {
4011
+ const workerId = resolveWorkerId();
4012
+ const runnerPid = resolveRunnerPid(this.config);
4013
+ await withCoordinatorLock(this.paths, this.environment.readyTimeoutMs, async () => {
4014
+ const stateResult = await readCoordinatorState(this.paths);
4015
+ if (stateResult.kind !== "ready") {
4016
+ return;
4017
+ }
4018
+ const existingState = stateResult.state;
4019
+ const remainingLeases = pruneLeases(existingState.leases).filter((lease) => lease.workerId !== workerId);
4020
+ if (remainingLeases.length > 0) {
4021
+ await writeCoordinatorState(this.paths, {
4022
+ session: existingState.session,
4023
+ leases: remainingLeases
4024
+ });
4025
+ return;
4026
+ }
4027
+ const shouldRetainSession = runnerIsAlive(runnerPid) && !shouldStopSessionOnRelease();
4028
+ if (shouldRetainSession) {
4029
+ await writeCoordinatorState(this.paths, {
4030
+ session: existingState.session,
4031
+ leases: []
4032
+ });
4033
+ return;
4034
+ }
4035
+ await this.client.stop(existingState.session.sessionId);
4036
+ await stopRunnerReaper(this.paths);
4037
+ await clearCoordinatorState(this.paths);
4038
+ });
4039
+ }
4040
+ upsertLease(leases, workerId) {
4041
+ const nextLease = {
4042
+ workerId,
4043
+ pid: process.pid,
4044
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4045
+ };
4046
+ const withoutExisting = leases.filter((lease) => lease.workerId !== workerId);
4047
+ return [...withoutExisting, nextLease];
4048
+ }
4049
+ async recoverCorruptCoordinatorState(serviceNames, requestedSeeds, workerId, runnerPid) {
4050
+ const reaperState = await readRunnerReaperState(this.paths);
4051
+ if (!reaperState) {
4052
+ await clearCoordinatorState(this.paths);
4053
+ return null;
4054
+ }
4055
+ if (!reaperState.sessionId) {
4056
+ await stopRunnerReaper(this.paths);
4057
+ await clearCoordinatorState(this.paths);
4058
+ return null;
4059
+ }
4060
+ if (!runnerIsAlive(runnerPid)) {
4061
+ await this.client.stop(reaperState.sessionId).catch(() => void 0);
4062
+ await stopRunnerReaper(this.paths);
4063
+ await clearCoordinatorState(this.paths);
4064
+ return null;
4065
+ }
4066
+ const reusableSession = await this.client.reuseRouteSession(
4067
+ {
4068
+ sessionId: reaperState.sessionId,
4069
+ apiBaseUrls: {},
4070
+ resolvedRuntime: {
4071
+ resolvedServices: [],
4072
+ resolvedSeeds: {},
4073
+ manifestVersions: {}
4074
+ }
4075
+ },
4076
+ serviceNames
4077
+ );
4078
+ if (reusableSession && requestedSeedsMatchSession(requestedSeeds, reusableSession)) {
4079
+ await this.ensureRunnerReaper(reusableSession.sessionId, runnerPid);
4080
+ if (trimEnv("ARCHAL_VITEST_DISABLE_RUNNER_REAPER") === "1") {
4081
+ await fs.rm(this.paths.reaperPidPath, { force: true }).catch(() => void 0);
4082
+ }
4083
+ await writeCoordinatorState(this.paths, {
4084
+ session: reusableSession,
4085
+ leases: this.upsertLease([], workerId)
4086
+ });
4087
+ return reusableSession;
4088
+ }
4089
+ await this.client.stop(reaperState.sessionId).catch(() => void 0);
4090
+ await stopRunnerReaper(this.paths);
4091
+ await clearCoordinatorState(this.paths);
4092
+ return null;
4093
+ }
4094
+ async tryReuseSession(session, serviceNames) {
4095
+ try {
4096
+ return await this.client.reuseRouteSession(session, serviceNames);
4097
+ } catch {
4098
+ return null;
4099
+ }
4100
+ }
4101
+ async ensureRunnerReaper(sessionId, runnerPid) {
4102
+ if (trimEnv("ARCHAL_VITEST_DISABLE_RUNNER_REAPER") === "1") {
4103
+ return;
4104
+ }
4105
+ const existingReaper = await readRunnerReaperState(this.paths);
4106
+ if (existingReaper && isProcessAlive(existingReaper.pid)) {
4107
+ if (existingReaper.sessionId === sessionId) {
4108
+ return;
4109
+ }
4110
+ await stopRunnerReaper(this.paths);
4111
+ }
4112
+ const reaperPath = resolveRuntimeModule("./runtime/hosted-session-reaper.js");
4113
+ const child = spawn(process.execPath, [reaperPath], {
4114
+ detached: true,
4115
+ stdio: "ignore",
4116
+ env: {
4117
+ ...process.env,
4118
+ ARCHAL_VITEST_API_URL: this.environment.apiBaseUrl,
4119
+ ARCHAL_VITEST_REAPER_API_BASE_URL: this.environment.apiBaseUrl,
4120
+ ARCHAL_VITEST_REAPER_SESSION_ID: sessionId,
4121
+ ARCHAL_VITEST_REAPER_RUNNER_PID: String(runnerPid),
4122
+ ARCHAL_VITEST_REAPER_RENEW_INTERVAL_MS: String(this.environment.renewIntervalMs),
4123
+ ARCHAL_VITEST_REAPER_COORDINATOR_DIR: this.paths.directory,
4124
+ ARCHAL_VITEST_REAPER_LOCK_DIR: this.paths.lockDirectory,
4125
+ ARCHAL_VITEST_REAPER_STATE_PATH: this.paths.statePath,
4126
+ ARCHAL_VITEST_REAPER_PID_PATH: this.paths.reaperPidPath
4127
+ }
4128
+ });
4129
+ child.unref();
4130
+ if (!child.pid) {
4131
+ throw new Error("Archal Vitest runner reaper failed to start.");
4132
+ }
4133
+ await writeRunnerReaperState(this.paths, { pid: child.pid, sessionId });
4134
+ }
4135
+ };
4136
+ function buildNoSession() {
4137
+ return {
4138
+ sessionId: `archal-vitest-${randomUUID()}`,
4139
+ services: [],
4140
+ resolvedRuntime: {
4141
+ resolvedServices: [],
4142
+ resolvedSeeds: {},
4143
+ manifestVersions: {}
4144
+ },
4145
+ // No hosted session was provisioned (route-mode config had no services)
4146
+ // so there's no cold-start cost to report. Mark as `reused` so the
4147
+ // bootstrap's clone-startup log treats this as zero-cost and stays quiet.
4148
+ acquisition: "reused"
4149
+ };
4150
+ }
4151
+ function buildRoutedServices(serviceNames, apiBaseUrls, environment) {
4152
+ return serviceNames.map((serviceName) => {
4153
+ const baseUrl = apiBaseUrls[serviceName];
4154
+ if (!baseUrl) {
4155
+ throw new Error(
4156
+ `Hosted Vitest session did not return apiBaseUrls.${serviceName}.`
4157
+ );
4158
+ }
4159
+ const manifest = getSharedRouteManifest(serviceName);
4160
+ return {
4161
+ name: serviceName,
4162
+ mode: "route",
4163
+ baseUrl,
4164
+ upstreamBasePath: manifest?.upstreamBasePath,
4165
+ routedRequestHeaders: () => {
4166
+ const authorization = environment.auth.getAuthorizationHeader();
4167
+ if (!authorization) {
4168
+ throw new Error("Hosted Vitest routing auth is unavailable.");
4169
+ }
4170
+ const workerId = currentVitestWorkerId();
4171
+ const headers = {
4172
+ [ROUTE_CONTROL_AUTHORIZATION_HEADER2]: authorization,
4173
+ [ROUTE_SERVICE_ORIGIN_HEADER2]: "1"
4174
+ };
4175
+ if (workerId) headers[WORKER_ID_HEADER] = workerId;
4176
+ return headers;
4177
+ }
4178
+ };
4179
+ });
4180
+ }
4181
+ async function startHostedArchalVitestSession(config) {
4182
+ assertSupportedArchalVitestServices(config.services);
4183
+ const routedServiceNames = Object.entries(config.services).filter(([, serviceConfig]) => serviceConfig.mode === "route").map(([serviceName]) => serviceName);
4184
+ if (routedServiceNames.length === 0) {
4185
+ return buildNoSession();
4186
+ }
4187
+ const requestedSeeds = resolveRequestedSeeds(config, routedServiceNames);
4188
+ const environment = await resolveHostedSessionEnvironment();
4189
+ const hostedClient = new HostedSessionClient(environment);
4190
+ const coordinator = new CoordinatedHostedSessionManager(
4191
+ resolveCoordinatorPaths(config),
4192
+ hostedClient,
4193
+ environment,
4194
+ config
4195
+ );
4196
+ try {
4197
+ const { session, acquisition } = await coordinator.acquire(routedServiceNames, requestedSeeds);
4198
+ return {
4199
+ sessionId: session.sessionId,
4200
+ services: buildRoutedServices(routedServiceNames, session.apiBaseUrls, environment),
4201
+ resolvedRuntime: session.resolvedRuntime,
4202
+ acquisition,
4203
+ stop: async () => {
4204
+ try {
4205
+ await coordinator.release();
4206
+ } finally {
4207
+ environment.auth.stop();
4208
+ }
4209
+ }
4210
+ };
4211
+ } catch (error) {
4212
+ environment.auth.stop();
4213
+ throw error;
4214
+ }
4215
+ }
4216
+ var VITEST_AUTH_LEASE_OPTIONS = {
4217
+ apiUrlEnvVar: "ARCHAL_VITEST_API_URL"
4218
+ };
4219
+
4220
+ // src/runtime/bootstrap.ts
4221
+ var GLOBAL_RUNTIME_KEY = "__archalVitestRuntime";
4222
+ var GLOBAL_SESSION_KEY = "__archalVitestSession";
4223
+ var GLOBAL_CLEANUP_HANDLER_KEY = "__archalVitestRuntimeCleanupHandlerRegistered";
4224
+ var GLOBAL_BOOTSTRAP_PROMISE_KEY = "__archalVitestRuntimeBootstrapPromise";
4225
+ var GLOBAL_STOP_PROMISE_KEY = "__archalVitestRuntimeStopPromise";
4226
+ var ARCHAL_VITEST_ROUTE_TRACE_ENV = "ARCHAL_VITEST_ROUTE_TRACE";
4227
+ var activeFullSession;
4228
+ var baselineCloneStates = /* @__PURE__ */ new Map();
4229
+ var baselineCaptured = false;
4230
+ var sessionStartedAt;
4231
+ function emitUsageEstimate(services) {
4232
+ if (sessionStartedAt === void 0 || services.length === 0) return;
4233
+ if (process.env["ARCHAL_VITEST_QUIET_USAGE"] === "1") return;
4234
+ const durationSec = (Date.now() - sessionStartedAt) / 1e3;
4235
+ const cloneMinutes = Math.max(1, Math.ceil(durationSec / 60)) * services.length;
4236
+ const cloneList = services.map((s) => s.name).sort().join(", ");
4237
+ process.stderr.write(
4238
+ `\x1B[2m[archal] ~${cloneMinutes} clone-minute${cloneMinutes === 1 ? "" : "s"} for this run (${durationSec.toFixed(1)}s x ${services.length} clone${services.length === 1 ? "" : "s"}: ${cloneList})\x1B[0m
4239
+ `
4240
+ );
4241
+ }
4242
+ function cloneStateUrl(serviceBaseUrl) {
4243
+ return `${cloneRootUrl(serviceBaseUrl)}/state`;
4244
+ }
4245
+ function resolveRoutedHeaders(service) {
4246
+ try {
4247
+ const headers = service.routedRequestHeaders;
4248
+ if (typeof headers === "function") return headers();
4249
+ return headers ?? {};
4250
+ } catch (err) {
4251
+ activeFullSession = void 0;
4252
+ throw new Error(
4253
+ `Archal auth expired mid-test-run for clone "${service.name}". Re-run tests to refresh. (${err instanceof Error ? err.message : String(err)})`
4254
+ );
4255
+ }
4256
+ }
4257
+ var WORKER_ID_HEADER2 = "x-archal-worker-id";
4258
+ var ROUTE_SERVICE_ORIGIN_HEADER3 = "x-route-service-origin";
4259
+ function resolveControlHeaders(service) {
4260
+ const all = resolveRoutedHeaders(service);
4261
+ const { [ROUTE_SERVICE_ORIGIN_HEADER3]: _discarded, ...rest } = all;
4262
+ return rest;
4263
+ }
4264
+ function resolveDefaultScopeHeaders(service) {
4265
+ const all = resolveControlHeaders(service);
4266
+ const { [WORKER_ID_HEADER2]: _discarded, ...rest } = all;
4267
+ return rest;
4268
+ }
4269
+ function cloneRootUrl(serviceBaseUrl) {
4270
+ const trimmed = serviceBaseUrl.replace(/\/+$/, "");
4271
+ if (/\/mcp$/i.test(trimmed)) {
4272
+ return `${trimmed.slice(0, -4)}/api`;
4273
+ }
4274
+ if (/\/(?:api|rest)$/i.test(trimmed)) {
4275
+ return trimmed;
4276
+ }
4277
+ try {
4278
+ const parsed = new URL(trimmed);
4279
+ if (/\/runtime\/[^/]+\/[^/]+$/i.test(parsed.pathname)) {
4280
+ return `${trimmed}/api`;
4281
+ }
4282
+ } catch {
4283
+ }
4284
+ return trimmed;
4285
+ }
4286
+ async function fetchArchalClone(serviceName, path, init) {
4287
+ const session = activeFullSession;
4288
+ if (!session) {
4289
+ throw new Error(
4290
+ "Archal clone fetch called before the route runtime was installed. Ensure archalVitestProject() or withArchal() is wired into your vitest config, and that setup has completed."
4291
+ );
4292
+ }
4293
+ const service = session.services.find((s) => s.name === serviceName);
4294
+ if (!service) {
4295
+ const available = session.services.map((s) => s.name).sort().join(", ") || "(none)";
4296
+ throw new Error(`No clone named "${serviceName}" in this session. Available: ${available}.`);
4297
+ }
4298
+ const headers = resolveControlHeaders(service);
4299
+ const suffix = path.startsWith("/") ? path : `/${path}`;
4300
+ const url = `${cloneRootUrl(service.baseUrl)}${suffix}`;
4301
+ const requestInit = {
4302
+ ...init,
4303
+ headers: { ...headers, ...init?.headers }
4304
+ };
4305
+ return fetch(url, requestInit);
4306
+ }
4307
+ async function fetchArchalCloneDefaultScope(serviceName, path, init) {
4308
+ const session = activeFullSession;
4309
+ if (!session) {
4310
+ throw new Error("Archal clone fetch: no active session");
4311
+ }
4312
+ const service = session.services.find((s) => s.name === serviceName);
4313
+ if (!service) {
4314
+ throw new Error(`No clone "${serviceName}" in session`);
4315
+ }
4316
+ const headers = resolveDefaultScopeHeaders(service);
4317
+ const suffix = path.startsWith("/") ? path : `/${path}`;
4318
+ const url = `${cloneRootUrl(service.baseUrl)}${suffix}`;
4319
+ return fetch(url, {
4320
+ ...init,
4321
+ headers: { ...headers, ...init?.headers }
4322
+ });
4323
+ }
4324
+ async function captureBaselineCloneStates(session) {
4325
+ baselineCloneStates.clear();
4326
+ baselineCaptured = false;
4327
+ await Promise.all(
4328
+ session.services.map(async (service) => {
4329
+ try {
4330
+ const headers = resolveDefaultScopeHeaders(service);
4331
+ const res = await fetch(cloneStateUrl(service.baseUrl), {
4332
+ method: "GET",
4333
+ headers: { ...headers, accept: "application/json" }
4334
+ });
4335
+ if (!res.ok) {
4336
+ process.stderr.write(
4337
+ `[archal] warning: failed to capture baseline for clone "${service.name}": ${res.status} ${res.statusText}
4338
+ `
4339
+ );
4340
+ return;
4341
+ }
4342
+ baselineCloneStates.set(service.name, await res.text());
4343
+ } catch (err) {
4344
+ process.stderr.write(
4345
+ `[archal] warning: failed to capture baseline for clone "${service.name}": ${err instanceof Error ? err.message : String(err)}
4346
+ `
4347
+ );
4348
+ }
4349
+ })
4350
+ );
4351
+ baselineCaptured = true;
4352
+ }
4353
+ async function resetArchalCloneState() {
4354
+ const session = activeFullSession;
4355
+ if (!session) {
4356
+ throw new Error(
4357
+ "resetArchalClones() called before the Archal route runtime was installed. Ensure archalVitestProject() or withArchal() is wired into your vitest config, and that setup has completed."
4358
+ );
4359
+ }
4360
+ if (!baselineCaptured) {
4361
+ throw new Error(
4362
+ "resetArchalClones() called but baseline state was never captured. This usually means bootstrapArchalVitestRouting() has not completed."
4363
+ );
4364
+ }
4365
+ await Promise.all(
4366
+ session.services.map(async (service) => {
4367
+ const baseline = baselineCloneStates.get(service.name);
4368
+ if (baseline === void 0) {
4369
+ throw new Error(
4370
+ `No baseline state captured for clone "${service.name}". This usually means the clone was not reachable during setup.`
4371
+ );
4372
+ }
4373
+ const stateUrl = cloneStateUrl(service.baseUrl);
4374
+ const defaultHeaders = resolveDefaultScopeHeaders(service);
4375
+ const defaultRes = await fetch(stateUrl, {
4376
+ method: "PUT",
4377
+ headers: { ...defaultHeaders, "content-type": "application/json" },
4378
+ body: baseline
4379
+ });
4380
+ if (!defaultRes.ok) {
4381
+ throw new Error(
4382
+ `Failed to reset shared state for clone "${service.name}": ${defaultRes.status} ${defaultRes.statusText}`
4383
+ );
4384
+ }
4385
+ const workerHeaders = resolveControlHeaders(service);
4386
+ const workerRes = await fetch(stateUrl, {
4387
+ method: "PUT",
4388
+ headers: { ...workerHeaders, "content-type": "application/json" },
4389
+ body: baseline
4390
+ });
4391
+ if (!workerRes.ok) {
4392
+ throw new Error(
4393
+ `Failed to reset per-worker state for clone "${service.name}": ${workerRes.status} ${workerRes.statusText}`
4394
+ );
4395
+ }
4396
+ try {
4397
+ const whRes = await fetch(`${cloneRootUrl(service.baseUrl)}/webhooks/pending`, {
4398
+ method: "DELETE",
4399
+ headers: workerHeaders
4400
+ });
4401
+ if (!whRes.ok && whRes.status !== 404) {
4402
+ process.stderr.write(
4403
+ `[archal] warning: failed to drain webhook queue for "${service.name}": ${whRes.status} ${whRes.statusText}
4404
+ `
4405
+ );
4406
+ }
4407
+ } catch {
4408
+ }
4409
+ })
4410
+ );
4411
+ }
4412
+ async function resetArchalClones() {
4413
+ await resetArchalCloneState();
4414
+ }
4415
+ function emitManifestDriftWarning(session) {
4416
+ if (process.env["ARCHAL_VITEST_QUIET_DRIFT"] === "1") return;
4417
+ const serverVersions = session.resolvedRuntime?.manifestVersions ?? {};
4418
+ const drifts = [];
4419
+ for (const service of session.services) {
4420
+ const serverVersion = serverVersions[service.name];
4421
+ const local = getSharedRouteManifest(service.name);
4422
+ if (!serverVersion || !local) continue;
4423
+ if (local.manifestVersion !== serverVersion) {
4424
+ drifts.push({
4425
+ service: service.name,
4426
+ server: serverVersion,
4427
+ client: local.manifestVersion
4428
+ });
4429
+ }
4430
+ }
4431
+ if (drifts.length === 0) return;
4432
+ const lines = drifts.map((d) => ` ${d.service}: client ${d.client} vs server ${d.server}`);
4433
+ process.stderr.write(
4434
+ "\x1B[33m[archal] manifest version drift - upgrade the `archal` package to match the hosted clones:\n" + lines.join("\n") + "\x1B[0m\n"
4435
+ );
4436
+ }
4437
+ function emitProgressTicker(config) {
4438
+ if (process.env["ARCHAL_VITEST_QUIET_PROGRESS"] === "1") {
4439
+ return () => {
4440
+ };
4441
+ }
4442
+ const serviceNames = Object.keys(config.services).sort();
4443
+ const services = serviceNames.join(", ");
4444
+ const cloneWord = serviceNames.length === 1 ? "clone" : "clones";
4445
+ const start = Date.now();
4446
+ const ticker = setInterval(() => {
4447
+ const elapsed = Math.round((Date.now() - start) / 1e3);
4448
+ if (elapsed >= 60) {
4449
+ clearInterval(ticker);
4450
+ return;
4451
+ }
4452
+ process.stderr.write(
4453
+ `\x1B[2m[archal] provisioning ${services} ${cloneWord}... ${elapsed}s\x1B[0m
4454
+ `
4455
+ );
4456
+ }, 5e3);
4457
+ ticker.unref();
4458
+ return () => clearInterval(ticker);
4459
+ }
4460
+ function isTruthyEnv(value) {
4461
+ if (!value) {
4462
+ return false;
4463
+ }
4464
+ switch (value.trim().toLowerCase()) {
4465
+ case "1":
4466
+ case "true":
4467
+ case "yes":
4468
+ case "on":
4469
+ return true;
4470
+ default:
4471
+ return false;
4472
+ }
4473
+ }
4474
+ function formatTraceRecord(record) {
4475
+ const manifestStatus = record.manifestMatched ? `matched ${record.service ?? "manifest"}` : "no manifest match";
4476
+ const destination = record.target === "twin" ? "service endpoint" : record.target === "upstream" ? "real upstream" : "blocked";
4477
+ const status = record.statusCode === void 0 ? record.error ? `error=${record.error}` : "no status" : `${record.statusCode}${record.statusText ? ` ${record.statusText}` : ""}`;
4478
+ return `\x1B[2m[archal:route] ${record.method} ${record.sourceUrl} | ${manifestStatus} | ${record.outcome} -> ${destination} | ${status}\x1B[0m
4479
+ `;
4480
+ }
4481
+ async function startSession(config) {
4482
+ const stopTicker = emitProgressTicker(config);
4483
+ try {
4484
+ sessionStartedAt = Date.now();
4485
+ return await startHostedArchalVitestSession(config);
4486
+ } finally {
4487
+ stopTicker();
4488
+ }
4489
+ }
4490
+ var DEFAULT_STARTUP_LOG_MIN_MS = 3e3;
4491
+ function resolveStartupLogThresholdMs() {
4492
+ const raw = process.env["ARCHAL_VITEST_STARTUP_LOG_MIN_MS"];
4493
+ if (!raw) return DEFAULT_STARTUP_LOG_MIN_MS;
4494
+ const parsed = Number.parseInt(raw.trim(), 10);
4495
+ if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_STARTUP_LOG_MIN_MS;
4496
+ return parsed;
4497
+ }
4498
+ function emitCloneStartupSummary(phases) {
4499
+ if (process.env["ARCHAL_VITEST_QUIET_STARTUP"] === "1") return;
4500
+ const threshold = resolveStartupLogThresholdMs();
4501
+ if (phases.totalMs < threshold) return;
4502
+ if (phases.cloneCount === 0) return;
4503
+ const cloneLabel = phases.cloneCount === 1 ? "clone" : "clones";
4504
+ const cloneList = phases.cloneNames.length > 0 ? ` [${phases.cloneNames.slice().sort().join(", ")}]` : "";
4505
+ const parts = [];
4506
+ if (phases.acquireMs >= 10) parts.push(`acquire=${phases.acquireMs}ms`);
4507
+ if (phases.seedLoadMs >= 10) parts.push(`seed-load=${phases.seedLoadMs}ms`);
4508
+ if (phases.baselineMs >= 10) parts.push(`baseline=${phases.baselineMs}ms`);
4509
+ const breakdown = parts.length > 0 ? ` (${parts.join(", ")})` : "";
4510
+ process.stderr.write(
4511
+ `\x1B[2m[archal/vitest] Started ${phases.cloneCount} ${cloneLabel}${cloneList} in ${phases.totalMs}ms${breakdown}\x1B[0m
4512
+ `
4513
+ );
4514
+ }
4515
+ var pendingStartupPhases;
4516
+ async function buildRuntimeConfig(scope) {
4517
+ const config = readArchalVitestConfigFromEnv();
4518
+ let acquireMs = 0;
4519
+ let acquisition = "unknown";
4520
+ let session = activeFullSession;
4521
+ if (!session) {
4522
+ const acquireStart = Date.now();
4523
+ session = await startSession(config);
4524
+ acquireMs = Date.now() - acquireStart;
4525
+ acquisition = session.acquisition ?? "unknown";
4526
+ } else {
4527
+ acquisition = "reused";
4528
+ }
4529
+ activeFullSession = session;
4530
+ pendingStartupPhases = {
4531
+ acquireMs,
4532
+ seedLoadMs: 0,
4533
+ baselineMs: 0,
4534
+ totalMs: acquireMs,
4535
+ acquisition,
4536
+ cloneCount: session.services.length,
4537
+ cloneNames: session.services.map((s) => s.name)
4538
+ };
4539
+ scope[GLOBAL_SESSION_KEY] = redactSessionSnapshot(session);
4540
+ try {
4541
+ const { writeFileSync, mkdirSync } = await import("fs");
4542
+ const { dirname: dirname2 } = await import("path");
4543
+ const sessionIdPath = getSessionIdFilePath(config.sessionKey);
4544
+ mkdirSync(dirname2(sessionIdPath), { recursive: true });
4545
+ writeFileSync(sessionIdPath, session.sessionId, "utf-8");
4546
+ } catch {
4547
+ }
4548
+ return {
4549
+ profiles: resolveArchalVitestServiceProfiles(config.services),
4550
+ services: session.services,
4551
+ trace: isTruthyEnv(process.env[ARCHAL_VITEST_ROUTE_TRACE_ENV]) ? {
4552
+ enabled: true,
4553
+ onTrace: (record) => {
4554
+ process.stderr.write(formatTraceRecord(record));
4555
+ }
4556
+ } : void 0
4557
+ };
4558
+ }
4559
+ async function stopArchalVitestRouting() {
4560
+ const scope = globalThis;
4561
+ if (scope[GLOBAL_BOOTSTRAP_PROMISE_KEY]) {
4562
+ await scope[GLOBAL_BOOTSTRAP_PROMISE_KEY].catch(() => {
4563
+ });
4564
+ }
4565
+ if (scope[GLOBAL_STOP_PROMISE_KEY]) {
4566
+ await scope[GLOBAL_STOP_PROMISE_KEY];
4567
+ return;
4568
+ }
4569
+ scope[GLOBAL_STOP_PROMISE_KEY] = (async () => {
4570
+ const runtime = scope[GLOBAL_RUNTIME_KEY];
4571
+ const session = activeFullSession;
4572
+ const services = session?.services.map((s) => ({ name: s.name })) ?? [];
4573
+ delete scope[GLOBAL_RUNTIME_KEY];
4574
+ delete scope[GLOBAL_SESSION_KEY];
4575
+ emitUsageEstimate(services);
4576
+ sessionStartedAt = void 0;
4577
+ activeFullSession = void 0;
4578
+ baselineCloneStates.clear();
4579
+ baselineCaptured = false;
4580
+ await runtime?.stop();
4581
+ await session?.stop?.();
4582
+ })();
4583
+ try {
4584
+ await scope[GLOBAL_STOP_PROMISE_KEY];
4585
+ } finally {
4586
+ delete scope[GLOBAL_STOP_PROMISE_KEY];
4587
+ }
4588
+ }
4589
+ async function bootstrapArchalVitestRouting() {
4590
+ const scope = globalThis;
4591
+ if (scope[GLOBAL_RUNTIME_KEY]) {
4592
+ return scope[GLOBAL_RUNTIME_KEY];
4593
+ }
4594
+ if (scope[GLOBAL_BOOTSTRAP_PROMISE_KEY]) {
4595
+ return await scope[GLOBAL_BOOTSTRAP_PROMISE_KEY];
4596
+ }
4597
+ scope[GLOBAL_BOOTSTRAP_PROMISE_KEY] = (async () => {
4598
+ const runtime = new NodeRouteRuntime(await buildRuntimeConfig(scope));
4599
+ await runtime.start();
4600
+ runtime.installRouting();
4601
+ scope[GLOBAL_RUNTIME_KEY] = runtime;
4602
+ const config = readArchalVitestConfigFromEnv();
4603
+ if (config.fileSeedContents && Object.keys(config.fileSeedContents).length > 0) {
4604
+ const seedStart = Date.now();
4605
+ await loadFileSeedsIntoClones(config, fetchArchalCloneDefaultScope, {
4606
+ timeoutEnvVar: "ARCHAL_VITEST_SEED_LOAD_TIMEOUT_MS",
4607
+ ...activeFullSession?.sessionId ? { hostedSessionId: activeFullSession.sessionId } : {}
4608
+ });
4609
+ if (pendingStartupPhases) {
4610
+ pendingStartupPhases.seedLoadMs = Date.now() - seedStart;
4611
+ }
4612
+ }
4613
+ if (activeFullSession) {
4614
+ emitManifestDriftWarning(activeFullSession);
4615
+ const baselineStart = Date.now();
4616
+ await captureBaselineCloneStates(activeFullSession);
4617
+ if (pendingStartupPhases) {
4618
+ pendingStartupPhases.baselineMs = Date.now() - baselineStart;
4619
+ }
4620
+ }
4621
+ if (pendingStartupPhases) {
4622
+ pendingStartupPhases.totalMs = pendingStartupPhases.acquireMs + pendingStartupPhases.seedLoadMs + pendingStartupPhases.baselineMs;
4623
+ emitCloneStartupSummary(pendingStartupPhases);
4624
+ pendingStartupPhases = void 0;
4625
+ }
4626
+ if (!scope[GLOBAL_CLEANUP_HANDLER_KEY]) {
4627
+ const asyncCleanupAndExit = (exitCode) => {
4628
+ void stopArchalVitestRouting().finally(() => {
4629
+ process.exit(exitCode);
4630
+ });
4631
+ };
4632
+ process.once("beforeExit", () => {
4633
+ void stopArchalVitestRouting();
4634
+ });
4635
+ process.once("disconnect", () => {
4636
+ void stopArchalVitestRouting();
4637
+ });
4638
+ process.once("SIGINT", () => asyncCleanupAndExit(130));
4639
+ process.once("SIGTERM", () => asyncCleanupAndExit(143));
4640
+ scope[GLOBAL_CLEANUP_HANDLER_KEY] = true;
4641
+ }
4642
+ return runtime;
4643
+ })();
4644
+ try {
4645
+ return await scope[GLOBAL_BOOTSTRAP_PROMISE_KEY];
4646
+ } finally {
4647
+ delete scope[GLOBAL_BOOTSTRAP_PROMISE_KEY];
4648
+ }
4649
+ }
4650
+ function getInstalledArchalVitestRuntime() {
4651
+ return globalThis[GLOBAL_RUNTIME_KEY];
4652
+ }
4653
+ function getInstalledArchalVitestSession() {
4654
+ return globalThis[GLOBAL_SESSION_KEY];
4655
+ }
4656
+
4657
+ export {
4658
+ resolveRuntimeModule,
4659
+ ARCHAL_VITEST_CONFIG_ENV,
4660
+ readArchalVitestConfig,
4661
+ assertSupportedArchalVitestServices,
4662
+ fetchArchalClone,
4663
+ resetArchalClones,
4664
+ bootstrapArchalVitestRouting,
4665
+ getInstalledArchalVitestRuntime,
4666
+ getInstalledArchalVitestSession
4667
+ };