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