deepline 0.3.97 → 0.3.98

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 (36) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +144 -4
  2. package/dist/bundling-sources/sdk/src/index.ts +17 -4
  3. package/dist/bundling-sources/sdk/src/monitor-fleets.ts +1 -0
  4. package/dist/bundling-sources/sdk/src/monitor-input-contract.ts +1 -0
  5. package/dist/bundling-sources/sdk/src/monitors.ts +8 -25
  6. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  7. package/dist/bundling-sources/shared_libs/integration-contracts/index.ts +1 -0
  8. package/dist/bundling-sources/shared_libs/integration-contracts/monitor-fleet-contract.ts +218 -16
  9. package/dist/bundling-sources/shared_libs/integration-contracts/monitor-input-contract.ts +455 -0
  10. package/dist/bundling-sources/shared_libs/monitors/fleets/plan.ts +6 -1
  11. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +2 -1
  12. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +12 -0
  13. package/dist/bundling-sources/shared_libs/observability/scheduled-work-manual.ts +1 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/internal-bootstrap-token.ts +19 -3
  15. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +5 -1
  16. package/dist/cli/index.js +1004 -289
  17. package/dist/cli/index.mjs +1004 -289
  18. package/dist/index.d.mts +43 -24
  19. package/dist/index.d.ts +43 -24
  20. package/dist/index.js +604 -41
  21. package/dist/index.mjs +600 -41
  22. package/dist/install-integrity.json +6 -0
  23. package/dist/monitor-fleet-contract.d.mts +44 -8
  24. package/dist/monitor-fleet-contract.d.ts +44 -8
  25. package/dist/monitor-fleet-contract.js +354 -27
  26. package/dist/monitor-fleet-contract.mjs +343 -27
  27. package/dist/monitor-input-contract.d.mts +33 -0
  28. package/dist/monitor-input-contract.d.ts +33 -0
  29. package/dist/monitor-input-contract.js +392 -0
  30. package/dist/monitor-input-contract.mjs +353 -0
  31. package/dist/plays/bundle-play-file.mjs +1 -1
  32. package/dist/release.d.mts +1 -1
  33. package/dist/release.d.ts +1 -1
  34. package/dist/release.js +1 -1
  35. package/dist/release.mjs +1 -1
  36. package/package.json +7 -1
