rebase-git 0.0.2-nightly.20260822.35

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.
@@ -0,0 +1,2555 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/apps/server/cli.ts
4
+ import { existsSync as existsSync2, realpathSync } from "node:fs";
5
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
6
+
7
+ // src/contracts/environment-connection/environment-transport-limits.contract.ts
8
+ import { Schema } from "effect";
9
+ var ByteLimit = Schema.Int.check(
10
+ Schema.isBetween({ minimum: 1024, maximum: 67108864 })
11
+ ), QueueLength = Schema.Int.check(
12
+ Schema.isBetween({ minimum: 1, maximum: 4096 })
13
+ ), TimeoutMilliseconds = Schema.Int.check(
14
+ Schema.isBetween({ minimum: 100, maximum: 6e4 })
15
+ ), TransportLimits = Schema.Struct({
16
+ maxHttpRequestBytes: ByteLimit,
17
+ maxHttpResponseBytes: ByteLimit,
18
+ maxWebSocketRequestBytes: ByteLimit,
19
+ maxWebSocketResponseBytes: ByteLimit,
20
+ maxQueuedEvents: QueueLength,
21
+ maxQueuedEventBytes: ByteLimit,
22
+ maxCapturedOutputBytes: ByteLimit,
23
+ helloTimeoutMilliseconds: TimeoutMilliseconds
24
+ }), ClientReceiveLimits = Schema.Struct({
25
+ maxCapturedOutputBytes: ByteLimit,
26
+ maxHttpResponseBytes: ByteLimit,
27
+ maxWebSocketResponseBytes: ByteLimit
28
+ }), currentTransportLimits = {
29
+ helloTimeoutMilliseconds: 5e3,
30
+ maxCapturedOutputBytes: 1048576,
31
+ maxHttpRequestBytes: 65536,
32
+ maxHttpResponseBytes: 65536,
33
+ maxQueuedEventBytes: 262144,
34
+ maxQueuedEvents: 128,
35
+ maxWebSocketRequestBytes: 16384,
36
+ maxWebSocketResponseBytes: 16384
37
+ }, currentClientReceiveLimits = {
38
+ maxCapturedOutputBytes: currentTransportLimits.maxCapturedOutputBytes,
39
+ maxHttpResponseBytes: currentTransportLimits.maxHttpResponseBytes,
40
+ maxWebSocketResponseBytes: currentTransportLimits.maxWebSocketResponseBytes
41
+ };
42
+
43
+ // src/contracts/environment-connection/negotiation/environment-protocol.contract.ts
44
+ import { Schema as Schema2 } from "effect";
45
+ var ProtocolNumber = Schema2.Natural.check(Schema2.isLessThanOrEqualTo(65535)), CapabilityName = Schema2.String.check(
46
+ Schema2.isMinLength(1),
47
+ Schema2.isMaxLength(64)
48
+ ), ProductVersion = Schema2.String.check(
49
+ Schema2.isMinLength(1),
50
+ Schema2.isMaxLength(64)
51
+ ), ProtocolRange = Schema2.Struct({
52
+ major: ProtocolNumber,
53
+ minor: ProtocolNumber,
54
+ minimumSupportedMinor: ProtocolNumber
55
+ }).check(
56
+ Schema2.makeFilter(
57
+ (range) => range.minimumSupportedMinor <= range.minor || "minimumSupportedMinor must not exceed minor"
58
+ )
59
+ ), EnvironmentCapability = Schema2.Struct({
60
+ name: CapabilityName,
61
+ version: Schema2.Int.check(
62
+ Schema2.isGreaterThan(0),
63
+ Schema2.isLessThanOrEqualTo(65535)
64
+ ),
65
+ introducedInMinor: ProtocolNumber
66
+ }), EnvironmentCapabilities = Schema2.Array(
67
+ EnvironmentCapability
68
+ ).check(
69
+ Schema2.isMaxLength(32),
70
+ Schema2.makeFilter((capabilities) => new Set(capabilities.map((capability) => capability.name)).size === capabilities.length || "capability names must be unique")
71
+ ), ProductVersionSchema = ProductVersion, currentEnvironmentProtocol = {
72
+ major: 1,
73
+ minor: 2,
74
+ minimumSupportedMinor: 0
75
+ }, currentEnvironmentCapabilities = [
76
+ {
77
+ introducedInMinor: 0,
78
+ name: "environment-events",
79
+ version: 1
80
+ },
81
+ {
82
+ introducedInMinor: 1,
83
+ name: "sequence-resnapshot",
84
+ version: 1
85
+ }
86
+ ];
87
+
88
+ // src/contracts/environment-connection/websocket/environment-live-connection.contract.ts
89
+ import { Schema as Schema3 } from "effect";
90
+ var EnvironmentHello = Schema3.TaggedStruct("Hello", {
91
+ productVersion: ProductVersionSchema,
92
+ protocol: ProtocolRange,
93
+ capabilities: EnvironmentCapabilities,
94
+ receiveLimits: ClientReceiveLimits,
95
+ lastObservedSequence: Schema3.optionalKey(Schema3.Natural)
96
+ }), SnapshotApplied = Schema3.TaggedStruct("SnapshotApplied", {
97
+ sequence: Schema3.Natural
98
+ }), EnvironmentClientMessage = Schema3.Union([
99
+ EnvironmentHello,
100
+ SnapshotApplied
101
+ ]), ProtocolMajorMismatch = Schema3.TaggedStruct("ProtocolMajorMismatch", {
102
+ clientMajor: Schema3.Natural,
103
+ serverMajor: Schema3.Natural,
104
+ requiredUpdate: Schema3.Literals(["client", "server"])
105
+ }), ProtocolMinorMismatch = Schema3.TaggedStruct("ProtocolMinorMismatch", {
106
+ clientMinor: Schema3.Natural,
107
+ clientMinimumSupportedMinor: Schema3.Natural,
108
+ serverMinor: Schema3.Natural,
109
+ serverMinimumSupportedMinor: Schema3.Natural
110
+ }), InvalidMessage = Schema3.TaggedStruct("InvalidMessage", {}), PayloadTooLarge = Schema3.TaggedStruct("PayloadTooLarge", {
111
+ limitBytes: Schema3.Natural
112
+ }), HandshakeRequired = Schema3.TaggedStruct("HandshakeRequired", {}), HandshakeAlreadyCompleted = Schema3.TaggedStruct(
113
+ "HandshakeAlreadyCompleted",
114
+ {}
115
+ ), EnvironmentTransportFailure = Schema3.Union([
116
+ ProtocolMajorMismatch,
117
+ ProtocolMinorMismatch,
118
+ InvalidMessage,
119
+ PayloadTooLarge,
120
+ HandshakeRequired,
121
+ HandshakeAlreadyCompleted
122
+ ]), HelloAccepted = Schema3.TaggedStruct("HelloAccepted", {
123
+ environmentId: Schema3.String.check(Schema3.isUUID(4)),
124
+ protocol: Schema3.Struct({
125
+ major: Schema3.Natural,
126
+ minor: Schema3.Natural
127
+ }),
128
+ capabilities: EnvironmentCapabilities,
129
+ limits: TransportLimits,
130
+ currentSequence: Schema3.Natural
131
+ }), HelloRejected = Schema3.TaggedStruct("HelloRejected", {
132
+ failure: EnvironmentTransportFailure
133
+ }), EnvironmentChanged = Schema3.TaggedStruct("EnvironmentChanged", {
134
+ sequence: Schema3.Natural
135
+ }), ResnapshotRequired = Schema3.TaggedStruct("ResnapshotRequired", {
136
+ currentSequence: Schema3.Natural,
137
+ reason: Schema3.Literals(["SequenceGap", "OutgoingQueueOverflow"])
138
+ }), EnvironmentHelloResult = Schema3.Union([
139
+ HelloAccepted,
140
+ HelloRejected
141
+ ]), EnvironmentServerMessage = Schema3.Union([
142
+ HelloAccepted,
143
+ HelloRejected,
144
+ EnvironmentChanged,
145
+ ResnapshotRequired
146
+ ]);
147
+
148
+ // src/contracts/environment-authorization/environment-authorization.contract.ts
149
+ import { Schema as Schema4 } from "effect";
150
+ var environmentAuthorizationRoles = [
151
+ "viewer",
152
+ "contributor",
153
+ "maintainer",
154
+ "owner",
155
+ "custom"
156
+ ], environmentAccessCapabilities = [
157
+ "environment.read",
158
+ "repository.read",
159
+ "repository.write",
160
+ "history.rewrite",
161
+ "worktree.manage",
162
+ "authorization.manage",
163
+ "environment.manage"
164
+ ], EnvironmentAuthorizationRole = Schema4.Literals(
165
+ environmentAuthorizationRoles
166
+ ), EnvironmentAccessCapability = Schema4.Literals(
167
+ environmentAccessCapabilities
168
+ ), AuthorizationId = Schema4.String.check(Schema4.isUUID(4)), SecretMaterial = Schema4.String.check(
169
+ Schema4.isMinLength(32),
170
+ Schema4.isMaxLength(512)
171
+ ), PairingCode = Schema4.String.check(Schema4.isPattern(/^\d{3}-\d{3}$/)), DeviceLabel = Schema4.String.check(
172
+ Schema4.isMinLength(1),
173
+ Schema4.isMaxLength(128)
174
+ ), IsoDate = Schema4.String, CreateEnvironmentPairing = Schema4.Struct({
175
+ role: EnvironmentAuthorizationRole,
176
+ capabilities: Schema4.Array(EnvironmentAccessCapability).check(
177
+ Schema4.isMaxLength(environmentAccessCapabilities.length)
178
+ )
179
+ }), EnvironmentPairingCreated = Schema4.Struct({
180
+ pairingUrl: Schema4.String,
181
+ expiresAt: IsoDate
182
+ }), ExchangeEnvironmentPairing = Schema4.Struct({
183
+ pairingMaterial: PairingCode,
184
+ label: DeviceLabel
185
+ }), EnvironmentDeviceAuthorization = Schema4.Struct({
186
+ id: AuthorizationId,
187
+ label: DeviceLabel,
188
+ role: EnvironmentAuthorizationRole,
189
+ capabilities: Schema4.Array(EnvironmentAccessCapability)
190
+ }), EnvironmentPairingExchanged = Schema4.Struct({
191
+ authorization: EnvironmentDeviceAuthorization,
192
+ credential: SecretMaterial
193
+ }), EnvironmentWebSocketTicket = Schema4.Struct({
194
+ expiresAt: IsoDate,
195
+ ticket: SecretMaterial
196
+ }), RevokeEnvironmentAuthorization = Schema4.Struct({
197
+ authorizationId: AuthorizationId
198
+ }), EnvironmentAuthorizationRevoked = Schema4.Struct({
199
+ authorizationId: AuthorizationId,
200
+ revokedAt: IsoDate
201
+ }), InvalidHost = Schema4.TaggedStruct("InvalidHost", {}), InvalidOrigin = Schema4.TaggedStruct("InvalidOrigin", {}), InvalidGrant = Schema4.TaggedStruct("InvalidGrant", {}), ExpiredGrant = Schema4.TaggedStruct("ExpiredGrant", {}), RevokedGrant = Schema4.TaggedStruct("RevokedGrant", {}), CapabilityDenied = Schema4.TaggedStruct("CapabilityDenied", {
202
+ capability: EnvironmentAccessCapability
203
+ }), InvalidPairing = Schema4.TaggedStruct("InvalidPairing", {}), ExpiredPairing = Schema4.TaggedStruct("ExpiredPairing", {}), PairingAlreadyUsed = Schema4.TaggedStruct("PairingAlreadyUsed", {}), InvalidTicket = Schema4.TaggedStruct("InvalidTicket", {}), ExpiredTicket = Schema4.TaggedStruct("ExpiredTicket", {}), TicketAlreadyUsed = Schema4.TaggedStruct("TicketAlreadyUsed", {}), EnvironmentAuthorizationFailure = Schema4.Union([
204
+ InvalidHost,
205
+ InvalidOrigin,
206
+ InvalidGrant,
207
+ ExpiredGrant,
208
+ RevokedGrant,
209
+ CapabilityDenied,
210
+ InvalidPairing,
211
+ ExpiredPairing,
212
+ PairingAlreadyUsed,
213
+ InvalidTicket,
214
+ ExpiredTicket,
215
+ TicketAlreadyUsed
216
+ ]), EnvironmentGrantHttpFailure = Schema4.Union([
217
+ InvalidHost,
218
+ InvalidOrigin,
219
+ InvalidGrant,
220
+ ExpiredGrant,
221
+ RevokedGrant,
222
+ CapabilityDenied,
223
+ InvalidMessage,
224
+ PayloadTooLarge
225
+ ]), EnvironmentPairingExchangeHttpFailure = Schema4.Union([
226
+ InvalidHost,
227
+ InvalidOrigin,
228
+ InvalidPairing,
229
+ ExpiredPairing,
230
+ PairingAlreadyUsed,
231
+ InvalidMessage,
232
+ PayloadTooLarge
233
+ ]), EnvironmentAuthorizationHttpFailure = Schema4.Union([
234
+ EnvironmentGrantHttpFailure,
235
+ EnvironmentPairingExchangeHttpFailure
236
+ ]), environmentPairingExchangePath = "/api/authorization/pairings/exchange", environmentPairingsPath = "/api/authorization/pairings", environmentWebSocketTicketsPath = "/api/authorization/websocket-tickets", environmentAuthorizationRevocationPath = "/api/authorization/revocations", EnvironmentAuthorizationHttpApi = {
237
+ createPairing: {
238
+ failure: EnvironmentGrantHttpFailure,
239
+ failureStatuses: [400, 401, 403, 410, 413],
240
+ method: "POST",
241
+ path: environmentPairingsPath,
242
+ request: CreateEnvironmentPairing,
243
+ success: EnvironmentPairingCreated,
244
+ successStatus: 201
245
+ },
246
+ exchangePairing: {
247
+ failure: EnvironmentPairingExchangeHttpFailure,
248
+ failureStatuses: [400, 401, 403, 409, 410, 413],
249
+ method: "POST",
250
+ path: environmentPairingExchangePath,
251
+ request: ExchangeEnvironmentPairing,
252
+ success: EnvironmentPairingExchanged,
253
+ successStatus: 201
254
+ },
255
+ mintWebSocketTicket: {
256
+ failure: EnvironmentGrantHttpFailure,
257
+ failureStatuses: [400, 401, 403, 410, 413],
258
+ method: "POST",
259
+ path: environmentWebSocketTicketsPath,
260
+ success: EnvironmentWebSocketTicket,
261
+ successStatus: 201
262
+ },
263
+ revokeAuthorization: {
264
+ failure: EnvironmentGrantHttpFailure,
265
+ failureStatuses: [400, 401, 403, 410, 413],
266
+ method: "POST",
267
+ path: environmentAuthorizationRevocationPath,
268
+ request: RevokeEnvironmentAuthorization,
269
+ success: EnvironmentAuthorizationRevoked,
270
+ successStatus: 200
271
+ }
272
+ };
273
+
274
+ // src/contracts/environment-connection/http/environment-discovery.contract.ts
275
+ import { Schema as Schema5 } from "effect";
276
+ var environmentDiscoveryPath = "/api/discovery", environmentSnapshotPath = "/api/environment/snapshot", environmentLivePath = "/api/environment/live", EnvironmentDiscovery = Schema5.Struct({
277
+ environmentId: Schema5.String.check(Schema5.isUUID(4)),
278
+ productVersion: ProductVersionSchema,
279
+ protocol: ProtocolRange,
280
+ capabilities: EnvironmentCapabilities,
281
+ limits: TransportLimits,
282
+ routes: Schema5.Struct({
283
+ snapshot: Schema5.Literal(environmentSnapshotPath),
284
+ live: Schema5.Literal(environmentLivePath)
285
+ })
286
+ }), EnvironmentSnapshot = Schema5.Struct({
287
+ environmentId: Schema5.String.check(Schema5.isUUID(4)),
288
+ sequence: Schema5.Natural
289
+ }), EnvironmentHttpFailure = Schema5.Union([
290
+ InvalidMessage,
291
+ PayloadTooLarge
292
+ ]), EnvironmentDiscoveryHttpFailure = Schema5.Union([
293
+ EnvironmentHttpFailure,
294
+ InvalidHost
295
+ ]), EnvironmentHttpApi = {
296
+ discovery: {
297
+ failure: EnvironmentDiscoveryHttpFailure,
298
+ failureStatuses: [400, 403, 413],
299
+ method: "GET",
300
+ path: environmentDiscoveryPath,
301
+ success: EnvironmentDiscovery,
302
+ successStatus: 200
303
+ },
304
+ snapshot: {
305
+ failure: EnvironmentGrantHttpFailure,
306
+ failureStatuses: [400, 401, 403, 410, 413],
307
+ method: "GET",
308
+ path: environmentSnapshotPath,
309
+ success: EnvironmentSnapshot,
310
+ successStatus: 200
311
+ }
312
+ };
313
+ function createCurrentEnvironmentDiscovery(environmentId, productVersion2) {
314
+ return {
315
+ capabilities: currentEnvironmentCapabilities,
316
+ environmentId,
317
+ limits: currentTransportLimits,
318
+ productVersion: productVersion2,
319
+ protocol: currentEnvironmentProtocol,
320
+ routes: {
321
+ live: environmentLivePath,
322
+ snapshot: environmentSnapshotPath
323
+ }
324
+ };
325
+ }
326
+
327
+ // src/contracts/environment-connection/negotiation/negotiate-environment-protocol.ts
328
+ function negotiateEnvironmentHello(discovery, hello, currentSequence) {
329
+ if (hello.protocol.major !== discovery.protocol.major)
330
+ return {
331
+ _tag: "HelloRejected",
332
+ failure: {
333
+ _tag: "ProtocolMajorMismatch",
334
+ clientMajor: hello.protocol.major,
335
+ requiredUpdate: hello.protocol.major < discovery.protocol.major ? "client" : "server",
336
+ serverMajor: discovery.protocol.major
337
+ }
338
+ };
339
+ let negotiatedMinor = Math.min(
340
+ hello.protocol.minor,
341
+ discovery.protocol.minor
342
+ ), minimumCompatibleMinor = Math.max(
343
+ hello.protocol.minimumSupportedMinor,
344
+ discovery.protocol.minimumSupportedMinor
345
+ );
346
+ return negotiatedMinor < minimumCompatibleMinor ? {
347
+ _tag: "HelloRejected",
348
+ failure: {
349
+ _tag: "ProtocolMinorMismatch",
350
+ clientMinor: hello.protocol.minor,
351
+ clientMinimumSupportedMinor: hello.protocol.minimumSupportedMinor,
352
+ serverMinor: discovery.protocol.minor,
353
+ serverMinimumSupportedMinor: discovery.protocol.minimumSupportedMinor
354
+ }
355
+ } : {
356
+ _tag: "HelloAccepted",
357
+ capabilities: negotiateCapabilities(
358
+ discovery.capabilities,
359
+ hello.capabilities,
360
+ negotiatedMinor
361
+ ),
362
+ currentSequence,
363
+ environmentId: discovery.environmentId,
364
+ limits: negotiatedLimits(discovery.limits, hello.receiveLimits),
365
+ protocol: {
366
+ major: discovery.protocol.major,
367
+ minor: negotiatedMinor
368
+ }
369
+ };
370
+ }
371
+ function negotiateCapabilities(serverCapabilities, clientCapabilities, negotiatedMinor) {
372
+ let clientByName = new Map(
373
+ clientCapabilities.map((capability) => [capability.name, capability])
374
+ );
375
+ return serverCapabilities.flatMap((serverCapability) => {
376
+ let clientCapability = clientByName.get(serverCapability.name);
377
+ return clientCapability === void 0 || serverCapability.introducedInMinor > negotiatedMinor || clientCapability.introducedInMinor > negotiatedMinor ? [] : [
378
+ {
379
+ introducedInMinor: Math.max(
380
+ serverCapability.introducedInMinor,
381
+ clientCapability.introducedInMinor
382
+ ),
383
+ name: serverCapability.name,
384
+ version: Math.min(serverCapability.version, clientCapability.version)
385
+ }
386
+ ];
387
+ });
388
+ }
389
+ function negotiatedLimits(serverLimits, clientLimits) {
390
+ return {
391
+ helloTimeoutMilliseconds: serverLimits.helloTimeoutMilliseconds,
392
+ maxCapturedOutputBytes: Math.min(
393
+ serverLimits.maxCapturedOutputBytes,
394
+ clientLimits.maxCapturedOutputBytes
395
+ ),
396
+ maxHttpRequestBytes: serverLimits.maxHttpRequestBytes,
397
+ maxHttpResponseBytes: Math.min(
398
+ serverLimits.maxHttpResponseBytes,
399
+ clientLimits.maxHttpResponseBytes
400
+ ),
401
+ maxQueuedEventBytes: serverLimits.maxQueuedEventBytes,
402
+ maxQueuedEvents: serverLimits.maxQueuedEvents,
403
+ maxWebSocketRequestBytes: serverLimits.maxWebSocketRequestBytes,
404
+ maxWebSocketResponseBytes: Math.min(
405
+ serverLimits.maxWebSocketResponseBytes,
406
+ clientLimits.maxWebSocketResponseBytes
407
+ )
408
+ };
409
+ }
410
+
411
+ // src/apps/server/cli.ts
412
+ import { Deferred, Effect as Effect20 } from "effect";
413
+
414
+ // src/apps/server/features/browser-client/default-browser.ts
415
+ import { spawn } from "node:child_process";
416
+ import { readFileSync } from "node:fs";
417
+ import { release } from "node:os";
418
+ function openDefaultBrowser(url) {
419
+ if (process.env.BROWSER !== "none")
420
+ try {
421
+ let invocation = browserInvocation(url), child = spawn(invocation.command, invocation.arguments, {
422
+ detached: !0,
423
+ stdio: "ignore",
424
+ windowsHide: !0
425
+ });
426
+ child.once("error", () => {
427
+ }), child.unref();
428
+ } catch {
429
+ }
430
+ }
431
+ function browserInvocation(url) {
432
+ return process.platform === "darwin" ? { arguments: [url], command: "open" } : process.platform === "win32" || isWindowsSubsystemForLinux() ? {
433
+ arguments: ["/d", "/s", "/c", "start", "", url],
434
+ command: "cmd.exe"
435
+ } : { arguments: [url], command: "xdg-open" };
436
+ }
437
+ function isWindowsSubsystemForLinux() {
438
+ if (process.env.WSL_DISTRO_NAME !== void 0) return !0;
439
+ try {
440
+ return readFileSync("/proc/sys/kernel/osrelease", "utf8").toLowerCase().includes("microsoft");
441
+ } catch {
442
+ return release().toLowerCase().includes("microsoft");
443
+ }
444
+ }
445
+
446
+ // src/apps/server/features/environment-server/server/start-environment-server.ts
447
+ import { and } from "drizzle-orm";
448
+ import { Effect as Effect19 } from "effect";
449
+
450
+ // src/apps/server/features/environment-authorization/environment-authorization.ts
451
+ import { randomUUID } from "node:crypto";
452
+ import { eq } from "drizzle-orm";
453
+ import { Effect } from "effect";
454
+
455
+ // src/apps/server/features/environment-authorization/environment-authorization.contract.ts
456
+ import { Data } from "effect";
457
+ var EnvironmentAuthorizationError = class extends Data.TaggedError(
458
+ "EnvironmentAuthorizationError"
459
+ ) {
460
+ };
461
+ function isEnvironmentAuthorizationError(value) {
462
+ return value instanceof EnvironmentAuthorizationError;
463
+ }
464
+
465
+ // src/apps/server/features/environment-authorization/environment-authorization-secret.ts
466
+ import {
467
+ createHash,
468
+ createHmac,
469
+ randomBytes,
470
+ randomInt,
471
+ timingSafeEqual
472
+ } from "node:crypto";
473
+ var credentialPrefix = "rebase.v1";
474
+ function createDeviceCredential(serverSecret, authorizationId) {
475
+ let secret = createSecretMaterial(), unsigned = `${credentialPrefix}.${authorizationId}.${secret}`, signature = signCredential(serverSecret, unsigned);
476
+ return `${unsigned}.${signature}`;
477
+ }
478
+ function verifyDeviceCredential(serverSecret, credential) {
479
+ if (credential === void 0)
480
+ return;
481
+ let [product, version, authorizationId, secret, signature, excess] = credential.split(".");
482
+ if (product !== "rebase" || version !== "v1" || authorizationId === void 0 || !/^[0-9a-f-]{36}$/.test(authorizationId) || secret === void 0 || !/^[A-Za-z0-9_-]{43}$/.test(secret) || signature === void 0 || !/^[A-Za-z0-9_-]{43}$/.test(signature) || excess !== void 0)
483
+ return;
484
+ let unsigned = `${credentialPrefix}.${authorizationId}.${secret}`, expected = Buffer.from(signCredential(serverSecret, unsigned)), received = Buffer.from(signature);
485
+ return expected.byteLength === received.byteLength && timingSafeEqual(expected, received) ? authorizationId : void 0;
486
+ }
487
+ function createSecretMaterial() {
488
+ return randomBytes(32).toString("base64url");
489
+ }
490
+ function createPairingCode() {
491
+ let digits = randomInt(1e6).toString().padStart(6, "0");
492
+ return `${digits.slice(0, 3)}-${digits.slice(3)}`;
493
+ }
494
+ function digestSecretMaterial(material) {
495
+ return createHash("sha256").update(material).digest("base64url");
496
+ }
497
+ function signCredential(serverSecret, unsigned) {
498
+ return createHmac("sha256", serverSecret).update(unsigned).digest("base64url");
499
+ }
500
+
501
+ // src/apps/server/features/environment-authorization/environment-capabilities.ts
502
+ var viewerCapabilities = [
503
+ "environment.read",
504
+ "repository.read"
505
+ ], contributorCapabilities = [
506
+ ...viewerCapabilities,
507
+ "repository.write"
508
+ ], maintainerCapabilities = [
509
+ ...contributorCapabilities,
510
+ "history.rewrite",
511
+ "worktree.manage"
512
+ ], ownerCapabilities = [
513
+ ...maintainerCapabilities,
514
+ "authorization.manage",
515
+ "environment.manage"
516
+ ];
517
+ function capabilitiesForRole(role, customCapabilities) {
518
+ switch (role) {
519
+ case "viewer":
520
+ return viewerCapabilities;
521
+ case "contributor":
522
+ return contributorCapabilities;
523
+ case "maintainer":
524
+ return maintainerCapabilities;
525
+ case "owner":
526
+ return ownerCapabilities;
527
+ case "custom":
528
+ return [...new Set(customCapabilities)];
529
+ }
530
+ }
531
+
532
+ // src/apps/server/persistence/environment-state.schema.ts
533
+ import { sql } from "drizzle-orm";
534
+ import {
535
+ check,
536
+ foreignKey,
537
+ index,
538
+ integer,
539
+ primaryKey,
540
+ sqliteTable,
541
+ text
542
+ } from "drizzle-orm/sqlite-core";
543
+
544
+ // src/apps/server/domain/environment-state.contract.ts
545
+ var operationStatuses = [
546
+ "queued",
547
+ "running",
548
+ "succeeded",
549
+ "failed",
550
+ "cancelled",
551
+ "outcome_unknown"
552
+ ];
553
+
554
+ // src/apps/server/persistence/environment-state.schema.ts
555
+ var environmentTable = sqliteTable(
556
+ "environment",
557
+ {
558
+ automaticPort: integer("automatic_port"),
559
+ id: text("id").notNull().unique(),
560
+ singleton: integer("singleton").primaryKey()
561
+ },
562
+ (environment) => [
563
+ check(
564
+ "environment_automatic_port_check",
565
+ sql`${environment.automaticPort} BETWEEN 1 AND 65535`
566
+ ),
567
+ check("environment_singleton_check", sql`${environment.singleton} = 1`)
568
+ ]
569
+ ), authorizationMetadataTable = sqliteTable(
570
+ "authorization_metadata",
571
+ {
572
+ createdAt: text("created_at").notNull(),
573
+ id: text("id").primaryKey(),
574
+ label: text("label").notNull(),
575
+ lastSeenAt: text("last_seen_at"),
576
+ revokedAt: text("revoked_at"),
577
+ role: text("role", { enum: environmentAuthorizationRoles }).notNull()
578
+ },
579
+ (authorization) => [
580
+ check(
581
+ "authorization_metadata_role_check",
582
+ sql`${authorization.role} IN (${sql.raw(sqlValues(environmentAuthorizationRoles))})`
583
+ )
584
+ ]
585
+ ), authorizationCapabilityTable = sqliteTable(
586
+ "authorization_capability",
587
+ {
588
+ authorizationId: text("authorization_id").notNull(),
589
+ capability: text("capability", {
590
+ enum: environmentAccessCapabilities
591
+ }).notNull()
592
+ },
593
+ (authorizationCapability) => [
594
+ primaryKey({
595
+ columns: [
596
+ authorizationCapability.authorizationId,
597
+ authorizationCapability.capability
598
+ ]
599
+ }),
600
+ foreignKey({
601
+ columns: [authorizationCapability.authorizationId],
602
+ foreignColumns: [authorizationMetadataTable.id]
603
+ }).onDelete("cascade"),
604
+ check(
605
+ "authorization_capability_value_check",
606
+ sql`${authorizationCapability.capability} IN (${sql.raw(sqlValues(environmentAccessCapabilities))})`
607
+ )
608
+ ]
609
+ ), operationActivityTable = sqliteTable(
610
+ "operation_activity",
611
+ {
612
+ finishedAt: text("finished_at"),
613
+ id: text("id").primaryKey(),
614
+ kind: text("kind").notNull(),
615
+ startedAt: text("started_at").notNull(),
616
+ status: text("status", { enum: operationStatuses }).notNull()
617
+ },
618
+ (operation) => [
619
+ check(
620
+ "operation_activity_status_check",
621
+ sql`${operation.status} IN (${sql.raw(sqlValues(operationStatuses))})`
622
+ ),
623
+ index("operation_activity_started_at").on(
624
+ sql`${operation.startedAt} DESC`,
625
+ sql`${operation.id} DESC`
626
+ )
627
+ ]
628
+ );
629
+ function sqlValues(values) {
630
+ return values.map((value) => `'${value.replaceAll("'", "''")}'`).join(", ");
631
+ }
632
+
633
+ // src/apps/server/features/environment-authorization/environment-authorization.ts
634
+ var pairingLifetimeMilliseconds = 600 * 1e3, ticketLifetimeMilliseconds = 30 * 1e3, authorizationInactivityMilliseconds = 2160 * 60 * 60 * 1e3, retainedMaterialMilliseconds = 1440 * 60 * 1e3;
635
+ function createEnvironmentAuthorization(context, serverSecret, options = {}) {
636
+ let clock = options.clock ?? systemClock, pairings = /* @__PURE__ */ new Map(), tickets = /* @__PURE__ */ new Map();
637
+ return {
638
+ authorize: (credential, capability) => authorizeCredential(context, serverSecret, clock, credential, capability),
639
+ consumeTicket: (ticket) => consumeTicket(context, clock, tickets, ticket),
640
+ createPairing: (pairing) => Effect.sync(() => createPairing(clock, pairings, pairing)),
641
+ exchangePairing: (exchange) => exchangePairing(context, serverSecret, clock, pairings, exchange),
642
+ mintTicket: (credential) => mintTicket(context, serverSecret, clock, tickets, credential),
643
+ revoke: (credential, authorizationId) => revokeAuthorization(
644
+ context,
645
+ serverSecret,
646
+ clock,
647
+ credential,
648
+ authorizationId
649
+ )
650
+ };
651
+ }
652
+ function createPairing(clock, pairings, pairing) {
653
+ let now = clock.now().getTime();
654
+ removeOldMaterial(pairings, now);
655
+ let material = createAvailablePairingCode(pairings), expiresAt = now + pairingLifetimeMilliseconds;
656
+ return pairings.set(digestSecretMaterial(material), {
657
+ capabilities: capabilitiesForRole(pairing.role, pairing.capabilities),
658
+ expiresAt,
659
+ role: pairing.role,
660
+ used: !1
661
+ }), { expiresAt: new Date(expiresAt).toISOString(), material };
662
+ }
663
+ function createAvailablePairingCode(pairings) {
664
+ let code = createPairingCode();
665
+ for (; pairings.has(digestSecretMaterial(code)); )
666
+ code = createPairingCode();
667
+ return code;
668
+ }
669
+ function exchangePairing(context, serverSecret, clock, pairings, exchange) {
670
+ return Effect.gen(function* () {
671
+ let now = clock.now(), pairing = yield* consumePairing(
672
+ pairings,
673
+ exchange.pairingMaterial,
674
+ now
675
+ ), authorization = {
676
+ capabilities: pairing.capabilities,
677
+ id: randomUUID(),
678
+ label: exchange.label,
679
+ role: pairing.role
680
+ };
681
+ return yield* context.write(
682
+ "Could not save device authorization",
683
+ (database) => database.transaction(
684
+ (transaction) => {
685
+ transaction.insert(authorizationMetadataTable).values({
686
+ createdAt: now.toISOString(),
687
+ id: authorization.id,
688
+ label: authorization.label,
689
+ role: authorization.role
690
+ }).run(), authorization.role === "custom" && authorization.capabilities.length > 0 && transaction.insert(authorizationCapabilityTable).values(
691
+ authorization.capabilities.map((capability) => ({
692
+ authorizationId: authorization.id,
693
+ capability
694
+ }))
695
+ ).run();
696
+ },
697
+ { behavior: "immediate" }
698
+ )
699
+ ).pipe(
700
+ Effect.tapError(
701
+ () => Effect.sync(() => {
702
+ pairing.used = !1;
703
+ })
704
+ )
705
+ ), {
706
+ authorization,
707
+ credential: createDeviceCredential(serverSecret, authorization.id)
708
+ };
709
+ });
710
+ }
711
+ function consumePairing(pairings, material, now) {
712
+ let pairing = pairings.get(digestSecretMaterial(material));
713
+ return pairing === void 0 ? failAuthorization({ _tag: "InvalidPairing" }) : pairing.used ? failAuthorization({ _tag: "PairingAlreadyUsed" }) : now.getTime() >= pairing.expiresAt ? failAuthorization({ _tag: "ExpiredPairing" }) : (pairing.used = !0, Effect.succeed(pairing));
714
+ }
715
+ function authorizeCredential(context, serverSecret, clock, credential, capability) {
716
+ let authorizationId = verifyDeviceCredential(serverSecret, credential);
717
+ return authorizationId === void 0 ? failAuthorization({ _tag: "InvalidGrant" }) : authorizeStoredGrant(context, clock, authorizationId, capability);
718
+ }
719
+ function authorizeStoredGrant(context, clock, authorizationId, capability) {
720
+ let now = clock.now();
721
+ return context.write("Could not authenticate device authorization", (database) => {
722
+ let metadata = database.select().from(authorizationMetadataTable).where(eq(authorizationMetadataTable.id, authorizationId)).get();
723
+ if (metadata === void 0)
724
+ return authorizationFailure({ _tag: "InvalidGrant" });
725
+ if (metadata.revokedAt !== null)
726
+ return authorizationFailure({ _tag: "RevokedGrant" });
727
+ let activeSince = new Date(
728
+ metadata.lastSeenAt ?? metadata.createdAt
729
+ ).getTime();
730
+ if (now.getTime() - activeSince >= authorizationInactivityMilliseconds)
731
+ return authorizationFailure({ _tag: "ExpiredGrant" });
732
+ let capabilities = capabilitiesForRole(
733
+ metadata.role,
734
+ metadata.role === "custom" ? database.select({ capability: authorizationCapabilityTable.capability }).from(authorizationCapabilityTable).where(
735
+ eq(
736
+ authorizationCapabilityTable.authorizationId,
737
+ authorizationId
738
+ )
739
+ ).all().map(({ capability: storedCapability }) => storedCapability) : []
740
+ );
741
+ return database.update(authorizationMetadataTable).set({ lastSeenAt: now.toISOString() }).where(eq(authorizationMetadataTable.id, authorizationId)).run(), capabilities.includes(capability) ? authorizationSuccess({
742
+ capabilities,
743
+ id: metadata.id,
744
+ label: metadata.label,
745
+ role: metadata.role
746
+ }) : authorizationFailure({ _tag: "CapabilityDenied", capability });
747
+ }).pipe(
748
+ Effect.flatMap(
749
+ (result) => result.success ? Effect.succeed(result.authorization) : failAuthorization(result.failure)
750
+ )
751
+ );
752
+ }
753
+ function mintTicket(context, serverSecret, clock, tickets, credential) {
754
+ return Effect.gen(function* () {
755
+ let authorization = yield* authorizeCredential(
756
+ context,
757
+ serverSecret,
758
+ clock,
759
+ credential,
760
+ "environment.read"
761
+ ), now = clock.now().getTime();
762
+ removeOldMaterial(tickets, now);
763
+ let ticket = createSecretMaterial(), expiresAt = now + ticketLifetimeMilliseconds;
764
+ return tickets.set(digestSecretMaterial(ticket), {
765
+ authorizationId: authorization.id,
766
+ expiresAt,
767
+ used: !1
768
+ }), { expiresAt: new Date(expiresAt).toISOString(), ticket };
769
+ });
770
+ }
771
+ function consumeTicket(context, clock, tickets, ticket) {
772
+ if (ticket === void 0)
773
+ return failAuthorization({ _tag: "InvalidTicket" });
774
+ let stored = tickets.get(digestSecretMaterial(ticket));
775
+ return stored === void 0 ? failAuthorization({ _tag: "InvalidTicket" }) : stored.used ? failAuthorization({ _tag: "TicketAlreadyUsed" }) : clock.now().getTime() >= stored.expiresAt ? failAuthorization({ _tag: "ExpiredTicket" }) : (stored.used = !0, authorizeStoredGrant(
776
+ context,
777
+ clock,
778
+ stored.authorizationId,
779
+ "environment.read"
780
+ ).pipe(
781
+ Effect.tapError(
782
+ (error) => error._tag === "EnvironmentStorageError" ? Effect.sync(() => {
783
+ stored.used = !1;
784
+ }) : Effect.void
785
+ )
786
+ ));
787
+ }
788
+ function revokeAuthorization(context, serverSecret, clock, credential, authorizationId) {
789
+ return Effect.gen(function* () {
790
+ yield* authorizeCredential(
791
+ context,
792
+ serverSecret,
793
+ clock,
794
+ credential,
795
+ "authorization.manage"
796
+ );
797
+ let revokedAt = clock.now().toISOString();
798
+ return (yield* context.write(
799
+ "Could not revoke device authorization",
800
+ (database) => database.update(authorizationMetadataTable).set({ revokedAt }).where(eq(authorizationMetadataTable.id, authorizationId)).run().changes > 0
801
+ )) ? { authorizationId, revokedAt } : yield* failAuthorization({ _tag: "InvalidGrant" });
802
+ });
803
+ }
804
+ function removeOldMaterial(entries, now) {
805
+ for (let [digest, entry] of entries)
806
+ entry.expiresAt + retainedMaterialMilliseconds <= now && entries.delete(digest);
807
+ }
808
+ function authorizationFailure(failure) {
809
+ return { failure, success: !1 };
810
+ }
811
+ function authorizationSuccess(authorization) {
812
+ return { authorization, success: !0 };
813
+ }
814
+ function failAuthorization(failure) {
815
+ return Effect.fail(new EnvironmentAuthorizationError({ failure }));
816
+ }
817
+ var systemClock = {
818
+ now: () => /* @__PURE__ */ new Date()
819
+ };
820
+
821
+ // src/apps/server/features/environment-connection/events/environment-event-publisher.ts
822
+ function createEnvironmentEventPublisher() {
823
+ let sequence = 0, subscribers = /* @__PURE__ */ new Set();
824
+ return {
825
+ currentSequence: () => sequence,
826
+ publishChanged: () => {
827
+ sequence += 1;
828
+ for (let subscriber of subscribers)
829
+ subscriber(sequence);
830
+ return sequence;
831
+ },
832
+ subscribe: (subscriber) => (subscribers.add(subscriber), () => {
833
+ subscribers.delete(subscriber);
834
+ })
835
+ };
836
+ }
837
+
838
+ // src/apps/server/features/environment-server/environment-state.specifications.ts
839
+ import { eq as eq2, isNull } from "drizzle-orm";
840
+ function isCurrentEnvironment() {
841
+ return eq2(environmentTable.singleton, 1);
842
+ }
843
+ function hasNoAutomaticPort() {
844
+ return isNull(environmentTable.automaticPort);
845
+ }
846
+
847
+ // src/apps/server/features/environment-server/runtime/runtime-marker.ts
848
+ import { randomUUID as randomUUID2 } from "node:crypto";
849
+ import { mkdir as mkdir2, readFile, rename, rm, writeFile } from "node:fs/promises";
850
+ import { dirname } from "node:path";
851
+ import { Effect as Effect3 } from "effect";
852
+
853
+ // src/apps/server/error-inspection.ts
854
+ function isFileSystemError(error) {
855
+ return error instanceof Error && "code" in error;
856
+ }
857
+ function errorMessage(error) {
858
+ return error instanceof Error ? error.message : String(error);
859
+ }
860
+
861
+ // src/apps/server/features/environment-server/runtime/runtime-errors.contract.ts
862
+ import { Data as Data2 } from "effect";
863
+ var RuntimeMarkerError = class extends Data2.TaggedError("RuntimeMarkerError") {
864
+ }, RuntimeRequirementsError = class extends Data2.TaggedError(
865
+ "RuntimeRequirementsError"
866
+ ) {
867
+ };
868
+
869
+ // src/apps/server/persistence/storage/environment-paths.ts
870
+ import { chmod, mkdir } from "node:fs/promises";
871
+ import { homedir } from "node:os";
872
+ import { join } from "node:path";
873
+ import { Effect as Effect2 } from "effect";
874
+
875
+ // src/apps/server/persistence/storage/storage-error.contract.ts
876
+ import { Data as Data3 } from "effect";
877
+ var EnvironmentStorageError = class extends Data3.TaggedError(
878
+ "EnvironmentStorageError"
879
+ ) {
880
+ };
881
+
882
+ // src/apps/server/persistence/storage/environment-paths.ts
883
+ function defaultEnvironmentPaths() {
884
+ return environmentPaths(join(homedir(), ".rebase"));
885
+ }
886
+ function environmentPaths(root) {
887
+ let cacheDirectory = join(root, "cache"), runtimeDirectory = join(root, "runtime"), secretsDirectory = join(root, "secrets"), settingsDirectory = join(root, "settings"), stateDirectory = join(root, "state");
888
+ return {
889
+ cacheDirectory,
890
+ root,
891
+ runtimeDirectory,
892
+ runtimeMarker: join(runtimeDirectory, "runtime.json"),
893
+ secretsDirectory,
894
+ serverSecret: join(secretsDirectory, "server.key"),
895
+ settingsDirectory,
896
+ stateDatabase: join(stateDirectory, "state.sqlite"),
897
+ stateDirectory
898
+ };
899
+ }
900
+ function prepareEnvironmentDirectories(paths) {
901
+ let directories = [
902
+ paths.root,
903
+ paths.stateDirectory,
904
+ paths.runtimeDirectory,
905
+ paths.settingsDirectory,
906
+ paths.secretsDirectory,
907
+ paths.cacheDirectory
908
+ ];
909
+ return Effect2.tryPromise({
910
+ try: async () => {
911
+ for (let directory of directories)
912
+ await mkdir(directory, { mode: 448, recursive: !0 }), process.platform !== "win32" && await chmod(directory, 448);
913
+ },
914
+ catch: (cause) => new EnvironmentStorageError({
915
+ cause,
916
+ message: `Could not prepare Environment storage: ${errorMessage(cause)}`
917
+ })
918
+ });
919
+ }
920
+
921
+ // src/apps/server/features/environment-server/runtime/runtime-marker.ts
922
+ function defaultRuntimePath() {
923
+ return defaultEnvironmentPaths().runtimeMarker;
924
+ }
925
+ function acquireRuntimeMarker(marker, runtimePath = defaultRuntimePath()) {
926
+ return Effect3.acquireRelease(
927
+ writeRuntimeMarker(runtimePath, marker),
928
+ () => removeRuntimeMarker(runtimePath, marker.pid).pipe(
929
+ Effect3.catchTag("RuntimeMarkerError", reportCleanupFailure)
930
+ )
931
+ ).pipe(Effect3.asVoid);
932
+ }
933
+ function writeRuntimeMarker(runtimePath, marker) {
934
+ let temporaryPath = `${runtimePath}.${process.pid}.${randomUUID2()}.tmp`;
935
+ return Effect3.gen(function* () {
936
+ yield* fileSystemOperation(
937
+ () => mkdir2(dirname(runtimePath), { recursive: !0 })
938
+ ), yield* fileSystemOperation(
939
+ () => writeFile(temporaryPath, `${JSON.stringify(marker)}
940
+ `, { mode: 384 })
941
+ ), yield* replaceFile(temporaryPath, runtimePath);
942
+ }).pipe(
943
+ Effect3.ensuring(
944
+ fileSystemOperation(() => rm(temporaryPath, { force: !0 })).pipe(
945
+ Effect3.orDie
946
+ )
947
+ )
948
+ );
949
+ }
950
+ function removeRuntimeMarker(runtimePath, pid) {
951
+ return Effect3.gen(function* () {
952
+ let contents = yield* fileSystemOperation(
953
+ () => readFile(runtimePath, "utf8")
954
+ );
955
+ (yield* Effect3.try({
956
+ try: () => JSON.parse(contents),
957
+ catch: runtimeMarkerError
958
+ })).pid === pid && (yield* fileSystemOperation(() => rm(runtimePath)));
959
+ }).pipe(
960
+ Effect3.catchTag(
961
+ "RuntimeMarkerError",
962
+ (error) => isMissingFile(error.cause) ? Effect3.void : Effect3.fail(error)
963
+ )
964
+ );
965
+ }
966
+ function replaceFile(source, destination) {
967
+ return fileSystemOperation(() => rename(source, destination)).pipe(
968
+ Effect3.catchTag("RuntimeMarkerError", (error) => isReplaceConflict(error.cause) ? Effect3.gen(function* () {
969
+ yield* fileSystemOperation(() => rm(destination, { force: !0 })), yield* fileSystemOperation(() => rename(source, destination));
970
+ }) : Effect3.fail(error))
971
+ );
972
+ }
973
+ function fileSystemOperation(operation) {
974
+ return Effect3.tryPromise({
975
+ try: operation,
976
+ catch: runtimeMarkerError
977
+ });
978
+ }
979
+ function runtimeMarkerError(cause) {
980
+ return new RuntimeMarkerError({
981
+ cause,
982
+ message: `Could not update the runtime marker: ${errorMessage(cause)}`
983
+ });
984
+ }
985
+ function reportCleanupFailure(error) {
986
+ return Effect3.sync(() => {
987
+ process.stderr.write(`${error.message}
988
+ `);
989
+ });
990
+ }
991
+ function isMissingFile(error) {
992
+ return isFileSystemError(error) && error.code === "ENOENT";
993
+ }
994
+ function isReplaceConflict(error) {
995
+ return isFileSystemError(error) && (error.code === "EEXIST" || error.code === "EPERM");
996
+ }
997
+
998
+ // src/apps/server/features/environment-server/runtime/runtime-requirements.ts
999
+ import { execFile } from "node:child_process";
1000
+ import { Effect as Effect4 } from "effect";
1001
+ var requiredNodeMajor = 24, minimumGitVersion = [2, 34, 0], verifyRuntimeRequirements = Effect4.gen(function* () {
1002
+ yield* captureRequirement(
1003
+ () => assertSupportedNodeVersion(process.versions.node)
1004
+ );
1005
+ let gitVersion = yield* readGitVersion();
1006
+ yield* captureRequirement(() => assertSupportedGitVersion(gitVersion));
1007
+ });
1008
+ function assertSupportedNodeVersion(version) {
1009
+ let [major] = parseVersion(version, "Node");
1010
+ if (major !== requiredNodeMajor)
1011
+ throw new Error(`Node 24 is required. Found Node ${version}.`);
1012
+ }
1013
+ function assertSupportedGitVersion(version) {
1014
+ let parsed = parseVersion(version, "Git");
1015
+ if (compareVersions(parsed, minimumGitVersion) < 0)
1016
+ throw new Error(`Git 2.34 or newer is required. Found Git ${version}.`);
1017
+ }
1018
+ function parseGitVersion(output) {
1019
+ let match = output.match(/^git version (\d+\.\d+\.\d+)/i);
1020
+ if (!match?.[1])
1021
+ throw new Error(
1022
+ `Could not determine the Git version from "${output.trim()}".`
1023
+ );
1024
+ return match[1];
1025
+ }
1026
+ function readGitVersion() {
1027
+ return Effect4.callback((resume, signal) => {
1028
+ execFile(
1029
+ "git",
1030
+ ["--version"],
1031
+ { encoding: "utf8", signal, timeout: 5e3 },
1032
+ (error, stdout) => {
1033
+ if (!error) {
1034
+ resume(captureRequirement(() => parseGitVersion(stdout)));
1035
+ return;
1036
+ }
1037
+ let message = error.code === "ENOENT" ? "Git 2.34 or newer is required, but Git was not found." : `Could not run Git: ${error.message}`;
1038
+ resume(
1039
+ Effect4.fail(new RuntimeRequirementsError({ cause: error, message }))
1040
+ );
1041
+ }
1042
+ );
1043
+ });
1044
+ }
1045
+ function captureRequirement(check2) {
1046
+ return Effect4.try({
1047
+ try: check2,
1048
+ catch: (cause) => new RuntimeRequirementsError({
1049
+ cause,
1050
+ message: cause instanceof Error ? cause.message : String(cause)
1051
+ })
1052
+ });
1053
+ }
1054
+ function parseVersion(version, product) {
1055
+ let match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
1056
+ if (!match?.[1] || !match[2] || !match[3])
1057
+ throw new Error(`Could not parse ${product} version "${version}".`);
1058
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
1059
+ }
1060
+ function compareVersions(left, right) {
1061
+ for (let index2 of [0, 1, 2]) {
1062
+ let difference = left[index2] - right[index2];
1063
+ if (difference !== 0) return difference;
1064
+ }
1065
+ return 0;
1066
+ }
1067
+
1068
+ // src/apps/server/features/environment-server/server/environment-listener.ts
1069
+ import { createServer } from "node:http";
1070
+ import { Effect as Effect14, FiberSet as FiberSet2 } from "effect";
1071
+
1072
+ // src/apps/server/features/environment-connection/http/environment-http-handler.ts
1073
+ import { Effect as Effect9 } from "effect";
1074
+
1075
+ // src/apps/server/features/browser-client/browser-assets.ts
1076
+ import { readFile as readFile2 } from "node:fs/promises";
1077
+ import { isAbsolute, relative, resolve } from "node:path";
1078
+ import { Effect as Effect5 } from "effect";
1079
+ var contentTypes = {
1080
+ ".css": "text/css; charset=utf-8",
1081
+ ".html": "text/html; charset=utf-8",
1082
+ ".js": "text/javascript; charset=utf-8",
1083
+ ".json": "application/json; charset=utf-8",
1084
+ ".svg": "image/svg+xml"
1085
+ };
1086
+ function respondWithBrowserAsset(request, response, assetsRoot) {
1087
+ let asset = resolveBrowserAsset(request.url, assetsRoot);
1088
+ return asset === void 0 ? Effect5.succeed(!1) : request.method !== "GET" && request.method !== "HEAD" ? (response.writeHead(405, { allow: "GET, HEAD" }).end(), Effect5.succeed(!0)) : Effect5.promise(async () => {
1089
+ try {
1090
+ let content = await readFile2(asset.path);
1091
+ response.writeHead(
1092
+ 200,
1093
+ browserAssetHeaders(asset.extension, asset.cache)
1094
+ ), response.end(request.method === "HEAD" ? void 0 : content);
1095
+ } catch (error) {
1096
+ let status = isMissingAsset(error) ? 404 : 500;
1097
+ response.writeHead(status).end();
1098
+ }
1099
+ return !0;
1100
+ });
1101
+ }
1102
+ function resolveBrowserAsset(url, assetsRoot) {
1103
+ let pathname;
1104
+ try {
1105
+ pathname = decodeURIComponent(
1106
+ new URL(url ?? "/", "http://127.0.0.1").pathname
1107
+ );
1108
+ } catch {
1109
+ return;
1110
+ }
1111
+ let relativePath = browserAssetPath(pathname);
1112
+ if (relativePath === void 0)
1113
+ return;
1114
+ let path = resolve(assetsRoot, relativePath), pathFromRoot = relative(resolve(assetsRoot), path);
1115
+ if (pathFromRoot.startsWith("..") || isAbsolute(pathFromRoot))
1116
+ return;
1117
+ let extensionIndex = relativePath.lastIndexOf(".");
1118
+ return {
1119
+ cache: relativePath.startsWith("assets/"),
1120
+ extension: extensionIndex === -1 ? "" : relativePath.slice(extensionIndex),
1121
+ path
1122
+ };
1123
+ }
1124
+ function browserAssetPath(pathname) {
1125
+ if (pathname === "/" || pathname === "/pair" || pathname === "/pair/")
1126
+ return "index.html";
1127
+ if (pathname === "/favicon.svg" || pathname.startsWith("/assets/") && pathname !== "/assets/")
1128
+ return pathname.slice(1);
1129
+ }
1130
+ function browserAssetHeaders(extension, cache) {
1131
+ return {
1132
+ "cache-control": cache ? "public, max-age=31536000, immutable" : "no-store",
1133
+ "content-security-policy": "default-src 'self'; base-uri 'none'; connect-src 'self' ws://127.0.0.1:*; form-action 'none'; frame-ancestors 'none'; img-src 'self' data:; object-src 'none'; script-src 'self'; style-src 'self'",
1134
+ "content-type": contentTypes[extension] ?? "application/octet-stream",
1135
+ "x-content-type-options": "nosniff"
1136
+ };
1137
+ }
1138
+ function isMissingAsset(error) {
1139
+ return error !== null && typeof error == "object" && "code" in error && (error.code === "ENOENT" || error.code === "EISDIR");
1140
+ }
1141
+
1142
+ // src/apps/server/features/environment-authorization/http/environment-authorization-http-handler.ts
1143
+ import { Effect as Effect7, Schema as Schema7 } from "effect";
1144
+
1145
+ // src/apps/server/features/environment-connection/environment-request-authorization.ts
1146
+ import { Effect as Effect6 } from "effect";
1147
+ function validateRequestHost(request) {
1148
+ let port = request.socket.localPort, expectedHost = port === void 0 ? void 0 : `127.0.0.1:${port}`;
1149
+ return request.headers.host === expectedHost ? Effect6.void : failAuthorization2({ _tag: "InvalidHost" });
1150
+ }
1151
+ function validateRequestOrigin(request, required) {
1152
+ let origin = request.headers.origin;
1153
+ return origin === void 0 && !required ? Effect6.void : origin === expectedRequestOrigin(request) ? Effect6.void : failAuthorization2({ _tag: "InvalidOrigin" });
1154
+ }
1155
+ function expectedRequestOrigin(request) {
1156
+ return `http://127.0.0.1:${request.socket.localPort ?? 0}`;
1157
+ }
1158
+ function readBearerCredential(request) {
1159
+ let authorization = request.headers.authorization;
1160
+ return authorization?.startsWith("Bearer ") ? authorization.slice(7) : void 0;
1161
+ }
1162
+ function authorizationFailureStatus(failure) {
1163
+ switch (failure._tag) {
1164
+ case "InvalidHost":
1165
+ case "InvalidOrigin":
1166
+ case "CapabilityDenied":
1167
+ return 403;
1168
+ case "InvalidGrant":
1169
+ case "RevokedGrant":
1170
+ case "InvalidPairing":
1171
+ case "InvalidTicket":
1172
+ return 401;
1173
+ case "ExpiredGrant":
1174
+ case "ExpiredPairing":
1175
+ case "ExpiredTicket":
1176
+ return 410;
1177
+ case "PairingAlreadyUsed":
1178
+ case "TicketAlreadyUsed":
1179
+ return 409;
1180
+ }
1181
+ }
1182
+ function failAuthorization2(failure) {
1183
+ return Effect6.fail(new EnvironmentAuthorizationError({ failure }));
1184
+ }
1185
+
1186
+ // src/apps/server/features/environment-connection/http/environment-http-request-body.contract.ts
1187
+ import { Data as Data4 } from "effect";
1188
+ var EnvironmentHttpBodyError = class extends Data4.TaggedError(
1189
+ "EnvironmentHttpBodyError"
1190
+ ) {
1191
+ };
1192
+
1193
+ // src/apps/server/features/environment-connection/http/environment-http-response.ts
1194
+ import { Schema as Schema6 } from "effect";
1195
+ function writeJson(response, status, schema, value) {
1196
+ writeJsonValue(response, status, Schema6.encodeSync(schema)(value));
1197
+ }
1198
+ function writeJsonValue(response, status, value) {
1199
+ response.writeHead(status, {
1200
+ "cache-control": "no-store",
1201
+ "content-type": "application/json; charset=utf-8"
1202
+ }), response.end(JSON.stringify(value));
1203
+ }
1204
+
1205
+ // src/apps/server/features/environment-authorization/http/environment-authorization-http-handler.ts
1206
+ function respondToEnvironmentAuthorizationRequest(request, response, body, authorization) {
1207
+ return Effect7.gen(function* () {
1208
+ if (request.url === EnvironmentAuthorizationHttpApi.exchangePairing.path) {
1209
+ yield* requireMethod(
1210
+ request,
1211
+ response,
1212
+ EnvironmentAuthorizationHttpApi.exchangePairing.method
1213
+ ), yield* validateRequestOrigin(request, !1);
1214
+ let result = yield* authorization.exchangePairing(
1215
+ yield* decodeRequestBody(ExchangeEnvironmentPairing, body)
1216
+ );
1217
+ return writeJson(
1218
+ response,
1219
+ EnvironmentAuthorizationHttpApi.exchangePairing.successStatus,
1220
+ EnvironmentPairingExchanged,
1221
+ result
1222
+ ), !0;
1223
+ }
1224
+ if (request.url === EnvironmentAuthorizationHttpApi.createPairing.path) {
1225
+ yield* requireMethod(
1226
+ request,
1227
+ response,
1228
+ EnvironmentAuthorizationHttpApi.createPairing.method
1229
+ ), yield* validateRequestOrigin(request, !1), yield* authorization.authorize(
1230
+ readBearerCredential(request),
1231
+ "authorization.manage"
1232
+ );
1233
+ let pairing = yield* authorization.createPairing(
1234
+ yield* decodeRequestBody(CreateEnvironmentPairing, body)
1235
+ );
1236
+ return writeJson(
1237
+ response,
1238
+ EnvironmentAuthorizationHttpApi.createPairing.successStatus,
1239
+ EnvironmentPairingCreated,
1240
+ {
1241
+ expiresAt: pairing.expiresAt,
1242
+ pairingUrl: `${expectedRequestOrigin(request)}/pair#${pairing.material}`
1243
+ }
1244
+ ), !0;
1245
+ }
1246
+ if (request.url === EnvironmentAuthorizationHttpApi.mintWebSocketTicket.path) {
1247
+ yield* requireMethod(
1248
+ request,
1249
+ response,
1250
+ EnvironmentAuthorizationHttpApi.mintWebSocketTicket.method
1251
+ ), yield* validateRequestOrigin(request, !1), yield* requireEmptyBody(body);
1252
+ let ticket = yield* authorization.mintTicket(
1253
+ readBearerCredential(request)
1254
+ );
1255
+ return writeJson(
1256
+ response,
1257
+ EnvironmentAuthorizationHttpApi.mintWebSocketTicket.successStatus,
1258
+ EnvironmentWebSocketTicket,
1259
+ ticket
1260
+ ), !0;
1261
+ }
1262
+ if (request.url === EnvironmentAuthorizationHttpApi.revokeAuthorization.path) {
1263
+ yield* requireMethod(
1264
+ request,
1265
+ response,
1266
+ EnvironmentAuthorizationHttpApi.revokeAuthorization.method
1267
+ ), yield* validateRequestOrigin(request, !1);
1268
+ let revocation = yield* decodeRequestBody(
1269
+ RevokeEnvironmentAuthorization,
1270
+ body
1271
+ ), revoked = yield* authorization.revoke(
1272
+ readBearerCredential(request),
1273
+ revocation.authorizationId
1274
+ );
1275
+ return writeJson(
1276
+ response,
1277
+ EnvironmentAuthorizationHttpApi.revokeAuthorization.successStatus,
1278
+ EnvironmentAuthorizationRevoked,
1279
+ revoked
1280
+ ), !0;
1281
+ }
1282
+ return !1;
1283
+ });
1284
+ }
1285
+ function requireMethod(request, response, method) {
1286
+ return request.method === method ? Effect7.void : Effect7.sync(
1287
+ () => response.writeHead(405, { allow: method }).end()
1288
+ ).pipe(Effect7.andThen(Effect7.interrupt));
1289
+ }
1290
+ function requireEmptyBody(body) {
1291
+ return body.byteLength === 0 ? Effect7.void : Effect7.fail(
1292
+ new EnvironmentHttpBodyError({
1293
+ failure: { _tag: "InvalidMessage" }
1294
+ })
1295
+ );
1296
+ }
1297
+ function decodeRequestBody(schema, body) {
1298
+ return Effect7.try({
1299
+ try: () => Schema7.decodeUnknownSync(schema)(JSON.parse(body.toString("utf8")), {
1300
+ onExcessProperty: "error"
1301
+ }),
1302
+ catch: () => new EnvironmentHttpBodyError({ failure: { _tag: "InvalidMessage" } })
1303
+ });
1304
+ }
1305
+
1306
+ // src/apps/server/features/environment-connection/http/environment-http-request-body.ts
1307
+ import { Effect as Effect8 } from "effect";
1308
+ var maximumRequestBytes = currentTransportLimits.maxHttpRequestBytes;
1309
+ function readEnvironmentHttpRequestBody(request) {
1310
+ return Effect8.callback((resume) => {
1311
+ let state = {
1312
+ chunks: [],
1313
+ receivedBytes: 0
1314
+ }, finish = finishBodyRead(resume), handlers = createBodyHandlers(request, state, finish);
1315
+ if (declaredBodyLength(request) > maximumRequestBytes) {
1316
+ rejectPayloadTooLarge(request, finish);
1317
+ return;
1318
+ }
1319
+ return attachBodyHandlers(request, handlers), Effect8.sync(() => detachBodyHandlers(request, handlers));
1320
+ });
1321
+ }
1322
+ function createBodyHandlers(request, state, finish) {
1323
+ return {
1324
+ end: () => completeBodyRead(state, finish),
1325
+ receive: (chunk) => receiveBodyChunk(
1326
+ state,
1327
+ chunk,
1328
+ () => rejectPayloadTooLarge(request, finish)
1329
+ ),
1330
+ rejectInvalidMessage: () => {
1331
+ finish(Effect8.fail(invalidMessageFailure()));
1332
+ }
1333
+ };
1334
+ }
1335
+ function finishBodyRead(resume) {
1336
+ let settled = !1;
1337
+ return (effect) => {
1338
+ settled || (settled = !0, resume(effect));
1339
+ };
1340
+ }
1341
+ function rejectPayloadTooLarge(request, finish) {
1342
+ finish(Effect8.fail(payloadTooLargeFailure())), request.resume();
1343
+ }
1344
+ function receiveBodyChunk(state, chunk, rejectPayload) {
1345
+ if (state.receivedBytes += chunk.byteLength, state.receivedBytes > maximumRequestBytes) {
1346
+ rejectPayload();
1347
+ return;
1348
+ }
1349
+ state.chunks.push(chunk);
1350
+ }
1351
+ function completeBodyRead(state, finish) {
1352
+ finish(Effect8.succeed(Buffer.concat(state.chunks, state.receivedBytes)));
1353
+ }
1354
+ function attachBodyHandlers(request, handlers) {
1355
+ request.on("data", handlers.receive), request.on("end", handlers.end), request.on("aborted", handlers.rejectInvalidMessage), request.on("error", handlers.rejectInvalidMessage);
1356
+ }
1357
+ function detachBodyHandlers(request, handlers) {
1358
+ request.off("data", handlers.receive), request.off("end", handlers.end), request.off("aborted", handlers.rejectInvalidMessage), request.off("error", handlers.rejectInvalidMessage);
1359
+ }
1360
+ function declaredBodyLength(request) {
1361
+ return Number(request.headers["content-length"] ?? 0);
1362
+ }
1363
+ function invalidMessageFailure() {
1364
+ return new EnvironmentHttpBodyError({
1365
+ failure: { _tag: "InvalidMessage" }
1366
+ });
1367
+ }
1368
+ function payloadTooLargeFailure() {
1369
+ return new EnvironmentHttpBodyError({
1370
+ failure: {
1371
+ _tag: "PayloadTooLarge",
1372
+ limitBytes: maximumRequestBytes
1373
+ }
1374
+ });
1375
+ }
1376
+
1377
+ // src/apps/server/features/environment-connection/http/environment-http-handler.ts
1378
+ function createEnvironmentHttpHandler(state, authorization, ready, runEnvironmentEffect, browserAssetsRoot) {
1379
+ return (request, response) => {
1380
+ let lifetime = startHttpRequestLifetime(request, response);
1381
+ runEnvironmentEffect(
1382
+ createEnvironmentHttpResponse(
1383
+ request,
1384
+ response,
1385
+ state,
1386
+ authorization,
1387
+ ready(),
1388
+ browserAssetsRoot
1389
+ ).pipe(Effect9.ensuring(Effect9.sync(lifetime.release))),
1390
+ lifetime.signal
1391
+ );
1392
+ };
1393
+ }
1394
+ function startHttpRequestLifetime(request, response) {
1395
+ let abortController = new AbortController(), abort = () => abortController.abort();
1396
+ return request.once("aborted", abort), response.once("close", abort), {
1397
+ release: () => {
1398
+ request.off("aborted", abort), response.off("close", abort);
1399
+ },
1400
+ signal: abortController.signal
1401
+ };
1402
+ }
1403
+ function createEnvironmentHttpResponse(request, response, state, authorization, ready, browserAssetsRoot) {
1404
+ return respondToEnvironmentRequest(
1405
+ request,
1406
+ response,
1407
+ state,
1408
+ authorization,
1409
+ ready,
1410
+ browserAssetsRoot
1411
+ ).pipe(
1412
+ Effect9.catch(
1413
+ (error) => Effect9.sync(() => writeEnvironmentHttpError(response, error))
1414
+ )
1415
+ );
1416
+ }
1417
+ function respondToEnvironmentRequest(request, response, state, authorization, ready, browserAssetsRoot) {
1418
+ return Effect9.gen(function* () {
1419
+ if (request.url === "/health") {
1420
+ yield* requireMethod2(request, response, "GET"), yield* requireEmptyBody2(yield* readEnvironmentHttpRequestBody(request)), writeJsonValue(response, ready ? 200 : 503, {
1421
+ status: ready ? "ready" : "starting"
1422
+ });
1423
+ return;
1424
+ }
1425
+ if (yield* validateRequestHost(request), browserAssetsRoot !== void 0 && (yield* respondWithBrowserAsset(request, response, browserAssetsRoot)))
1426
+ return;
1427
+ let body = yield* readEnvironmentHttpRequestBody(request);
1428
+ if (request.url === EnvironmentHttpApi.discovery.path) {
1429
+ yield* requireMethod2(
1430
+ request,
1431
+ response,
1432
+ EnvironmentHttpApi.discovery.method
1433
+ ), yield* requireEmptyBody2(body), writeJson(
1434
+ response,
1435
+ EnvironmentHttpApi.discovery.successStatus,
1436
+ EnvironmentDiscovery,
1437
+ state.discovery
1438
+ );
1439
+ return;
1440
+ }
1441
+ if (request.url === EnvironmentHttpApi.snapshot.path) {
1442
+ yield* requireMethod2(
1443
+ request,
1444
+ response,
1445
+ EnvironmentHttpApi.snapshot.method
1446
+ ), yield* requireEmptyBody2(body), yield* validateRequestOrigin(request, !1), yield* authorization.authorize(
1447
+ readBearerCredential(request),
1448
+ "environment.read"
1449
+ ), writeJson(
1450
+ response,
1451
+ EnvironmentHttpApi.snapshot.successStatus,
1452
+ EnvironmentSnapshot,
1453
+ {
1454
+ environmentId: state.discovery.environmentId,
1455
+ sequence: state.events.currentSequence()
1456
+ }
1457
+ );
1458
+ return;
1459
+ }
1460
+ (yield* respondToEnvironmentAuthorizationRequest(
1461
+ request,
1462
+ response,
1463
+ body,
1464
+ authorization
1465
+ )) || response.writeHead(404).end();
1466
+ });
1467
+ }
1468
+ function requireMethod2(request, response, method) {
1469
+ return request.method === method ? Effect9.void : Effect9.sync(
1470
+ () => response.writeHead(405, { allow: method }).end()
1471
+ ).pipe(Effect9.andThen(Effect9.interrupt));
1472
+ }
1473
+ function requireEmptyBody2(body) {
1474
+ return body.byteLength === 0 ? Effect9.void : Effect9.fail(
1475
+ new EnvironmentHttpBodyError({
1476
+ failure: { _tag: "InvalidMessage" }
1477
+ })
1478
+ );
1479
+ }
1480
+ function writeEnvironmentHttpError(response, error) {
1481
+ if (!response.writableEnded) {
1482
+ if (error._tag === "EnvironmentAuthorizationError") {
1483
+ writeJson(
1484
+ response,
1485
+ authorizationFailureStatus(error.failure),
1486
+ EnvironmentAuthorizationFailure,
1487
+ error.failure
1488
+ );
1489
+ return;
1490
+ }
1491
+ if (error._tag === "EnvironmentHttpBodyError") {
1492
+ writeJson(
1493
+ response,
1494
+ error.failure._tag === "PayloadTooLarge" ? 413 : 400,
1495
+ EnvironmentHttpFailure,
1496
+ error.failure
1497
+ );
1498
+ return;
1499
+ }
1500
+ response.writeHead(500).end();
1501
+ }
1502
+ }
1503
+
1504
+ // src/apps/server/features/environment-connection/websocket/environment-websocket-server.ts
1505
+ import { Cause as Cause2, Effect as Effect13 } from "effect";
1506
+ import { WebSocketServer } from "ws";
1507
+
1508
+ // src/apps/server/features/environment-connection/websocket/environment-websocket-session.ts
1509
+ import { Effect as Effect12, FiberSet, Option, Queue as Queue2, Schema as Schema9 } from "effect";
1510
+ import { WebSocket as WebSocketState } from "ws";
1511
+
1512
+ // src/apps/server/features/environment-connection/websocket/environment-websocket-error.contract.ts
1513
+ import { Data as Data5 } from "effect";
1514
+ var EnvironmentWebSocketSessionClosed = class extends Data5.TaggedError(
1515
+ "EnvironmentWebSocketSessionClosed"
1516
+ ) {
1517
+ }, EnvironmentWebSocketSessionRejected = class extends Data5.TaggedError(
1518
+ "EnvironmentWebSocketSessionRejected"
1519
+ ) {
1520
+ }, EnvironmentWebSocketWriteError = class extends Data5.TaggedError(
1521
+ "EnvironmentWebSocketWriteError"
1522
+ ) {
1523
+ };
1524
+
1525
+ // src/apps/server/features/environment-connection/websocket/environment-websocket-inbox.ts
1526
+ import { Cause, Effect as Effect10, Queue } from "effect";
1527
+ function acquireEnvironmentWebSocketInbox(socket, state) {
1528
+ return Effect10.gen(function* () {
1529
+ let messages = yield* Queue.bounded(state.discovery.limits.maxQueuedEvents), handlers = createInboxHandlers(socket, messages);
1530
+ return yield* Effect10.acquireRelease(
1531
+ Effect10.sync(() => attachInboxHandlers(socket, handlers)),
1532
+ () => Effect10.sync(() => detachInboxHandlers(socket, handlers)).pipe(
1533
+ Effect10.andThen(Queue.shutdown(messages))
1534
+ )
1535
+ ), messages;
1536
+ });
1537
+ }
1538
+ function createInboxHandlers(socket, messages) {
1539
+ let close = () => {
1540
+ Queue.failCauseUnsafe(
1541
+ messages,
1542
+ Cause.fail(new EnvironmentWebSocketSessionClosed())
1543
+ );
1544
+ };
1545
+ return {
1546
+ close,
1547
+ receive: (data, isBinary) => {
1548
+ Queue.offerUnsafe(messages, { data, isBinary }) || (socket.close(1013, "IncomingQueueOverflow"), close());
1549
+ }
1550
+ };
1551
+ }
1552
+ function attachInboxHandlers(socket, handlers) {
1553
+ socket.on("message", handlers.receive), socket.on("close", handlers.close), socket.on("error", handlers.close);
1554
+ }
1555
+ function detachInboxHandlers(socket, handlers) {
1556
+ socket.off("message", handlers.receive), socket.off("close", handlers.close), socket.off("error", handlers.close);
1557
+ }
1558
+
1559
+ // src/apps/server/features/environment-connection/websocket/environment-websocket-writer.ts
1560
+ import { Effect as Effect11, Ref, Schema as Schema8, Semaphore } from "effect";
1561
+ import { WebSocket } from "ws";
1562
+
1563
+ // src/apps/server/features/environment-connection/websocket/outgoing-message-queue.ts
1564
+ function createOutgoingMessageQueue() {
1565
+ return { messages: [], overflowed: !1, queuedBytes: 0 };
1566
+ }
1567
+ function enqueueOutgoingMessage(queue, value, overflowMessage, limits) {
1568
+ if (queue.overflowed)
1569
+ return queue;
1570
+ let message = queuedMessage(value), overCount = queue.messages.length + 1 > limits.maxQueuedEvents, overBytes = queue.queuedBytes + message.byteLength > limits.maxQueuedEventBytes;
1571
+ if (overCount || overBytes) {
1572
+ let overflow = queuedMessage(overflowMessage);
1573
+ return {
1574
+ messages: [overflow],
1575
+ overflowed: !0,
1576
+ queuedBytes: overflow.byteLength
1577
+ };
1578
+ }
1579
+ return {
1580
+ messages: [...queue.messages, message],
1581
+ overflowed: !1,
1582
+ queuedBytes: queue.queuedBytes + message.byteLength
1583
+ };
1584
+ }
1585
+ function dequeueOutgoingMessage(queue) {
1586
+ let [message, ...remaining] = queue.messages;
1587
+ return message === void 0 ? { message: void 0, queue } : {
1588
+ message: message.value,
1589
+ queue: {
1590
+ messages: remaining,
1591
+ overflowed: queue.overflowed,
1592
+ queuedBytes: queue.queuedBytes - message.byteLength
1593
+ }
1594
+ };
1595
+ }
1596
+ function resetOutgoingMessageQueue() {
1597
+ return createOutgoingMessageQueue();
1598
+ }
1599
+ function replaceWithResnapshotMessage(queue, value) {
1600
+ let message = queuedMessage(value);
1601
+ return {
1602
+ messages: [message],
1603
+ overflowed: queue.overflowed,
1604
+ queuedBytes: message.byteLength
1605
+ };
1606
+ }
1607
+ function queuedMessage(value) {
1608
+ return { byteLength: Buffer.byteLength(value), value };
1609
+ }
1610
+
1611
+ // src/apps/server/features/environment-connection/websocket/environment-websocket-writer.ts
1612
+ function createEnvironmentWebSocketWriter(socket, state) {
1613
+ return Effect11.gen(function* () {
1614
+ let writerState = yield* Ref.make({
1615
+ limits: state.discovery.limits,
1616
+ queue: createOutgoingMessageQueue(),
1617
+ requiredSnapshotSequence: void 0,
1618
+ supportsResnapshot: !1
1619
+ }), sendPermit = yield* Semaphore.make(1), flush = flushOutgoingMessages(socket, writerState, sendPermit), enqueue = (message) => enqueueServerMessage(writerState, state, message);
1620
+ return {
1621
+ acknowledgeSnapshot: (sequence) => acknowledgeSnapshot(writerState, state, sendPermit, sequence),
1622
+ enqueue,
1623
+ flush,
1624
+ send: (message) => sendServerMessage(enqueue, flush, message),
1625
+ setNegotiatedContract: (limits, supportsResnapshot) => setNegotiatedContract(writerState, limits, supportsResnapshot)
1626
+ };
1627
+ });
1628
+ }
1629
+ function flushOutgoingMessages(socket, writerState, sendPermit) {
1630
+ return sendPermit.withPermit(
1631
+ Effect11.gen(function* () {
1632
+ for (; ; ) {
1633
+ if (socket.readyState !== WebSocket.OPEN)
1634
+ return yield* Effect11.fail(
1635
+ new EnvironmentWebSocketWriteError({
1636
+ closeCode: 1011,
1637
+ reason: "WebSocketNotOpen"
1638
+ })
1639
+ );
1640
+ let next = yield* dequeueServerMessage(writerState);
1641
+ if (next === void 0)
1642
+ return;
1643
+ yield* sendWebSocketMessage(socket, next);
1644
+ }
1645
+ })
1646
+ );
1647
+ }
1648
+ function dequeueServerMessage(writerState) {
1649
+ return Ref.modify(writerState, (current) => {
1650
+ let dequeued = dequeueOutgoingMessage(current.queue);
1651
+ return [dequeued.message, { ...current, queue: dequeued.queue }];
1652
+ });
1653
+ }
1654
+ function enqueueServerMessage(writerState, transportState, message) {
1655
+ return Effect11.gen(function* () {
1656
+ let result = yield* Ref.modify(
1657
+ writerState,
1658
+ (current) => enqueueMessage(current, message, transportState.events.currentSequence())
1659
+ );
1660
+ return result._tag === "Rejected" ? yield* result.error : result._tag === "Enqueued";
1661
+ });
1662
+ }
1663
+ function sendServerMessage(enqueue, flush, message) {
1664
+ return Effect11.gen(function* () {
1665
+ (yield* enqueue(message)) && (yield* flush);
1666
+ });
1667
+ }
1668
+ function acknowledgeSnapshot(writerState, transportState, sendPermit, sequence) {
1669
+ return sendPermit.withPermit(
1670
+ Ref.modify(writerState, (current) => current.requiredSnapshotSequence !== sequence || sequence !== transportState.events.currentSequence() || current.queue.messages.length > 0 ? [!1, current] : [
1671
+ !0,
1672
+ {
1673
+ ...current,
1674
+ queue: resetOutgoingMessageQueue(),
1675
+ requiredSnapshotSequence: void 0
1676
+ }
1677
+ ])
1678
+ );
1679
+ }
1680
+ function setNegotiatedContract(writerState, limits, supportsResnapshot) {
1681
+ return Ref.update(writerState, (current) => ({
1682
+ ...current,
1683
+ limits,
1684
+ supportsResnapshot
1685
+ }));
1686
+ }
1687
+ function enqueueMessage(state, message, currentSequence) {
1688
+ if (message._tag === "EnvironmentChanged" && state.requiredSnapshotSequence !== void 0)
1689
+ return [
1690
+ { _tag: "Ignored" },
1691
+ {
1692
+ ...state,
1693
+ requiredSnapshotSequence: Math.max(
1694
+ state.requiredSnapshotSequence,
1695
+ message.sequence
1696
+ )
1697
+ }
1698
+ ];
1699
+ let encoded = encodeMessage(message);
1700
+ if (Buffer.byteLength(encoded) > state.limits.maxWebSocketResponseBytes)
1701
+ return [
1702
+ {
1703
+ _tag: "Rejected",
1704
+ error: new EnvironmentWebSocketWriteError({
1705
+ closeCode: 1009,
1706
+ reason: "PayloadTooLarge"
1707
+ })
1708
+ },
1709
+ state
1710
+ ];
1711
+ if (message._tag === "ResnapshotRequired" && state.queue.overflowed)
1712
+ return [
1713
+ { _tag: "Enqueued" },
1714
+ {
1715
+ ...state,
1716
+ queue: replaceWithResnapshotMessage(state.queue, encoded),
1717
+ requiredSnapshotSequence: message.currentSequence
1718
+ }
1719
+ ];
1720
+ let overflow = encodeMessage({
1721
+ _tag: "ResnapshotRequired",
1722
+ currentSequence,
1723
+ reason: "OutgoingQueueOverflow"
1724
+ }), queue = enqueueOutgoingMessage(
1725
+ state.queue,
1726
+ encoded,
1727
+ overflow,
1728
+ state.limits
1729
+ ), overflowed = !state.queue.overflowed && queue.overflowed;
1730
+ return overflowed && !state.supportsResnapshot ? [
1731
+ {
1732
+ _tag: "Rejected",
1733
+ error: new EnvironmentWebSocketWriteError({
1734
+ closeCode: 1013,
1735
+ reason: "OutgoingQueueOverflow"
1736
+ })
1737
+ },
1738
+ { ...state, queue }
1739
+ ] : [
1740
+ { _tag: "Enqueued" },
1741
+ {
1742
+ ...state,
1743
+ queue,
1744
+ requiredSnapshotSequence: message._tag === "ResnapshotRequired" ? message.currentSequence : overflowed ? currentSequence : state.requiredSnapshotSequence
1745
+ }
1746
+ ];
1747
+ }
1748
+ function sendWebSocketMessage(socket, message) {
1749
+ return Effect11.callback((resume) => {
1750
+ socket.send(message, (error) => {
1751
+ resume(
1752
+ error == null ? Effect11.void : Effect11.fail(
1753
+ new EnvironmentWebSocketWriteError({
1754
+ closeCode: 1011,
1755
+ reason: "WebSocketWriteFailed"
1756
+ })
1757
+ )
1758
+ );
1759
+ });
1760
+ });
1761
+ }
1762
+ function encodeMessage(message) {
1763
+ return JSON.stringify(
1764
+ Schema8.encodeSync(EnvironmentServerMessage)(message)
1765
+ );
1766
+ }
1767
+
1768
+ // src/apps/server/features/environment-connection/websocket/environment-websocket-session.ts
1769
+ function runEnvironmentWebSocketSession(socket, state) {
1770
+ return runSession(socket, state).pipe(
1771
+ Effect12.catchTag("EnvironmentWebSocketSessionClosed", () => Effect12.void),
1772
+ Effect12.catchTag(
1773
+ "EnvironmentWebSocketSessionRejected",
1774
+ ({ result }) => rejectAndClose(socket, result)
1775
+ ),
1776
+ Effect12.catchTag(
1777
+ "EnvironmentWebSocketWriteError",
1778
+ (error) => closeForWriteError(socket, error)
1779
+ ),
1780
+ Effect12.scoped
1781
+ );
1782
+ }
1783
+ function runSession(socket, state) {
1784
+ return Effect12.gen(function* () {
1785
+ let messages = yield* acquireEnvironmentWebSocketInbox(socket, state), writer = yield* createEnvironmentWebSocketWriter(socket, state), runSessionEffect = yield* FiberSet.makeRuntime(), hello = yield* readHello(messages, state), result = yield* negotiateSession(state, hello), supportsResnapshot = yield* initializeNegotiatedSession(
1786
+ socket,
1787
+ state,
1788
+ runSessionEffect,
1789
+ writer,
1790
+ hello,
1791
+ result
1792
+ );
1793
+ yield* processClientMessages(messages, writer, state, supportsResnapshot);
1794
+ });
1795
+ }
1796
+ function negotiateSession(state, hello) {
1797
+ let result = negotiateEnvironmentHello(
1798
+ state.discovery,
1799
+ hello,
1800
+ state.events.currentSequence()
1801
+ );
1802
+ return result._tag === "HelloRejected" ? Effect12.fail(new EnvironmentWebSocketSessionRejected({ result })) : Effect12.succeed(result);
1803
+ }
1804
+ function initializeNegotiatedSession(socket, state, runSessionEffect, writer, hello, result) {
1805
+ return Effect12.gen(function* () {
1806
+ let capabilities = new Set(
1807
+ result.capabilities.map((capability) => capability.name)
1808
+ ), supportsEnvironmentEvents = capabilities.has("environment-events"), supportsResnapshot = capabilities.has("sequence-resnapshot");
1809
+ return yield* writer.setNegotiatedContract(result.limits, supportsResnapshot), yield* writer.enqueue(result), supportsEnvironmentEvents && (yield* acquireEventSubscription(
1810
+ socket,
1811
+ state,
1812
+ runSessionEffect,
1813
+ writer.send
1814
+ )), yield* enqueueInitialResnapshot(writer.enqueue, hello, result, state), yield* writer.flush, supportsResnapshot;
1815
+ });
1816
+ }
1817
+ function processClientMessages(messages, writer, state, supportsResnapshot) {
1818
+ return Effect12.gen(function* () {
1819
+ for (; ; ) {
1820
+ let message = decodeSocketMessage(yield* Queue2.take(messages));
1821
+ yield* handleClientMessage(message, writer, state, supportsResnapshot);
1822
+ }
1823
+ });
1824
+ }
1825
+ function handleClientMessage(message, writer, state, supportsResnapshot) {
1826
+ return message === void 0 ? rejectSession("InvalidMessage") : message._tag === "Hello" ? rejectSession("HandshakeAlreadyCompleted") : supportsResnapshot ? Effect12.gen(function* () {
1827
+ (yield* writer.acknowledgeSnapshot(message.sequence)) || (yield* writer.send({
1828
+ _tag: "ResnapshotRequired",
1829
+ currentSequence: state.events.currentSequence(),
1830
+ reason: "SequenceGap"
1831
+ }));
1832
+ }) : rejectSession("InvalidMessage");
1833
+ }
1834
+ function readHello(messages, state) {
1835
+ return Effect12.gen(function* () {
1836
+ let first = yield* Queue2.take(messages).pipe(
1837
+ Effect12.timeoutOption(state.discovery.limits.helloTimeoutMilliseconds)
1838
+ );
1839
+ if (Option.isNone(first))
1840
+ return yield* rejectSession("HandshakeRequired");
1841
+ let message = decodeSocketMessage(first.value);
1842
+ return message === void 0 ? yield* rejectSession("InvalidMessage") : message._tag !== "Hello" ? yield* rejectSession("HandshakeRequired") : message;
1843
+ });
1844
+ }
1845
+ function acquireEventSubscription(socket, state, runSessionEffect, send) {
1846
+ return Effect12.acquireRelease(
1847
+ Effect12.sync(
1848
+ () => state.events.subscribe((sequence) => {
1849
+ runSessionEffect(
1850
+ send({ _tag: "EnvironmentChanged", sequence }).pipe(
1851
+ Effect12.catchTag(
1852
+ "EnvironmentWebSocketWriteError",
1853
+ (error) => closeForWriteError(socket, error)
1854
+ )
1855
+ )
1856
+ );
1857
+ })
1858
+ ),
1859
+ (unsubscribe) => Effect12.sync(unsubscribe)
1860
+ );
1861
+ }
1862
+ function enqueueInitialResnapshot(enqueue, hello, result, state) {
1863
+ return !result.capabilities.some(
1864
+ (capability) => capability.name === "sequence-resnapshot"
1865
+ ) || (hello.lastObservedSequence === void 0 || hello.lastObservedSequence === result.currentSequence) && result.currentSequence === state.events.currentSequence() ? Effect12.void : enqueue({
1866
+ _tag: "ResnapshotRequired",
1867
+ currentSequence: state.events.currentSequence(),
1868
+ reason: "SequenceGap"
1869
+ });
1870
+ }
1871
+ function decodeSocketMessage(message) {
1872
+ if (!message.isBinary)
1873
+ try {
1874
+ return Schema9.decodeUnknownSync(EnvironmentClientMessage)(
1875
+ JSON.parse(message.data.toString()),
1876
+ { onExcessProperty: "error" }
1877
+ );
1878
+ } catch {
1879
+ return;
1880
+ }
1881
+ }
1882
+ function rejectSession(failure) {
1883
+ return Effect12.fail(
1884
+ new EnvironmentWebSocketSessionRejected({
1885
+ result: { _tag: "HelloRejected", failure: sessionFailure(failure) }
1886
+ })
1887
+ );
1888
+ }
1889
+ function sessionFailure(failure) {
1890
+ switch (failure) {
1891
+ case "HandshakeAlreadyCompleted":
1892
+ return { _tag: "HandshakeAlreadyCompleted" };
1893
+ case "HandshakeRequired":
1894
+ return { _tag: "HandshakeRequired" };
1895
+ case "InvalidMessage":
1896
+ return { _tag: "InvalidMessage" };
1897
+ }
1898
+ }
1899
+ function rejectAndClose(socket, result) {
1900
+ if (result._tag !== "HelloRejected")
1901
+ return Effect12.void;
1902
+ let encoded = JSON.stringify(
1903
+ Schema9.encodeSync(EnvironmentHelloResult)(result)
1904
+ );
1905
+ return Effect12.callback((resume) => {
1906
+ if (socket.readyState !== WebSocketState.OPEN) {
1907
+ socket.terminate(), resume(Effect12.void);
1908
+ return;
1909
+ }
1910
+ socket.send(encoded, (error) => {
1911
+ error != null ? socket.terminate() : socket.close(1008, result.failure._tag), resume(Effect12.void);
1912
+ });
1913
+ });
1914
+ }
1915
+ function closeForWriteError(socket, error) {
1916
+ return Effect12.sync(() => {
1917
+ socket.readyState === WebSocketState.OPEN ? socket.close(error.closeCode, error.reason) : socket.terminate();
1918
+ });
1919
+ }
1920
+
1921
+ // src/apps/server/features/environment-connection/websocket/environment-websocket-server.ts
1922
+ function attachEnvironmentWebSocketServer(server, state, authorization, runEnvironmentEffect) {
1923
+ let webSocketServer = new WebSocketServer({
1924
+ clientTracking: !0,
1925
+ maxPayload: currentTransportLimits.maxWebSocketRequestBytes,
1926
+ noServer: !0,
1927
+ perMessageDeflate: !1
1928
+ }), upgrade = (request, socket, head) => {
1929
+ let url = readUpgradeUrl(request);
1930
+ if (url?.pathname !== environmentLivePath) {
1931
+ socket.end(`HTTP/1.1 404 Not Found\r
1932
+ Connection: close\r
1933
+ \r
1934
+ `);
1935
+ return;
1936
+ }
1937
+ runEnvironmentEffect(
1938
+ Effect13.gen(function* () {
1939
+ yield* validateRequestHost(request), yield* validateRequestOrigin(request, !1);
1940
+ let tickets = url.searchParams.getAll("ticket");
1941
+ yield* authorization.consumeTicket(
1942
+ tickets.length === 1 ? tickets[0] : void 0
1943
+ ), yield* Effect13.sync(() => {
1944
+ webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {
1945
+ webSocketServer.emit("connection", webSocket, request);
1946
+ });
1947
+ });
1948
+ }).pipe(
1949
+ Effect13.catchCause(
1950
+ (cause) => Effect13.sync(() => rejectUpgrade(socket, Cause2.squash(cause)))
1951
+ )
1952
+ )
1953
+ );
1954
+ };
1955
+ return server.on("upgrade", upgrade), webSocketServer.on("connection", (socket) => {
1956
+ runEnvironmentEffect(runEnvironmentWebSocketSession(socket, state));
1957
+ }), {
1958
+ close: () => closeWebSocketServer(server, webSocketServer, upgrade)
1959
+ };
1960
+ }
1961
+ function readUpgradeUrl(request) {
1962
+ try {
1963
+ return new URL(request.url ?? "", expectedRequestOrigin(request));
1964
+ } catch {
1965
+ return;
1966
+ }
1967
+ }
1968
+ function rejectUpgrade(socket, error) {
1969
+ let authorizationError = isEnvironmentAuthorizationError(error) ? error : void 0, status = authorizationError === void 0 ? 503 : authorizationFailureStatus(authorizationError.failure), body = JSON.stringify(
1970
+ authorizationError === void 0 ? { _tag: "EnvironmentUnavailable" } : authorizationError.failure
1971
+ );
1972
+ socket.end(
1973
+ `HTTP/1.1 ${status} ${statusText(status)}\r
1974
+ Content-Type: application/json; charset=utf-8\r
1975
+ Cache-Control: no-store\r
1976
+ Content-Length: ${Buffer.byteLength(body)}\r
1977
+ Connection: close\r
1978
+ \r
1979
+ ${body}`
1980
+ );
1981
+ }
1982
+ function statusText(status) {
1983
+ switch (status) {
1984
+ case 401:
1985
+ return "Unauthorized";
1986
+ case 403:
1987
+ return "Forbidden";
1988
+ case 409:
1989
+ return "Conflict";
1990
+ case 410:
1991
+ return "Gone";
1992
+ default:
1993
+ return "Service Unavailable";
1994
+ }
1995
+ }
1996
+ function closeWebSocketServer(server, webSocketServer, upgrade) {
1997
+ server.off("upgrade", upgrade);
1998
+ for (let client of webSocketServer.clients)
1999
+ client.terminate();
2000
+ return new Promise((resolveClosed, rejectClosed) => {
2001
+ webSocketServer.close((error) => {
2002
+ error !== void 0 ? rejectClosed(error) : resolveClosed();
2003
+ });
2004
+ });
2005
+ }
2006
+
2007
+ // src/apps/server/features/environment-server/server/environment-server-error.contract.ts
2008
+ import { Data as Data6 } from "effect";
2009
+ var EnvironmentServerStartError = class extends Data6.TaggedError(
2010
+ "EnvironmentServerStartError"
2011
+ ) {
2012
+ };
2013
+
2014
+ // src/apps/server/features/environment-server/server/environment-listener.ts
2015
+ var loopbackHost = "127.0.0.1";
2016
+ function acquireEnvironmentListener(options) {
2017
+ return Effect14.gen(function* () {
2018
+ let port = options.port ?? 0, readiness = { value: !1 }, state = {
2019
+ discovery: createCurrentEnvironmentDiscovery(
2020
+ options.environmentId,
2021
+ options.productVersion
2022
+ ),
2023
+ events: options.events
2024
+ }, runFork = yield* FiberSet2.makeRuntime(), runEnvironmentEffect = (effect, signal) => {
2025
+ runFork(effect, signal === void 0 ? void 0 : { signal });
2026
+ }, server = yield* Effect14.acquireRelease(
2027
+ createHttpServer(
2028
+ readiness,
2029
+ state,
2030
+ options.authorization,
2031
+ port,
2032
+ runEnvironmentEffect,
2033
+ options.browserAssetsRoot
2034
+ ),
2035
+ (acquiredServer) => Effect14.promise(() => closeServer(acquiredServer)).pipe(Effect14.orDie)
2036
+ );
2037
+ yield* Effect14.acquireRelease(
2038
+ Effect14.try({
2039
+ try: () => attachEnvironmentWebSocketServer(
2040
+ server,
2041
+ state,
2042
+ options.authorization,
2043
+ runEnvironmentEffect
2044
+ ),
2045
+ catch: (cause) => environmentServerError(cause, port)
2046
+ }),
2047
+ (webSockets) => Effect14.promise(webSockets.close).pipe(Effect14.orDie)
2048
+ ), yield* listen(server, port);
2049
+ let listeningPort = yield* readListeningPort(server, port);
2050
+ return {
2051
+ host: loopbackHost,
2052
+ origin: `http://${loopbackHost}:${listeningPort}`,
2053
+ port: listeningPort,
2054
+ readiness,
2055
+ server
2056
+ };
2057
+ });
2058
+ }
2059
+ function createHttpServer(readiness, state, authorization, port, runEnvironmentEffect, browserAssetsRoot) {
2060
+ return Effect14.try({
2061
+ try: () => createServer(
2062
+ { maxHeaderSize: 16384 },
2063
+ createEnvironmentHttpHandler(
2064
+ state,
2065
+ authorization,
2066
+ () => readiness.value,
2067
+ runEnvironmentEffect,
2068
+ browserAssetsRoot
2069
+ )
2070
+ ),
2071
+ catch: (cause) => environmentServerError(cause, port)
2072
+ });
2073
+ }
2074
+ function readListeningPort(server, requestedPort) {
2075
+ return Effect14.try({
2076
+ try: () => {
2077
+ let address = server.address();
2078
+ if (address === null || typeof address == "string")
2079
+ throw new Error("The HTTP listener has no TCP address.");
2080
+ return address.port;
2081
+ },
2082
+ catch: (cause) => environmentServerError(cause, requestedPort)
2083
+ });
2084
+ }
2085
+ function listen(server, port) {
2086
+ return Effect14.callback(
2087
+ (resume, signal) => {
2088
+ let failed = (cause) => {
2089
+ detach(), resume(Effect14.fail(environmentServerError(cause, port)));
2090
+ }, listening = () => {
2091
+ detach(), resume(Effect14.void);
2092
+ }, detach = () => {
2093
+ server.off("error", failed), server.off("listening", listening);
2094
+ };
2095
+ server.once("error", failed), server.once("listening", listening);
2096
+ try {
2097
+ server.listen({ exclusive: !0, host: loopbackHost, port, signal });
2098
+ } catch (cause) {
2099
+ failed(cause);
2100
+ }
2101
+ return Effect14.sync(detach);
2102
+ }
2103
+ );
2104
+ }
2105
+ function closeServer(server) {
2106
+ return new Promise((resolveClosed, rejectClosed) => {
2107
+ server.close((error) => {
2108
+ error && !isServerNotRunning(error) ? rejectClosed(error) : resolveClosed();
2109
+ }), server.closeAllConnections();
2110
+ });
2111
+ }
2112
+ function isServerNotRunning(error) {
2113
+ return isFileSystemError(error) && error.code === "ERR_SERVER_NOT_RUNNING";
2114
+ }
2115
+ function environmentServerError(cause, port) {
2116
+ return new EnvironmentServerStartError({
2117
+ cause,
2118
+ message: listenerErrorMessage(cause, port)
2119
+ });
2120
+ }
2121
+ function listenerErrorMessage(cause, port) {
2122
+ return isFileSystemError(cause) && cause.code === "EADDRINUSE" && port !== 0 ? `Port ${port} is already in use on ${loopbackHost}.` : `Could not start the Environment server: ${errorMessage(cause)}`;
2123
+ }
2124
+
2125
+ // src/apps/server/persistence/environment-context.ts
2126
+ import { randomUUID as randomUUID3 } from "node:crypto";
2127
+ import { eq as eq3 } from "drizzle-orm";
2128
+ import { drizzle as drizzle2 } from "drizzle-orm/node-sqlite";
2129
+ import { Effect as Effect18, Semaphore as Semaphore2 } from "effect";
2130
+
2131
+ // src/apps/server/persistence/sqlite/database.ts
2132
+ import { chmodSync } from "node:fs";
2133
+ import { DatabaseSync } from "node:sqlite";
2134
+ import { drizzle } from "drizzle-orm/node-sqlite";
2135
+ import { Effect as Effect16, Schedule } from "effect";
2136
+
2137
+ // src/apps/server/persistence/sqlite/migrations.ts
2138
+ import { existsSync } from "node:fs";
2139
+ import { fileURLToPath } from "node:url";
2140
+ import { readMigrationFiles } from "drizzle-orm/migrator";
2141
+ import { migrate } from "drizzle-orm/node-sqlite/migrator";
2142
+ var migrationsFolder = resolveMigrationsFolder(), migrationsTable = "__drizzle_migrations";
2143
+ function migrateEnvironmentState(database) {
2144
+ let localMigrations = readMigrationFiles({ migrationsFolder }), appliedMigrations = readAppliedMigrations(database.$client);
2145
+ if (validateMigrationHistory(localMigrations, appliedMigrations), appliedMigrations.length !== localMigrations.length)
2146
+ try {
2147
+ migrate(database, { migrationsFolder, migrationsTable });
2148
+ } catch (error) {
2149
+ let migrationsAfterFailure = readAppliedMigrations(database.$client);
2150
+ if (validateMigrationHistory(localMigrations, migrationsAfterFailure), migrationsAfterFailure.length !== localMigrations.length) throw error;
2151
+ }
2152
+ }
2153
+ function readAppliedMigrations(database) {
2154
+ return database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(migrationsTable) === void 0 ? [] : database.prepare(`SELECT id, name, hash FROM ${migrationsTable} ORDER BY id`).all().map(parseAppliedMigration);
2155
+ }
2156
+ function validateMigrationHistory(localMigrations, appliedMigrations) {
2157
+ for (let [index2, applied] of appliedMigrations.entries()) {
2158
+ let expected = localMigrations[index2];
2159
+ if (expected === void 0)
2160
+ throw new Error(
2161
+ `The state database is at version ${applied.id}, but this Rebase build supports version ${localMigrations.length}.`
2162
+ );
2163
+ if (applied.id !== index2 + 1 || applied.name !== expected.name)
2164
+ throw new Error(
2165
+ `Migration ${applied.id} "${applied.name}" does not match the expected migration "${expected.name}".`
2166
+ );
2167
+ if (applied.hash !== expected.hash)
2168
+ throw new Error(
2169
+ `Migration ${applied.id} "${applied.name}" no longer matches its applied checksum.`
2170
+ );
2171
+ }
2172
+ }
2173
+ function parseAppliedMigration(row) {
2174
+ if (typeof row.id != "number" || typeof row.name != "string" || typeof row.hash != "string")
2175
+ throw new Error("The migration history contains an invalid record.");
2176
+ return {
2177
+ hash: row.hash,
2178
+ id: row.id,
2179
+ name: row.name
2180
+ };
2181
+ }
2182
+ function resolveMigrationsFolder() {
2183
+ let folder = [
2184
+ new URL("./migrations", import.meta.url),
2185
+ new URL("../migrations", import.meta.url)
2186
+ ].find((candidate) => existsSync(candidate));
2187
+ if (folder === void 0)
2188
+ throw new Error("Rebase database migrations are missing.");
2189
+ return fileURLToPath(folder);
2190
+ }
2191
+
2192
+ // src/apps/server/persistence/sqlite/storage-operation.ts
2193
+ import { Effect as Effect15 } from "effect";
2194
+ function serializedPromise(writer, message, operation) {
2195
+ return writer.withPermit(storagePromise(message, operation));
2196
+ }
2197
+ function storagePromise(message, operation) {
2198
+ return Effect15.tryPromise({
2199
+ try: async () => operation(),
2200
+ catch: (cause) => storageError(message, cause)
2201
+ });
2202
+ }
2203
+ function storageSync(message, operation) {
2204
+ return Effect15.try({
2205
+ try: operation,
2206
+ catch: (cause) => storageError(message, cause)
2207
+ });
2208
+ }
2209
+ function storageError(message, cause) {
2210
+ return new EnvironmentStorageError({
2211
+ cause,
2212
+ message: `${message}: ${errorMessage(cause)}`
2213
+ });
2214
+ }
2215
+
2216
+ // src/apps/server/persistence/sqlite/database.ts
2217
+ var busyTimeoutMilliseconds = 1e3, databaseLockRetryDelay = "10 millis", databaseLockRetryCount = 10;
2218
+ function openEnvironmentDatabase(paths) {
2219
+ return Effect16.gen(function* () {
2220
+ let database = yield* storageSync(
2221
+ "Could not open Environment state",
2222
+ () => new DatabaseSync(paths.stateDatabase, {
2223
+ allowUnknownNamedParameters: !1,
2224
+ enableForeignKeyConstraints: !0,
2225
+ timeout: busyTimeoutMilliseconds
2226
+ })
2227
+ );
2228
+ return yield* Effect16.gen(function* () {
2229
+ return yield* storageSync(
2230
+ "Could not open Environment state",
2231
+ () => configureDatabase(database)
2232
+ ).pipe(retryDatabaseLock), yield* migrateDatabase(database), yield* storageSync(
2233
+ "Could not open Environment state",
2234
+ () => restrictDatabasePermissions(paths.stateDatabase)
2235
+ ), database;
2236
+ }).pipe(Effect16.onError(() => closeEnvironmentDatabase(database)));
2237
+ });
2238
+ }
2239
+ function closeEnvironmentDatabase(database) {
2240
+ return Effect16.sync(() => database.close()).pipe(Effect16.orDie);
2241
+ }
2242
+ function readDatabaseSettings(database) {
2243
+ let foreignKeys = database.prepare("PRAGMA foreign_keys").get();
2244
+ return {
2245
+ busyTimeout: database.prepare("PRAGMA busy_timeout").get().timeout,
2246
+ foreignKeys: foreignKeys.foreign_keys === 1,
2247
+ journalMode: readJournalMode(database)
2248
+ };
2249
+ }
2250
+ function configureDatabase(database) {
2251
+ database.exec(`
2252
+ PRAGMA busy_timeout = ${busyTimeoutMilliseconds};
2253
+ PRAGMA synchronous = NORMAL;
2254
+ `), enableWriteAheadLogging(database);
2255
+ }
2256
+ function migrateDatabase(database) {
2257
+ return storageSync(
2258
+ "Could not open Environment state",
2259
+ () => migrateEnvironmentState(drizzle({ client: database }))
2260
+ ).pipe(retryDatabaseLock);
2261
+ }
2262
+ function retryDatabaseLock(effect) {
2263
+ return effect.pipe(
2264
+ Effect16.retry({
2265
+ schedule: Schedule.spaced(databaseLockRetryDelay),
2266
+ times: databaseLockRetryCount,
2267
+ while: isDatabaseLocked
2268
+ })
2269
+ );
2270
+ }
2271
+ function isDatabaseLocked(error) {
2272
+ return error.message.includes("database is locked");
2273
+ }
2274
+ function enableWriteAheadLogging(database) {
2275
+ try {
2276
+ database.exec("PRAGMA journal_mode = WAL;");
2277
+ } catch (error) {
2278
+ if (readJournalMode(database) !== "wal") throw error;
2279
+ }
2280
+ }
2281
+ function readJournalMode(database) {
2282
+ return database.prepare("PRAGMA journal_mode").get().journal_mode;
2283
+ }
2284
+ function restrictDatabasePermissions(path) {
2285
+ process.platform !== "win32" && chmodSync(path, 384);
2286
+ }
2287
+
2288
+ // src/apps/server/persistence/storage/server-secret.ts
2289
+ import { randomBytes as randomBytes2 } from "node:crypto";
2290
+ import { chmod as chmod2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
2291
+ import { setTimeout } from "node:timers/promises";
2292
+ import { Effect as Effect17 } from "effect";
2293
+ var secretCreationRetryCount = 10, secretCreationRetryDelayMilliseconds = 10;
2294
+ function ensureServerSecret(paths) {
2295
+ return Effect17.tryPromise({
2296
+ try: () => readOrCreateServerSecret(paths.serverSecret),
2297
+ catch: (cause) => new EnvironmentStorageError({
2298
+ cause,
2299
+ message: `Could not prepare the server secret: ${errorMessage(cause)}`
2300
+ })
2301
+ });
2302
+ }
2303
+ async function readOrCreateServerSecret(path) {
2304
+ let secret;
2305
+ try {
2306
+ secret = await readFile3(path, "utf8");
2307
+ } catch (error) {
2308
+ if (!isMissingFile2(error))
2309
+ throw error;
2310
+ let generated = `${randomBytes2(32).toString("base64url")}
2311
+ `;
2312
+ try {
2313
+ await writeFile2(path, generated, { flag: "wx", mode: 384 }), secret = generated;
2314
+ } catch (writeError) {
2315
+ if (!isExistingFile(writeError))
2316
+ throw writeError;
2317
+ secret = await readSecretCreatedByAnotherProcess(path);
2318
+ }
2319
+ }
2320
+ if (!/^[A-Za-z0-9_-]{43}$/.test(secret.trim()))
2321
+ throw new Error("The server secret is invalid.");
2322
+ return process.platform !== "win32" && await chmod2(path, 384), secret.trim();
2323
+ }
2324
+ async function readSecretCreatedByAnotherProcess(path) {
2325
+ for (let attempt = 0; attempt <= secretCreationRetryCount; attempt += 1) {
2326
+ let secret = await readFile3(path, "utf8");
2327
+ if (/^[A-Za-z0-9_-]{43}$/.test(secret.trim()))
2328
+ return secret;
2329
+ if (attempt === secretCreationRetryCount)
2330
+ break;
2331
+ await setTimeout(secretCreationRetryDelayMilliseconds);
2332
+ }
2333
+ throw new Error("The server secret is invalid.");
2334
+ }
2335
+ function isMissingFile2(error) {
2336
+ return isFileSystemError(error) && error.code === "ENOENT";
2337
+ }
2338
+ function isExistingFile(error) {
2339
+ return isFileSystemError(error) && error.code === "EEXIST";
2340
+ }
2341
+
2342
+ // src/apps/server/persistence/environment-context.ts
2343
+ function acquireEnvironmentContext(paths = defaultEnvironmentPaths()) {
2344
+ return Effect18.gen(function* () {
2345
+ yield* prepareEnvironmentDirectories(paths);
2346
+ let serverSecret = yield* ensureServerSecret(paths), database = yield* Effect18.acquireRelease(
2347
+ openEnvironmentDatabase(paths),
2348
+ closeEnvironmentDatabase
2349
+ ), context = yield* storageSync(
2350
+ "Could not read Environment state settings",
2351
+ () => createEnvironmentContext(database, serverSecret)
2352
+ );
2353
+ return yield* initializeEnvironment(context), context;
2354
+ });
2355
+ }
2356
+ function createEnvironmentContext(database, serverSecret) {
2357
+ let drizzleDatabase = drizzle2({ client: database }), writer = Semaphore2.makeUnsafe(1);
2358
+ return {
2359
+ database: drizzleDatabase,
2360
+ databaseSettings: readDatabaseSettings(database),
2361
+ serverSecret,
2362
+ read: (message, operation) => storagePromise(message, () => operation(drizzleDatabase)),
2363
+ write: (message, operation) => serializedPromise(writer, message, () => operation(drizzleDatabase))
2364
+ };
2365
+ }
2366
+ function initializeEnvironment(context) {
2367
+ return context.write(
2368
+ "Could not initialize Environment state",
2369
+ async (database) => {
2370
+ if (await database.insert(environmentTable).values({ id: randomUUID3(), singleton: 1 }).onConflictDoNothing({ target: environmentTable.singleton }), await database.select({ id: environmentTable.id }).from(environmentTable).where(eq3(environmentTable.singleton, 1)).get() === void 0)
2371
+ throw new Error("The Environment identity is missing.");
2372
+ }
2373
+ );
2374
+ }
2375
+
2376
+ // src/apps/server/product-version.ts
2377
+ var productVersion = "0.0.2-nightly.20260822.35";
2378
+
2379
+ // src/apps/server/features/environment-server/server/start-environment-server.ts
2380
+ function startEnvironmentServer(options = {}) {
2381
+ return Effect19.gen(function* () {
2382
+ yield* verifyRuntimeRequirements;
2383
+ let paths = defaultEnvironmentPaths(), context = yield* acquireEnvironmentContext(paths), environment = yield* readCurrentEnvironment(context), authorization = createEnvironmentAuthorization(
2384
+ context,
2385
+ context.serverSecret
2386
+ ), useAutomaticPort = options.port === void 0 || options.port === 0, requestedPort = useAutomaticPort ? environment.automaticPort ?? 0 : options.port, events = createEnvironmentEventPublisher(), listener = yield* acquireEnvironmentListener({
2387
+ authorization,
2388
+ ...options.browserAssetsRoot === void 0 ? {} : { browserAssetsRoot: options.browserAssetsRoot },
2389
+ environmentId: environment.id,
2390
+ events,
2391
+ port: requestedPort,
2392
+ productVersion
2393
+ });
2394
+ useAutomaticPort && environment.automaticPort === null && (yield* claimAutomaticPort(context, listener.port)), yield* acquireRuntimeMarker(runtimeMarker(listener), paths.runtimeMarker);
2395
+ let pairing = yield* authorization.createPairing({
2396
+ capabilities: [],
2397
+ role: "owner"
2398
+ });
2399
+ return yield* markListenerReady(listener), {
2400
+ environmentId: environment.id,
2401
+ origin: listener.origin,
2402
+ pairingUrl: `${listener.origin}/pair#${pairing.material}`,
2403
+ port: listener.port
2404
+ };
2405
+ });
2406
+ }
2407
+ function readCurrentEnvironment(context) {
2408
+ return context.read("Could not read Environment state", async (database) => {
2409
+ let environment = await database.select().from(environmentTable).where(isCurrentEnvironment()).get();
2410
+ if (environment === void 0)
2411
+ throw new Error("The Environment identity is missing.");
2412
+ return environment;
2413
+ });
2414
+ }
2415
+ function claimAutomaticPort(context, port) {
2416
+ return context.write(
2417
+ "Could not save the automatic port",
2418
+ async (database) => {
2419
+ await database.update(environmentTable).set({ automaticPort: port }).where(and(isCurrentEnvironment(), hasNoAutomaticPort()));
2420
+ let selected = await database.select({ automaticPort: environmentTable.automaticPort }).from(environmentTable).where(isCurrentEnvironment()).get();
2421
+ if (selected?.automaticPort === null || selected === void 0)
2422
+ throw new Error("The automatic port was not saved.");
2423
+ if (selected.automaticPort !== port)
2424
+ throw new Error(
2425
+ `Another server selected automatic port ${selected.automaticPort}.`
2426
+ );
2427
+ }
2428
+ );
2429
+ }
2430
+ function runtimeMarker(listener) {
2431
+ return {
2432
+ host: listener.host,
2433
+ origin: listener.origin,
2434
+ pid: process.pid,
2435
+ port: listener.port,
2436
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
2437
+ };
2438
+ }
2439
+ function markListenerReady(listener) {
2440
+ return Effect19.gen(function* () {
2441
+ yield* Effect19.addFinalizer(
2442
+ () => Effect19.sync(() => {
2443
+ listener.readiness.value = !1;
2444
+ })
2445
+ ), yield* Effect19.sync(() => {
2446
+ listener.readiness.value = !0;
2447
+ });
2448
+ });
2449
+ }
2450
+
2451
+ // src/apps/server/cli.ts
2452
+ var usage = "Usage: rebase [serve] [--port <1-65535>]";
2453
+ function runCli(arguments_ = process.argv.slice(2)) {
2454
+ if (arguments_.includes("--help") || arguments_.includes("-h"))
2455
+ return writeOutput(usage).pipe(Effect20.as(0));
2456
+ if (arguments_.includes("--version") || arguments_.includes("-v"))
2457
+ return writeOutput(versionOutput()).pipe(Effect20.as(0));
2458
+ let program = Effect20.try({
2459
+ try: () => parseServeArguments(arguments_),
2460
+ catch: normalizeError
2461
+ }).pipe(Effect20.flatMap(serve));
2462
+ return Effect20.matchEffect(program, {
2463
+ onFailure: (error) => writeFailure(`rebase serve failed: ${error.message}`).pipe(Effect20.as(1)),
2464
+ onSuccess: () => Effect20.succeed(0)
2465
+ });
2466
+ }
2467
+ async function main() {
2468
+ process.exitCode = await Effect20.runPromise(runCli());
2469
+ }
2470
+ function parseServeArguments(arguments_) {
2471
+ let normalizedArguments = arguments_.length === 0 || arguments_[0] === "--port" ? ["serve", ...arguments_] : arguments_, [command, ...options] = normalizedArguments;
2472
+ if (command !== "serve") throw new Error(usage);
2473
+ let browserAssetsRoot = resolveBrowserAssetsRoot();
2474
+ if (options.length === 0) return { browserAssetsRoot };
2475
+ if (options.length !== 2 || options[0] !== "--port" || !options[1])
2476
+ throw new Error(usage);
2477
+ let port = Number(options[1]);
2478
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
2479
+ throw new Error(
2480
+ `Port must be an integer between 1 and 65535. Found "${options[1]}".`
2481
+ );
2482
+ return { browserAssetsRoot, port };
2483
+ }
2484
+ function serve(options) {
2485
+ return Effect20.scoped(
2486
+ Effect20.gen(function* () {
2487
+ let shutdown = yield* acquireShutdownSignal(), server = yield* Effect20.raceFirst(
2488
+ startEnvironmentServer(options),
2489
+ Deferred.await(shutdown)
2490
+ );
2491
+ server !== void 0 && (yield* Effect20.sync(() => {
2492
+ process.stdout.write(`Listening URL: ${server.origin}
2493
+ `), process.stdout.write(`Pairing URL: ${server.pairingUrl}
2494
+ `), openDefaultBrowser(server.pairingUrl);
2495
+ }), yield* Deferred.await(shutdown));
2496
+ })
2497
+ );
2498
+ }
2499
+ function acquireShutdownSignal() {
2500
+ return Effect20.gen(function* () {
2501
+ let shutdown = yield* Deferred.make();
2502
+ return yield* Effect20.acquireRelease(
2503
+ Effect20.sync(() => {
2504
+ let stop = () => {
2505
+ Deferred.doneUnsafe(shutdown, Effect20.void);
2506
+ };
2507
+ return process.once("SIGINT", stop), process.once("SIGTERM", stop), stop;
2508
+ }),
2509
+ (stop) => Effect20.sync(() => {
2510
+ process.off("SIGINT", stop), process.off("SIGTERM", stop);
2511
+ })
2512
+ ), shutdown;
2513
+ });
2514
+ }
2515
+ function writeOutput(line) {
2516
+ return Effect20.sync(() => {
2517
+ process.stdout.write(`${line}
2518
+ `);
2519
+ });
2520
+ }
2521
+ function writeFailure(line) {
2522
+ return Effect20.sync(() => {
2523
+ process.stderr.write(`${line}
2524
+ `);
2525
+ });
2526
+ }
2527
+ function normalizeError(error) {
2528
+ return error instanceof Error ? error : new Error(String(error));
2529
+ }
2530
+ function resolveBrowserAssetsRoot() {
2531
+ let root = [
2532
+ new URL("./web", import.meta.url),
2533
+ new URL("../web/dist/web", import.meta.url),
2534
+ new URL("../../web/dist/web", import.meta.url)
2535
+ ].find((candidate) => existsSync2(candidate));
2536
+ if (root === void 0)
2537
+ throw new Error(
2538
+ "Browser assets are missing. Reinstall Rebase or rebuild the web client."
2539
+ );
2540
+ return fileURLToPath2(root);
2541
+ }
2542
+ function versionOutput() {
2543
+ let protocol = currentEnvironmentProtocol;
2544
+ return [
2545
+ `Rebase ${productVersion}`,
2546
+ `Environment protocol ${protocol.major}.${protocol.minor} (minimum ${protocol.major}.${protocol.minimumSupportedMinor})`
2547
+ ].join(`
2548
+ `);
2549
+ }
2550
+ var entryPoint = process.argv[1];
2551
+ entryPoint && realpathSync(fileURLToPath2(import.meta.url)) === realpathSync(entryPoint) && await main();
2552
+ export {
2553
+ main,
2554
+ runCli
2555
+ };