@malloy-publisher/server 0.0.225 → 0.0.226

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 (38) hide show
  1. package/dist/app/api-doc.yaml +29 -24
  2. package/dist/app/assets/{EnvironmentPage-DYTeXDll.js → EnvironmentPage-DvOJ7L_b.js} +1 -1
  3. package/dist/app/assets/{HomePage-pDK2BPJY.js → HomePage-CXguJsXS.js} +1 -1
  4. package/dist/app/assets/{LightMode-C2bwGPY1.js → LightMode-ZsshUznu.js} +1 -1
  5. package/dist/app/assets/{MainPage-WtBulMH_.js → MainPage-BIe0VwBa.js} +2 -2
  6. package/dist/app/assets/{MaterializationsPage-hMgOtflG.js → MaterializationsPage-BuZ6UJVx.js} +1 -1
  7. package/dist/app/assets/{ModelPage-B2N5kYII.js → ModelPage-DsPf-s8B.js} +1 -1
  8. package/dist/app/assets/{PackagePage-CEN90nQG.js → PackagePage-CEVNAKZa.js} +1 -1
  9. package/dist/app/assets/{RouteError-BG2c5Zf0.js → RouteError-Chn7lL96.js} +1 -1
  10. package/dist/app/assets/{ThemeEditorPage-DNfeUwEZ.js → ThemeEditorPage-DWC_FdNU.js} +1 -1
  11. package/dist/app/assets/{WorkbookPage-NKI1BhFS.js → WorkbookPage-CGrsFz8p.js} +1 -1
  12. package/dist/app/assets/{core-C6anj5c0.es-DDLHqpzt.js → core-vVgoO8IR.es-BD_THWs_.js} +1 -1
  13. package/dist/app/assets/{index-DMQtnaf4.js → index-BioohWQj.js} +1 -1
  14. package/dist/app/assets/{index-CzNqKMVl.js → index-D6YtyiJ0.js} +1 -1
  15. package/dist/app/assets/{index-C6gZ6sSY.js → index-DNUZpnaa.js} +4 -4
  16. package/dist/app/assets/{index-JXgvyZna.js → index-gEWxu09x.js} +1 -1
  17. package/dist/app/index.html +1 -1
  18. package/dist/instrumentation.mjs +71 -15
  19. package/dist/package_load_worker.mjs +83 -16
  20. package/dist/server.mjs +128 -57
  21. package/package.json +1 -1
  22. package/src/controller/package.controller.spec.ts +17 -17
  23. package/src/controller/package.controller.ts +7 -6
  24. package/src/logger.spec.ts +193 -0
  25. package/src/logger.ts +115 -18
  26. package/src/mcp/skills/skills_bundle.json +1 -1
  27. package/src/package_load/package_load_pool.ts +5 -1
  28. package/src/package_load/package_load_worker.ts +7 -0
  29. package/src/package_load/protocol.ts +5 -1
  30. package/src/service/build_plan.spec.ts +42 -60
  31. package/src/service/build_plan.ts +37 -61
  32. package/src/service/environment.ts +4 -0
  33. package/src/service/materialization_test_fixtures.ts +6 -8
  34. package/src/service/package.ts +83 -44
  35. package/src/service/package_manifest.spec.ts +22 -1
  36. package/src/service/package_manifest.ts +29 -0
  37. package/src/service/persistence_policy.spec.ts +234 -0
  38. package/src/service/materialization_cron_gate.spec.ts +0 -181
@@ -81,7 +81,10 @@ import { fileURLToPath, pathToFileURL } from "url";
81
81
 
82
82
  import { ModelCompilationError } from "../errors";
83
83
  import { logger } from "../logger";
