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
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/monitor-fleet-contract.ts
@@ -32,21 +42,215 @@ __export(monitor_fleet_contract_exports, {
32
42
  evaluateMonitorFleetValue: () => evaluateMonitorFleetValue,
33
43
  fleetColumn: () => fleetColumn,
34
44
  fleetKey: () => fleetKey,
45
+ lintMonitorFleetInput: () => lintMonitorFleetInput,
35
46
  validateMonitorFleetDefinition: () => validateMonitorFleetDefinition
36
47
  });
37
48
  module.exports = __toCommonJS(monitor_fleet_contract_exports);
38
49
 
50
+ // ../integration-contracts/monitor-input-contract.ts
51
+ var import_handlebars = __toESM(require("handlebars"), 1);
52
+ function authoredMonitorFieldPath(path, field) {
53
+ const batch = /^monitors\.(\d+)$/.exec(path);
54
+ return `${batch ? `monitors[${batch[1]}]` : path}.${field}`;
55
+ }
56
+ function record(value) {
57
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
58
+ }
59
+ function json(value, path, issues) {
60
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) {
61
+ return;
62
+ }
63
+ if (Array.isArray(value)) {
64
+ value.forEach((child, index) => json(child, `${path}.${index}`, issues));
65
+ return;
66
+ }
67
+ const object = record(value);
68
+ if (!object) {
69
+ issues.push({
70
+ severity: "error",
71
+ code: "invalid_monitor_json_value",
72
+ path,
73
+ message: "Monitor values must be JSON values."
74
+ });
75
+ return;
76
+ }
77
+ for (const [key, child] of Object.entries(object)) {
78
+ json(child, `${path}.${key}`, issues);
79
+ }
80
+ }
81
+ function monitorSpec(value, path, issues) {
82
+ const input = record(value);
83
+ if (!input) {
84
+ issues.push({
85
+ severity: "error",
86
+ code: "invalid_monitor_spec",
87
+ path,
88
+ message: "A monitor must be a JSON object."
89
+ });
90
+ return null;
91
+ }
92
+ const allowed = /* @__PURE__ */ new Set(["key", "tool", "name", "payload"]);
93
+ for (const key2 of Object.keys(input)) {
94
+ if (!allowed.has(key2)) {
95
+ if (key2 === "controls") {
96
+ issues.push({
97
+ severity: "error",
98
+ code: "CONTROLS_NOT_SUPPORTED",
99
+ path: authoredMonitorFieldPath(path, "controls"),
100
+ message: "Operational controls are managed by Deepline and cannot be authored in Batch or Fleet JSON.",
101
+ suggestion: "Remove controls. Bind downstream automation in a Play with sqlListeners."
102
+ });
103
+ continue;
104
+ }
105
+ if (key2 === "play") {
106
+ issues.push({
107
+ severity: "error",
108
+ code: "PLAY_BINDING_NOT_IN_MONITOR_SPEC",
109
+ path: authoredMonitorFieldPath(path, "play"),
110
+ message: "A monitor does not own Play bindings.",
111
+ suggestion: "Declare sqlListeners in the Play, or run: deepline plays bootstrap monitor-triggered --out on-event.play.ts"
112
+ });
113
+ continue;
114
+ }
115
+ issues.push({
116
+ severity: "error",
117
+ code: "unknown_monitor_field",
118
+ path: `${path}.${key2}`,
119
+ message: `Monitor definitions do not accept '${key2}'.`
120
+ });
121
+ }
122
+ }
123
+ const key = typeof input.key === "string" ? input.key.trim() : "";
124
+ const tool = typeof input.tool === "string" ? input.tool.trim() : "";
125
+ const name = input.name === void 0 ? void 0 : typeof input.name === "string" && input.name.trim() ? input.name.trim() : null;
126
+ const payload = record(input.payload);
127
+ if (!key) {
128
+ issues.push({
129
+ severity: "error",
130
+ code: "invalid_monitor_key",
131
+ path: `${path}.key`,
132
+ message: "Monitor key must be a non-empty string."
133
+ });
134
+ }
135
+ if (!tool) {
136
+ issues.push({
137
+ severity: "error",
138
+ code: "invalid_monitor_tool",
139
+ path: `${path}.tool`,
140
+ message: "Monitor tool must be a non-empty literal string."
141
+ });
142
+ }
143
+ if (name === null) {
144
+ issues.push({
145
+ severity: "error",
146
+ code: "invalid_monitor_name",
147
+ path: `${path}.name`,
148
+ message: "Monitor name must be a non-empty string when supplied."
149
+ });
150
+ }
151
+ if (!payload) {
152
+ issues.push({
153
+ severity: "error",
154
+ code: "invalid_monitor_payload",
155
+ path: `${path}.payload`,
156
+ message: "Monitor payload must be an object."
157
+ });
158
+ } else {
159
+ json(payload, `${path}.payload`, issues);
160
+ }
161
+ if (!key || !tool || !payload || name === null) {
162
+ return null;
163
+ }
164
+ return {
165
+ key,
166
+ tool,
167
+ ...name ? { name } : {},
168
+ payload
169
+ };
170
+ }
171
+ function issueOrder(left, right) {
172
+ return left.path.localeCompare(right.path) || left.severity.localeCompare(right.severity) || left.code.localeCompare(right.code);
173
+ }
174
+ function validateHandlebars(value, path, issues) {
175
+ if (!value.includes("{{")) return;
176
+ try {
177
+ import_handlebars.default.parse(value);
178
+ } catch (error) {
179
+ issues.push({
180
+ severity: "error",
181
+ code: "invalid_handlebars_template",
182
+ path,
183
+ message: error instanceof Error ? error.message : "Invalid Handlebars template."
184
+ });
185
+ return;
186
+ }
187
+ const expressions = value.match(/{{[\s\S]*?}}/g) ?? [];
188
+ if (expressions.some(
189
+ (expression) => !/^{{\s*[A-Za-z_][A-Za-z0-9_]*\s*}}$/.test(expression)
190
+ )) {
191
+ issues.push({
192
+ severity: "error",
193
+ code: "unsupported_handlebars_expression",
194
+ path,
195
+ message: "Fleet templates allow only direct source-row fields such as {{domain}}; helpers, blocks, partials, paths, and prototype access are disabled."
196
+ });
197
+ }
198
+ }
199
+ function walkTemplates(value, path, issues) {
200
+ if (typeof value === "string") return validateHandlebars(value, path, issues);
201
+ if (Array.isArray(value)) {
202
+ value.forEach(
203
+ (item, index) => walkTemplates(item, `${path}.${index}`, issues)
204
+ );
205
+ return;
206
+ }
207
+ const input = record(value);
208
+ if (!input) return;
209
+ for (const [key, child] of Object.entries(input)) {
210
+ walkTemplates(child, `${path}.${key}`, issues);
211
+ }
212
+ }
213
+ function lintMonitorSpecTemplate(value, path = "monitor") {
214
+ const issues = [];
215
+ const normalized = monitorSpec(value, path, issues);
216
+ const input = record(value);
217
+ if (input && typeof input.tool === "string" && input.tool.includes("{{")) {
218
+ issues.push({
219
+ severity: "error",
220
+ code: "templated_monitor_tool",
221
+ path: `${path}.tool`,
222
+ message: "Fleet monitor tool must be literal; only key, name, and payload may be templated."
223
+ });
224
+ }
225
+ if (input) {
226
+ for (const field of ["key", "name", "payload"]) {
227
+ if (input[field] !== void 0)
228
+ walkTemplates(input[field], `${path}.${field}`, issues);
229
+ }
230
+ }
231
+ const ordered = issues.sort(issueOrder);
232
+ return ordered.some((issue) => issue.severity === "error") ? { issues: ordered } : { normalized, issues: ordered };
233
+ }
234
+
39
235
  // ../integration-contracts/monitor-fleet-contract.ts