package/dist/index.js CHANGED
@@ -84,6 +84,10 @@ __export(src_exports, {
84
84
  isPlayBootstrapTemplate: () => isPlayBootstrapTemplate,
85
85
  isProviderUnavailable: () => isProviderUnavailable,
86
86
  isProviderWaterfallUnavailableError: () => isProviderWaterfallUnavailableError,
87
+ lintMonitorBatchInput: () => lintMonitorBatchInput,
88
+ lintMonitorFleetInput: () => lintMonitorFleetInput,
89
+ lintMonitorSpecTemplate: () => lintMonitorSpecTemplate,
90
+ renderMonitorSpecTemplate: () => renderMonitorSpecTemplate,
87
91
  resolveConfig: () => resolveConfig,
88
92
  runIf: () => runIf,
89
93
  steps: () => steps,
@@ -856,7 +860,7 @@ var SDK_RELEASE = {
856
860
  // getters keep their established compatibility behavior.
857
861
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
858
862
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
859
- version: "0.3.97",
863
+ version: "0.3.98",
860
864
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
861
865
  packageCapabilities: {
862
866
  updatePreferences: 1
@@ -2894,9 +2898,9 @@ function nonEmptyString(value) {
2894
2898
  }
2895
2899
  function normalizeDatasetBornFrom(value) {
2896
2900
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2897
- const record2 = value;
2898
- const table = nonEmptyString(record2.table);
2899
- const rowCountIn = finiteNonNegativeInteger2(record2.rowCountIn);
2901
+ const record3 = value;
2902
+ const table = nonEmptyString(record3.table);
2903
+ const rowCountIn = finiteNonNegativeInteger2(record3.rowCountIn);
2900
2904
  if (!table || rowCountIn === null) return null;
2901
2905
  return { table, rowCountIn };
2902
2906
  }
@@ -3985,21 +3989,21 @@ function normalizePlayRuntimeNamespace(value) {
3985
3989
  }
3986
3990
  function normalizePlayRuntimeSelection(value) {
3987
3991
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3988
- const record2 = value;
3989
- if (Object.keys(record2).some(
3992
+ const record3 = value;
3993
+ if (Object.keys(record3).some(
3990
3994
  (key) => key !== "environment" && key !== "namespace" && key !== "backend"
3991
- ) || record2.environment !== "preview") {
3995
+ ) || record3.environment !== "preview") {
3992
3996
  return null;
3993
3997
  }
3994
- const namespace = normalizePlayRuntimeNamespace(record2.namespace);
3998
+ const namespace = normalizePlayRuntimeNamespace(record3.namespace);
3995
3999
  if (!namespace) return null;
3996
- if (record2.backend === void 0) {
4000
+ if (record3.backend === void 0) {
3997
4001
  return { environment: "preview", namespace };
3998
4002
  }
3999
- if (record2.backend !== PLAY_RUNTIME_BACKENDS.daytona && record2.backend !== PLAY_RUNTIME_BACKENDS.modal) {
4003
+ if (record3.backend !== PLAY_RUNTIME_BACKENDS.daytona && record3.backend !== PLAY_RUNTIME_BACKENDS.modal) {
4000
4004
  return null;
4001
4005
  }
4002
- return { environment: "preview", namespace, backend: record2.backend };
4006
+ return { environment: "preview", namespace, backend: record3.backend };
4003
4007
  }
4004
4008
  function normalizePlayRuntimeEnvironment(value) {
4005
4009
  return typeof value === "string" && PLAY_RUNTIME_ENVIRONMENTS.includes(value) ? value : null;
@@ -4644,10 +4648,18 @@ var DeeplineClient = class {
4644
4648
  repair: (options2) => this.repairMonitors(options2),
4645
4649
  health: (options2) => this.readMonitorsHealth(options2),
4646
4650
  batch: {
4651
+ deploy: (definitions, options2) => this.deployMonitorBatch(definitions, options2),
4647
4652
  submit: (options2) => this.submitMonitorBatch(options2),
4648
4653
  get: (operationId, options2) => this.getMonitorBatch(operationId, options2)
4649
4654
  },
4655
+ jobs: {
4656
+ get: (jobId) => this.getMonitorJob(jobId),
4657
+ logs: (jobId, options2) => this.getMonitorJobLogs(jobId, options2),
4658
+ cancel: (jobId) => this.cancelMonitorJob(jobId),
4659
+ wait: (jobId, options2) => this.waitForMonitorJob(jobId, options2)
4660
+ },
4650
4661
  fleets: {
4662
+ deploy: (definitionOrId, options2) => this.syncMonitorFleet(definitionOrId, options2),
4651
4663
  sync: (definitionOrId, options2) => this.syncMonitorFleet(definitionOrId, options2),
4652
4664
  get: (fleetId, options2) => fleetId === void 0 ? this.listMonitorFleets() : this.getMonitorFleet(fleetId, options2),
4653
4665
  list: () => this.listMonitorFleets(),
@@ -7399,6 +7411,68 @@ var DeeplineClient = class {
7399
7411
  );
7400
7412
  }
7401
7413
  // ——————————————————————————————————————————————————————————
7414
+ // Monitor Jobs, Batches, and Fleets
7415
+ // ——————————————————————————————————————————————————————————
7416
+ monitorJobIdempotencyKey(operation) {
7417
+ const uuid = globalThis.crypto?.randomUUID?.();
7418
+ return `monitor-job-${operation}-${uuid ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;
7419
+ }
7420
+ monitorJobHeaders(operation, idempotencyKey) {
7421
+ return {
7422
+ "Idempotency-Key": idempotencyKey?.trim() || this.monitorJobIdempotencyKey(operation)
7423
+ };
7424
+ }
7425
+ async deployMonitorBatch(definitions, options) {
7426
+ return this.http.request(
7427
+ "/api/v2/monitors/batch/deploy",
7428
+ {
7429
+ method: "POST",
7430
+ body: { version: 1, kind: "batch", monitors: definitions },
7431
+ headers: this.monitorJobHeaders(
7432
+ "batch-deploy",
7433
+ options?.idempotencyKey
7434
+ ),
7435
+ maxRetries: 0,
7436
+ exactUrlOnly: true
7437
+ }
7438
+ );
7439
+ }
7440
+ async getMonitorJob(jobId) {
7441
+ return this.http.request(
7442
+ `/api/v2/monitors/jobs/${encodeURIComponent(jobId)}`,
7443
+ { method: "GET" }
7444
+ );
7445
+ }
7446
+ async getMonitorJobLogs(jobId, options) {
7447
+ const query = options?.limit === void 0 ? "" : `?limit=${encodeURIComponent(String(options.limit))}`;
7448
+ return this.http.request(
7449
+ `/api/v2/monitors/jobs/${encodeURIComponent(jobId)}/logs${query}`,
7450
+ { method: "GET" }
7451
+ );
7452
+ }
7453
+ async cancelMonitorJob(jobId) {
7454
+ return this.http.request(
7455
+ `/api/v2/monitors/jobs/${encodeURIComponent(jobId)}/cancel`,
7456
+ { method: "POST", body: {}, maxRetries: 0, exactUrlOnly: true }
7457
+ );
7458
+ }
7459
+ async waitForMonitorJob(jobId, options) {
7460
+ const timeoutMs = Math.max(1, options?.timeoutMs ?? 10 * 6e4);
7461
+ const pollIntervalMs = Math.max(1, options?.pollIntervalMs ?? 2e3);
7462
+ const terminal = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled"]);
7463
+ const startedAt = Date.now();
7464
+ let latest = await this.getMonitorJob(jobId);
7465
+ while (true) {
7466
+ options?.onProgress?.(latest);
7467
+ const job = latest.job;
7468
+ const state = typeof job?.state === "string" ? job.state : void 0;
7469
+ if (state && terminal.has(state)) return latest;
7470
+ if (Date.now() - startedAt >= timeoutMs) return latest;
7471
+ await sleep2(pollIntervalMs);
7472
+ latest = await this.getMonitorJob(jobId);
7473
+ }
7474
+ }
7475
+ // ——————————————————————————————————————————————————————————
7402
7476
  // Monitor Fleets
7403
7477
  // ——————————————————————————————————————————————————————————
7404
7478
  monitorFleetIdempotencyKey(operation) {
@@ -8557,8 +8631,8 @@ function firstExperienceDate(value) {
8557
8631
  return null;
8558
8632
  }
8559
8633
  function normalizeJobChange(value) {
8560
- const record2 = isRecord8(value) ? value : {};
8561
- const nested = isRecord8(record2.job_change) ? record2.job_change : record2;
8634
+ const record3 = isRecord8(value) ? value : {};
8635
+ const nested = isRecord8(record3.job_change) ? record3.job_change : record3;
8562
8636
  const output = isRecord8(nested.output) ? nested.output : nested;
8563
8637
  const person = isRecord8(output.person) ? output.person : {};
8564
8638
  const status = normalizeJobChangeStatus(
@@ -11231,25 +11305,388 @@ function defineMonitor(definition) {
11231
11305
  if (definition.name !== void 0 && typeof definition.name !== "string") {
11232
11306
  throw new Error('defineMonitor(definition) "name" must be a string.');
11233
11307
  }
11234
- if (definition.controls !== void 0 && (typeof definition.controls !== "object" || Array.isArray(definition.controls))) {
11308
+ const authored = definition;
11309
+ if ("controls" in authored) {
11235
11310
  throw new Error(
11236
- 'defineMonitor(definition) "controls" must be a JSON object.'
11311
+ 'defineMonitor(definition) does not accept "controls". Operational controls are managed by Deepline; bind downstream automation in a Play with sqlListeners.'
11312
+ );
11313
+ }
11314
+ if ("play" in authored) {
11315
+ throw new Error(
11316
+ 'defineMonitor(definition) does not accept "play". A monitor does not own Play bindings; declare sqlListeners in the Play instead.'
11237
11317
  );
11238
11318
  }
11239
11319
  return definition;
11240
11320
  }
11241
11321
 
11322
+ // ../integration-contracts/monitor-input-contract.ts
11323
+ var import_handlebars = __toESM(require("handlebars"), 1);
11324
+ function authoredMonitorFieldPath(path, field) {
11325
+ const batch = /^monitors\.(\d+)$/.exec(path);
11326
+ return `${batch ? `monitors[${batch[1]}]` : path}.${field}`;
11327
+ }
11328
+ function record(value) {
11329
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
11330
+ }
11331
+ function json(value, path, issues) {
11332
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
11333
+ return;
11334
+ }
11335
+ if (Array.isArray(value)) {
11336
+ value.forEach((child, index) => json(child, `${path}.${index}`, issues));
11337
+ return;
11338
+ }
11339
+ const object = record(value);
11340
+ if (!object) {
11341
+ issues.push({
11342
+ severity: "error",
11343
+ code: "invalid_monitor_json_value",
11344
+ path,
11345
+ message: "Monitor values must be JSON values."
11346
+ });
11347
+ return;
11348
+ }
11349
+ for (const [key, child] of Object.entries(object)) {
11350
+ json(child, `${path}.${key}`, issues);
11351
+ }
11352
+ }
11353
+ function monitorSpec(value, path, issues) {
11354
+ const input = record(value);
11355
+ if (!input) {
11356
+ issues.push({
11357
+ severity: "error",
11358
+ code: "invalid_monitor_spec",
11359
+ path,
11360
+ message: "A monitor must be a JSON object."
11361
+ });
11362
+ return null;
11363
+ }
11364
+ const allowed = /* @__PURE__ */ new Set(["key", "tool", "name", "payload"]);
11365
+ for (const key2 of Object.keys(input)) {
11366
+ if (!allowed.has(key2)) {
11367
+ if (key2 === "controls") {
11368
+ issues.push({
11369
+ severity: "error",
11370
+ code: "CONTROLS_NOT_SUPPORTED",
11371
+ path: authoredMonitorFieldPath(path, "controls"),
11372
+ message: "Operational controls are managed by Deepline and cannot be authored in Batch or Fleet JSON.",
11373
+ suggestion: "Remove controls. Bind downstream automation in a Play with sqlListeners."
11374
+ });
11375
+ continue;
11376
+ }
11377
+ if (key2 === "play") {
11378
+ issues.push({
11379
+ severity: "error",
11380
+ code: "PLAY_BINDING_NOT_IN_MONITOR_SPEC",
11381
+ path: authoredMonitorFieldPath(path, "play"),
11382
+ message: "A monitor does not own Play bindings.",
11383
+ suggestion: "Declare sqlListeners in the Play, or run: deepline plays bootstrap monitor-triggered --out on-event.play.ts"
11384
+ });
11385
+ continue;
11386
+ }
11387
+ issues.push({
11388
+ severity: "error",
11389
+ code: "unknown_monitor_field",
11390
+ path: `${path}.${key2}`,
11391
+ message: `Monitor definitions do not accept '${key2}'.`
11392
+ });
11393
+ }
11394
+ }
11395
+ const key = typeof input.key === "string" ? input.key.trim() : "";
11396
+ const tool = typeof input.tool === "string" ? input.tool.trim() : "";
11397
+ const name = input.name === void 0 ? void 0 : typeof input.name === "string" && input.name.trim() ? input.name.trim() : null;
11398
+ const payload = record(input.payload);
11399
+ if (!key) {
11400
+ issues.push({
11401
+ severity: "error",
11402
+ code: "invalid_monitor_key",
11403
+ path: `${path}.key`,
11404
+ message: "Monitor key must be a non-empty string."
11405
+ });
11406
+ }
11407
+ if (!tool) {
11408
+ issues.push({
11409
+ severity: "error",
11410
+ code: "invalid_monitor_tool",
11411
+ path: `${path}.tool`,
11412
+ message: "Monitor tool must be a non-empty literal string."
11413
+ });
11414
+ }
11415
+ if (name === null) {
11416
+ issues.push({
11417
+ severity: "error",
11418
+ code: "invalid_monitor_name",
11419
+ path: `${path}.name`,
11420
+ message: "Monitor name must be a non-empty string when supplied."
11421
+ });
11422
+ }
11423
+ if (!payload) {
11424
+ issues.push({
11425
+ severity: "error",
11426
+ code: "invalid_monitor_payload",
11427
+ path: `${path}.payload`,
11428
+ message: "Monitor payload must be an object."
11429
+ });
11430
+ } else {
11431
+ json(payload, `${path}.payload`, issues);
11432
+ }
11433
+ if (!key || !tool || !payload || name === null) {
11434
+ return null;
11435
+ }
11436
+ return {
11437
+ key,
11438
+ tool,
11439
+ ...name ? { name } : {},
11440
+ payload
11441
+ };
11442
+ }
11443
+ function issueOrder(left, right) {
11444
+ return left.path.localeCompare(right.path) || left.severity.localeCompare(right.severity) || left.code.localeCompare(right.code);
11445
+ }
11446
+ function lintMonitorBatchInput(value) {
11447
+ const issues = [];
11448
+ const versionlessEnvelope = !Array.isArray(value) && (() => {
11449
+ const candidate = record(value);
11450
+ return candidate !== null && Object.keys(candidate).length === 1 && Object.prototype.hasOwnProperty.call(candidate, "monitors");
11451
+ })();
11452
+ const legacy = Array.isArray(value) || versionlessEnvelope;
11453
+ const input = legacy ? null : record(value);
11454
+ let monitors = Array.isArray(value) ? value : versionlessEnvelope ? (() => {
11455
+ const candidate = record(value)?.monitors;
11456
+ return Array.isArray(candidate) ? candidate : null;
11457
+ })() : null;
11458
+ if (Array.isArray(value)) {
11459
+ issues.push({
11460
+ severity: "warning",
11461
+ code: "deprecated_batch_array",
11462
+ path: "",
11463
+ message: "Bare monitor arrays are supported for compatibility.",
11464
+ suggestion: 'Wrap the list in {"version":1,"kind":"batch","monitors":[...]}.'
11465
+ });
11466
+ } else if (versionlessEnvelope) {
11467
+ issues.push({
11468
+ severity: "warning",
11469
+ code: "deprecated_batch_missing_envelope_fields",
11470
+ path: "",
11471
+ message: "Versionless monitor envelopes are supported for compatibility.",
11472
+ suggestion: 'Add "version": 1 and "kind": "batch".'
11473
+ });
11474
+ if (!monitors) {
11475
+ issues.push({
11476
+ severity: "error",
11477
+ code: "invalid_batch_monitors",
11478
+ path: "monitors",
11479
+ message: "Batch monitors must be an array."
11480
+ });
11481
+ }
11482
+ } else if (!input) {
11483
+ issues.push({
11484
+ severity: "error",
11485
+ code: "invalid_batch",
11486
+ path: "",
11487
+ message: "Batch input must be an object or a legacy array."
11488
+ });
11489
+ } else {
11490
+ for (const key of Object.keys(input)) {
11491
+ if (!["version", "kind", "monitors"].includes(key)) {
11492
+ issues.push({
11493
+ severity: "error",
11494
+ code: key === "source" || key === "monitor" ? "batch_fleet_shape_mixup" : "unknown_batch_field",
11495
+ path: key,
11496
+ message: `Batch input does not accept '${key}'.`
11497
+ });
11498
+ }
11499
+ }
11500
+ if (input.version !== 1) {
11501
+ issues.push({
11502
+ severity: "error",
11503
+ code: "unsupported_batch_version",
11504
+ path: "version",
11505
+ message: "Batch input version must be 1."
11506
+ });
11507
+ }
11508
+ if (input.kind !== "batch") {
11509
+ issues.push({
11510
+ severity: "error",
11511
+ code: "invalid_batch_kind",
11512
+ path: "kind",
11513
+ message: 'Batch input kind must be "batch".'
11514
+ });
11515
+ }
11516
+ monitors = Array.isArray(input.monitors) ? input.monitors : null;
11517
+ if (!monitors) {
11518
+ issues.push({
11519
+ severity: "error",
11520
+ code: "invalid_batch_monitors",
11521
+ path: "monitors",
11522
+ message: "Batch monitors must be an array."
11523
+ });
11524
+ }
11525
+ }
11526
+ if (!monitors || monitors.length === 0) {
11527
+ if (monitors?.length === 0) {
11528
+ issues.push({
11529
+ severity: "error",
11530
+ code: "empty_batch",
11531
+ path: "monitors",
11532
+ message: "A Batch needs at least one monitor."
11533
+ });
11534
+ }
11535
+ return { issues: issues.sort(issueOrder) };
11536
+ }
11537
+ const normalized = monitors.map(
11538
+ (item, index) => monitorSpec(item, `monitors.${index}`, issues)
11539
+ );
11540
+ const seen = /* @__PURE__ */ new Map();
11541
+ normalized.forEach((item, index) => {
11542
+ if (!item) return;
11543
+ const first = seen.get(item.key);
11544
+ if (first !== void 0) {
11545
+ for (const position of [first, index]) {
11546
+ issues.push({
11547
+ severity: "error",
11548
+ code: "duplicate_monitor_key",
11549
+ path: `monitors.${position}.key`,
11550
+ message: `Duplicate monitor key '${item.key}'.`
11551
+ });
11552
+ }
11553
+ } else {
11554
+ seen.set(item.key, index);
11555
+ }
11556
+ });
11557
+ const ordered = issues.sort(issueOrder);
11558
+ return ordered.some((issue) => issue.severity === "error") ? { issues: ordered } : {
11559
+ normalized: {
11560
+ version: 1,
11561
+ kind: "batch",
11562
+ monitors: normalized
11563
+ },
11564
+ issues: ordered
11565
+ };
11566
+ }
11567
+ function validateHandlebars(value, path, issues) {
11568
+ if (!value.includes("{{")) return;
11569
+ try {
11570
+ import_handlebars.default.parse(value);
11571
+ } catch (error) {
11572
+ issues.push({
11573
+ severity: "error",
11574
+ code: "invalid_handlebars_template",
11575
+ path,
11576
+ message: error instanceof Error ? error.message : "Invalid Handlebars template."
11577
+ });
11578
+ return;
11579
+ }
11580
+ const expressions = value.match(/{{[\s\S]*?}}/g) ?? [];
11581
+ if (expressions.some(
11582
+ (expression) => !/^{{\s*[A-Za-z_][A-Za-z0-9_]*\s*}}$/.test(expression)
11583
+ )) {
11584
+ issues.push({
11585
+ severity: "error",
11586
+ code: "unsupported_handlebars_expression",
11587
+ path,
11588
+ message: "Fleet templates allow only direct source-row fields such as {{domain}}; helpers, blocks, partials, paths, and prototype access are disabled."
11589
+ });
11590
+ }
11591
+ }
11592
+ function walkTemplates(value, path, issues) {
11593
+ if (typeof value === "string") return validateHandlebars(value, path, issues);
11594
+ if (Array.isArray(value)) {
11595
+ value.forEach(
11596
+ (item, index) => walkTemplates(item, `${path}.${index}`, issues)
11597
+ );
11598
+ return;
11599
+ }
11600
+ const input = record(value);
11601
+ if (!input) return;
11602
+ for (const [key, child] of Object.entries(input)) {
11603
+ walkTemplates(child, `${path}.${key}`, issues);
11604
+ }
11605
+ }
11606
+ function lintMonitorSpecTemplate(value, path = "monitor") {
11607
+ const issues = [];
11608
+ const normalized = monitorSpec(value, path, issues);
11609
+ const input = record(value);
11610
+ if (input && typeof input.tool === "string" && input.tool.includes("{{")) {
11611
+ issues.push({
11612
+ severity: "error",
11613
+ code: "templated_monitor_tool",
11614
+ path: `${path}.tool`,
11615
+ message: "Fleet monitor tool must be literal; only key, name, and payload may be templated."
11616
+ });
11617
+ }
11618
+ if (input) {
11619
+ for (const field of ["key", "name", "payload"]) {
11620
+ if (input[field] !== void 0)
11621
+ walkTemplates(input[field], `${path}.${field}`, issues);
11622
+ }
11623
+ }
11624
+ const ordered = issues.sort(issueOrder);
11625
+ return ordered.some((issue) => issue.severity === "error") ? { issues: ordered } : { normalized, issues: ordered };
11626
+ }
11627
+ function renderValue(value, row) {
11628
+ if (typeof value === "string" && value.includes("{{")) {
11629
+ const context = Object.assign(/* @__PURE__ */ Object.create(null), row);
11630
+ return import_handlebars.default.compile(value, {
11631
+ noEscape: true,
11632
+ strict: true,
11633
+ knownHelpersOnly: true,
11634
+ preventIndent: true
11635
+ })(context, {
11636
+ allowProtoMethodsByDefault: false,
11637
+ allowProtoPropertiesByDefault: false,
11638
+ helpers: /* @__PURE__ */ Object.create(null),
11639
+ partials: /* @__PURE__ */ Object.create(null)
11640
+ });
11641
+ }
11642
+ if (Array.isArray(value)) return value.map((item) => renderValue(item, row));
11643
+ const input = record(value);
11644
+ if (!input) return value;
11645
+ return Object.fromEntries(
11646
+ Object.entries(input).map(([key, item]) => [key, renderValue(item, row)])
11647
+ );
11648
+ }
11649
+ function renderMonitorSpecTemplate(template, row) {
11650
+ const lint = lintMonitorSpecTemplate(template);
11651
+ if (!lint.normalized) return { issues: lint.issues };
11652
+ try {
11653
+ const rendered = renderValue(lint.normalized, row);
11654
+ const issues = [];
11655
+ const spec = monitorSpec(rendered, "monitor", issues);
11656
+ return spec ? { spec, issues: issues.sort(issueOrder) } : { issues: issues.sort(issueOrder) };
11657
+ } catch (error) {
11658
+ return {
11659
+ issues: [
11660
+ {
11661
+ severity: "error",
11662
+ code: "monitor_template_render_failed",
11663
+ path: "monitor",
11664
+ message: error instanceof Error ? error.message : "Fleet template rendering failed."
11665
+ }
11666
+ ]
11667
+ };
11668
+ }
11669
+ }
11670
+
11242
11671
  // ../integration-contracts/monitor-fleet-contract.ts
11243
11672
  var MONITOR_FLEET_MAX_MEMBERS = 1e3;
11244
11673
  var MONITOR_FLEET_FRONTIER_MAX_ROWS = 1e4;
11245
- var MONITOR_FLEET_AUTHORING_CONTRACT_EDITION = 1;
11674
+ var MONITOR_FLEET_AUTHORING_CONTRACT_EDITION = 2;
11246
11675
  var SUPPORTED_MONITOR_FLEET_AUTHORING_CONTRACT_EDITIONS = [
11247
- MONITOR_FLEET_AUTHORING_CONTRACT_EDITION
11676
+ 1,
11677
+ 2
11248
11678
  ];
11249
11679
  var MONITOR_FLEET_AUTHORING_CONTRACT_CHANGELOG = [
11250
11680
  {
11251
11681
  edition: 1,
11252
- changed: "Initial Fleet JSON contract: bounded sticky table membership, one daily sync, and a seven-day removal grace.",
11682
+ changed: "Initial Fleet JSON contract: bounded sticky table membership, source refresh, and a seven-day removal grace.",
11683
+ compatibilityOwner: "Monitors Runtime",
11684
+ newWritesEnd: null,
11685
+ readerRemoval: null
11686
+ },
11687
+ {
11688
+ edition: 2,
11689
+ changed: "Canonical table-backed Fleet envelope with one Handlebars MonitorSpec template.",
11253
11690
  compatibilityOwner: "Monitors Runtime",
11254
11691
  newWritesEnd: null,
11255
11692
  readerRemoval: null
@@ -11261,11 +11698,11 @@ var MONITOR_FLEET_DOCUMENTATION = {
11261
11698
  authoredFields: {
11262
11699
  id: "Stable lowercase fleet id used by sync, get, and deactivate.",
11263
11700
  source: "Customer DB table, unique row key, and optional equality filters that select candidate rows.",
11264
- member: "Ordinary monitor tool, stable key template, and payload evaluated for each selected source row.",
11701
+ monitor: "One Batch-compatible MonitorSpec. Direct Handlebars source-row fields such as {{domain}} are rendered for each selected row.",
11265
11702
  selection: "Deterministic ranking and an active-member limit between 1 and 1,000."
11266
11703
  },
11267
11704
  fixedPolicy: {
11268
- cadence: "daily",
11705
+ cadence: "60s fair bounded source refresh",
11269
11706
  membership: "sticky",
11270
11707
  removalGrace: "7d",
11271
11708
  onRemoved: "deactivate",
@@ -11273,9 +11710,10 @@ var MONITOR_FLEET_DOCUMENTATION = {
11273
11710
  billing: "No Fleet fee, permit, or renewal. Dry-run reports ordinary monitor lifecycle charges due now."
11274
11711
  },
11275
11712
  stickyMembership: "Ranking fills vacancies but never replaces a current member merely because another row ranks higher.",
11276
- columnExpression: 'Use { "$fleet": "column", "name": "column_name" } in a member key or payload to read a value from each source row.'
11713
+ templating: "Use direct Handlebars fields such as {{domain}} in monitor key, name, or payload. Helpers, blocks, partials, paths, and prototype access are disabled.",
11714
+ legacyCompatibility: 'Edition 1 member/$fleet files remain readable with a lint warning; new files should use version: 2, kind: "fleet", and monitor.'
11277
11715
  };
11278
- function record(value) {
11716
+ function record2(value) {
11279
11717
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
11280
11718
  }
11281
11719
  function identifier(value) {
@@ -11300,7 +11738,7 @@ function jsonValue(value, path, issues) {
11300
11738
  value.forEach((item, index) => jsonValue(item, `${path}.${index}`, issues));
11301
11739
  return;
11302
11740
  }
11303
- const input = record(value);
11741
+ const input = record2(value);
11304
11742
  if (!input) {
11305
11743
  issues.push({
11306
11744
  path,
@@ -11326,7 +11764,7 @@ function defineMonitorFleet(definition) {
11326
11764
  return definition;
11327
11765
  }
11328
11766
  function validateExpression(value, path, issues) {
11329
- const input = record(value);
11767
+ const input = record2(value);
11330
11768
  if (!input || input.$fleet !== "column" || !identifier(input.name)) {
11331
11769
  issues.push({
11332
11770
  path,
@@ -11344,7 +11782,7 @@ function walkPayload(value, path, issues) {
11344
11782
  );
11345
11783
  return;
11346
11784
  }
11347
- const input = record(value);
11785
+ const input = record2(value);
11348
11786
  if (!input) return;
11349
11787
  if ("$fleet" in input) {
11350
11788
  validateExpression(input, path, issues);
@@ -11354,9 +11792,9 @@ function walkPayload(value, path, issues) {
11354
11792
  walkPayload(item, `${path}.${key}`, issues);
11355
11793
  }
11356
11794
  }
11357
- function validateMonitorFleetDefinition(value) {
11795
+ function validateMonitorFleetDefinitionV1(value) {
11358
11796
  const issues = [];
11359
- const input = record(value);
11797
+ const input = record2(value);
11360
11798
  if (!input) {
11361
11799
  return {
11362
11800
  valid: false,
@@ -11377,7 +11815,7 @@ function validateMonitorFleetDefinition(value) {
11377
11815
  message: "Fleet id must be lowercase kebab-case."
11378
11816
  });
11379
11817
  }
11380
- const source = record(input.source);
11818
+ const source = record2(input.source);
11381
11819
  exactKeys(
11382
11820
  source,
11383
11821
  ["kind", "schema", "table", "key", "where"],
@@ -11398,7 +11836,7 @@ function validateMonitorFleetDefinition(value) {
11398
11836
  message: "Source schema and table must be SQL identifiers."
11399
11837
  });
11400
11838
  }
11401
- const sourceKey = record(source?.key);
11839
+ const sourceKey = record2(source?.key);
11402
11840
  exactKeys(sourceKey, ["column", "type"], "source.key", issues);
11403
11841
  if (!sourceKey || !identifier(sourceKey.column) || !["text", "uuid", "int4", "int8"].includes(String(sourceKey.type))) {
11404
11842
  issues.push({
@@ -11408,7 +11846,7 @@ function validateMonitorFleetDefinition(value) {
11408
11846
  });
11409
11847
  }
11410
11848
  if (source?.where !== void 0) {
11411
- const where = record(source.where);
11849
+ const where = record2(source.where);
11412
11850
  if (!where) {
11413
11851
  issues.push({
11414
11852
  path: "source.where",
@@ -11434,9 +11872,9 @@ function validateMonitorFleetDefinition(value) {
11434
11872
  }
11435
11873
  }
11436
11874
  }
11437
- const member = record(input.member);
11875
+ const member = record2(input.member);
11438
11876
  exactKeys(member, ["tool", "key", "payload"], "member", issues);
11439
- const key = record(member?.key);
11877
+ const key = record2(member?.key);
11440
11878
  if (!member || typeof member.tool !== "string" || !member.tool.trim()) {
11441
11879
  issues.push({
11442
11880
  path: "member.tool",
@@ -11457,7 +11895,7 @@ function validateMonitorFleetDefinition(value) {
11457
11895
  validateExpression(part, `member.key.parts.${index}`, issues);
11458
11896
  }
11459
11897
  if (sourceKey && !key.parts.some(
11460
- (part) => record(part)?.$fleet === "column" && record(part)?.name === sourceKey.column
11898
+ (part) => record2(part)?.$fleet === "column" && record2(part)?.name === sourceKey.column
11461
11899
  )) {
11462
11900
  issues.push({
11463
11901
  path: "member.key.parts",
@@ -11476,7 +11914,7 @@ function validateMonitorFleetDefinition(value) {
11476
11914
  jsonValue(member.payload, "member.payload", issues);
11477
11915
  }
11478
11916
  walkPayload(member?.payload, "member.payload", issues);
11479
- const selection = record(input.selection);
11917
+ const selection = record2(input.selection);
11480
11918
  exactKeys(selection, ["limit", "orderBy", "membership"], "selection", issues);
11481
11919
  if (!selection || !Number.isSafeInteger(selection.limit) || Number(selection.limit) < 1 || Number(selection.limit) > MONITOR_FLEET_MAX_MEMBERS) {
11482
11920
  issues.push({
@@ -11502,7 +11940,7 @@ function validateMonitorFleetDefinition(value) {
11502
11940
  } else {
11503
11941
  const orderColumns = /* @__PURE__ */ new Set();
11504
11942
  for (const [index, order] of orderBy.entries()) {
11505
- const item = record(order);
11943
+ const item = record2(order);
11506
11944
  exactKeys(
11507
11945
  item,
11508
11946
  ["column", "direction"],
@@ -11525,7 +11963,7 @@ function validateMonitorFleetDefinition(value) {
11525
11963
  orderColumns.add(item.column);
11526
11964
  }
11527
11965
  }
11528
- if (sourceKey && record(orderBy.at(-1))?.column !== sourceKey.column) {
11966
+ if (sourceKey && record2(orderBy.at(-1))?.column !== sourceKey.column) {
11529
11967
  issues.push({
11530
11968
  path: "selection.orderBy",
11531
11969
  code: "fleet_order_missing_key",
@@ -11544,9 +11982,104 @@ function validateMonitorFleetDefinition(value) {
11544
11982
  }
11545
11983
  return issues.length === 0 ? { valid: true, definition: value, issues } : { valid: false, issues };
11546
11984
  }
11547
- function admitMonitorFleetAuthoringContract(value, edition = MONITOR_FLEET_AUTHORING_CONTRACT_EDITION) {
11985
+ function v2Issue(issue) {
11986
+ return { path: issue.path, code: issue.code, message: issue.message };
11987
+ }
11988
+ function validateMonitorFleetDefinitionV2(value) {
11989
+ const issues = [];
11990
+ const input = record2(value);
11991
+ if (!input) {
11992
+ return {
11993
+ valid: false,
11994
+ issues: [
11995
+ {
11996
+ path: "",
11997
+ code: "invalid_fleet",
11998
+ message: "Fleet must be an object."
11999
+ }
12000
+ ]
12001
+ };
12002
+ }
12003
+ exactKeys(
12004
+ input,
12005
+ ["version", "kind", "id", "source", "monitor", "selection"],
12006
+ "",
12007
+ issues
12008
+ );
12009
+ if (input.version !== 2) {
12010
+ issues.push({
12011
+ path: "version",
12012
+ code: "unsupported_fleet_version",
12013
+ message: "Canonical Fleet input version must be 2."
12014
+ });
12015
+ }
12016
+ if (input.kind !== "fleet") {
12017
+ issues.push({
12018
+ path: "kind",
12019
+ code: "invalid_fleet_kind",
12020
+ message: 'Canonical Fleet input kind must be "fleet".'
12021
+ });
12022
+ }
12023
+ const source = record2(input.source);
12024
+ const sourceKey = record2(source?.key);
12025
+ const legacyShape = {
12026
+ id: input.id,
12027
+ source: input.source,
12028
+ member: {
12029
+ tool: record2(input.monitor)?.tool,
12030
+ key: {
12031
+ $fleet: "template",
12032
+ parts: [
12033
+ "fleet-",
12034
+ {
12035
+ $fleet: "column",
12036
+ name: typeof sourceKey?.column === "string" ? sourceKey.column : "invalid_source_key"
12037
+ }
12038
+ ]
12039
+ },
12040
+ payload: record2(input.monitor)?.payload
12041
+ },
12042
+ selection: input.selection
12043
+ };
12044
+ const legacyCheck = validateMonitorFleetDefinitionV1(legacyShape);
12045
+ for (const issue of legacyCheck.issues) {
12046
+ issues.push({
12047
+ ...issue,
12048
+ path: issue.path.replace(/^member\./, "monitor.")
12049
+ });
12050
+ }
12051
+ const monitorInput = record2(input.monitor);
12052
+ const monitorKey = monitorInput?.key;
12053
+ const monitor = lintMonitorSpecTemplate(input.monitor);
12054
+ issues.push(...monitor.issues.map(v2Issue));
12055
+ if (typeof monitorKey === "string" && typeof sourceKey?.column === "string" && !monitorKey.includes(`{{${sourceKey.column}}}`)) {
12056
+ issues.push({
12057
+ path: "monitor.key",
12058
+ code: "fleet_key_missing_source_key",
12059
+ message: "Monitor key must include the source key as a direct Handlebars expression."
12060
+ });
12061
+ }
12062
+ return issues.length === 0 && monitor.normalized ? {
12063
+ valid: true,
12064
+ definition: {
12065
+ version: 2,
12066
+ kind: "fleet",
12067
+ id: String(input.id),
12068
+ source: input.source,
12069
+ monitor: monitor.normalized,
12070
+ selection: input.selection
12071
+ },
12072
+ issues
12073
+ } : { valid: false, issues };
12074
+ }
12075
+ function validateMonitorFleetDefinition(value) {
12076
+ const input = record2(value);
12077
+ return input?.version === 2 || input?.kind === "fleet" ? validateMonitorFleetDefinitionV2(value) : validateMonitorFleetDefinitionV1(value);
12078
+ }
12079
+ function admitMonitorFleetAuthoringContract(value, edition) {
12080
+ const detectedEdition = edition ?? (record2(value)?.version === 2 || record2(value)?.kind === "fleet" ? 2 : 1);
11548
12081
  if (!SUPPORTED_MONITOR_FLEET_AUTHORING_CONTRACT_EDITIONS.includes(
11549
- edition
12082
+ detectedEdition
11550
12083
  )) {
11551
12084
  return {
11552
12085
  valid: false,
@@ -11554,16 +12087,16 @@ function admitMonitorFleetAuthoringContract(value, edition = MONITOR_FLEET_AUTHO
11554
12087
  {
11555
12088
  path: "authoring_contract_edition",
11556
12089
  code: "unsupported_fleet_authoring_contract_edition",
11557
- message: `Monitor Fleet authoring contract edition ${edition} is not supported.`
12090
+ message: `Monitor Fleet authoring contract edition ${detectedEdition} is not supported.`
11558
12091
  }
11559
12092
  ]
11560
12093
  };
11561
12094
  }
11562
- const result = validateMonitorFleetDefinition(value);
12095
+ const result = detectedEdition === 2 ? validateMonitorFleetDefinitionV2(value) : validateMonitorFleetDefinitionV1(value);
11563
12096
  return result.valid && result.definition ? {
11564
12097
  valid: true,
11565
12098
  contract: {
11566
- edition,
12099
+ edition: detectedEdition,
11567
12100
  // JSON cloning prevents later caller mutation from changing the
11568
12101
  // admitted snapshot.
11569
12102
  definition: JSON.parse(
@@ -11573,6 +12106,32 @@ function admitMonitorFleetAuthoringContract(value, edition = MONITOR_FLEET_AUTHO
11573
12106
  issues: []
11574
12107
  } : { valid: false, issues: result.issues };
11575
12108
  }
12109
+ function lintMonitorFleetInput(value) {
12110
+ const input = record2(value);
12111
+ const legacy = input?.member !== void 0;
12112
+ const admitted = admitMonitorFleetAuthoringContract(value);
12113
+ const issues = admitted.issues.map((issue) => ({
12114
+ ...issue,
12115
+ severity: "error"
12116
+ }));
12117
+ if (legacy) {
12118
+ issues.push({
12119
+ severity: "warning",
12120
+ code: "deprecated_fleet_member",
12121
+ path: "member",
12122
+ message: "Legacy Fleet member syntax remains supported."
12123
+ });
12124
+ if (JSON.stringify(input).includes('"$fleet"')) {
12125
+ issues.push({
12126
+ severity: "warning",
12127
+ code: "deprecated_fleet_expression",
12128
+ path: "member",
12129
+ message: "Legacy $fleet expressions remain supported; use direct Handlebars fields in monitor instead."
12130
+ });
12131
+ }
12132
+ }
12133
+ return admitted.valid && admitted.contract ? { normalized: admitted.contract.definition, issues } : { issues };
12134
+ }
11576
12135
 
11577
12136
  // ../plays/bootstrap-routes.ts
11578
12137
  var DEEPLINE_TOOL_CATEGORIES = [
@@ -11899,6 +12458,10 @@ function extractSummaryFields(payload) {
11899
12458
  isPlayBootstrapTemplate,
11900
12459
  isProviderUnavailable,
11901
12460
  isProviderWaterfallUnavailableError,
12461
+ lintMonitorBatchInput,
12462
+ lintMonitorFleetInput,
12463
+ lintMonitorSpecTemplate,
12464
+ renderMonitorSpecTemplate,
11902
12465
  resolveConfig,
11903
12466
  runIf,
11904
12467
  steps,