84
- import type { PackageMaterializationConfig } from "../service/package_manifest";
84
+ import type {
85
+ PackageMaterializationConfig,
86
+ PackageScope,
87
+ } from "../service/package_manifest";
85
88
  import type {
86
89
  ConnectionMetadataRequest,
87
90
  ConnectionMetadataResponse,
@@ -235,6 +238,7 @@ export interface LoadPackageOutcome {
235
238
  queryableSources?: "declared" | "all";
236
239
  manifestLocation?: string | null;
237
240
  materialization?: PackageMaterializationConfig | null;
241
+ scope?: PackageScope;
238
242
  };
239
243
  models: Array<
240
244
  Omit<SerializedModel, "modelDef" | "sourceInfos"> & {
@@ -85,7 +85,9 @@ import { validateAuthorizeProbes } from "../service/authorize";
85
85
  import { type FilterDefinition } from "../service/filter";
86
86
  import {
87
87
  PackageMaterializationConfig,
88
+ PackageScope,
88
89
  parsePackageMaterialization,
90
+ parsePackageScope,
89
91
  } from "../service/package_manifest";
90
92
  import {
91
93
  extractQueriesFromModelDef,
@@ -387,6 +389,7 @@ async function readPackageMetadata(packagePath: string): Promise<{
387
389
  queryableSources?: "declared" | "all";
388
390
  manifestLocation?: string | null;
389
391
  materialization?: PackageMaterializationConfig | null;
392
+ scope?: PackageScope;
390
393
  }> {
391
394
  const manifestPath = path.join(packagePath, PACKAGE_MANIFEST_NAME);
392
395
  const contents = await fs.promises.readFile(manifestPath, "utf8");
@@ -397,6 +400,7 @@ async function readPackageMetadata(packagePath: string): Promise<{
397
400
  queryableSources?: unknown;
398
401
  manifestLocation?: unknown;
399
402
  materialization?: unknown;
403
+ scope?: unknown;
400
404
  };
401
405
  return {
402
406
  name: parsed.name,
@@ -416,6 +420,9 @@ async function readPackageMetadata(packagePath: string): Promise<{
416
420
  // Package-level Malloy Persistence policy; surfaced to the control plane,
417
421
  // which owns scheduling. Only `schedule` is read today.
418
422
  materialization: parsePackageMaterialization(parsed.materialization),
423
+ // Package-level persist scope mode; defaults to "package". An invalid
424
+ // value throws here and fails the load (scope is load-bearing).
425
+ scope: parsePackageScope(parsed.scope),
419
426
  };
420
427
  }
421
428
 
@@ -65,7 +65,10 @@
65
65
  */
66
66
 
67
67
  import type { SQLSourceDef, TableSourceDef } from "@malloydata/malloy";
68
- import type { PackageMaterializationConfig } from "../service/package_manifest";
68
+ import type {
69
+ PackageMaterializationConfig,
70
+ PackageScope,
71
+ } from "../service/package_manifest";
69
72
 
70
73
  // ──────────────────────────────────────────────────────────────────────
71
74
  // Direction: main ──▶ worker (load-package job)
@@ -181,6 +184,7 @@ export interface LoadPackageResult {
181
184
  queryableSources?: "declared" | "all";
182
185
  manifestLocation?: string | null;
183
186
  materialization?: PackageMaterializationConfig | null;
187
+ scope?: PackageScope;
184
188
  };
185
189
  models: SerializedModel[];
186
190
  /** Wall-clock ms inside the worker for the full package load. */
@@ -10,7 +10,7 @@ import {
10
10
  deriveBuildPlan,
11
11
  flattenDependsOn,
12
12
  iterGraphSources,
13
- resolveFreshnessSchedule,
13
+ resolveFreshness,
14
14
  resolvePackageConnections,
15
15
  } from "./build_plan";
16
16
  import { fakeSource } from "./materialization_test_fixtures";
@@ -213,10 +213,10 @@ describe("deriveBuildPlan", () => {
213
213
  });
214
214
  });
215
215
 
216
- it("reports declared sharing/refresh verbatim and null when unset", () => {
217
- // The control plane distinguishes unset from an explicit `shared` (it
218
- // applies the platform default itself), so the publisher must report
219
- // the declared value verbatim never substitute the default.
216
+ it("reports declared refresh verbatim (null when unset) and does not emit sharing/schedule", () => {
217
+ // `refresh` is a metadata pass-through; `sharing`/`schedule` were retired
218
+ // from the contract and must not be emitted as typed fields (they stay in
219
+ // the raw annotationFields for the publish-time validator to detect).
220
220
  const declared = fakeSource({
221
221
  name: "declared",
222
222
  sourceEntityId: "bid-d",
@@ -243,17 +243,22 @@ describe("deriveBuildPlan", () => {
243
243
  { duckdb: "dig" },
244
244
  );
245
245
 
246
- expect(plan.sources["declared@m"].sharing).toBe("private");
247
246
  expect(plan.sources["declared@m"].refresh).toBe("incremental");
248
- // The raw annotation map still carries every field alongside the typed
249
- // projections.
247
+ // Retired typed fields are absent from the wire projection.
248
+ expect(
249
+ (plan.sources["declared@m"] as Record<string, unknown>).sharing,
250
+ ).toBeUndefined();
251
+ expect(
252
+ (plan.sources["declared@m"] as Record<string, unknown>).schedule,
253
+ ).toBeUndefined();
254
+ // The raw annotation map still carries every field (so the validator can
255
+ // reject a source-level sharing/schedule at publish).
250
256
  expect(plan.sources["declared@m"].annotationFields).toEqual({
251
257
  name: "d_table",
252
258
  sharing: "private",
253
259
  refresh: "incremental",
254
260
  });
255
- // Unset is null — not "shared" — on the wire.
256
- expect(plan.sources["unset@m"].sharing).toBeNull();
261
+ // Unset refresh is null on the wire.
257
262
  expect(plan.sources["unset@m"].refresh).toBeNull();
258
263
  });
259
264
 
@@ -297,71 +302,48 @@ describe("deriveBuildPlan", () => {
297
302
  });
298
303
  });
299
304
 
300
- describe("resolveFreshnessSchedule", () => {
301
- it("reports source-level freshness + schedule verbatim", () => {
305
+ describe("resolveFreshness", () => {
306
+ it("reports source-level freshness verbatim", () => {
302
307
  const source = fakeSource({
303
308
  name: "s",
304
309
  sourceEntityId: "bid",
305
310
  freshnessSchedule: {
306
311
  freshness: { window: "1h", fallback: "stale_ok" },
307
- schedule: "0 */6 * * *",
308
312
  },
309
313
  });
310
- expect(resolveFreshnessSchedule(source, null)).toEqual({
311
- freshness: { window: "1h", fallback: "stale_ok" },
312
- schedule: "0 */6 * * *",
314
+ expect(resolveFreshness(source, null)).toEqual({
315
+ window: "1h",
316
+ fallback: "stale_ok",
313
317
  });
314
318
  });
315
319
 
316
- it("returns null for both when unset at every level", () => {
320
+ it("returns null when unset at every level", () => {
317
321
  const source = fakeSource({ name: "s", sourceEntityId: "bid" });
318
- expect(resolveFreshnessSchedule(source, null)).toEqual({
319
- freshness: null,
320
- schedule: null,
321
- });
322
+ expect(resolveFreshness(source, null)).toBeNull();
322
323
  });
323
324
 
324
325
  it("falls back to model-file then package per field (most-specific-wins)", () => {
325
- // freshness.window from source, freshness.fallback from model-file. The
326
- // package-level cron is NOT inherited as the source's effective schedule;
327
- // schedule here comes only from the source's own model-file default.
326
+ // freshness.window from source, freshness.fallback from model-file.
328
327
  const source = fakeSource({
329
328
  name: "s",
330
329
  sourceEntityId: "bid",
331
330
  freshnessSchedule: { freshness: { window: "1h" } },
332
- modelFreshnessSchedule: {
333
- freshness: { fallback: "fail" },
334
- schedule: "0 3 * * *",
335
- },
331
+ modelFreshnessSchedule: { freshness: { fallback: "fail" } },
336
332
  });
337
333
  const pkg = {
338
- schedule: "0 0 * * *",
334
+ schedule: null,
339
335
  freshness: { window: "24h", fallback: "live" as const },
340
336
  };
341
- expect(resolveFreshnessSchedule(source, pkg)).toEqual({
342
- freshness: { window: "1h", fallback: "fail" },
343
- schedule: "0 3 * * *",
344
- });
345
- });
346
-
347
- it("does not inherit the package-level cron as the source's schedule", () => {
348
- const source = fakeSource({ name: "s", sourceEntityId: "bid" });
349
- const pkg = { schedule: "0 0 * * *", freshness: { window: "24h" } };
350
- // Freshness inherits from the package; schedule stays null (the package
351
- // cron is gated separately, not folded into the per-source cron).
352
- expect(resolveFreshnessSchedule(source, pkg)).toEqual({
353
- freshness: { window: "24h" },
354
- schedule: null,
337
+ expect(resolveFreshness(source, pkg)).toEqual({
338
+ window: "1h",
339
+ fallback: "fail",
355
340
  });
356
341
  });
357
342
 
358
343
  it("inherits the package freshness when the source and model are unset", () => {
359
344
  const source = fakeSource({ name: "s", sourceEntityId: "bid" });
360
345
  const pkg = { schedule: null, freshness: { window: "24h" } };
361
- expect(resolveFreshnessSchedule(source, pkg)).toEqual({
362
- freshness: { window: "24h" },
363
- schedule: null,
364
- });
346
+ expect(resolveFreshness(source, pkg)).toEqual({ window: "24h" });
365
347
  });
366
348
 
367
349
  it("drops an invalid fallback rather than defaulting it", () => {
@@ -370,23 +352,17 @@ describe("resolveFreshnessSchedule", () => {
370
352
  sourceEntityId: "bid",
371
353
  freshnessSchedule: { freshness: { window: "1h", fallback: "bogus" } },
372
354
  });
373
- expect(resolveFreshnessSchedule(source, null)).toEqual({
374
- freshness: { window: "1h" },
375
- schedule: null,
376
- });
355
+ expect(resolveFreshness(source, null)).toEqual({ window: "1h" });
377
356
  });
378
357
  });
379
358
 
380
- describe("deriveBuildPlan freshness/schedule", () => {
381
- it("projects the resolved per-source freshness + schedule onto the plan", () => {
359
+ describe("deriveBuildPlan freshness", () => {
360
+ it("projects the resolved per-source freshness onto the plan (no schedule/sharing)", () => {
382
361
  const source = fakeSource({
383
362
  name: "s",
384
363
  sourceEntityId: "bid",
385
- annotationFields: { name: "s_table", sharing: "private" },
386
- freshnessSchedule: {
387
- freshness: { window: "1h" },
388
- schedule: "0 */6 * * *",
389
- },
364
+ annotationFields: { name: "s_table" },
365
+ freshnessSchedule: { freshness: { window: "1h" } },
390
366
  });
391
367
  const plan = deriveBuildPlan(
392
368
  [
@@ -406,12 +382,18 @@ describe("deriveBuildPlan freshness/schedule", () => {
406
382
  );
407
383
 
408
384
  // Source window wins over the package default; package fallback fills the
409
- // unset source fallback; source schedule reported verbatim.
385
+ // unset source fallback.
410
386
  expect(plan.sources["s@m"].freshness).toEqual({
411
387
  window: "1h",
412
388
  fallback: "live",
413
389
  });
414
- expect(plan.sources["s@m"].schedule).toBe("0 */6 * * *");
390
+ // Retired fields are not emitted.
391
+ expect(
392
+ (plan.sources["s@m"] as Record<string, unknown>).schedule,
393
+ ).toBeUndefined();
394
+ expect(
395
+ (plan.sources["s@m"] as Record<string, unknown>).sharing,
396
+ ).toBeUndefined();
415
397
  });
416
398
  });
417
399
 
@@ -25,11 +25,10 @@ type WirePackageMaterialization =
25
25
  const FRESHNESS_FALLBACKS = ["live", "stale_ok", "fail"] as const;
26
26
  type FreshnessFallback = (typeof FRESHNESS_FALLBACKS)[number];
27
27
 
28
- /** One layer's contribution to the resolved freshness/schedule (all optional). */
29
- interface FreshnessScheduleLayer {
28
+ /** One layer's contribution to the resolved freshness (all optional). */
29
+ interface FreshnessLayer {
30
30
  window?: string;
31
31
  fallback?: FreshnessFallback;
32
- schedule?: string;
33
32
  }
34
33
 
35
34
  /** Minimal path-reader over a Malloy `Tag` (see `@malloydata/malloy-tag`). */
@@ -121,16 +120,17 @@ export function deriveAnnotationFields(
121
120
  }
122
121
 
123
122
  /**
124
- * Read the freshness/schedule keys (`freshness.window`, `freshness.fallback`,
125
- * `schedule`) from one Malloy tag layer, keeping only recognized values. These
126
- * are dotted/nested tag properties, so they are NOT captured by the scalar
127
- * {@link deriveAnnotationFields} loop and must be read by path here.
123
+ * Read the freshness keys (`freshness.window`, `freshness.fallback`) from one
124
+ * Malloy tag layer, keeping only recognized values. These are dotted/nested tag
125
+ * properties, so they are NOT captured by the scalar {@link deriveAnnotationFields}
126
+ * loop and must be read by path here. (Per-source `sharing`/`schedule` were
127
+ * retired — scope is package-level and a schedule is package-root-only — so they
128
+ * are not resolved here; declaring either on a source is a publish-time manifest
129
+ * error, enforced in Package.persistencePolicyWarnings.)
128
130
  */
129
- function tagFreshnessScheduleLayer(
130
- tag: ReadableTag | undefined,
131
- ): FreshnessScheduleLayer {
131
+ function tagFreshnessLayer(tag: ReadableTag | undefined): FreshnessLayer {
132
132
  if (!tag || typeof tag.text !== "function") return {};
133
- const layer: FreshnessScheduleLayer = {};
133
+ const layer: FreshnessLayer = {};
134
134
  const window = tag.text("freshness", "window");
135
135
  if (typeof window === "string") layer.window = window;
136
136
  const fallback = tag.text("freshness", "fallback");
@@ -140,24 +140,15 @@ function tagFreshnessScheduleLayer(
140
140
  ) {
141
141
  layer.fallback = fallback as FreshnessFallback;
142
142
  }
143
- const schedule = tag.text("schedule");
144
- if (typeof schedule === "string") layer.schedule = schedule;
145
143
  return layer;
146
144
  }
147
145
 
148
- /**
149
- * The package-level `materialization` config as a freshness resolution layer.
150
- * Only `freshness` inherits down to a source: the package-level cron
151
- * (`materialization.schedule`) is a distinct package-grain concept surfaced on
152
- * `PackageMaterializationConfig.schedule` and gated on its own, so it is NOT
153
- * folded into a source's effective per-source cron (doing so would double-count
154
- * against the package cron gate).
155
- */
146
+ /** The package-level `materialization.freshness` as a resolution layer. */
156
147
  function packageFreshnessLayer(
157
148
  cfg: WirePackageMaterialization | null | undefined,
158
- ): Pick<FreshnessScheduleLayer, "window" | "fallback"> {
149
+ ): FreshnessLayer {
159
150
  if (!cfg) return {};
160
- const layer: Pick<FreshnessScheduleLayer, "window" | "fallback"> = {};
151
+ const layer: FreshnessLayer = {};
161
152
  if (cfg.freshness?.window) layer.window = cfg.freshness.window;
162
153
  const fallback = cfg.freshness?.fallback;
163
154
  if (
@@ -192,36 +183,30 @@ function safeModelTag(source: PersistSource): ReadableTag | undefined {
192
183
  }
193
184
 
194
185
  /**
195
- * Resolve a source's EFFECTIVE freshness + schedule, reported verbatim (unset
196
- * at every level stays null so the control plane can distinguish "unset" —
197
- * apply platform default from an explicit declaration). Invalid `fallback`
198
- * values are dropped, never defaulted. Precedence, per field, most-specific-wins:
199
- *
200
- * - `freshness`: source annotation > model-file default > package default.
201
- * - `schedule`: source annotation > model-file default (the source's OWN cron).
202
- * The package-level cron is a package-grain concept, surfaced and gated
203
- * separately, so it is not folded into the per-source effective cron.
186
+ * Resolve a source's EFFECTIVE freshness with most-specific-wins precedence,
187
+ * per field: source annotation > model-file default > package default. Reported
188
+ * verbatim (unset at every level stays null so the control plane can
189
+ * distinguish "unset" apply platform default from an explicit declaration).
190
+ * Invalid `fallback` values are dropped, never defaulted. Freshness is valid in
191
+ * both scope modes.
204
192
  */
205
- export function resolveFreshnessSchedule(
193
+ export function resolveFreshness(
206
194
  source: PersistSource,
207
195
  packageMaterialization: WirePackageMaterialization | null | undefined,
208
- ): { freshness: WireFreshness | null; schedule: string | null } {
209
- const sourceLayer = tagFreshnessScheduleLayer(safeSourceTag(source));
210
- const modelLayer = tagFreshnessScheduleLayer(safeModelTag(source));
196
+ ): WireFreshness | null {
197
+ const sourceLayer = tagFreshnessLayer(safeSourceTag(source));
198
+ const modelLayer = tagFreshnessLayer(safeModelTag(source));
211
199
  const pkgLayer = packageFreshnessLayer(packageMaterialization);
212
200
 
213
201
  const window = sourceLayer.window ?? modelLayer.window ?? pkgLayer.window;
214
202
  const fallback =
215
203
  sourceLayer.fallback ?? modelLayer.fallback ?? pkgLayer.fallback;
216
- const schedule = sourceLayer.schedule ?? modelLayer.schedule ?? null;
217
204
 
218
- let freshness: WireFreshness | null = null;
219
- if (window !== undefined || fallback !== undefined) {
220
- freshness = {};
221
- if (window !== undefined) freshness.window = window;
222
- if (fallback !== undefined) freshness.fallback = fallback;
223
- }
224
- return { freshness, schedule };
205
+ if (window === undefined && fallback === undefined) return null;
206
+ const freshness: WireFreshness = {};
207
+ if (window !== undefined) freshness.window = window;
208
+ if (fallback !== undefined) freshness.fallback = fallback;
209
+ return freshness;
225
210
  }
226
211
 
227
212
  /** Flatten Malloy's nested BuildNode.dependsOn into a list of sourceIDs. */
@@ -439,15 +424,14 @@ export function deriveBuildPlan(
439
424
  for (const [sourceID, source] of Object.entries(sources)) {
440
425
  if (include && !include.has(source.name)) continue;
441
426
  const annotationFields = deriveAnnotationFields(source);
442
- // EFFECTIVE per-source freshness + schedule, resolved most-specific-wins
427
+ // EFFECTIVE per-source freshness, resolved most-specific-wins
443
428
  // (source > model-file > package) and reported verbatim (null = unset at
444
- // every level). Unlike `sharing`/`refresh` these are dotted/nested tag
445
- // keys, so they come from resolveFreshnessSchedule rather than the scalar
446
- // annotationFields map.
447
- const { freshness, schedule } = resolveFreshnessSchedule(
448
- source,
449
- packageMaterialization,
450
- );
429
+ // every level). Freshness is a dotted/nested tag key, so it comes from
430
+ // resolveFreshness rather than the scalar annotationFields map, and is
431
+ // valid in both scope modes. Per-source `sharing`/`schedule` are NOT
432
+ // emitted (retired from the contract); if a source declares either it is
433
+ // rejected at publish (Package.persistencePolicyWarnings) — the raw keys
434
+ // still ride `annotationFields` so the validator can detect them.
451
435
  wireSources[sourceID] = {
452
436
  name: source.name,
453
437
  sourceID: source.sourceID,
@@ -455,16 +439,8 @@ export function deriveBuildPlan(
455
439
  dialect: source.dialectName,
456
440
  sourceEntityId: computeSourceEntityId(source, connectionDigests),
457
441
  sql: source.getSQL(),
458
- // Declared `#@ persist` scope/refresh knobs, reported VERBATIM (null
459
- // = unset). The control plane applies the platform default (unset =>
460
- // shared) itself, so the publisher must never substitute it — unset
461
- // has to stay distinguishable from an explicit `shared`. The source's
462
- // own annotation is the most-specific declaration layer, and the only
463
- // one that exists today.
464
- sharing: annotationFields.sharing ?? null,
465
442
  refresh: annotationFields.refresh ?? null,
466
- freshness,
467
- schedule,
443
+ freshness: resolveFreshness(source, packageMaterialization),
468
444
  columns: deriveColumns(source),
469
445
  annotationFields,
470
446
  modelPath: sourceModelPaths?.[sourceID],
@@ -1347,6 +1347,10 @@ export class Environment {
1347
1347
  // control plane misread the gap as a schedule removal. It is not a
1348
1348
  // PATCH-editable field, so always preserve the existing value.
1349
1349
  materialization: existing.materialization,
1350
+ // Same rationale for the manifest-derived persist `scope`: not
1351
+ // PATCH-editable, so preserve it rather than dropping it to the
1352
+ // default until the next reload.
1353
+ scope: existing.scope,
1350
1354
  });
1351
1355
 
1352
1356
  // Strict-reject, symmetric with the publish path
@@ -75,17 +75,16 @@ export function makeInstruction(
75
75
  * build internals touch (name/id, deterministic sourceEntityId, SQL, and the
76
76
  * `#@ persist name=` annotation reader, defaulted to "unset").
77
77
  */
78
- /** A freshness/schedule layer for the fake source's `#@` or `##` tag. */
78
+ /** A freshness layer for the fake source's `#@` or `##` tag. */
79
79
  interface FakeFreshnessSchedule {
80
80
  freshness?: { window?: string; fallback?: string };
81
- schedule?: string;
82
81
  }
83
82
 
84
83
  /**
85
84
  * Build a fake Malloy `Tag` supporting both readers the build plan uses:
86
85
  * `entries()` (scalar `#@ persist` key=value pairs, for deriveAnnotationFields)
87
- * and the path-based `text(...at)` (dotted `freshness.window` / `schedule`, for
88
- * resolveFreshnessSchedule).
86
+ * and the path-based `text(...at)` (dotted `freshness.window`, for
87
+ * resolveFreshness).
89
88
  */
90
89
  function fakeTag(
91
90
  fields: Record<string, string> | undefined,
@@ -99,7 +98,6 @@ function fakeTag(
99
98
  },
100
99
  text(...at: string[]): string | undefined {
101
100
  if (at.length === 1) {
102
- if (at[0] === "schedule") return fs?.schedule;
103
101
  return fields?.[at[0]];
104
102
  }
105
103
  if (at.length === 2 && at[0] === "freshness") {
@@ -116,11 +114,11 @@ export function fakeSource(opts: {
116
114
  sql?: string;
117
115
  connectionName?: string;
118
116
  dialectName?: string;
119
- /** key=value fields of the `#@ persist` annotation (e.g. sharing). */
117
+ /** key=value fields of the `#@ persist` annotation (e.g. name, refresh). */
120
118
  annotationFields?: Record<string, string>;
121
- /** Source-level (`#@`) freshness/schedule (dotted keys). */
119
+ /** Source-level (`#@`) freshness (dotted keys). */
122
120
  freshnessSchedule?: FakeFreshnessSchedule;
123
- /** Model-file-level (`##`) freshness/schedule default. */
121
+ /** Model-file-level (`##`) freshness default. */
124
122
  modelFreshnessSchedule?: FakeFreshnessSchedule;
125
123
  }): PersistSource {
126
124
  const fields = opts.annotationFields;