40
236
  var MONITOR_FLEET_MAX_MEMBERS = 1e3;
41
237
  var MONITOR_FLEET_FRONTIER_MAX_ROWS = 1e4;
42
- var MONITOR_FLEET_AUTHORING_CONTRACT_EDITION = 1;
238
+ var MONITOR_FLEET_AUTHORING_CONTRACT_EDITION = 2;
43
239
  var SUPPORTED_MONITOR_FLEET_AUTHORING_CONTRACT_EDITIONS = [
44
- MONITOR_FLEET_AUTHORING_CONTRACT_EDITION
240
+ 1,
241
+ 2
45
242
  ];
46
243
  var MONITOR_FLEET_AUTHORING_CONTRACT_CHANGELOG = [
47
244
  {
48
245
  edition: 1,
49
- changed: "Initial Fleet JSON contract: bounded sticky table membership, one daily sync, and a seven-day removal grace.",
246
+ changed: "Initial Fleet JSON contract: bounded sticky table membership, source refresh, and a seven-day removal grace.",
247
+ compatibilityOwner: "Monitors Runtime",
248
+ newWritesEnd: null,
249
+ readerRemoval: null
250
+ },
251
+ {
252
+ edition: 2,
253
+ changed: "Canonical table-backed Fleet envelope with one Handlebars MonitorSpec template.",
50
254
  compatibilityOwner: "Monitors Runtime",
51
255
  newWritesEnd: null,
52
256
  readerRemoval: null
@@ -58,11 +262,11 @@ var MONITOR_FLEET_DOCUMENTATION = {
58
262
  authoredFields: {
59
263
  id: "Stable lowercase fleet id used by sync, get, and deactivate.",
60
264
  source: "Customer DB table, unique row key, and optional equality filters that select candidate rows.",
61
- member: "Ordinary monitor tool, stable key template, and payload evaluated for each selected source row.",
265
+ monitor: "One Batch-compatible MonitorSpec. Direct Handlebars source-row fields such as {{domain}} are rendered for each selected row.",
62
266
  selection: "Deterministic ranking and an active-member limit between 1 and 1,000."
63
267
  },
64
268
  fixedPolicy: {
65
- cadence: "daily",
269
+ cadence: "60s fair bounded source refresh",
66
270
  membership: "sticky",
67
271
  removalGrace: "7d",
68
272
  onRemoved: "deactivate",
@@ -70,9 +274,10 @@ var MONITOR_FLEET_DOCUMENTATION = {
70
274
  billing: "No Fleet fee, permit, or renewal. Dry-run reports ordinary monitor lifecycle charges due now."
71
275
  },
72
276
  stickyMembership: "Ranking fills vacancies but never replaces a current member merely because another row ranks higher.",
73
- columnExpression: 'Use { "$fleet": "column", "name": "column_name" } in a member key or payload to read a value from each source row.'
277
+ templating: "Use direct Handlebars fields such as {{domain}} in monitor key, name, or payload. Helpers, blocks, partials, paths, and prototype access are disabled.",
278
+ legacyCompatibility: 'Edition 1 member/$fleet files remain readable with a lint warning; new files should use version: 2, kind: "fleet", and monitor.'
74
279
  };
75
- function record(value) {
280
+ function record2(value) {
76
281
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
77
282
  }
78
283
  function identifier(value) {
@@ -97,7 +302,7 @@ function jsonValue(value, path, issues) {
97
302
  value.forEach((item, index) => jsonValue(item, `${path}.${index}`, issues));
98
303
  return;
99
304
  }
100
- const input = record(value);
305
+ const input = record2(value);
101
306
  if (!input) {
102
307
  issues.push({
103
308
  path,
@@ -123,7 +328,7 @@ function defineMonitorFleet(definition) {
123
328
  return definition;
124
329
  }
125
330
  function validateExpression(value, path, issues) {
126
- const input = record(value);
331
+ const input = record2(value);
127
332
  if (!input || input.$fleet !== "column" || !identifier(input.name)) {
128
333
  issues.push({
129
334
  path,
@@ -141,7 +346,7 @@ function walkPayload(value, path, issues) {
141
346
  );
142
347
  return;
143
348
  }
144
- const input = record(value);
349
+ const input = record2(value);
145
350
  if (!input) return;
146
351
  if ("$fleet" in input) {
147
352
  validateExpression(input, path, issues);
@@ -151,9 +356,9 @@ function walkPayload(value, path, issues) {
151
356
  walkPayload(item, `${path}.${key}`, issues);
152
357
  }
153
358
  }
154
- function validateMonitorFleetDefinition(value) {
359
+ function validateMonitorFleetDefinitionV1(value) {
155
360
  const issues = [];
156
- const input = record(value);
361
+ const input = record2(value);
157
362
  if (!input) {
158
363
  return {
159
364
  valid: false,
@@ -174,7 +379,7 @@ function validateMonitorFleetDefinition(value) {
174
379
  message: "Fleet id must be lowercase kebab-case."
175
380
  });
176
381
  }
177
- const source = record(input.source);
382
+ const source = record2(input.source);
178
383
  exactKeys(
179
384
  source,
180
385
  ["kind", "schema", "table", "key", "where"],
@@ -195,7 +400,7 @@ function validateMonitorFleetDefinition(value) {
195
400
  message: "Source schema and table must be SQL identifiers."
196
401
  });
197
402
  }
198
- const sourceKey = record(source?.key);
403
+ const sourceKey = record2(source?.key);
199
404
  exactKeys(sourceKey, ["column", "type"], "source.key", issues);
200
405
  if (!sourceKey || !identifier(sourceKey.column) || !["text", "uuid", "int4", "int8"].includes(String(sourceKey.type))) {
201
406
  issues.push({
@@ -205,7 +410,7 @@ function validateMonitorFleetDefinition(value) {
205
410
  });
206
411
  }
207
412
  if (source?.where !== void 0) {
208
- const where = record(source.where);
413
+ const where = record2(source.where);
209
414
  if (!where) {
210
415
  issues.push({
211
416
  path: "source.where",
@@ -231,9 +436,9 @@ function validateMonitorFleetDefinition(value) {
231
436
  }
232
437
  }
233
438
  }
234
- const member = record(input.member);
439
+ const member = record2(input.member);
235
440
  exactKeys(member, ["tool", "key", "payload"], "member", issues);
236
- const key = record(member?.key);
441
+ const key = record2(member?.key);
237
442
  if (!member || typeof member.tool !== "string" || !member.tool.trim()) {
238
443
  issues.push({
239
444
  path: "member.tool",
@@ -254,7 +459,7 @@ function validateMonitorFleetDefinition(value) {
254
459
  validateExpression(part, `member.key.parts.${index}`, issues);
255
460
  }
256
461
  if (sourceKey && !key.parts.some(
257
- (part) => record(part)?.$fleet === "column" && record(part)?.name === sourceKey.column
462
+ (part) => record2(part)?.$fleet === "column" && record2(part)?.name === sourceKey.column
258
463
  )) {
259
464
  issues.push({
260
465
  path: "member.key.parts",
@@ -273,7 +478,7 @@ function validateMonitorFleetDefinition(value) {
273
478
  jsonValue(member.payload, "member.payload", issues);
274
479
  }
275
480
  walkPayload(member?.payload, "member.payload", issues);
276
- const selection = record(input.selection);
481
+ const selection = record2(input.selection);
277
482
  exactKeys(selection, ["limit", "orderBy", "membership"], "selection", issues);
278
483
  if (!selection || !Number.isSafeInteger(selection.limit) || Number(selection.limit) < 1 || Number(selection.limit) > MONITOR_FLEET_MAX_MEMBERS) {
279
484
  issues.push({
@@ -299,7 +504,7 @@ function validateMonitorFleetDefinition(value) {
299
504
  } else {
300
505
  const orderColumns = /* @__PURE__ */ new Set();
301
506
  for (const [index, order] of orderBy.entries()) {
302
- const item = record(order);
507
+ const item = record2(order);
303
508
  exactKeys(
304
509
  item,
305
510
  ["column", "direction"],
@@ -322,7 +527,7 @@ function validateMonitorFleetDefinition(value) {
322
527
  orderColumns.add(item.column);
323
528
  }
324
529
  }
325
- if (sourceKey && record(orderBy.at(-1))?.column !== sourceKey.column) {
530
+ if (sourceKey && record2(orderBy.at(-1))?.column !== sourceKey.column) {
326
531
  issues.push({
327
532
  path: "selection.orderBy",
328
533
  code: "fleet_order_missing_key",
@@ -341,9 +546,104 @@ function validateMonitorFleetDefinition(value) {
341
546
  }
342
547
  return issues.length === 0 ? { valid: true, definition: value, issues } : { valid: false, issues };
343
548
  }
344
- function admitMonitorFleetAuthoringContract(value, edition = MONITOR_FLEET_AUTHORING_CONTRACT_EDITION) {
549
+ function v2Issue(issue) {
550
+ return { path: issue.path, code: issue.code, message: issue.message };
551
+ }
552
+ function validateMonitorFleetDefinitionV2(value) {
553
+ const issues = [];
554
+ const input = record2(value);
555
+ if (!input) {
556
+ return {
557
+ valid: false,
558
+ issues: [
559
+ {
560
+ path: "",
561
+ code: "invalid_fleet",
562
+ message: "Fleet must be an object."
563
+ }
564
+ ]
565
+ };
566
+ }
567
+ exactKeys(
568
+ input,
569
+ ["version", "kind", "id", "source", "monitor", "selection"],
570
+ "",
571
+ issues
572
+ );
573
+ if (input.version !== 2) {
574
+ issues.push({
575
+ path: "version",
576
+ code: "unsupported_fleet_version",
577
+ message: "Canonical Fleet input version must be 2."
578
+ });
579
+ }
580
+ if (input.kind !== "fleet") {
581
+ issues.push({
582
+ path: "kind",
583
+ code: "invalid_fleet_kind",
584
+ message: 'Canonical Fleet input kind must be "fleet".'
585
+ });
586
+ }
587
+ const source = record2(input.source);
588
+ const sourceKey = record2(source?.key);
589
+ const legacyShape = {
590
+ id: input.id,
591
+ source: input.source,
592
+ member: {
593
+ tool: record2(input.monitor)?.tool,
594
+ key: {
595
+ $fleet: "template",
596
+ parts: [
597
+ "fleet-",
598
+ {
599
+ $fleet: "column",
600
+ name: typeof sourceKey?.column === "string" ? sourceKey.column : "invalid_source_key"
601
+ }
602
+ ]
603
+ },
604
+ payload: record2(input.monitor)?.payload
605
+ },
606
+ selection: input.selection
607
+ };
608
+ const legacyCheck = validateMonitorFleetDefinitionV1(legacyShape);
609
+ for (const issue of legacyCheck.issues) {
610
+ issues.push({
611
+ ...issue,
612
+ path: issue.path.replace(/^member\./, "monitor.")
613
+ });
614
+ }
615
+ const monitorInput = record2(input.monitor);
616
+ const monitorKey = monitorInput?.key;
617
+ const monitor = lintMonitorSpecTemplate(input.monitor);
618
+ issues.push(...monitor.issues.map(v2Issue));
619
+ if (typeof monitorKey === "string" && typeof sourceKey?.column === "string" && !monitorKey.includes(`{{${sourceKey.column}}}`)) {
620
+ issues.push({
621
+ path: "monitor.key",
622
+ code: "fleet_key_missing_source_key",
623
+ message: "Monitor key must include the source key as a direct Handlebars expression."
624
+ });
625
+ }
626
+ return issues.length === 0 && monitor.normalized ? {
627
+ valid: true,
628
+ definition: {
629
+ version: 2,
630
+ kind: "fleet",
631
+ id: String(input.id),
632
+ source: input.source,
633
+ monitor: monitor.normalized,
634
+ selection: input.selection
635
+ },
636
+ issues
637
+ } : { valid: false, issues };
638
+ }
639
+ function validateMonitorFleetDefinition(value) {
640
+ const input = record2(value);
641
+ return input?.version === 2 || input?.kind === "fleet" ? validateMonitorFleetDefinitionV2(value) : validateMonitorFleetDefinitionV1(value);
642
+ }
643
+ function admitMonitorFleetAuthoringContract(value, edition) {
644
+ const detectedEdition = edition ?? (record2(value)?.version === 2 || record2(value)?.kind === "fleet" ? 2 : 1);
345
645
  if (!SUPPORTED_MONITOR_FLEET_AUTHORING_CONTRACT_EDITIONS.includes(
346
- edition
646
+ detectedEdition
347
647
  )) {
348
648
  return {
349
649
  valid: false,
@@ -351,16 +651,16 @@ function admitMonitorFleetAuthoringContract(value, edition = MONITOR_FLEET_AUTHO
351
651
  {
352
652
  path: "authoring_contract_edition",
353
653
  code: "unsupported_fleet_authoring_contract_edition",
354
- message: `Monitor Fleet authoring contract edition ${edition} is not supported.`
654
+ message: `Monitor Fleet authoring contract edition ${detectedEdition} is not supported.`
355
655
  }
356
656
  ]
357
657
  };
358
658
  }
359
- const result = validateMonitorFleetDefinition(value);
659
+ const result = detectedEdition === 2 ? validateMonitorFleetDefinitionV2(value) : validateMonitorFleetDefinitionV1(value);
360
660
  return result.valid && result.definition ? {
361
661
  valid: true,
362
662
  contract: {
363
- edition,
663
+ edition: detectedEdition,
364
664
  // JSON cloning prevents later caller mutation from changing the
365
665
  // admitted snapshot.
366
666
  definition: JSON.parse(
@@ -370,10 +670,36 @@ function admitMonitorFleetAuthoringContract(value, edition = MONITOR_FLEET_AUTHO
370
670
  issues: []
371
671
  } : { valid: false, issues: result.issues };
372
672
  }
673
+ function lintMonitorFleetInput(value) {
674
+ const input = record2(value);
675
+ const legacy = input?.member !== void 0;
676
+ const admitted = admitMonitorFleetAuthoringContract(value);
677
+ const issues = admitted.issues.map((issue) => ({
678
+ ...issue,
679
+ severity: "error"
680
+ }));
681
+ if (legacy) {
682
+ issues.push({
683
+ severity: "warning",
684
+ code: "deprecated_fleet_member",
685
+ path: "member",
686
+ message: "Legacy Fleet member syntax remains supported."
687
+ });
688
+ if (JSON.stringify(input).includes('"$fleet"')) {
689
+ issues.push({
690
+ severity: "warning",
691
+ code: "deprecated_fleet_expression",
692
+ path: "member",
693
+ message: "Legacy $fleet expressions remain supported; use direct Handlebars fields in monitor instead."
694
+ });
695
+ }
696
+ }
697
+ return admitted.valid && admitted.contract ? { normalized: admitted.contract.definition, issues } : { issues };
698
+ }
373
699
  function evaluateMonitorFleetValue(value, row) {
374
700
  if (Array.isArray(value))
375
701
  return value.map((item) => evaluateMonitorFleetValue(item, row));
376
- const input = record(value);
702
+ const input = record2(value);
377
703
  if (!input) return value;
378
704
  if (input.$fleet === "column") return row[String(input.name)];
379
705
  if (input.$fleet === "template" && Array.isArray(input.parts)) {
@@ -402,5 +728,6 @@ function evaluateMonitorFleetValue(value, row) {
402
728
  evaluateMonitorFleetValue,
403
729
  fleetColumn,
404
730
  fleetKey,
731
+ lintMonitorFleetInput,
405
732
  validateMonitorFleetDefinition
406
733
  });