@malloy-publisher/server 0.0.231 → 0.0.232
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.docker.md +4 -0
- package/dist/app/api-doc.yaml +74 -3
- package/dist/app/assets/{EnvironmentPage-wa_EPkwK.js → EnvironmentPage-DXEaZIPx.js} +1 -1
- package/dist/app/assets/{HomePage-jnCrupQp.js → HomePage-kofsqpZt.js} +1 -1
- package/dist/app/assets/{LightMode-DYbwNULZ.js → LightMode-CNhIlIlJ.js} +1 -1
- package/dist/app/assets/{MainPage-CuJLrPNI.js → MainPage-Bgqo8jCy.js} +1 -1
- package/dist/app/assets/{MaterializationsPage-D_67x2ee.js → MaterializationsPage-CgBlgGz2.js} +1 -1
- package/dist/app/assets/{ModelPage-D5JtAWqR.js → ModelPage-B0TjoDtf.js} +1 -1
- package/dist/app/assets/{PackagePage-BRwtqUSG.js → PackagePage-BL8vnFj1.js} +1 -1
- package/dist/app/assets/{RouteError-CBNNrnSD.js → RouteError-BzPby0X2.js} +1 -1
- package/dist/app/assets/{ThemeEditorPage-CTCeBneA.js → ThemeEditorPage-CTEP_9r3.js} +1 -1
- package/dist/app/assets/{WorkbookPage-SN6f1RBm.js → WorkbookPage-BwM3BmKw.js} +1 -1
- package/dist/app/assets/{core-Dp3q5Ieu.es-CD5FvM2s.js → core-CK68iv6w.es-CpRxXBt7.js} +1 -1
- package/dist/app/assets/{index-C_tJstcx.js → index-B33zGctF.js} +15 -15
- package/dist/app/assets/{index-DU4r7GdU.js → index-BabP-V-S.js} +346 -321
- package/dist/app/assets/{index-B3Nn8Vm2.js → index-BkiWKaAF.js} +266 -265
- package/dist/app/assets/{index-BLCx1EdC.js → index-CmkW1MiE.js} +1 -1
- package/dist/app/assets/{index-CfmBVB6M.js → index-tXJXwdyj.js} +1 -1
- package/dist/app/index.html +1 -1
- package/dist/instrumentation.mjs +2 -0
- package/dist/package_load_worker.mjs +11 -1
- package/dist/server.mjs +3186 -433
- package/package.json +12 -12
- package/scripts/bake-duckdb-extensions.js +4 -1
- package/src/config.spec.ts +39 -0
- package/src/config.ts +135 -0
- package/src/controller/materialization.controller.spec.ts +62 -0
- package/src/controller/materialization.controller.ts +15 -0
- package/src/controller/package.controller.spec.ts +6 -0
- package/src/controller/package.controller.ts +7 -2
- package/src/errors.ts +19 -0
- package/src/logger.spec.ts +18 -1
- package/src/logger.ts +3 -1
- package/src/materialization_metrics.spec.ts +89 -4
- package/src/materialization_metrics.ts +155 -5
- package/src/mcp/skills/skills_bundle.json +1 -1
- package/src/mcp/tools/embedding_index.spec.ts +1236 -0
- package/src/mcp/tools/embedding_index.ts +808 -0
- package/src/mcp/tools/get_context_eval.ts +194 -45
- package/src/mcp/tools/get_context_tool.spec.ts +295 -2
- package/src/mcp/tools/get_context_tool.ts +159 -10
- package/src/pg_helpers.spec.ts +201 -0
- package/src/pg_helpers.ts +44 -5
- package/src/server.ts +24 -0
- package/src/service/build_plan.spec.ts +128 -2
- package/src/service/build_plan.ts +239 -17
- package/src/service/connection.ts +263 -7
- package/src/service/connection_config.spec.ts +48 -0
- package/src/service/connection_config.ts +21 -8
- package/src/service/connection_federation.spec.ts +184 -0
- package/src/service/embedding_provider.spec.ts +329 -0
- package/src/service/embedding_provider.ts +236 -0
- package/src/service/environment.ts +274 -12
- package/src/service/environment_store.spec.ts +678 -3
- package/src/service/environment_store.ts +449 -33
- package/src/service/environment_store_clone.spec.ts +350 -0
- package/src/service/manifest_loader.spec.ts +68 -13
- package/src/service/manifest_loader.ts +67 -19
- package/src/service/materialization_build_session.spec.ts +435 -0
- package/src/service/materialization_build_session.ts +681 -0
- package/src/service/materialization_eligibility.spec.ts +158 -0
- package/src/service/materialization_eligibility.ts +305 -0
- package/src/service/materialization_serve_transform.spec.ts +1003 -0
- package/src/service/materialization_serve_transform.ts +779 -0
- package/src/service/materialization_service.spec.ts +774 -7
- package/src/service/materialization_service.ts +1107 -42
- package/src/service/materialization_test_fixtures.ts +7 -0
- package/src/service/model.spec.ts +207 -0
- package/src/service/model.ts +540 -52
- package/src/service/model_storage_serve.spec.ts +193 -0
- package/src/service/model_storage_serve_joins.spec.ts +193 -0
- package/src/service/package.spec.ts +196 -0
- package/src/service/package.ts +385 -17
- package/src/service/persistence_policy.spec.ts +109 -0
- package/src/storage/duckdb/schema.ts +37 -0
- package/tests/fixtures/xlsx/database.xlsx +0 -0
- package/tests/integration/first_boot/readiness_line.integration.spec.ts +177 -0
- package/tests/integration/materialization/manifest_binding.integration.spec.ts +104 -0
- package/tests/integration/mcp/mcp_get_context_semantic.integration.spec.ts +235 -0
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
PackageNotFoundError,
|
|
21
21
|
ServiceUnavailableError,
|
|
22
22
|
} from "../errors";
|
|
23
|
+
import { getPersistStorageMode } from "../config";
|
|
23
24
|
import { logger } from "../logger";
|
|
24
25
|
import { redactPgSecrets } from "../pg_helpers";
|
|
25
26
|
import { recordManifestBind } from "../materialization_metrics";
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
29
30
|
assertSafeRelativeModelPath,
|
|
30
31
|
safeJoinUnderRoot,
|
|
31
32
|
} from "../path_safety";
|
|
32
|
-
import { FreshnessManifest } from "../storage/DatabaseInterface";
|
|
33
|
+
import { FreshnessManifest, ManifestEntry } from "../storage/DatabaseInterface";
|
|
33
34
|
import { URL_READER } from "../utils";
|
|
34
35
|
import {
|
|
35
36
|
buildEnvironmentMalloyConfig,
|
|
@@ -37,7 +38,11 @@ import {
|
|
|
37
38
|
EnvironmentMalloyConfig,
|
|
38
39
|
InternalConnection,
|
|
39
40
|
} from "./connection";
|
|
40
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
fetchManifestEntries,
|
|
43
|
+
splitManifestEntries,
|
|
44
|
+
type FetchedManifest,
|
|
45
|
+
} from "./manifest_loader";
|
|
41
46
|
import { ApiConnection } from "./model";
|
|
42
47
|
import { Package } from "./package";
|
|
43
48
|
import type { PackageMemoryGovernor } from "./package_memory_governor";
|
|
@@ -167,6 +172,17 @@ export class Environment {
|
|
|
167
172
|
private apiConnections: ApiConnection[];
|
|
168
173
|
private environmentPath: string;
|
|
169
174
|
private environmentName: string;
|
|
175
|
+
// Resolves a package's latest persisted materialization manifest entries
|
|
176
|
+
// (the full map — colocated tableName entries AND `storage=` cross-connection
|
|
177
|
+
// entries), so serve routing for BOTH tiers is re-established when a package
|
|
178
|
+
// (re)loads — e.g. after a worker restart — instead of only when a build's
|
|
179
|
+
// auto-load runs. Injected by the EnvironmentStore, which owns the
|
|
180
|
+
// materialization repository. Undefined ⇒ no re-bind on load (routing then
|
|
181
|
+
// depends on a fresh build, the old behavior). See
|
|
182
|
+
// {@link rebindServeBindingsFromLocalStore}.
|
|
183
|
+
private storageBindingResolver?: (
|
|
184
|
+
packageName: string,
|
|
185
|
+
) => Promise<Record<string, ManifestEntry>>;
|
|
170
186
|
public metadata: ApiEnvironment;
|
|
171
187
|
// The shared memory governor that consults process RSS. Optional —
|
|
172
188
|
// when null the gate is a no-op and the environment behaves exactly
|
|
@@ -663,11 +679,10 @@ export class Environment {
|
|
|
663
679
|
// the same reason). A log line was the old destination; this
|
|
664
680
|
// one is an HTTP response body.
|
|
665
681
|
//
|
|
666
|
-
//
|
|
667
|
-
//
|
|
668
|
-
//
|
|
669
|
-
// supplied verbatim in config
|
|
670
|
-
// through. Widen the helper rather than trusting this call.
|
|
682
|
+
// redactPgSecrets covers both keyword-form `password=` (what
|
|
683
|
+
// buildPgConnectionString emits) and URI userinfo
|
|
684
|
+
// (`postgres://user:pass@host`), so a URL-form connectionString
|
|
685
|
+
// supplied verbatim in config is redacted here too.
|
|
671
686
|
this.failedPackages.set(
|
|
672
687
|
packageName,
|
|
673
688
|
redactPgSecrets(
|
|
@@ -803,6 +818,88 @@ export class Environment {
|
|
|
803
818
|
this.memoryGovernor = governor;
|
|
804
819
|
}
|
|
805
820
|
|
|
821
|
+
/**
|
|
822
|
+
* Inject the resolver that fetches a package's latest persisted
|
|
823
|
+
* materialization entries — both tiers (see {@link storageBindingResolver}).
|
|
824
|
+
*/
|
|
825
|
+
public setStorageBindingResolver(
|
|
826
|
+
resolver: (packageName: string) => Promise<Record<string, ManifestEntry>>,
|
|
827
|
+
): void {
|
|
828
|
+
this.storageBindingResolver = resolver;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Re-establish a package's serve routing from its latest persisted
|
|
833
|
+
* materialization when it (re)loads, so serving survives a restart — both
|
|
834
|
+
* tiers, not just one. Serve bindings are otherwise in-memory, set only by a
|
|
835
|
+
* build's post-run auto-load, so a worker restart silently reverted a
|
|
836
|
+
* materialized source to serving live until the next build. Runs beside
|
|
837
|
+
* {@link bindManifestIfConfigured} — the same "bind serve state on load" step.
|
|
838
|
+
* Best-effort: a lookup failure logs and leaves the package serving live (a
|
|
839
|
+
* subsequent build will bind it).
|
|
840
|
+
*
|
|
841
|
+
* Both tiers are restored from the SAME persisted manifest, split by
|
|
842
|
+
* {@link splitManifestEntries}, mirroring {@link bindManifest}:
|
|
843
|
+
* - **colocated** (same-connection) → {@link Package.bindColocatedServeManifest},
|
|
844
|
+
* applied as a per-query `buildManifest` override at serve time (no
|
|
845
|
+
* recompile — the load-time compile already produced flag-carrying models).
|
|
846
|
+
* Independent of `PERSIST_STORAGE_MODE`: colocated is the v0 path and is
|
|
847
|
+
* not gated by the storage kill switch (a plain `#@ persist` materializes
|
|
848
|
+
* and serves even when the tier is `off`), so its routing is restored
|
|
849
|
+
* regardless of mode.
|
|
850
|
+
* - **storage=** (cross-connection) → {@link Package.bindStorageServeBindings},
|
|
851
|
+
* the virtual-source transform. Restored only when the tier is not `off`:
|
|
852
|
+
* storage serve routing requires the tier on, so an off deployment skips it.
|
|
853
|
+
*
|
|
854
|
+
* Skipped entirely when the package has a bound `manifestLocation`: the two
|
|
855
|
+
* binding producers (this local store and the host's fetched manifest, applied
|
|
856
|
+
* by {@link bindManifest}) are mutually exclusive by manifest presence, and the
|
|
857
|
+
* host is authoritative when one is set. An orchestrated publisher STILL
|
|
858
|
+
* persists its own local materialization records (the host triggers builds
|
|
859
|
+
* through it), so without this guard the local rebind — which runs AFTER
|
|
860
|
+
* {@link bindManifestIfConfigured} on load — would overwrite the host's
|
|
861
|
+
* bindings with a possibly-staler local generation.
|
|
862
|
+
*/
|
|
863
|
+
private async rebindServeBindingsFromLocalStore(
|
|
864
|
+
pkg: Package,
|
|
865
|
+
): Promise<void> {
|
|
866
|
+
if (!this.storageBindingResolver) return;
|
|
867
|
+
// Host-authoritative: a bound manifestLocation means bindManifest already
|
|
868
|
+
// supplied both tiers' serve bindings from the host's manifest; the local
|
|
869
|
+
// store must not clobber them (mutually-exclusive binding sources).
|
|
870
|
+
if (pkg.getPackageMetadata().manifestLocation) return;
|
|
871
|
+
const packageName = pkg.getPackageName();
|
|
872
|
+
try {
|
|
873
|
+
const rawEntries = await this.storageBindingResolver(packageName);
|
|
874
|
+
if (Object.keys(rawEntries).length === 0) return;
|
|
875
|
+
const { tableNameManifest, storageEntries } = splitManifestEntries(
|
|
876
|
+
rawEntries,
|
|
877
|
+
`local store (package ${packageName})`,
|
|
878
|
+
);
|
|
879
|
+
// Colocated: restore regardless of PERSIST_STORAGE_MODE (v0 path, not
|
|
880
|
+
// gated by the storage kill switch).
|
|
881
|
+
if (Object.keys(tableNameManifest).length > 0) {
|
|
882
|
+
pkg.bindColocatedServeManifest(tableNameManifest);
|
|
883
|
+
}
|
|
884
|
+
// Storage=: only meaningful when the tier is not off (serve routing to
|
|
885
|
+
// the external store requires it). Ships dark otherwise.
|
|
886
|
+
if (
|
|
887
|
+
getPersistStorageMode() !== "off" &&
|
|
888
|
+
Object.keys(storageEntries).length > 0
|
|
889
|
+
) {
|
|
890
|
+
pkg.bindStorageServeBindings(storageEntries);
|
|
891
|
+
}
|
|
892
|
+
} catch (err) {
|
|
893
|
+
logger.warn(
|
|
894
|
+
"Failed to rebind serve bindings from local store on load",
|
|
895
|
+
{
|
|
896
|
+
packageName,
|
|
897
|
+
error: err instanceof Error ? err.message : String(err),
|
|
898
|
+
},
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
806
903
|
/**
|
|
807
904
|
* Choke-point check called from every code path that would allocate
|
|
808
905
|
* a *new* package into the in-memory map (lazy load on cache miss,
|
|
@@ -930,6 +1027,7 @@ export class Environment {
|
|
|
930
1027
|
() => this.malloyConfig.malloyConfig,
|
|
931
1028
|
);
|
|
932
1029
|
await this.bindManifestIfConfigured(_package);
|
|
1030
|
+
await this.rebindServeBindingsFromLocalStore(_package);
|
|
933
1031
|
if (existingPackage !== undefined && reload) {
|
|
934
1032
|
this.retireConnectionGeneration(`package ${packageName}`, () =>
|
|
935
1033
|
existingPackage.getMalloyConfig().shutdown("close"),
|
|
@@ -1210,6 +1308,7 @@ export class Environment {
|
|
|
1210
1308
|
// rollback window: a manifest that can't be fetched must not undo an
|
|
1211
1309
|
// otherwise-successful install (the package serves live instead).
|
|
1212
1310
|
await this.bindManifestIfConfigured(newPackage);
|
|
1311
|
+
await this.rebindServeBindingsFromLocalStore(newPackage);
|
|
1213
1312
|
|
|
1214
1313
|
this.packages.set(packageName, newPackage);
|
|
1215
1314
|
this.setPackageStatus(packageName, PackageStatus.SERVING);
|
|
@@ -1250,6 +1349,19 @@ export class Environment {
|
|
|
1250
1349
|
* holding the per-package mutex for the duration of the disk reads.
|
|
1251
1350
|
* Replaces direct `Package.reloadAllModels` calls from outside
|
|
1252
1351
|
* `Environment`.
|
|
1352
|
+
*
|
|
1353
|
+
* Skips the recompile when there is nothing to substitute now AND nothing was
|
|
1354
|
+
* substituted before — the same condition {@link bindManifest} applies to a
|
|
1355
|
+
* pure-storage manifest flip, and for the same reason: a same-connection
|
|
1356
|
+
* `tableName` manifest is resolved at COMPILE time, so an empty one over an
|
|
1357
|
+
* already-empty one changes nothing a recompile could express. The previous
|
|
1358
|
+
* state has to be checked too, because a package that just dropped its last
|
|
1359
|
+
* colocated entry still has to recompile to revert the substitution.
|
|
1360
|
+
*
|
|
1361
|
+
* This matters because every materialization run lands here. Without the
|
|
1362
|
+
* guard a `storage=`-only package — or one with no persist sources at all —
|
|
1363
|
+
* paid a full package recompile per run: measured at ~1.1MB of RSS per run on
|
|
1364
|
+
* the production image, never reclaimed, against a `main` build that reclaims.
|
|
1253
1365
|
*/
|
|
1254
1366
|
public async reloadAllModelsForPackage(
|
|
1255
1367
|
packageName: string,
|
|
@@ -1263,10 +1375,107 @@ export class Environment {
|
|
|
1263
1375
|
`Package ${packageName} is not loaded`,
|
|
1264
1376
|
);
|
|
1265
1377
|
}
|
|
1378
|
+
const has = Object.keys(manifest).length > 0;
|
|
1379
|
+
const had = pkg.hasBoundTableNameManifest();
|
|
1380
|
+
if (!has && !had) return;
|
|
1266
1381
|
await pkg.reloadAllModels(manifest);
|
|
1267
1382
|
});
|
|
1268
1383
|
}
|
|
1269
1384
|
|
|
1385
|
+
/**
|
|
1386
|
+
* Bind a package's `storage=` serve bindings from a build's FULL manifest
|
|
1387
|
+
* entries (carrying `storageConnectionName` + captured `schema`), so a query
|
|
1388
|
+
* against a materialized-into-storage source can be routed through the
|
|
1389
|
+
* virtual-source serve transform. Distinct from
|
|
1390
|
+
* {@link reloadAllModelsForPackage}, which binds the tableName-only manifest
|
|
1391
|
+
* for same-connection persistence. No-op (logged) if the package isn't
|
|
1392
|
+
* loaded — best-effort, like the post-build auto-load itself.
|
|
1393
|
+
*/
|
|
1394
|
+
public async bindPackageStorageServeBindings(
|
|
1395
|
+
packageName: string,
|
|
1396
|
+
entries: Record<string, ManifestEntry>,
|
|
1397
|
+
): Promise<void> {
|
|
1398
|
+
assertSafePackageName(packageName);
|
|
1399
|
+
return this.withPackageLock(packageName, async () => {
|
|
1400
|
+
const pkg = this.packages.get(packageName);
|
|
1401
|
+
if (!pkg) {
|
|
1402
|
+
logger.warn(
|
|
1403
|
+
"Cannot bind storage serve bindings: package not loaded",
|
|
1404
|
+
{ packageName },
|
|
1405
|
+
);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
// Host-authoritative: when a manifestLocation is bound, the host's
|
|
1409
|
+
// manifest is the sole source of storage serve bindings (see
|
|
1410
|
+
// bindManifest + rebindServeBindingsFromLocalStore' matching guard). This
|
|
1411
|
+
// method is the LOCAL-store binding path (post-build auto-load and the
|
|
1412
|
+
// post-delete rebind); on an orchestrated deployment the publisher
|
|
1413
|
+
// still writes its own local materialization records, so without this
|
|
1414
|
+
// guard a build's auto-load or a routine retire of a superseded record
|
|
1415
|
+
// would clobber the host's bindings with a possibly-staler local
|
|
1416
|
+
// generation. Standalone (no manifestLocation) is unaffected.
|
|
1417
|
+
if (pkg.getPackageMetadata().manifestLocation) {
|
|
1418
|
+
logger.debug(
|
|
1419
|
+
"Skipping local-store storage serve binding: manifestLocation " +
|
|
1420
|
+
"is bound (host authoritative)",
|
|
1421
|
+
{ packageName },
|
|
1422
|
+
);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
pkg.bindStorageServeBindings(entries);
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/**
|
|
1430
|
+
* Re-establish a package's COLOCATED (same-connection) serve routing from a
|
|
1431
|
+
* FreshnessManifest, holding the package lock — the colocated analogue of
|
|
1432
|
+
* {@link bindPackageStorageServeBindings}, used by the post-delete rebind so a
|
|
1433
|
+
* reclaimed colocated table is not left routed. No-op (logged) if the package
|
|
1434
|
+
* isn't loaded, and skipped when a `manifestLocation` is bound (host
|
|
1435
|
+
* authoritative, same as the storage variant). An empty manifest reverts to
|
|
1436
|
+
* serving live.
|
|
1437
|
+
*
|
|
1438
|
+
* Unlike the storage tier — and unlike the load-time colocated rebind
|
|
1439
|
+
* ({@link Package.bindColocatedServeManifest}, which restores routing onto
|
|
1440
|
+
* FRESHLY-compiled models with a per-query overlay, no recompile) — this path
|
|
1441
|
+
* runs against models that a build's auto-load already recompiled with the
|
|
1442
|
+
* substitution BAKED IN. Clearing the per-query overlay would not strip a
|
|
1443
|
+
* baked substitution, so reverting/rebinding here requires a recompile
|
|
1444
|
+
* ({@link Package.reloadAllModels}), exactly as {@link bindManifest} does for
|
|
1445
|
+
* colocated. Skipped when there is nothing to substitute AND nothing was
|
|
1446
|
+
* previously substituted to clear (no needless recompile for a storage-only or
|
|
1447
|
+
* never-materialized package).
|
|
1448
|
+
*/
|
|
1449
|
+
public async bindPackageColocatedServeManifest(
|
|
1450
|
+
packageName: string,
|
|
1451
|
+
entries: FreshnessManifest,
|
|
1452
|
+
): Promise<void> {
|
|
1453
|
+
assertSafePackageName(packageName);
|
|
1454
|
+
return this.withPackageLock(packageName, async () => {
|
|
1455
|
+
const pkg = this.packages.get(packageName);
|
|
1456
|
+
if (!pkg) {
|
|
1457
|
+
logger.warn(
|
|
1458
|
+
"Cannot bind colocated serve manifest: package not loaded",
|
|
1459
|
+
{ packageName },
|
|
1460
|
+
);
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
if (pkg.getPackageMetadata().manifestLocation) {
|
|
1464
|
+
logger.debug(
|
|
1465
|
+
"Skipping local-store colocated serve binding: manifestLocation " +
|
|
1466
|
+
"is bound (host authoritative)",
|
|
1467
|
+
{ packageName },
|
|
1468
|
+
);
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
const hasColocated = Object.keys(entries).length > 0;
|
|
1472
|
+
const hadColocated = pkg.hasBoundTableNameManifest();
|
|
1473
|
+
if (hasColocated || hadColocated) {
|
|
1474
|
+
await pkg.reloadAllModels(entries);
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1270
1479
|
/**
|
|
1271
1480
|
* If the freshly-loaded package declares a `manifestLocation`, fetch the
|
|
1272
1481
|
* control-plane-computed build manifest and rebind its models so persist
|
|
@@ -1294,16 +1503,47 @@ export class Environment {
|
|
|
1294
1503
|
// manifest store must not block serving indefinitely — bound is the
|
|
1295
1504
|
// intended state, live is the degraded fallback. Race the fetch against
|
|
1296
1505
|
// a timeout and fall back to live on either failure.
|
|
1297
|
-
const
|
|
1506
|
+
const { tableNameManifest, storageEntries } =
|
|
1298
1507
|
await this.fetchManifestEntriesWithTimeout(manifestLocation);
|
|
1299
|
-
|
|
1508
|
+
|
|
1509
|
+
// Tier split. Storage entries (cross-connection) bind as serve bindings
|
|
1510
|
+
// WITHOUT a recompile — they apply to the already-compiled models via
|
|
1511
|
+
// Model.setServeBindings. The host is the authoritative producer here,
|
|
1512
|
+
// so this also supersedes the local-store rebind (see
|
|
1513
|
+
// rebindServeBindingsFromLocalStore, which no-ops when manifestLocation
|
|
1514
|
+
// is set).
|
|
1515
|
+
// Bind whenever there ARE storage entries OR there WERE (bindStorage-
|
|
1516
|
+
// ServeBindings with the now-empty set clears them): a manifest whose
|
|
1517
|
+
// storage entries vanished must drop the old bindings, not leave them
|
|
1518
|
+
// routing at a table the host no longer vouches for. Mirrors the
|
|
1519
|
+
// hadColocated guard below.
|
|
1520
|
+
const hasStorage = Object.keys(storageEntries).length > 0;
|
|
1521
|
+
const hadStorage = pkg.hasStorageServeBindings();
|
|
1522
|
+
if (hasStorage || hadStorage) {
|
|
1523
|
+
pkg.bindStorageServeBindings(storageEntries);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// colocated entries drive the same-connection tableName substitution, which
|
|
1527
|
+
// is resolved at COMPILE time — so they require a reloadAllModels
|
|
1528
|
+
// recompile (v0's existing cost). Skip the recompile for a pure-storage
|
|
1529
|
+
// manifest flip: only when there is nothing to substitute AND nothing
|
|
1530
|
+
// previously substituted to clear (otherwise a package that just dropped
|
|
1531
|
+
// its last colocated entry must still recompile to revert it).
|
|
1532
|
+
const hasColocated = Object.keys(tableNameManifest).length > 0;
|
|
1533
|
+
const hadColocated = pkg.hasBoundTableNameManifest();
|
|
1534
|
+
if (hasColocated || hadColocated) {
|
|
1535
|
+
await pkg.reloadAllModels(tableNameManifest);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1300
1538
|
pkg.setBoundManifestUri(manifestLocation);
|
|
1301
1539
|
recordManifestBind("success");
|
|
1302
1540
|
logger.info("Bound build manifest to package", {
|
|
1303
1541
|
environmentName: this.environmentName,
|
|
1304
1542
|
packageName,
|
|
1305
1543
|
manifestLocation,
|
|
1306
|
-
|
|
1544
|
+
tableNameEntryCount: Object.keys(tableNameManifest).length,
|
|
1545
|
+
storageEntryCount: Object.keys(storageEntries).length,
|
|
1546
|
+
recompiled: hasColocated || hadColocated,
|
|
1307
1547
|
});
|
|
1308
1548
|
} catch (err) {
|
|
1309
1549
|
pkg.markManifestBindFailed();
|
|
@@ -1327,7 +1567,7 @@ export class Environment {
|
|
|
1327
1567
|
*/
|
|
1328
1568
|
private async fetchManifestEntriesWithTimeout(
|
|
1329
1569
|
manifestLocation: string,
|
|
1330
|
-
): Promise<
|
|
1570
|
+
): Promise<FetchedManifest> {
|
|
1331
1571
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1332
1572
|
const timeout = new Promise<never>((_, reject) => {
|
|
1333
1573
|
timer = setTimeout(
|
|
@@ -1487,7 +1727,7 @@ export class Environment {
|
|
|
1487
1727
|
// policy gate below (a rejection there fails the orchestrated run) or
|
|
1488
1728
|
// reset the persisted policy. `manifestLocation` is deliberately
|
|
1489
1729
|
// different — null there means "clear" (revert to live), which the
|
|
1490
|
-
//
|
|
1730
|
+
// caller's orchestrated build path relies on.
|
|
1491
1731
|
const scopeProvided = body.scope != null;
|
|
1492
1732
|
const materializationProvided = body.materialization != null;
|
|
1493
1733
|
const editingPolicy = scopeProvided || materializationProvided;
|
|
@@ -1551,7 +1791,12 @@ export class Environment {
|
|
|
1551
1791
|
if (body.manifestLocation) {
|
|
1552
1792
|
await this.bindManifest(_package, body.manifestLocation);
|
|
1553
1793
|
} else {
|
|
1794
|
+
// Revert to live: drop the colocated tableName substitution AND the
|
|
1795
|
+
// cross-connection storage serve bindings the prior bindManifest
|
|
1796
|
+
// applied, so no query still routes to a materialized table after
|
|
1797
|
+
// the operator explicitly cleared the manifest.
|
|
1554
1798
|
await _package.reloadAllModels({});
|
|
1799
|
+
_package.bindStorageServeBindings({});
|
|
1555
1800
|
}
|
|
1556
1801
|
}
|
|
1557
1802
|
|
|
@@ -1563,6 +1808,23 @@ export class Environment {
|
|
|
1563
1808
|
return this.packageStatuses.get(packageName);
|
|
1564
1809
|
}
|
|
1565
1810
|
|
|
1811
|
+
/**
|
|
1812
|
+
* Packages this environment is actually serving: registered statuses minus
|
|
1813
|
+
* any recorded as failed or un-mounted. Disjoint from getFailedPackages()
|
|
1814
|
+
* by construction, so the readiness line's packages= and load_errors=
|
|
1815
|
+
* cannot double-count a package that is seeded SERVING at boot and only
|
|
1816
|
+
* pruned later by a side-effect load (which a transient DB or memory-
|
|
1817
|
+
* pressure error can skip). Cheap: no package load is triggered.
|
|
1818
|
+
*/
|
|
1819
|
+
public getServingPackageCount(): number {
|
|
1820
|
+
const failed = this.getFailedPackages();
|
|
1821
|
+
let serving = 0;
|
|
1822
|
+
for (const name of this.packageStatuses.keys()) {
|
|
1823
|
+
if (!failed.has(name)) serving += 1;
|
|
1824
|
+
}
|
|
1825
|
+
return serving;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1566
1828
|
/**
|
|
1567
1829
|
* Record why a configured package's location never mounted, so /status can
|
|
1568
1830
|
* name the real cause instead of the missing-manifest fallout it produces.
|