@malloy-publisher/server 0.0.205 → 0.0.206

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/app/api-doc.yaml +363 -395
  2. package/dist/app/assets/{EnvironmentPage-CAge6UHD.js → EnvironmentPage-BYwBeC2F.js} +1 -1
  3. package/dist/app/assets/{HomePage-DhTe8qpa.js → HomePage-ivu4vdpj.js} +1 -1
  4. package/dist/app/assets/{MainPage-CeTxxGex.js → MainPage-B2DnHEDU.js} +2 -2
  5. package/dist/app/assets/{MaterializationsPage-CpDHB70t.js → MaterializationsPage-BZEuwF9P.js} +1 -1
  6. package/dist/app/assets/{ModelPage-D9sSMb75.js → ModelPage-Dpu3bfPg.js} +1 -1
  7. package/dist/app/assets/{PackagePage-LRqQWrFY.js → PackagePage-B8PwcRHt.js} +1 -1
  8. package/dist/app/assets/{RouteError-xT6kuCNw.js → RouteError-BhbywAeC.js} +1 -1
  9. package/dist/app/assets/{WorkbookPage-DsIh9svZ.js → WorkbookPage-C-JXsJG0.js} +1 -1
  10. package/dist/app/assets/{core-C2sQrwVu.es-Bjem0hym.js → core-pPlPr7jK.es-CNEOlxKB.js} +1 -1
  11. package/dist/app/assets/{index-BdOZDcce.js → index-BHEm8Egc.js} +1 -1
  12. package/dist/app/assets/{index-DHHAcY5o.js → index-BsvDrV14.js} +1 -1
  13. package/dist/app/assets/{index-RX3QOTde.js → index-ChR1fKR2.js} +127 -127
  14. package/dist/app/assets/{index.umd-D2WH3D-f.js → index.umd-BVLPYNuj.js} +1 -1
  15. package/dist/app/index.html +1 -1
  16. package/dist/instrumentation.mjs +18 -8
  17. package/dist/package_load_worker.mjs +19 -2
  18. package/dist/server.mjs +1151 -1264
  19. package/package.json +1 -1
  20. package/src/constants.ts +12 -0
  21. package/src/controller/materialization.controller.ts +79 -35
  22. package/src/controller/package.controller.spec.ts +179 -0
  23. package/src/controller/package.controller.ts +60 -73
  24. package/src/dto/package.dto.ts +16 -1
  25. package/src/errors.spec.ts +12 -0
  26. package/src/errors.ts +18 -0
  27. package/src/health.spec.ts +34 -1
  28. package/src/instrumentation.ts +33 -17
  29. package/src/materialization_metrics.ts +66 -0
  30. package/src/package_load/package_load_pool.ts +7 -1
  31. package/src/package_load/package_load_worker.ts +44 -4
  32. package/src/package_load/protocol.ts +7 -1
  33. package/src/server-old.ts +7 -149
  34. package/src/server.ts +9 -188
  35. package/src/service/authorize_integration.spec.ts +67 -0
  36. package/src/service/environment.ts +212 -12
  37. package/src/service/environment_store.spec.ts +0 -81
  38. package/src/service/environment_store.ts +0 -23
  39. package/src/service/explore_visibility.spec.ts +434 -0
  40. package/src/service/exports_probe.spec.ts +107 -0
  41. package/src/service/manifest_loader.spec.ts +99 -0
  42. package/src/service/manifest_loader.ts +95 -0
  43. package/src/service/materialization_service.spec.ts +324 -512
  44. package/src/service/materialization_service.ts +816 -656
  45. package/src/service/model.ts +400 -13
  46. package/src/service/package.spec.ts +14 -2
  47. package/src/service/package.ts +254 -14
  48. package/src/service/package_rollback.spec.ts +190 -0
  49. package/src/service/package_worker_path.spec.ts +223 -0
  50. package/src/service/query_boundary.spec.ts +470 -0
  51. package/src/storage/DatabaseInterface.ts +35 -57
  52. package/src/storage/StorageManager.mock.ts +0 -9
  53. package/src/storage/StorageManager.ts +7 -290
  54. package/src/storage/duckdb/DuckDBRepository.ts +2 -35
  55. package/src/storage/duckdb/MaterializationRepository.ts +52 -27
  56. package/src/storage/duckdb/schema.ts +4 -20
  57. package/tests/integration/materialization/manifest_binding.integration.spec.ts +175 -0
  58. package/tests/integration/materialization/materialization_lifecycle.integration.spec.ts +277 -266
  59. package/tests/unit/duckdb/legacy_schema_migration.test.ts +7 -4
  60. package/src/controller/manifest.controller.ts +0 -38
  61. package/src/service/manifest_service.spec.ts +0 -206
  62. package/src/service/manifest_service.ts +0 -117
  63. package/src/service/materialized_table_gc.spec.ts +0 -384
  64. package/src/service/materialized_table_gc.ts +0 -231
  65. package/src/storage/duckdb/DuckDBManifestStore.ts +0 -70
  66. package/src/storage/duckdb/ManifestRepository.ts +0 -120
  67. package/src/storage/duckdb/manifest_store.spec.ts +0 -133
  68. package/src/storage/ducklake/DuckLakeManifestStore.ts +0 -155
  69. package/tests/unit/storage/StorageManager.test.ts +0 -166
