deepline 0.3.97 → 0.3.99

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 +152 -5
  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 +1030 -295
  17. package/dist/cli/index.mjs +1038 -303
  18. package/dist/index.d.mts +48 -25
  19. package/dist/index.d.ts +48 -25
  20. package/dist/index.js +606 -42
  21. package/dist/index.mjs +602 -42
  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.mjs CHANGED
@@ -768,7 +768,7 @@ var SDK_RELEASE = {
768
768
  // getters keep their established compatibility behavior.
769
769
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
770
770
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
771
- version: "0.3.97",
771
+ version: "0.3.99",
772
772
  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.",
773
773
  packageCapabilities: {
774
774
  updatePreferences: 1
@@ -2806,9 +2806,9 @@ function nonEmptyString(value) {
2806
2806
  }
2807
2807
  function normalizeDatasetBornFrom(value) {
2808
2808
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2809
- const record2 = value;
2810
- const table = nonEmptyString(record2.table);
2811
- const rowCountIn = finiteNonNegativeInteger2(record2.rowCountIn);
2809
+ const record3 = value;
2810
+ const table = nonEmptyString(record3.table);
2811
+ const rowCountIn = finiteNonNegativeInteger2(record3.rowCountIn);
2812
2812
  if (!table || rowCountIn === null) return null;
2813
2813
  return { table, rowCountIn };
2814
2814
  }
@@ -3897,21 +3897,21 @@ function normalizePlayRuntimeNamespace(value) {
3897
3897
  }
3898
3898
  function normalizePlayRuntimeSelection(value) {
3899
3899
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3900
- const record2 = value;
3901
- if (Object.keys(record2).some(
3900
+ const record3 = value;
3901
+ if (Object.keys(record3).some(
3902
3902
  (key) => key !== "environment" && key !== "namespace" && key !== "backend"
3903
- ) || record2.environment !== "preview") {
3903
+ ) || record3.environment !== "preview") {
3904
3904
  return null;
3905
3905
  }
3906
- const namespace = normalizePlayRuntimeNamespace(record2.namespace);
3906
+ const namespace = normalizePlayRuntimeNamespace(record3.namespace);
3907
3907
  if (!namespace) return null;
3908
- if (record2.backend === void 0) {
3908
+ if (record3.backend === void 0) {
3909
3909
  return { environment: "preview", namespace };
3910
3910
  }
3911
- if (record2.backend !== PLAY_RUNTIME_BACKENDS.daytona && record2.backend !== PLAY_RUNTIME_BACKENDS.modal) {
3911
+ if (record3.backend !== PLAY_RUNTIME_BACKENDS.daytona && record3.backend !== PLAY_RUNTIME_BACKENDS.modal) {
3912
3912
  return null;
3913
3913
  }
3914
- return { environment: "preview", namespace, backend: record2.backend };
3914
+ return { environment: "preview", namespace, backend: record3.backend };
3915
3915
  }
3916
3916
  function normalizePlayRuntimeEnvironment(value) {
3917
3917
  return typeof value === "string" && PLAY_RUNTIME_ENVIRONMENTS.includes(value) ? value : null;
@@ -4556,10 +4556,18 @@ var DeeplineClient = class {
4556
4556
  repair: (options2) => this.repairMonitors(options2),
4557
4557
  health: (options2) => this.readMonitorsHealth(options2),
4558
4558
  batch: {
4559
+ deploy: (definitions, options2) => this.deployMonitorBatch(definitions, options2),
4559
4560
  submit: (options2) => this.submitMonitorBatch(options2),
4560
4561
  get: (operationId, options2) => this.getMonitorBatch(operationId, options2)
4561
4562
  },
4563
+ jobs: {
4564
+ get: (jobId) => this.getMonitorJob(jobId),
4565
+ logs: (jobId, options2) => this.getMonitorJobLogs(jobId, options2),
4566
+ cancel: (jobId) => this.cancelMonitorJob(jobId),
4567
+ wait: (jobId, options2) => this.waitForMonitorJob(jobId, options2)
4568
+ },
4562
4569
  fleets: {
4570
+ deploy: (definitionOrId, options2) => this.syncMonitorFleet(definitionOrId, options2),
4563
4571
  sync: (definitionOrId, options2) => this.syncMonitorFleet(definitionOrId, options2),
4564
4572
  get: (fleetId, options2) => fleetId === void 0 ? this.listMonitorFleets() : this.getMonitorFleet(fleetId, options2),
4565
4573
  list: () => this.listMonitorFleets(),
@@ -6903,7 +6911,8 @@ var DeeplineClient = class {
6903
6911
  enabled: true,
6904
6912
  threshold_credits: options.thresholdCredits,
6905
6913
  refill_to_credits: options.refillToCredits,
6906
- ...options.purchaseLimitCents !== void 0 ? { purchase_limit_cents: options.purchaseLimitCents } : {}
6914
+ ...options.purchaseLimitCents !== void 0 ? { purchase_limit_cents: options.purchaseLimitCents } : {},
6915
+ ...options.purchaseLimitCredits !== void 0 ? { purchase_limit_credits: options.purchaseLimitCredits } : {}
6907
6916
  } : { enabled: false },
6908
6917
  { "Idempotency-Key": idempotencyKey }
6909
6918
  );
@@ -7311,6 +7320,68 @@ var DeeplineClient = class {
7311
7320
  );
7312
7321
  }
7313
7322
  // ——————————————————————————————————————————————————————————
7323
+ // Monitor Jobs, Batches, and Fleets
7324
+ // ——————————————————————————————————————————————————————————
7325
+ monitorJobIdempotencyKey(operation) {
7326
+ const uuid = globalThis.crypto?.randomUUID?.();
7327
+ return `monitor-job-${operation}-${uuid ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;
7328
+ }
7329
+ monitorJobHeaders(operation, idempotencyKey) {
7330
+ return {
7331
+ "Idempotency-Key": idempotencyKey?.trim() || this.monitorJobIdempotencyKey(operation)
7332
+ };
7333
+ }
7334
+ async deployMonitorBatch(definitions, options) {
7335
+ return this.http.request(
7336
+ "/api/v2/monitors/batch/deploy",
7337
+ {
7338
+ method: "POST",
7339
+ body: { version: 1, kind: "batch", monitors: definitions },
7340
+ headers: this.monitorJobHeaders(
7341
+ "batch-deploy",
7342
+ options?.idempotencyKey
7343
+ ),
7344
+ maxRetries: 0,
7345
+ exactUrlOnly: true
7346
+ }
7347
+ );
7348
+ }
7349
+ async getMonitorJob(jobId) {
7350
+ return this.http.request(
7351
+ `/api/v2/monitors/jobs/${encodeURIComponent(jobId)}`,
7352
+ { method: "GET" }
7353
+ );
7354
+ }
7355
+ async getMonitorJobLogs(jobId, options) {
7356
+ const query = options?.limit === void 0 ? "" : `?limit=${encodeURIComponent(String(options.limit))}`;
7357
+ return this.http.request(
7358
+ `/api/v2/monitors/jobs/${encodeURIComponent(jobId)}/logs${query}`,
7359
+ { method: "GET" }
7360
+ );
7361
+ }
7362
+ async cancelMonitorJob(jobId) {
7363
+ return this.http.request(
7364
+ `/api/v2/monitors/jobs/${encodeURIComponent(jobId)}/cancel`,
7365
+ { method: "POST", body: {}, maxRetries: 0, exactUrlOnly: true }
7366
+ );
7367
+ }
7368
+ async waitForMonitorJob(jobId, options) {
7369
+ const timeoutMs = Math.max(1, options?.timeoutMs ?? 10 * 6e4);
7370
+ const pollIntervalMs = Math.max(1, options?.pollIntervalMs ?? 2e3);
7371
+ const terminal = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled"]);
7372
+ const startedAt = Date.now();
7373
+ let latest = await this.getMonitorJob(jobId);
7374
+ while (true) {
7375
+ options?.onProgress?.(latest);
7376
+ const job = latest.job;
7377
+ const state = typeof job?.state === "string" ? job.state : void 0;
7378
+ if (state && terminal.has(state)) return latest;
7379
+ if (Date.now() - startedAt >= timeoutMs) return latest;
7380
+ await sleep2(pollIntervalMs);
7381
+ latest = await this.getMonitorJob(jobId);
7382
+ }
7383
+ }
7384
+ // ——————————————————————————————————————————————————————————
7314
7385
  // Monitor Fleets
7315
7386
  // ——————————————————————————————————————————————————————————
7316
7387
  monitorFleetIdempotencyKey(operation) {
@@ -8469,8 +8540,8 @@ function firstExperienceDate(value) {
8469
8540
  return null;
8470
8541
  }
8471
8542
  function normalizeJobChange(value) {
8472
- const record2 = isRecord8(value) ? value : {};
8473
- const nested = isRecord8(record2.job_change) ? record2.job_change : record2;
8543
+ const record3 = isRecord8(value) ? value : {};
8544
+ const nested = isRecord8(record3.job_change) ? record3.job_change : record3;
8474
8545
  const output = isRecord8(nested.output) ? nested.output : nested;
8475
8546
  const person = isRecord8(output.person) ? output.person : {};
8476
8547
  const status = normalizeJobChangeStatus(
@@ -11143,25 +11214,388 @@ function defineMonitor(definition) {
11143
11214
  if (definition.name !== void 0 && typeof definition.name !== "string") {
11144
11215
  throw new Error('defineMonitor(definition) "name" must be a string.');
11145
11216
  }
11146
- if (definition.controls !== void 0 && (typeof definition.controls !== "object" || Array.isArray(definition.controls))) {
11217
+ const authored = definition;
11218
+ if ("controls" in authored) {
11147
11219
  throw new Error(
11148
- 'defineMonitor(definition) "controls" must be a JSON object.'
11220
+ 'defineMonitor(definition) does not accept "controls". Operational controls are managed by Deepline; bind downstream automation in a Play with sqlListeners.'
11221
+ );
11222
+ }
11223
+ if ("play" in authored) {
11224
+ throw new Error(
11225
+ 'defineMonitor(definition) does not accept "play". A monitor does not own Play bindings; declare sqlListeners in the Play instead.'
11149
11226
  );
11150
11227
  }
11151
11228
  return definition;
11152
11229
  }
11153
11230
 
11231
+ // ../integration-contracts/monitor-input-contract.ts
11232
+ import Handlebars from "handlebars";
11233
+ function authoredMonitorFieldPath(path, field) {
11234
+ const batch = /^monitors\.(\d+)$/.exec(path);
11235
+ return `${batch ? `monitors[${batch[1]}]` : path}.${field}`;
11236
+ }
11237
+ function record(value) {
11238
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
11239
+ }
11240
+ function json(value, path, issues) {
11241
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
11242
+ return;
11243
+ }
11244
+ if (Array.isArray(value)) {
11245
+ value.forEach((child, index) => json(child, `${path}.${index}`, issues));
11246
+ return;
11247
+ }
11248
+ const object = record(value);
11249
+ if (!object) {
11250
+ issues.push({
11251
+ severity: "error",
11252
+ code: "invalid_monitor_json_value",
11253
+ path,
11254
+ message: "Monitor values must be JSON values."
11255
+ });
11256
+ return;
11257
+ }
11258
+ for (const [key, child] of Object.entries(object)) {
11259
+ json(child, `${path}.${key}`, issues);
11260
+ }
11261
+ }
11262
+ function monitorSpec(value, path, issues) {
11263
+ const input = record(value);
11264
+ if (!input) {
11265
+ issues.push({
11266
+ severity: "error",
11267
+ code: "invalid_monitor_spec",
11268
+ path,
11269
+ message: "A monitor must be a JSON object."
11270
+ });
11271
+ return null;
11272
+ }
11273
+ const allowed = /* @__PURE__ */ new Set(["key", "tool", "name", "payload"]);
11274
+ for (const key2 of Object.keys(input)) {
11275
+ if (!allowed.has(key2)) {
11276
+ if (key2 === "controls") {
11277
+ issues.push({
11278
+ severity: "error",
11279
+ code: "CONTROLS_NOT_SUPPORTED",
11280
+ path: authoredMonitorFieldPath(path, "controls"),
11281
+ message: "Operational controls are managed by Deepline and cannot be authored in Batch or Fleet JSON.",
11282
+ suggestion: "Remove controls. Bind downstream automation in a Play with sqlListeners."
11283
+ });
11284
+ continue;
11285
+ }
11286
+ if (key2 === "play") {
11287
+ issues.push({
11288
+ severity: "error",
11289
+ code: "PLAY_BINDING_NOT_IN_MONITOR_SPEC",
11290
+ path: authoredMonitorFieldPath(path, "play"),
11291
+ message: "A monitor does not own Play bindings.",
11292
+ suggestion: "Declare sqlListeners in the Play, or run: deepline plays bootstrap monitor-triggered --out on-event.play.ts"
11293
+ });
11294
+ continue;
11295
+ }
11296
+ issues.push({
11297
+ severity: "error",
11298
+ code: "unknown_monitor_field",
11299
+ path: `${path}.${key2}`,
11300
+ message: `Monitor definitions do not accept '${key2}'.`
11301
+ });
11302
+ }
11303
+ }
11304
+ const key = typeof input.key === "string" ? input.key.trim() : "";
11305
+ const tool = typeof input.tool === "string" ? input.tool.trim() : "";
11306
+ const name = input.name === void 0 ? void 0 : typeof input.name === "string" && input.name.trim() ? input.name.trim() : null;
11307
+ const payload = record(input.payload);
11308
+ if (!key) {
11309
+ issues.push({
11310
+ severity: "error",
11311
+ code: "invalid_monitor_key",
11312
+ path: `${path}.key`,
11313
+ message: "Monitor key must be a non-empty string."
11314
+ });
11315
+ }
11316
+ if (!tool) {
11317
+ issues.push({
11318
+ severity: "error",
11319
+ code: "invalid_monitor_tool",
11320
+ path: `${path}.tool`,
11321
+ message: "Monitor tool must be a non-empty literal string."
11322
+ });
11323
+ }
11324
+ if (name === null) {
11325
+ issues.push({
11326
+ severity: "error",
11327
+ code: "invalid_monitor_name",
11328
+ path: `${path}.name`,
11329
+ message: "Monitor name must be a non-empty string when supplied."
11330
+ });
11331
+ }
11332
+ if (!payload) {
11333
+ issues.push({
11334
+ severity: "error",
11335
+ code: "invalid_monitor_payload",
11336
+ path: `${path}.payload`,
11337
+ message: "Monitor payload must be an object."
11338
+ });
11339
+ } else {
11340
+ json(payload, `${path}.payload`, issues);
11341
+ }
11342
+ if (!key || !tool || !payload || name === null) {
11343
+ return null;
11344
+ }
11345
+ return {
11346
+ key,
11347
+ tool,
11348
+ ...name ? { name } : {},
11349
+ payload
11350
+ };
11351
+ }
11352
+ function issueOrder(left, right) {
11353
+ return left.path.localeCompare(right.path) || left.severity.localeCompare(right.severity) || left.code.localeCompare(right.code);
11354
+ }
11355
+ function lintMonitorBatchInput(value) {
11356
+ const issues = [];
11357
+ const versionlessEnvelope = !Array.isArray(value) && (() => {
11358
+ const candidate = record(value);
11359
+ return candidate !== null && Object.keys(candidate).length === 1 && Object.prototype.hasOwnProperty.call(candidate, "monitors");
11360
+ })();
11361
+ const legacy = Array.isArray(value) || versionlessEnvelope;
11362
+ const input = legacy ? null : record(value);
11363
+ let monitors = Array.isArray(value) ? value : versionlessEnvelope ? (() => {
11364
+ const candidate = record(value)?.monitors;
11365
+ return Array.isArray(candidate) ? candidate : null;
11366
+ })() : null;
11367
+ if (Array.isArray(value)) {
11368
+ issues.push({
11369
+ severity: "warning",
11370
+ code: "deprecated_batch_array",
11371
+ path: "",
11372
+ message: "Bare monitor arrays are supported for compatibility.",
11373
+ suggestion: 'Wrap the list in {"version":1,"kind":"batch","monitors":[...]}.'
11374
+ });
11375
+ } else if (versionlessEnvelope) {
11376
+ issues.push({
11377
+ severity: "warning",
11378
+ code: "deprecated_batch_missing_envelope_fields",
11379
+ path: "",
11380
+ message: "Versionless monitor envelopes are supported for compatibility.",
11381
+ suggestion: 'Add "version": 1 and "kind": "batch".'
11382
+ });
11383
+ if (!monitors) {
11384
+ issues.push({
11385
+ severity: "error",
11386
+ code: "invalid_batch_monitors",
11387
+ path: "monitors",
11388
+ message: "Batch monitors must be an array."
11389
+ });
11390
+ }
11391
+ } else if (!input) {
11392
+ issues.push({
11393
+ severity: "error",
11394
+ code: "invalid_batch",
11395
+ path: "",
11396
+ message: "Batch input must be an object or a legacy array."
11397
+ });
11398
+ } else {
11399
+ for (const key of Object.keys(input)) {
11400
+ if (!["version", "kind", "monitors"].includes(key)) {
11401
+ issues.push({
11402
+ severity: "error",
11403
+ code: key === "source" || key === "monitor" ? "batch_fleet_shape_mixup" : "unknown_batch_field",
11404
+ path: key,
11405
+ message: `Batch input does not accept '${key}'.`
11406
+ });
11407
+ }
11408
+ }
11409
+ if (input.version !== 1) {
11410
+ issues.push({
11411
+ severity: "error",
11412
+ code: "unsupported_batch_version",
11413
+ path: "version",
11414
+ message: "Batch input version must be 1."
11415
+ });
11416
+ }
11417
+ if (input.kind !== "batch") {
11418
+ issues.push({
11419
+ severity: "error",
11420
+ code: "invalid_batch_kind",
11421
+ path: "kind",
11422
+ message: 'Batch input kind must be "batch".'
11423
+ });
11424
+ }
11425
+ monitors = Array.isArray(input.monitors) ? input.monitors : null;
11426
+ if (!monitors) {
11427
+ issues.push({
11428
+ severity: "error",
11429
+ code: "invalid_batch_monitors",
11430
+ path: "monitors",
11431
+ message: "Batch monitors must be an array."
11432
+ });
11433
+ }
11434
+ }
11435
+ if (!monitors || monitors.length === 0) {
11436
+ if (monitors?.length === 0) {
11437
+ issues.push({
11438
+ severity: "error",
11439
+ code: "empty_batch",
11440
+ path: "monitors",
11441
+ message: "A Batch needs at least one monitor."
11442
+ });
11443
+ }
11444
+ return { issues: issues.sort(issueOrder) };
11445
+ }
11446
+ const normalized = monitors.map(
11447
+ (item, index) => monitorSpec(item, `monitors.${index}`, issues)
11448
+ );
11449
+ const seen = /* @__PURE__ */ new Map();
11450
+ normalized.forEach((item, index) => {
11451
+ if (!item) return;
11452
+ const first = seen.get(item.key);
11453
+ if (first !== void 0) {
11454
+ for (const position of [first, index]) {
11455
+ issues.push({
11456
+ severity: "error",
11457
+ code: "duplicate_monitor_key",
11458
+ path: `monitors.${position}.key`,
11459
+ message: `Duplicate monitor key '${item.key}'.`
11460
+ });
11461
+ }
11462
+ } else {
11463
+ seen.set(item.key, index);
11464
+ }
11465
+ });
11466
+ const ordered = issues.sort(issueOrder);
11467
+ return ordered.some((issue) => issue.severity === "error") ? { issues: ordered } : {
11468
+ normalized: {
11469
+ version: 1,
11470
+ kind: "batch",
11471
+ monitors: normalized
11472
+ },
11473
+ issues: ordered
11474
+ };
11475
+ }
11476
+ function validateHandlebars(value, path, issues) {
11477
+ if (!value.includes("{{")) return;
11478
+ try {
11479
+ Handlebars.parse(value);
11480
+ } catch (error) {
11481
+ issues.push({
11482
+ severity: "error",
11483
+ code: "invalid_handlebars_template",
11484
+ path,
11485
+ message: error instanceof Error ? error.message : "Invalid Handlebars template."
11486
+ });
11487
+ return;
11488
+ }
11489
+ const expressions = value.match(/{{[\s\S]*?}}/g) ?? [];
11490
+ if (expressions.some(
11491
+ (expression) => !/^{{\s*[A-Za-z_][A-Za-z0-9_]*\s*}}$/.test(expression)
11492
+ )) {
11493
+ issues.push({
11494
+ severity: "error",
11495
+ code: "unsupported_handlebars_expression",
11496
+ path,
11497
+ message: "Fleet templates allow only direct source-row fields such as {{domain}}; helpers, blocks, partials, paths, and prototype access are disabled."
11498
+ });
11499
+ }
11500
+ }
11501
+ function walkTemplates(value, path, issues) {
11502
+ if (typeof value === "string") return validateHandlebars(value, path, issues);
11503
+ if (Array.isArray(value)) {
11504
+ value.forEach(
11505
+ (item, index) => walkTemplates(item, `${path}.${index}`, issues)
11506
+ );
11507
+ return;
11508
+ }
11509
+ const input = record(value);
11510
+ if (!input) return;
11511
+ for (const [key, child] of Object.entries(input)) {
11512
+ walkTemplates(child, `${path}.${key}`, issues);
11513
+ }
11514
+ }
11515
+ function lintMonitorSpecTemplate(value, path = "monitor") {
11516
+ const issues = [];
11517
+ const normalized = monitorSpec(value, path, issues);
11518
+ const input = record(value);
11519
+ if (input && typeof input.tool === "string" && input.tool.includes("{{")) {
11520
+ issues.push({
11521
+ severity: "error",
11522
+ code: "templated_monitor_tool",
11523
+ path: `${path}.tool`,
11524
+ message: "Fleet monitor tool must be literal; only key, name, and payload may be templated."
11525
+ });
11526
+ }
11527
+ if (input) {
11528
+ for (const field of ["key", "name", "payload"]) {
11529
+ if (input[field] !== void 0)
11530
+ walkTemplates(input[field], `${path}.${field}`, issues);
11531
+ }
11532
+ }
11533
+ const ordered = issues.sort(issueOrder);
11534
+ return ordered.some((issue) => issue.severity === "error") ? { issues: ordered } : { normalized, issues: ordered };
11535
+ }
11536
+ function renderValue(value, row) {
11537
+ if (typeof value === "string" && value.includes("{{")) {
11538
+ const context = Object.assign(/* @__PURE__ */ Object.create(null), row);
11539
+ return Handlebars.compile(value, {
11540
+ noEscape: true,
11541
+ strict: true,
11542
+ knownHelpersOnly: true,
11543
+ preventIndent: true
11544
+ })(context, {
11545
+ allowProtoMethodsByDefault: false,
11546
+ allowProtoPropertiesByDefault: false,
11547
+ helpers: /* @__PURE__ */ Object.create(null),
11548
+ partials: /* @__PURE__ */ Object.create(null)
11549
+ });
11550
+ }
11551
+ if (Array.isArray(value)) return value.map((item) => renderValue(item, row));
11552
+ const input = record(value);
11553
+ if (!input) return value;
11554
+ return Object.fromEntries(
11555
+ Object.entries(input).map(([key, item]) => [key, renderValue(item, row)])
11556
+ );
11557
+ }
11558
+ function renderMonitorSpecTemplate(template, row) {
11559
+ const lint = lintMonitorSpecTemplate(template);
11560
+ if (!lint.normalized) return { issues: lint.issues };
11561
+ try {
11562
+ const rendered = renderValue(lint.normalized, row);
11563
+ const issues = [];
11564
+ const spec = monitorSpec(rendered, "monitor", issues);
11565
+ return spec ? { spec, issues: issues.sort(issueOrder) } : { issues: issues.sort(issueOrder) };
11566
+ } catch (error) {
11567
+ return {
11568
+ issues: [
11569
+ {
11570
+ severity: "error",
11571
+ code: "monitor_template_render_failed",
11572
+ path: "monitor",
11573
+ message: error instanceof Error ? error.message : "Fleet template rendering failed."
11574
+ }
11575
+ ]
11576
+ };
11577
+ }
11578
+ }
11579
+
11154
11580
  // ../integration-contracts/monitor-fleet-contract.ts
11155
11581
  var MONITOR_FLEET_MAX_MEMBERS = 1e3;
11156
11582
  var MONITOR_FLEET_FRONTIER_MAX_ROWS = 1e4;
11157
- var MONITOR_FLEET_AUTHORING_CONTRACT_EDITION = 1;
11583
+ var MONITOR_FLEET_AUTHORING_CONTRACT_EDITION = 2;
11158
11584
  var SUPPORTED_MONITOR_FLEET_AUTHORING_CONTRACT_EDITIONS = [
11159
- MONITOR_FLEET_AUTHORING_CONTRACT_EDITION
11585
+ 1,
11586
+ 2
11160
11587
  ];
11161
11588
  var MONITOR_FLEET_AUTHORING_CONTRACT_CHANGELOG = [
11162
11589
  {
11163
11590
  edition: 1,
11164
- changed: "Initial Fleet JSON contract: bounded sticky table membership, one daily sync, and a seven-day removal grace.",
11591
+ changed: "Initial Fleet JSON contract: bounded sticky table membership, source refresh, and a seven-day removal grace.",
11592
+ compatibilityOwner: "Monitors Runtime",
11593
+ newWritesEnd: null,
11594
+ readerRemoval: null
11595
+ },
11596
+ {
11597
+ edition: 2,
11598
+ changed: "Canonical table-backed Fleet envelope with one Handlebars MonitorSpec template.",
11165
11599
  compatibilityOwner: "Monitors Runtime",
11166
11600
  newWritesEnd: null,
11167
11601
  readerRemoval: null
@@ -11173,11 +11607,11 @@ var MONITOR_FLEET_DOCUMENTATION = {
11173
11607
  authoredFields: {
11174
11608
  id: "Stable lowercase fleet id used by sync, get, and deactivate.",
11175
11609
  source: "Customer DB table, unique row key, and optional equality filters that select candidate rows.",
11176
- member: "Ordinary monitor tool, stable key template, and payload evaluated for each selected source row.",
11610
+ monitor: "One Batch-compatible MonitorSpec. Direct Handlebars source-row fields such as {{domain}} are rendered for each selected row.",
11177
11611
  selection: "Deterministic ranking and an active-member limit between 1 and 1,000."
11178
11612
  },
11179
11613
  fixedPolicy: {
11180
- cadence: "daily",
11614
+ cadence: "60s fair bounded source refresh",
11181
11615
  membership: "sticky",
11182
11616
  removalGrace: "7d",
11183
11617
  onRemoved: "deactivate",
@@ -11185,9 +11619,10 @@ var MONITOR_FLEET_DOCUMENTATION = {
11185
11619
  billing: "No Fleet fee, permit, or renewal. Dry-run reports ordinary monitor lifecycle charges due now."
11186
11620
  },
11187
11621
  stickyMembership: "Ranking fills vacancies but never replaces a current member merely because another row ranks higher.",
11188
- columnExpression: 'Use { "$fleet": "column", "name": "column_name" } in a member key or payload to read a value from each source row.'
11622
+ templating: "Use direct Handlebars fields such as {{domain}} in monitor key, name, or payload. Helpers, blocks, partials, paths, and prototype access are disabled.",
11623
+ legacyCompatibility: 'Edition 1 member/$fleet files remain readable with a lint warning; new files should use version: 2, kind: "fleet", and monitor.'
11189
11624
  };
11190
- function record(value) {
11625
+ function record2(value) {
11191
11626
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
11192
11627
  }
11193
11628
  function identifier(value) {
@@ -11212,7 +11647,7 @@ function jsonValue(value, path, issues) {
11212
11647
  value.forEach((item, index) => jsonValue(item, `${path}.${index}`, issues));
11213
11648
  return;
11214
11649
  }
11215
- const input = record(value);
11650
+ const input = record2(value);
11216
11651
  if (!input) {
11217
11652
  issues.push({
11218
11653
  path,
@@ -11238,7 +11673,7 @@ function defineMonitorFleet(definition) {
11238
11673
  return definition;
11239
11674
  }
11240
11675
  function validateExpression(value, path, issues) {
11241
- const input = record(value);
11676
+ const input = record2(value);
11242
11677
  if (!input || input.$fleet !== "column" || !identifier(input.name)) {
11243
11678
  issues.push({
11244
11679
  path,
@@ -11256,7 +11691,7 @@ function walkPayload(value, path, issues) {
11256
11691
  );
11257
11692
  return;
11258
11693
  }
11259
- const input = record(value);
11694
+ const input = record2(value);
11260
11695
  if (!input) return;
11261
11696
  if ("$fleet" in input) {
11262
11697
  validateExpression(input, path, issues);
@@ -11266,9 +11701,9 @@ function walkPayload(value, path, issues) {
11266
11701
  walkPayload(item, `${path}.${key}`, issues);
11267
11702
  }
11268
11703
  }
11269
- function validateMonitorFleetDefinition(value) {
11704
+ function validateMonitorFleetDefinitionV1(value) {
11270
11705
  const issues = [];
11271
- const input = record(value);
11706
+ const input = record2(value);
11272
11707
  if (!input) {
11273
11708
  return {
11274
11709
  valid: false,
@@ -11289,7 +11724,7 @@ function validateMonitorFleetDefinition(value) {
11289
11724
  message: "Fleet id must be lowercase kebab-case."
11290
11725
  });
11291
11726
  }
11292
- const source = record(input.source);
11727
+ const source = record2(input.source);
11293
11728
  exactKeys(
11294
11729
  source,
11295
11730
  ["kind", "schema", "table", "key", "where"],
@@ -11310,7 +11745,7 @@ function validateMonitorFleetDefinition(value) {
11310
11745
  message: "Source schema and table must be SQL identifiers."
11311
11746
  });
11312
11747
  }
11313
- const sourceKey = record(source?.key);
11748
+ const sourceKey = record2(source?.key);
11314
11749
  exactKeys(sourceKey, ["column", "type"], "source.key", issues);
11315
11750
  if (!sourceKey || !identifier(sourceKey.column) || !["text", "uuid", "int4", "int8"].includes(String(sourceKey.type))) {
11316
11751
  issues.push({
@@ -11320,7 +11755,7 @@ function validateMonitorFleetDefinition(value) {
11320
11755
  });
11321
11756
  }
11322
11757
  if (source?.where !== void 0) {
11323
- const where = record(source.where);
11758
+ const where = record2(source.where);
11324
11759
  if (!where) {
11325
11760
  issues.push({
11326
11761
  path: "source.where",
@@ -11346,9 +11781,9 @@ function validateMonitorFleetDefinition(value) {
11346
11781
  }
11347
11782
  }
11348
11783
  }
11349
- const member = record(input.member);
11784
+ const member = record2(input.member);
11350
11785
  exactKeys(member, ["tool", "key", "payload"], "member", issues);
11351
- const key = record(member?.key);
11786
+ const key = record2(member?.key);
11352
11787
  if (!member || typeof member.tool !== "string" || !member.tool.trim()) {
11353
11788
  issues.push({
11354
11789
  path: "member.tool",
@@ -11369,7 +11804,7 @@ function validateMonitorFleetDefinition(value) {
11369
11804
  validateExpression(part, `member.key.parts.${index}`, issues);
11370
11805
  }
11371
11806
  if (sourceKey && !key.parts.some(
11372
- (part) => record(part)?.$fleet === "column" && record(part)?.name === sourceKey.column
11807
+ (part) => record2(part)?.$fleet === "column" && record2(part)?.name === sourceKey.column
11373
11808
  )) {
11374
11809
  issues.push({
11375
11810
  path: "member.key.parts",
@@ -11388,7 +11823,7 @@ function validateMonitorFleetDefinition(value) {
11388
11823
  jsonValue(member.payload, "member.payload", issues);
11389
11824
  }
11390
11825
  walkPayload(member?.payload, "member.payload", issues);
11391
- const selection = record(input.selection);
11826
+ const selection = record2(input.selection);
11392
11827
  exactKeys(selection, ["limit", "orderBy", "membership"], "selection", issues);
11393
11828
  if (!selection || !Number.isSafeInteger(selection.limit) || Number(selection.limit) < 1 || Number(selection.limit) > MONITOR_FLEET_MAX_MEMBERS) {
11394
11829
  issues.push({
@@ -11414,7 +11849,7 @@ function validateMonitorFleetDefinition(value) {
11414
11849
  } else {
11415
11850
  const orderColumns = /* @__PURE__ */ new Set();
11416
11851
  for (const [index, order] of orderBy.entries()) {
11417
- const item = record(order);
11852
+ const item = record2(order);
11418
11853
  exactKeys(
11419
11854
  item,
11420
11855
  ["column", "direction"],
@@ -11437,7 +11872,7 @@ function validateMonitorFleetDefinition(value) {
11437
11872
  orderColumns.add(item.column);
11438
11873
  }
11439
11874
  }
11440
- if (sourceKey && record(orderBy.at(-1))?.column !== sourceKey.column) {
11875
+ if (sourceKey && record2(orderBy.at(-1))?.column !== sourceKey.column) {
11441
11876
  issues.push({
11442
11877
  path: "selection.orderBy",
11443
11878
  code: "fleet_order_missing_key",
@@ -11456,9 +11891,104 @@ function validateMonitorFleetDefinition(value) {
11456
11891
  }
11457
11892
  return issues.length === 0 ? { valid: true, definition: value, issues } : { valid: false, issues };
11458
11893
  }
11459
- function admitMonitorFleetAuthoringContract(value, edition = MONITOR_FLEET_AUTHORING_CONTRACT_EDITION) {
11894
+ function v2Issue(issue) {
11895
+ return { path: issue.path, code: issue.code, message: issue.message };
11896
+ }
11897
+ function validateMonitorFleetDefinitionV2(value) {
11898
+ const issues = [];
11899
+ const input = record2(value);
11900
+ if (!input) {
11901
+ return {
11902
+ valid: false,
11903
+ issues: [
11904
+ {
11905
+ path: "",
11906
+ code: "invalid_fleet",
11907
+ message: "Fleet must be an object."
11908
+ }
11909
+ ]
11910
+ };
11911
+ }
11912
+ exactKeys(
11913
+ input,
11914
+ ["version", "kind", "id", "source", "monitor", "selection"],
11915
+ "",
11916
+ issues
11917
+ );
11918
+ if (input.version !== 2) {
11919
+ issues.push({
11920
+ path: "version",
11921
+ code: "unsupported_fleet_version",
11922
+ message: "Canonical Fleet input version must be 2."
11923
+ });
11924
+ }
11925
+ if (input.kind !== "fleet") {
11926
+ issues.push({
11927
+ path: "kind",
11928
+ code: "invalid_fleet_kind",
11929
+ message: 'Canonical Fleet input kind must be "fleet".'
11930
+ });
11931
+ }
11932
+ const source = record2(input.source);
11933
+ const sourceKey = record2(source?.key);
11934
+ const legacyShape = {
11935
+ id: input.id,
11936
+ source: input.source,
11937
+ member: {
11938
+ tool: record2(input.monitor)?.tool,
11939
+ key: {
11940
+ $fleet: "template",
11941
+ parts: [
11942
+ "fleet-",
11943
+ {
11944
+ $fleet: "column",
11945
+ name: typeof sourceKey?.column === "string" ? sourceKey.column : "invalid_source_key"
11946
+ }
11947
+ ]
11948
+ },
11949
+ payload: record2(input.monitor)?.payload
11950
+ },
11951
+ selection: input.selection
11952
+ };
11953
+ const legacyCheck = validateMonitorFleetDefinitionV1(legacyShape);
11954
+ for (const issue of legacyCheck.issues) {
11955
+ issues.push({
11956
+ ...issue,
11957
+ path: issue.path.replace(/^member\./, "monitor.")
11958
+ });
11959
+ }
11960
+ const monitorInput = record2(input.monitor);
11961
+ const monitorKey = monitorInput?.key;
11962
+ const monitor = lintMonitorSpecTemplate(input.monitor);
11963
+ issues.push(...monitor.issues.map(v2Issue));
11964
+ if (typeof monitorKey === "string" && typeof sourceKey?.column === "string" && !monitorKey.includes(`{{${sourceKey.column}}}`)) {
11965
+ issues.push({
11966
+ path: "monitor.key",
11967
+ code: "fleet_key_missing_source_key",
11968
+ message: "Monitor key must include the source key as a direct Handlebars expression."
11969
+ });
11970
+ }
11971
+ return issues.length === 0 && monitor.normalized ? {
11972
+ valid: true,
11973
+ definition: {
11974
+ version: 2,
11975
+ kind: "fleet",
11976
+ id: String(input.id),
11977
+ source: input.source,
11978
+ monitor: monitor.normalized,
11979
+ selection: input.selection
11980
+ },
11981
+ issues
11982
+ } : { valid: false, issues };
11983
+ }
11984
+ function validateMonitorFleetDefinition(value) {
11985
+ const input = record2(value);
11986
+ return input?.version === 2 || input?.kind === "fleet" ? validateMonitorFleetDefinitionV2(value) : validateMonitorFleetDefinitionV1(value);
11987
+ }
11988
+ function admitMonitorFleetAuthoringContract(value, edition) {
11989
+ const detectedEdition = edition ?? (record2(value)?.version === 2 || record2(value)?.kind === "fleet" ? 2 : 1);
11460
11990
  if (!SUPPORTED_MONITOR_FLEET_AUTHORING_CONTRACT_EDITIONS.includes(
11461
- edition
11991
+ detectedEdition
11462
11992
  )) {
11463
11993
  return {
11464
11994
  valid: false,
@@ -11466,16 +11996,16 @@ function admitMonitorFleetAuthoringContract(value, edition = MONITOR_FLEET_AUTHO
11466
11996
  {
11467
11997
  path: "authoring_contract_edition",
11468
11998
  code: "unsupported_fleet_authoring_contract_edition",
11469
- message: `Monitor Fleet authoring contract edition ${edition} is not supported.`
11999
+ message: `Monitor Fleet authoring contract edition ${detectedEdition} is not supported.`
11470
12000
  }
11471
12001
  ]
11472
12002
  };
11473
12003
  }
11474
- const result = validateMonitorFleetDefinition(value);
12004
+ const result = detectedEdition === 2 ? validateMonitorFleetDefinitionV2(value) : validateMonitorFleetDefinitionV1(value);
11475
12005
  return result.valid && result.definition ? {
11476
12006
  valid: true,
11477
12007
  contract: {
11478
- edition,
12008
+ edition: detectedEdition,
11479
12009
  // JSON cloning prevents later caller mutation from changing the
11480
12010
  // admitted snapshot.
11481
12011
  definition: JSON.parse(
@@ -11485,6 +12015,32 @@ function admitMonitorFleetAuthoringContract(value, edition = MONITOR_FLEET_AUTHO
11485
12015
  issues: []
11486
12016
  } : { valid: false, issues: result.issues };
11487
12017
  }
12018
+ function lintMonitorFleetInput(value) {
12019
+ const input = record2(value);
12020
+ const legacy = input?.member !== void 0;
12021
+ const admitted = admitMonitorFleetAuthoringContract(value);
12022
+ const issues = admitted.issues.map((issue) => ({
12023
+ ...issue,
12024
+ severity: "error"
12025
+ }));
12026
+ if (legacy) {
12027
+ issues.push({
12028
+ severity: "warning",
12029
+ code: "deprecated_fleet_member",
12030
+ path: "member",
12031
+ message: "Legacy Fleet member syntax remains supported."
12032
+ });
12033
+ if (JSON.stringify(input).includes('"$fleet"')) {
12034
+ issues.push({
12035
+ severity: "warning",
12036
+ code: "deprecated_fleet_expression",
12037
+ path: "member",
12038
+ message: "Legacy $fleet expressions remain supported; use direct Handlebars fields in monitor instead."
12039
+ });
12040
+ }
12041
+ }
12042
+ return admitted.valid && admitted.contract ? { normalized: admitted.contract.definition, issues } : { issues };
12043
+ }
11488
12044
 
11489
12045
  // ../plays/bootstrap-routes.ts
11490
12046
  var DEEPLINE_TOOL_CATEGORIES = [
@@ -11816,6 +12372,10 @@ export {
11816
12372
  isPlayBootstrapTemplate,
11817
12373
  isProviderUnavailable,
11818
12374
  isProviderWaterfallUnavailableError,
12375
+ lintMonitorBatchInput,
12376
+ lintMonitorFleetInput,
12377
+ lintMonitorSpecTemplate,
12378
+ renderMonitorSpecTemplate,
11819
12379
  resolveConfig,
11820
12380
  runIf,
11821
12381
  steps,