@springbrand/space 0.2.0-alpha.0 → 0.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,5 +25,4 @@ whether the caller may reach it.
25
25
  Preview publication writes static assets below one immutable R2 deployment
26
26
  prefix and returns that prefix to the host. Static requests can therefore go
27
27
  straight to a Workspace CDN; `serveApp()` is the dynamic-only path into the App
28
- Facet. `servePreview()` remains available for hosts that intentionally proxy
29
- both asset and backend traffic through SpaceDO.
28
+ Facet. SpaceDO never reads static assets for browser requests.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/space",
3
- "version": "0.2.0-alpha.0",
3
+ "version": "0.2.0-alpha.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -304,7 +304,6 @@ async function deployBranch(
304
304
  compatibilityDate: compatDate,
305
305
  },
306
306
  staticAssets: assets,
307
- assetConfig,
308
307
  } = bundle
309
308
 
310
309
  const now = Date.now()
@@ -318,16 +317,14 @@ async function deployBranch(
318
317
  try {
319
318
  ctx.sql.exec(
320
319
  `INSERT OR REPLACE INTO deployments
321
- (branch, commit_hash, main_module, modules, assets, asset_deployment_id, asset_count,
322
- asset_config, compatibility_date, deployed_at)
323
- VALUES (?, ?, ?, ?, '{}', ?, ?, ?, ?, ?)`,
320
+ (branch, commit_hash, main_module, modules, asset_deployment_id,
321
+ compatibility_date, deployed_at)
322
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
324
323
  deploymentId,
325
324
  commitHash,
326
325
  mainModule,
327
326
  JSON.stringify(serializedModules),
328
327
  assets.length > 0 ? assetDeploymentId : "",
329
- assets.length,
330
- assetConfig ? JSON.stringify(assetConfig) : "{}",
331
328
  compatDate,
332
329
  now
333
330
  )
@@ -558,8 +555,7 @@ async function getDeployment(
558
555
 
559
556
  const row = ctx.sql
560
557
  .exec(
561
- `SELECT branch, commit_hash, main_module, modules, assets, asset_deployment_id, asset_count,
562
- asset_config, deployed_at
558
+ `SELECT branch, commit_hash, main_module, modules, asset_deployment_id, deployed_at
563
559
  FROM deployments WHERE branch = ?`,
564
560
  branch
565
561
  )
@@ -570,14 +566,13 @@ async function getDeployment(
570
566
  }
571
567
 
572
568
  const r = row[0]
573
- const legacyAssets = JSON.parse((r.assets as string) || "{}")
574
569
  const assetDeploymentId = (r.asset_deployment_id as string) || ""
575
570
  return jsonResponse({
576
571
  branch: r.branch as string,
577
572
  commit_hash: r.commit_hash as string,
578
573
  main_module: r.main_module as string,
579
574
  modules: JSON.parse(r.modules as string),
580
- has_assets: Number(r.asset_count ?? 0) > 0 || Object.keys(legacyAssets).length > 0,
575
+ has_assets: Boolean(assetDeploymentId),
581
576
  deployed_at: new Date(r.deployed_at as number).toISOString(),
582
577
  asset_root: assetDeploymentId && ctx.assetPrefix
583
578
  ? `${ctx.assetPrefix}/deployments/${assetDeploymentId}`
@@ -590,21 +585,17 @@ async function getDeployment(
590
585
  async function listDeployments(ctx: DeployContext): Promise<Response> {
591
586
  const rows = ctx.sql
592
587
  .exec(
593
- `SELECT branch, commit_hash, main_module, assets, asset_count, deployed_at
588
+ `SELECT branch, commit_hash, main_module, asset_deployment_id, deployed_at
594
589
  FROM deployments ORDER BY deployed_at DESC`,
595
590
  )
596
591
  .toArray()
597
-
598
- const deployments = rows.map((r) => {
599
- const legacyAssets = JSON.parse((r.assets as string) || "{}")
600
- return {
601
- branch: r.branch as string,
602
- commit_hash: r.commit_hash as string,
603
- main_module: r.main_module as string,
604
- has_assets: Number(r.asset_count ?? 0) > 0 || Object.keys(legacyAssets).length > 0,
605
- deployed_at: new Date(r.deployed_at as number).toISOString(),
606
- }
607
- })
592
+ const deployments = rows.map((r) => ({
593
+ branch: r.branch as string,
594
+ commit_hash: r.commit_hash as string,
595
+ main_module: r.main_module as string,
596
+ has_assets: Boolean(r.asset_deployment_id),
597
+ deployed_at: new Date(r.deployed_at as number).toISOString(),
598
+ }))
608
599
 
609
600
  return jsonResponse(deployments)
610
601
  }
@@ -11,21 +11,12 @@ import {
11
11
  type SpaceDeploymentResult,
12
12
  } from "./deploy-engine"
13
13
  import { globInfos, readDirInfos, toFileInfo } from "./fileinfo"
14
- import {
15
- handleAssetRequest,
16
- buildAssetManifest,
17
- createMemoryStorage,
18
- type AssetConfig,
19
- type AssetManifest,
20
- type AssetStorage,
21
- } from "@cloudflare/worker-bundler"
22
14
  import {
23
15
  buildInspectorWrapperSource,
24
16
  VIBE_APP_MODULE,
25
17
  } from "./inspector-wrapper"
26
18
  import { ArtifactsBackend, resolveSpaceFsBackendMode, SqlBackend, spaceR2Prefix, type SpaceFsBackend } from "./fs-backend"
27
19
  import { createGitFs, stageWorkdir } from "./git-objects"
28
- import { stripPreviewSecurityHeaders } from "./preview-headers"
29
20
  import * as git from "isomorphic-git"
30
21
  import {
31
22
  defaultSpaceQuota,
@@ -102,41 +93,9 @@ interface DeploymentRow {
102
93
  commitHash: string
103
94
  mainModule: string
104
95
  modules: Record<string, string | Record<string, unknown>>
105
- legacyAssets: Record<string, string | ArrayBuffer>
106
- assetDeploymentId: string
107
- assetCount: number
108
- assetConfig: AssetConfig
109
96
  compatibilityDate: string
110
97
  }
111
98
 
112
- type SerializedAsset = string | { base64: string }
113
-
114
- function deserializeAssets(
115
- assets: Record<string, SerializedAsset>,
116
- ): Record<string, string | ArrayBuffer> {
117
- return Object.fromEntries(
118
- Object.entries(assets).map(([path, content]) => [
119
- path,
120
- typeof content === "string"
121
- ? content
122
- : exactArrayBuffer(base64ToBytes(content.base64)),
123
- ]),
124
- )
125
- }
126
-
127
- function exactArrayBuffer(bytes: Uint8Array): ArrayBuffer {
128
- return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer
129
- }
130
-
131
- function base64ToBytes(value: string): Uint8Array {
132
- const binary = atob(value)
133
- const bytes = new Uint8Array(binary.length)
134
- for (let index = 0; index < binary.length; index++) {
135
- bytes[index] = binary.charCodeAt(index)
136
- }
137
- return bytes
138
- }
139
-
140
99
  // Overlay-only paths that must never leak into a deploy, rollback tree, or any
141
100
  // file listing: git's object store and the ArtifactsFileSystem bookkeeping dir.
142
101
  const isReservedPath = isReservedSpacePath
@@ -149,22 +108,10 @@ const isReservedPath = isReservedSpacePath
149
108
  // Commits/deploys are mirrored to a per-app Cloudflare Artifacts repo
150
109
  // (see artifacts-sync.ts), which is the durable source of truth for history.
151
110
 
152
- // Built asset manifest + in-memory storage for a single deployment. Rebuilding
153
- // these on every request is wasteful (CWE-770 amplification under a preview
154
- // flood), so we cache them per `branch:commitHash` with an LRU + TTL bound.
155
- type CachedAssets = {
156
- manifest: AssetManifest
157
- storage: AssetStorage
158
- expiresAt: number
159
- }
160
- const ASSET_CACHE_MAX_ENTRIES = 8
161
- const ASSET_CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes
162
-
163
111
  export class SpaceDO extends DurableObject<Env>
164
112
  implements SpaceWorkspacePort, SpaceControlPort, SpaceAppPort {
165
113
  private backend!: SpaceFsBackend
166
114
  private initializationPromise: Promise<void> | null = null
167
- private assetCache = new Map<string, CachedAssets>()
168
115
  /**
169
116
  * One lane for every operation that reads or rewrites the whole tree.
170
117
  *
@@ -220,11 +167,8 @@ export class SpaceDO extends DurableObject<Env>
220
167
  commit_hash TEXT NOT NULL,
221
168
  main_module TEXT NOT NULL,
222
169
  modules TEXT NOT NULL,
223
- assets TEXT NOT NULL DEFAULT '{}',
224
170
  asset_deployment_id TEXT NOT NULL DEFAULT '',
225
- asset_count INTEGER NOT NULL DEFAULT 0,
226
- asset_config TEXT NOT NULL DEFAULT '{}',
227
- compatibility_date TEXT NOT NULL DEFAULT '',
171
+ compatibility_date TEXT NOT NULL,
228
172
  deployed_at INTEGER NOT NULL
229
173
  );
230
174
  CREATE TABLE IF NOT EXISTS deployment_assets (
@@ -238,13 +182,6 @@ export class SpaceDO extends DurableObject<Env>
238
182
  );
239
183
  `)
240
184
 
241
- // Migrate existing deployments tables that lack new columns
242
- try { this.ctx.storage.sql.exec(`ALTER TABLE deployments ADD COLUMN assets TEXT NOT NULL DEFAULT '{}'`) } catch {}
243
- try { this.ctx.storage.sql.exec(`ALTER TABLE deployments ADD COLUMN asset_deployment_id TEXT NOT NULL DEFAULT ''`) } catch {}
244
- try { this.ctx.storage.sql.exec(`ALTER TABLE deployments ADD COLUMN asset_count INTEGER NOT NULL DEFAULT 0`) } catch {}
245
- try { this.ctx.storage.sql.exec(`ALTER TABLE deployments ADD COLUMN asset_config TEXT NOT NULL DEFAULT '{}'`) } catch {}
246
- try { this.ctx.storage.sql.exec(`ALTER TABLE deployments ADD COLUMN compatibility_date TEXT NOT NULL DEFAULT ''`) } catch {}
247
-
248
185
  // Initialize git repo if not already done
249
186
  try {
250
187
  await this.git.init({ defaultBranch: "main" })
@@ -636,7 +573,6 @@ export class SpaceDO extends DurableObject<Env>
636
573
  }
637
574
  }
638
575
  await this.deleteSpilledObjects()
639
- this.assetCache.clear()
640
576
  await this.ctx.storage.deleteAll()
641
577
  this.initializationPromise = null
642
578
  })
@@ -1143,10 +1079,6 @@ export class SpaceDO extends DurableObject<Env>
1143
1079
  const res = await handleDeployCommand(ctx, "deploy", fakeRequest)
1144
1080
  const data = await res.json() as Record<string, unknown>
1145
1081
  if (typeof data.error === "string") throw new Error(data.error)
1146
- this.assetCache.clear()
1147
- const deploymentId = data.branch as string
1148
- data.preview_url =
1149
- `/space/${this.spaceName}/preview/${encodeURIComponent(deploymentId)}/`
1150
1082
 
1151
1083
  if (!revision) await this.backend.push(branch)
1152
1084
 
@@ -1191,7 +1123,6 @@ export class SpaceDO extends DurableObject<Env>
1191
1123
  assetPrefix: spaceR2Prefix(this.spaceName),
1192
1124
  }
1193
1125
  const res = await handleDeployCommand(ctx, "undeploy", fakeRequest)
1194
- this.assetCache.clear()
1195
1126
  return res.json()
1196
1127
  }
1197
1128
 
@@ -1240,13 +1171,12 @@ export class SpaceDO extends DurableObject<Env>
1240
1171
  return { fileCount, directoryCount, totalBytes }
1241
1172
  }
1242
1173
 
1243
- // ── Deployment row reader (shared by servePreview + DB-viewer) ──
1174
+ // ── Deployment row reader ──
1244
1175
 
1245
1176
  private readDeployment(branch: string): DeploymentRow | null {
1246
1177
  const rows = this.ctx.storage.sql
1247
1178
  .exec(
1248
- `SELECT branch, commit_hash, main_module, modules, assets,
1249
- asset_deployment_id, asset_count, asset_config, compatibility_date
1179
+ `SELECT branch, commit_hash, main_module, modules, compatibility_date
1250
1180
  FROM deployments WHERE branch = ?`,
1251
1181
  branch,
1252
1182
  )
@@ -1258,17 +1188,11 @@ export class SpaceDO extends DurableObject<Env>
1258
1188
  commitHash: r.commit_hash as string,
1259
1189
  mainModule: r.main_module as string,
1260
1190
  modules: JSON.parse(r.modules as string) as Record<string, string | Record<string, unknown>>,
1261
- legacyAssets: deserializeAssets(
1262
- JSON.parse((r.assets as string) || "{}") as Record<string, SerializedAsset>,
1263
- ),
1264
- assetDeploymentId: (r.asset_deployment_id as string) || "",
1265
- assetCount: Number(r.asset_count ?? 0),
1266
- assetConfig: JSON.parse((r.asset_config as string) || "{}") as AssetConfig,
1267
- compatibilityDate: (r.compatibility_date as string) || FALLBACK_COMPATIBILITY_DATE,
1191
+ compatibilityDate: r.compatibility_date as string,
1268
1192
  }
1269
1193
  }
1270
1194
 
1271
- // ── Preview serving via Dynamic Workers ─────────────────────────
1195
+ // ── App serving via Dynamic Workers ─────────────────────────────
1272
1196
  //
1273
1197
  // Architecture (matches Cloudflare's Durable Object Facets docs example):
1274
1198
  //
@@ -1276,10 +1200,8 @@ export class SpaceDO extends DurableObject<Env>
1276
1200
  // module. `App.fetch(request)` is the entire backend (Hono /
1277
1201
  // itty-router / vanilla — the LLM decides).
1278
1202
  // - SpaceDO acts as the supervisor ("AppRunner" in the docs).
1279
- // `servePreview` loads the user's worker via the Worker Loader,
1280
- // extracts the App class, and hosts it as a Facet keyed
1281
- // `app:<branch>`. Static assets are served host-side; everything
1282
- // else (including WebSocket upgrades) is forwarded into the Facet.
1203
+ // `serveApp` loads the user's worker via the Worker Loader, extracts the
1204
+ // App class, and hosts it as a Facet keyed `app:<branch>`.
1283
1205
  // - State is the Facet's own `ctx.storage` (SQLite + KV). No env.DB
1284
1206
  // binding is injected.
1285
1207
  // - To make the DB-viewer work without forcing the LLM to write
@@ -1289,40 +1211,12 @@ export class SpaceDO extends DurableObject<Env>
1289
1211
  // `App` that adds `__vibeInspectListTables` / `__vibeInspectRead`
1290
1212
  // / `__vibeWipe`. The subclass shares the same `ctx.storage`.
1291
1213
 
1292
- async servePreview(branch: string, request: Request): Promise<Response> {
1293
- await this.ensureInit()
1294
-
1295
- const dep = this.readDeployment(branch)
1296
- if (!dep) {
1297
- return new Response(`No deployment found for branch "${branch}"`, { status: 404 })
1298
- }
1299
-
1300
- // Serve static assets host-side before forwarding to the Facet. The built
1301
- // manifest/storage are cached per deployment so repeat asset reads don't
1302
- // re-spin the build on every request.
1303
- if (dep.assetCount > 0 || Object.keys(dep.legacyAssets).length > 0) {
1304
- const { manifest, storage } = await this.getCachedAssets(dep)
1305
- const assetResponse = await handleAssetRequest(request, manifest, storage, dep.assetConfig)
1306
- if (assetResponse) return assetResponse
1307
- }
1308
-
1309
- return this.serveDeploymentApp(branch, dep, request)
1310
- }
1311
-
1312
1214
  async serveApp(branch: string, request: Request): Promise<Response> {
1313
1215
  await this.ensureInit()
1314
1216
  const dep = this.readDeployment(branch)
1315
1217
  if (!dep) {
1316
1218
  return new Response(`No deployment found for branch "${branch}"`, { status: 404 })
1317
1219
  }
1318
- return this.serveDeploymentApp(branch, dep, request)
1319
- }
1320
-
1321
- private serveDeploymentApp(
1322
- branch: string,
1323
- dep: DeploymentRow,
1324
- request: Request,
1325
- ): Response | Promise<Response> {
1326
1220
  let appClass: DurableObjectClass
1327
1221
  try {
1328
1222
  appClass = this.loadAppClass(dep)
@@ -1337,73 +1231,6 @@ export class SpaceDO extends DurableObject<Env>
1337
1231
  return facet.fetch(request)
1338
1232
  }
1339
1233
 
1340
- /**
1341
- * Return the built asset manifest + in-memory storage for a deployment,
1342
- * reusing a cached build when available. Keyed by `branch:commitHash` so a
1343
- * redeploy (new commit) transparently rebuilds. Bounded by an LRU cap + TTL.
1344
- */
1345
- private async getCachedAssets(dep: DeploymentRow): Promise<CachedAssets> {
1346
- const key = `${dep.branch}:${dep.assetDeploymentId || dep.commitHash}`
1347
- const now = Date.now()
1348
-
1349
- const cached = this.assetCache.get(key)
1350
- if (cached && cached.expiresAt > now) {
1351
- // Refresh LRU recency.
1352
- this.assetCache.delete(key)
1353
- this.assetCache.set(key, cached)
1354
- return cached
1355
- }
1356
-
1357
- let manifest: AssetManifest
1358
- let storage: AssetStorage
1359
- if (dep.assetDeploymentId) {
1360
- const bucket = this.env.WORKSPACE_R2
1361
- if (!bucket) throw new Error("Static preview requires the WORKSPACE_R2 binding")
1362
- const rows = this.ctx.storage.sql
1363
- .exec(
1364
- `SELECT path, r2_key, content_type, etag
1365
- FROM deployment_assets WHERE deployment_id = ?`,
1366
- dep.assetDeploymentId,
1367
- )
1368
- .toArray()
1369
- const keys = new Map<string, string>()
1370
- manifest = new Map()
1371
- for (const row of rows) {
1372
- const path = row.path as string
1373
- keys.set(path, row.r2_key as string)
1374
- manifest.set(path, {
1375
- contentType: (row.content_type as string | null) ?? undefined,
1376
- etag: row.etag as string,
1377
- })
1378
- }
1379
- storage = {
1380
- async get(pathname) {
1381
- const r2Key = keys.get(pathname)
1382
- if (!r2Key) return null
1383
- return (await bucket.get(r2Key))?.body ?? null
1384
- },
1385
- }
1386
- } else {
1387
- manifest = await buildAssetManifest(dep.legacyAssets)
1388
- storage = createMemoryStorage(dep.legacyAssets)
1389
- }
1390
- const entry: CachedAssets = { manifest, storage, expiresAt: now + ASSET_CACHE_TTL_MS }
1391
-
1392
- this.assetCache.set(key, entry)
1393
-
1394
- // Evict expired / oldest entries to keep the cache bounded.
1395
- for (const [k, v] of this.assetCache) {
1396
- if (v.expiresAt <= now) this.assetCache.delete(k)
1397
- }
1398
- while (this.assetCache.size > ASSET_CACHE_MAX_ENTRIES) {
1399
- const oldest = this.assetCache.keys().next().value
1400
- if (oldest === undefined) break
1401
- this.assetCache.delete(oldest)
1402
- }
1403
-
1404
- return entry
1405
- }
1406
-
1407
1234
  // ── App-class loader ────────────────────────────────────────────
1408
1235
  //
1409
1236
  // Loads the dynamic worker for `branch`'s latest deployment with the
@@ -1428,7 +1255,7 @@ export class SpaceDO extends DurableObject<Env>
1428
1255
  // The date the app declared, not a date frozen into this file. A worker
1429
1256
  // built against one runtime and run under another is a bug the author
1430
1257
  // cannot see or fix.
1431
- compatibilityDate: dep.compatibilityDate || FALLBACK_COMPATIBILITY_DATE,
1258
+ compatibilityDate: dep.compatibilityDate,
1432
1259
  // Generated code gets no outbound network. Omitting this inherits the
1433
1260
  // parent Worker's full internet access, which would let an app the model
1434
1261
  // wrote reach anything this Worker can — including internal services.
@@ -1503,40 +1330,12 @@ export class SpaceDO extends DurableObject<Env>
1503
1330
  return { ok: true }
1504
1331
  }
1505
1332
 
1506
- // ── HTTP handler: preview serving + internal deploy commands ────
1333
+ // ── Internal deploy commands ──
1507
1334
 
1508
1335
  async fetch(request: Request): Promise<Response> {
1509
1336
  await this.ensureInit()
1510
1337
 
1511
- const url = new URL(request.url)
1512
- const path = url.pathname
1513
-
1514
- // Preview routes: /space/:name/preview/:branch/*
1515
- const previewMatch = path.match(/\/preview\/([^/]+)(\/.*)?$/)
1516
- if (previewMatch) {
1517
- const branch = decodeURIComponent(previewMatch[1])
1518
- const spaceName = this.ctx.id.name ?? "space"
1519
- const basePath = `/space/${spaceName}/preview/${encodeURIComponent(branch)}`
1520
-
1521
- // Rewrite the URL so the dynamic worker sees a clean path
1522
- const subPath = previewMatch[2] || "/"
1523
- const previewUrl = new URL(subPath, url.origin)
1524
- previewUrl.search = url.search
1525
- const previewRequest = new Request(previewUrl.toString(), request)
1526
- const response = await this.servePreview(branch, previewRequest)
1527
-
1528
- // Strip headers a generated app must not be able to set on the shared
1529
- // preview origin (e.g. Service-Worker-Allowed scope expansion) before
1530
- // any further rewriting.
1531
- const safeResponse = stripPreviewSecurityHeaders(response)
1532
-
1533
- // Rewrite root-relative paths in HTML responses so they resolve
1534
- // correctly when the preview is mounted on a sub-path
1535
- return rewritePreviewResponse(safeResponse, basePath)
1536
- }
1537
-
1538
- // Deploy command routes
1539
- const cmd = url.searchParams.get("cmd")
1338
+ const cmd = new URL(request.url).searchParams.get("cmd")
1540
1339
  if (cmd && ["deploy", "get_deployment", "list_deployments", "undeploy"].includes(cmd)) {
1541
1340
  const deployCtx: DeployContext = {
1542
1341
  sql: this.ctx.storage.sql,
@@ -1552,39 +1351,6 @@ export class SpaceDO extends DurableObject<Env>
1552
1351
  }
1553
1352
  }
1554
1353
 
1555
- // ─── Preview Response Rewriting ──────────────────────────────────────────────
1556
- // When a preview is served on /space/:name/preview/:branch/, root-relative
1557
- // paths like /style.css in HTML would resolve to the domain root instead of
1558
- // the preview path. We rewrite them so the browser fetches the correct URL.
1559
-
1560
- function rewritePreviewResponse(response: Response, basePath: string): Response {
1561
- // Rewrite Location header on redirects
1562
- const location = response.headers.get("location")
1563
- if (location?.startsWith("/")) {
1564
- const rewritten = new Response(response.body, response)
1565
- rewritten.headers.set("location", basePath + location)
1566
- return rewritten
1567
- }
1568
-
1569
- // Only rewrite HTML responses
1570
- const ct = response.headers.get("content-type") ?? ""
1571
- if (!ct.includes("text/html")) return response
1572
-
1573
- // Use HTMLRewriter to prefix root-relative src/href/action attributes
1574
- return new HTMLRewriter()
1575
- .on("[src],[href],[action]", {
1576
- element(el) {
1577
- for (const attr of ["src", "href", "action"] as const) {
1578
- const val = el.getAttribute(attr)
1579
- if (val?.startsWith("/") && !val.startsWith("//")) {
1580
- el.setAttribute(attr, basePath + val)
1581
- }
1582
- }
1583
- },
1584
- })
1585
- .transform(response)
1586
- }
1587
-
1588
1354
  // ─── Facet naming ───────────────────────────────────────────────────────────
1589
1355
 
1590
1356
  function facetNameForApp(branch: string): string {
@@ -1596,9 +1362,6 @@ function facetNameForApp(branch: string): string {
1596
1362
  /** Git's oid for an empty tree — what a repository with no content writes. */
1597
1363
  const EMPTY_TREE_OID = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
1598
1364
 
1599
- /** Used only for deployments recorded before the date was persisted. */
1600
- const FALLBACK_COMPATIBILITY_DATE = "2025-04-01"
1601
-
1602
1365
  const SPACE_AUTHOR = Object.freeze({
1603
1366
  name: "Workspace",
1604
1367
  email: "workspace@springbrand.local",
@@ -255,7 +255,6 @@ export interface SpaceAppPort {
255
255
  revision?: string,
256
256
  runtimeConfig?: DeploymentRuntimeConfig,
257
257
  ): Promise<BranchDeploymentBundle>;
258
- servePreview(branch: string, request: Request): Promise<Response>;
259
258
  serveApp(branch: string, request: Request): Promise<Response>;
260
259
  listAppTables(branch: string): Promise<unknown>;
261
260
  queryAppTable(branch: string, table: string, opts?: unknown): Promise<unknown>;