@@ -7,8 +7,14 @@ import * as fs from "fs";
7
7
  import * as path from "path";
8
8
  import { pathToFileURL } from "url";
9
9
  import { components } from "../api";
10
- import { API_PREFIX, README_NAME } from "../constants";
11
10
  import {
11
+ API_PREFIX,
12
+ normalizeModelPath,
13
+ NOTEBOOK_FILE_SUFFIX,
14
+ README_NAME,
15
+ } from "../constants";
16
+ import {
17
+ BadRequestError,
12
18
  ConnectionNotFoundError,
13
19
  EnvironmentNotFoundError,
14
20
  PackageNotFoundError,
@@ -29,6 +35,7 @@ import {
29
35
  EnvironmentMalloyConfig,
30
36
  InternalConnection,
31
37
  } from "./connection";
38
+ import { fetchManifestEntries } from "./manifest_loader";
32
39
  import { ApiConnection } from "./model";
33
40
  import { Package } from "./package";
34
41
  import type { PackageMemoryGovernor } from "./package_memory_governor";
@@ -187,10 +194,6 @@ export class Environment {
187
194
  await this.writeEnvironmentReadme(payload.readme);
188
195
  }
189
196
 
190
- if (payload.materializationStorage !== undefined) {
191
- this.metadata.materializationStorage = payload.materializationStorage;
192
- }
193
-
194
197
  // Handle connections update
195
198
  // TODO: Update environment connections should have its own API endpoint
196
199
  if (payload.connections) {
@@ -296,6 +299,17 @@ export class Environment {
296
299
  ): Promise<{ problems: LogMessage[]; sql?: string }> {
297
300
  assertSafePackageName(packageName);
298
301
  assertSafeRelativeModelPath(modelName);
302
+ // /compile appends the submitted source to the TARGET MODEL's content for
303
+ // namespace context. A notebook (.malloynb) is markdown + cells, not a
304
+ // model, so compiling against it only yields a confusing parse error —
305
+ // reject it up front with an actionable message. (Notebooks remain public
306
+ // for discovery/query; this is specific to the compile context.)
307
+ if (modelName.endsWith(NOTEBOOK_FILE_SUFFIX)) {
308
+ throw new BadRequestError(
309
+ `Cannot compile against a notebook ("${modelName}"). ` +
310
+ `/compile takes a .malloy model path for namespace context.`,
311
+ );
312
+ }
299
313
  // Hold the per-package mutex for the duration of every disk read —
300
314
  // both the explicit `fs.readFile(modelPath)` below and the implicit
301
315
  // import resolution that `runtime.loadModel` does through the URL
@@ -365,6 +379,14 @@ export class Environment {
365
379
  // and compilation surfaces its own error.
366
380
  const gateModel = pkg.getModel(modelName);
367
381
  if (gateModel) {
382
+ // Query boundary first (the *what* axis): /compile compiles ad-hoc
383
+ // text against a model, so gate it like an ad-hoc query — a
384
+ // non-`explores` model file, or text whose surface-resolved target
385
+ // is a non-curated model source (under queryableSources:
386
+ // "declared"), is rejected with a generic 404 before compilation
387
+ // can leak schema/SQL. Text the early gate can't pin is settled by
388
+ // the compiled backstop below.
389
+ gateModel.assertQueryBoundaryEarly(undefined, undefined, source);
368
390
  await gateModel.assertAuthorizedForText(source, givens ?? {});
369
391
  }
370
392
 
@@ -392,8 +414,8 @@ export class Environment {
392
414
  // gate or extract beyond the early text gate already applied.
393
415
  }
394
416
 
395
- // Compiled-source backstopruns REGARDLESS of includeSql. It gates
396
- // the source the COMPILED final query actually reads, closing
417
+ // Compiled-source backstopsrun REGARDLESS of includeSql. They
418
+ // gate the source the COMPILED final query actually reads, closing
397
419
  // named-query / multi-statement indirection the early surface-syntax
398
420
  // gate misses (e.g. `run: ungated\nrun: gated` — the early gate only
399
421
  // matches the FIRST `run:`, but the LAST statement is what executes).
@@ -401,9 +423,26 @@ export class Environment {
401
423
  // (field-not-found errors leak its columns), so this must not be
402
424
  // conditional on SQL extraction. (A `source: x is gated` alias makes
403
425
  // a new ungated source — that's the documented "extend doesn't
404
- // inherit authorize" footgun, the same as the query path.) Only run
405
- // when the model declares gates so ungated compiles don't pay for
406
- // the extra final-query compile.
426
+ // inherit authorize" footgun, the same as the query path.)
427
+
428
+ // Boundary backstop (the *what* axis, 404) before the authorize
429
+ // one (the *who* axis, 403). /compile text is always ad-hoc — the
430
+ // early gate can only positively deny, never fully clear — so the
431
+ // compiled final query's run target is the authority. Self-gates
432
+ // internally (no-ops when the boundary is inert: "all" / no
433
+ // explores), so it is deliberately NOT guarded by hasAuthorize().
434
+ // Text that compiles only source definitions (no final query) has
435
+ // no run target and nothing to gate.
436
+ if (queryMaterializer && gateModel) {
437
+ await gateModel.assertQueryBoundaryForRunnable(
438
+ queryMaterializer,
439
+ source,
440
+ );
441
+ }
442
+
443
+ // Authorize backstop (the *who* axis, 403). Only run when the model
444
+ // declares gates so ungated compiles don't pay for the extra
445
+ // final-query compile.
407
446
  if (queryMaterializer && gateModel?.hasAuthorize()) {
408
447
  await gateModel.assertAuthorizedForRunnable(
409
448
  queryMaterializer,
@@ -792,6 +831,7 @@ export class Environment {
792
831
  packagePath,
793
832
  () => this.malloyConfig.malloyConfig,
794
833
  );
834
+ await this.bindManifestIfConfigured(_package);
795
835
  if (existingPackage !== undefined && reload) {
796
836
  this.retireConnectionGeneration(`package ${packageName}`, () =>
797
837
  existingPackage.getMalloyConfig().shutdown("close"),
@@ -897,6 +937,7 @@ export class Environment {
897
937
  public async installPackage(
898
938
  packageName: string,
899
939
  downloader: (stagingPath: string) => Promise<void>,
940
+ validate?: (pkg: Package) => string | undefined,
900
941
  ): Promise<Package> {
901
942
  assertSafePackageName(packageName);
902
943
  const stagingPath = this.allocateStagingPath(packageName);
@@ -963,6 +1004,15 @@ export class Environment {
963
1004
  canonicalPath,
964
1005
  () => this.malloyConfig.malloyConfig,
965
1006
  );
1007
+ // Strict-reject hook (publish/update only — reload passes no
1008
+ // validator and stays fail-safe). Throw INSIDE the try so the
1009
+ // catch below rolls the swap back: the just-installed tree is
1010
+ // wiped and the retired tree (if any) is restored, so a rejected
1011
+ // publish/update never leaves the bad tree served on disk.
1012
+ const validationMsg = validate?.(newPackage);
1013
+ if (validationMsg) {
1014
+ throw new BadRequestError(validationMsg);
1015
+ }
966
1016
  logger.debug("install.phase2.committed", {
967
1017
  environmentName: this.environmentName,
968
1018
  packageName,
@@ -1008,6 +1058,11 @@ export class Environment {
1008
1058
  throw err;
1009
1059
  }
1010
1060
 
1061
+ // Best-effort manifest bind happens after the swap commits, outside the
1062
+ // rollback window: a manifest that can't be fetched must not undo an
1063
+ // otherwise-successful install (the package serves live instead).
1064
+ await this.bindManifestIfConfigured(newPackage);
1065
+
1011
1066
  this.packages.set(packageName, newPackage);
1012
1067
  this.setPackageStatus(packageName, PackageStatus.SERVING);
1013
1068
 
@@ -1062,6 +1117,47 @@ export class Environment {
1062
1117
  });
1063
1118
  }
1064
1119
 
1120
+ /**
1121
+ * If the freshly-loaded package declares a `manifestLocation`, fetch the
1122
+ * control-plane-computed build manifest and rebind its models so persist
1123
+ * references resolve to the materialized tables. Best-effort: a fetch/bind
1124
+ * failure logs a warning and leaves the package serving live (the models are
1125
+ * already loaded without a manifest). Callers must hold the package lock —
1126
+ * this rebinds `pkg` in place rather than re-entering {@link withPackageLock}.
1127
+ */
1128
+ private async bindManifestIfConfigured(pkg: Package): Promise<void> {
1129
+ const manifestLocation = pkg.getPackageMetadata().manifestLocation;
1130
+ if (!manifestLocation) {
1131
+ return;
1132
+ }
1133
+ await this.bindManifest(pkg, manifestLocation);
1134
+ }
1135
+
1136
+ /** Fetch + bind a specific manifest URI onto an already-loaded package. */
1137
+ private async bindManifest(
1138
+ pkg: Package,
1139
+ manifestLocation: string,
1140
+ ): Promise<void> {
1141
+ const packageName = pkg.getPackageName();
1142
+ try {
1143
+ const entries = await fetchManifestEntries(manifestLocation);
1144
+ await pkg.reloadAllModels(entries);
1145
+ logger.info("Bound build manifest to package", {
1146
+ environmentName: this.environmentName,
1147
+ packageName,
1148
+ manifestLocation,
1149
+ entryCount: Object.keys(entries).length,
1150
+ });
1151
+ } catch (err) {
1152
+ logger.warn("Failed to bind build manifest; serving live", {
1153
+ environmentName: this.environmentName,
1154
+ packageName,
1155
+ manifestLocation,
1156
+ error: err instanceof Error ? err.message : String(err),
1157
+ });
1158
+ }
1159
+ }
1160
+
1065
1161
  /**
1066
1162
  * Read a model's source text from disk, holding the per-package mutex
1067
1163
  * so the read is serialized against {@link installPackage} /
@@ -1086,7 +1182,13 @@ export class Environment {
1086
1182
 
1087
1183
  private async writePackageManifest(
1088
1184
  packageName: string,
1089
- metadata: { name: string; description?: string },
1185
+ metadata: {
1186
+ name: string;
1187
+ description?: string;
1188
+ explores?: string[];
1189
+ queryableSources?: "declared" | "all";
1190
+ manifestLocation?: string | null;
1191
+ },
1090
1192
  ): Promise<void> {
1091
1193
  const packagePath = safeJoinUnderRoot(this.environmentPath, packageName);
1092
1194
  const manifestPath = safeJoinUnderRoot(packagePath, "publisher.json");
@@ -1101,11 +1203,23 @@ export class Environment {
1101
1203
  logger.warn(`Could not read manifest for ${packageName}`);
1102
1204
  }
1103
1205
 
1104
- // Update with new metadata
1206
+ // Update with new metadata. `explores`/`queryableSources` are only
1207
+ // overwritten when the caller explicitly provides them; otherwise the
1208
+ // existing on-disk value is preserved via the spread (an undefined here
1209
+ // must not erase it).
1105
1210
  const updatedManifest = {
1106
1211
  ...existingManifest,
1107
1212
  name: metadata.name,
1108
1213
  description: metadata.description,
1214
+ ...(metadata.explores !== undefined
1215
+ ? { explores: metadata.explores }
1216
+ : {}),
1217
+ ...(metadata.queryableSources !== undefined
1218
+ ? { queryableSources: metadata.queryableSources }
1219
+ : {}),
1220
+ ...(metadata.manifestLocation !== undefined
1221
+ ? { manifestLocation: metadata.manifestLocation }
1222
+ : {}),
1109
1223
  };
1110
1224
 
1111
1225
  // Write back to file
@@ -1132,18 +1246,72 @@ export class Environment {
1132
1246
  if (body.name) {
1133
1247
  _package.setName(body.name);
1134
1248
  }
1249
+ // Preserve `explores` across a metadata PATCH. `setPackageMetadata`
1250
+ // replaces the whole object, so a name/description-only update must
1251
+ // carry the existing discovery surface through — otherwise the
1252
+ // in-memory `explores` is wiped and `listModels()` silently starts
1253
+ // serving every model until the next reload. When the body explicitly
1254
+ // carries `explores`, honor the new set instead.
1255
+ const existing = _package.getPackageMetadata();
1256
+ // Normalize API-body explores through the same helper the worker uses
1257
+ // for on-disk explores, so `["./index.malloy"]` / backslash paths
1258
+ // validate and persist identically regardless of input channel (no
1259
+ // misleading publish-time 400, no publish-vs-reload divergence).
1260
+ const normalizedExplores = body.explores?.map(normalizeModelPath);
1261
+ const explores =
1262
+ normalizedExplores !== undefined
1263
+ ? normalizedExplores
1264
+ : existing.explores;
1265
+ const queryableSources =
1266
+ body.queryableSources !== undefined
1267
+ ? body.queryableSources
1268
+ : existing.queryableSources;
1269
+ // Preserve the existing manifestLocation unless the body explicitly
1270
+ // sets it (including to null, which clears it and reverts to live).
1271
+ const manifestLocation =
1272
+ body.manifestLocation !== undefined
1273
+ ? body.manifestLocation
1274
+ : existing.manifestLocation;
1135
1275
  _package.setPackageMetadata({
1136
1276
  name: body.name,
1137
1277
  description: body.description,
1138
1278
  resource: body.resource,
1139
1279
  location: body.location,
1280
+ explores,
1281
+ queryableSources,
1282
+ manifestLocation,
1140
1283
  });
1141
1284
 
1285
+ // Strict-reject, symmetric with the publish path
1286
+ // (package.controller.addPackage): validate the resulting explores
1287
+ // against the live model set and restore the prior metadata before
1288
+ // rejecting, so a bad update neither persists nor mutates the served
1289
+ // surface.
1290
+ const invalidMsg = _package.formatInvalidExplores();
1291
+ if (invalidMsg) {
1292
+ _package.setPackageMetadata(existing);
1293
+ throw new BadRequestError(invalidMsg);
1294
+ }
1295
+
1142
1296
  await this.writePackageManifest(packageName, {
1143
1297
  name: packageName,
1144
1298
  description: body.description,
1299
+ explores: normalizedExplores,
1300
+ queryableSources: body.queryableSources,
1301
+ manifestLocation: body.manifestLocation,
1145
1302
  });
1146
1303
 
1304
+ // When the body changes manifestLocation, apply it now so the new
1305
+ // binding takes effect without a separate reload: a URI rebinds models
1306
+ // to the materialized tables; null/empty reverts the package to live.
1307
+ if (body.manifestLocation !== undefined) {
1308
+ if (body.manifestLocation) {
1309
+ await this.bindManifest(_package, body.manifestLocation);
1310
+ } else {
1311
+ await _package.reloadAllModels({});
1312
+ }
1313
+ }
1314
+
1147
1315
  return _package.getPackageMetadata();
1148
1316
  });
1149
1317
  }
@@ -1245,6 +1413,38 @@ export class Environment {
1245
1413
  });
1246
1414
  }
1247
1415
 
1416
+ /**
1417
+ * Evict a package from the in-memory caches WITHOUT touching its on-disk
1418
+ * directory — the non-destructive counterpart to {@link deletePackage}.
1419
+ *
1420
+ * Used to roll back a no-location `addPackage` (which registers a
1421
+ * *pre-existing*, user-owned directory) when post-load validation rejects
1422
+ * it: deleting the tree there would destroy content the publisher never
1423
+ * created. This still drains and closes the connections the just-created
1424
+ * `Package` opened, so the duckdb handle isn't leaked.
1425
+ */
1426
+ public async unloadPackage(packageName: string): Promise<void> {
1427
+ assertSafePackageName(packageName);
1428
+ return this.withPackageLock(packageName, async () => {
1429
+ const _package = this.packages.get(packageName);
1430
+ if (!_package) {
1431
+ return;
1432
+ }
1433
+ if (
1434
+ this.packageStatuses.get(packageName)?.status ===
1435
+ PackageStatus.SERVING
1436
+ ) {
1437
+ this.setPackageStatus(packageName, PackageStatus.UNLOADING);
1438
+ }
1439
+ // Same 30s connection drain as deletePackage — just no fs rename/rm.
1440
+ this.retireConnectionGeneration(`package ${packageName}`, () =>
1441
+ _package.getMalloyConfig().shutdown("close"),
1442
+ );
1443
+ this.packages.delete(packageName);
1444
+ this.packageStatuses.delete(packageName);
1445
+ });
1446
+ }
1447
+
1248
1448
  public updateConnections(
1249
1449
  malloyConfig: EnvironmentMalloyConfig,
1250
1450
  _apiConnections?: ApiConnection[],
@@ -11,12 +11,6 @@ import { EnvironmentStore } from "./environment_store";
11
11
 
12
12
  type MockData = Record<string, unknown>;
13
13
 
14
- const initializeDuckLakeCalls: Array<{
15
- environmentId: string;
16
- environmentName: string;
17
- config: { catalogUrl: string; dataPath: string };
18
- }> = [];
19
-
20
14
  mock.module("../storage/StorageManager", () => {
21
15
  return {
22
16
  StorageManager: class MockStorageManager {
@@ -24,18 +18,6 @@ mock.module("../storage/StorageManager", () => {
24
18
  return;
25
19
  }
26
20
 
27
- async initializeDuckLakeForEnvironment(
28
- environmentId: string,
29
- environmentName: string,
30
- config: { catalogUrl: string; dataPath: string },
31
- ): Promise<void> {
32
- initializeDuckLakeCalls.push({
33
- environmentId,
34
- environmentName,
35
- config,
36
- });
37
- }
38
-
39
21
  getRepository() {
40
22
  return {
41
23
  // ===== PROJECT METHODS =====
@@ -488,69 +470,6 @@ describe("EnvironmentStore Service", () => {
488
470
  expect(readmeContent).toBe("Updated README content");
489
471
  });
490
472
 
491
- it("should propagate materializationStorage on addEnvironment for new environment", async () => {
492
- writeFileSync(
493
- path.join(serverRootPath, "publisher.config.json"),
494
- JSON.stringify({ frozenConfig: false, environments: [] }),
495
- );
496
-
497
- await environmentStore.finishedInitialization;
498
-
499
- const materializationStorage = {
500
- catalogUrl:
501
- "postgres:host=localhost port=5432 dbname=ducklake user=u password=p",
502
- dataPath: "gs://test-bucket",
503
- };
504
-
505
- initializeDuckLakeCalls.length = 0;
506
- const project = await environmentStore.addEnvironment({
507
- name: projectName,
508
- materializationStorage,
509
- });
510
-
511
- expect(project.metadata.materializationStorage).toEqual(
512
- materializationStorage,
513
- );
514
- expect(initializeDuckLakeCalls).toHaveLength(1);
515
- expect(initializeDuckLakeCalls[0].config).toEqual(materializationStorage);
516
- });
517
-
518
- it("should propagate materializationStorage on update", async () => {
519
- const projectPath = path.join(serverRootPath, projectName);
520
- mkdirSync(projectPath, { recursive: true });
521
- writeFileSync(
522
- path.join(projectPath, "publisher.json"),
523
- JSON.stringify({ name: projectName, description: "Test package" }),
524
- );
525
- writeFileSync(
526
- path.join(serverRootPath, "publisher.config.json"),
527
- JSON.stringify({
528
- frozenConfig: false,
529
- environments: [
530
- {
531
- name: projectName,
532
- packages: [{ name: projectName, location: projectPath }],
533
- },
534
- ],
535
- }),
536
- );
537
-
538
- await environmentStore.finishedInitialization;
539
- const project = await environmentStore.getEnvironment(projectName);
540
-
541
- const materializationStorage = {
542
- catalogUrl:
543
- "postgres:host=localhost port=5432 dbname=ducklake user=u password=p",
544
- dataPath: "gs://test-bucket",
545
- };
546
-
547
- await project.update({ name: projectName, materializationStorage });
548
-
549
- expect(project.metadata.materializationStorage).toEqual(
550
- materializationStorage,
551
- );
552
- });
553
-
554
473
  it(
555
474
  "should handle project reload",
556
475
  async () => {
@@ -452,25 +452,6 @@ export class EnvironmentStore {
452
452
  dbEnvironment = await repository.createEnvironment(environmentData);
453
453
  }
454
454
 
455
- // Initialize DuckLake manifest storage if configured on the environment.
456
- const materializationStorage = environment.metadata
457
- ?.materializationStorage as
458
- | { catalogUrl?: string; dataPath?: string }
459
- | undefined;
460
- if (
461
- materializationStorage?.catalogUrl &&
462
- materializationStorage?.dataPath
463
- ) {
464
- await this.storageManager.initializeDuckLakeForEnvironment(
465
- dbEnvironment.id,
466
- dbEnvironment.name,
467
- {
468
- catalogUrl: materializationStorage.catalogUrl,
469
- dataPath: materializationStorage.dataPath,
470
- },
471
- );
472
- }
473
-
474
455
  return dbEnvironment;
475
456
  }
476
457
 
@@ -938,10 +919,6 @@ export class EnvironmentStore {
938
919
 
939
920
  if (!newEnvironment.metadata) newEnvironment.metadata = {};
940
921
  newEnvironment.metadata.location = absoluteEnvironmentPath;
941
- if (environment.materializationStorage !== undefined) {
942
- newEnvironment.metadata.materializationStorage =
943
- environment.materializationStorage;
944
- }
945
922
 
946
923
  this.environments.set(environmentName, newEnvironment);
947
924