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
@@ -996,6 +996,35 @@ export type MonitorValidateResult = Record<string, unknown>;
996
996
  */
997
997
  export type MonitorFleetResult = Record<string, unknown>;
998
998
 
999
+ /** Shared response envelope for immutable batch and desired-set Monitor Jobs. */
1000
+ export type MonitorJobResult = Record<string, unknown>;
1001
+
1002
+ export type MonitorJobDeployOptions = {
1003
+ /** Stable retry key; the server binds it to the submitted immutable set. */
1004
+ idempotencyKey?: string;
1005
+ };
1006
+
1007
+ export type MonitorJobLogsOptions = { limit?: number };
1008
+
1009
+ export type MonitorJobWaitOptions = {
1010
+ timeoutMs?: number;
1011
+ pollIntervalMs?: number;
1012
+ onProgress?: (payload: MonitorJobResult) => void;
1013
+ };
1014
+
1015
+ export type MonitorJobsNamespace = {
1016
+ get: (jobId: string) => Promise<MonitorJobResult>;
1017
+ logs: (
1018
+ jobId: string,
1019
+ options?: MonitorJobLogsOptions,
1020
+ ) => Promise<MonitorJobResult>;
1021
+ cancel: (jobId: string) => Promise<MonitorJobResult>;
1022
+ wait: (
1023
+ jobId: string,
1024
+ options?: MonitorJobWaitOptions,
1025
+ ) => Promise<MonitorJobResult>;
1026
+ };
1027
+
999
1028
  /** Server-computed fleet verdict. Never derived client-side. */
1000
1029
  export type MonitorFleetStatus =
1001
1030
  | 'converging'
@@ -1093,7 +1122,7 @@ export type MonitorBatchOperationResult = {
1093
1122
  | 'reactivate'
1094
1123
  | 'deploy'
1095
1124
  | 'repair';
1096
- state: 'running' | 'retry_scheduled' | 'completed' | 'failed';
1125
+ state: 'running' | 'retry_scheduled' | 'completed' | 'failed' | 'cancelled';
1097
1126
  dryRun: boolean;
1098
1127
  scope: unknown;
1099
1128
  submittedAt: number;
@@ -1113,7 +1142,15 @@ export type MonitorBatchOperationResult = {
1113
1142
 
1114
1143
  export type MonitorFleetsNamespace = {
1115
1144
  /**
1116
- * Create, update, or re-plan a fleet. Pass a definition to admit new source
1145
+ * Primary table-backed Fleet command. It has the same idempotent PUT
1146
+ * contract as `sync`; `sync` remains for installed-client compatibility.
1147
+ */
1148
+ deploy: (
1149
+ definitionOrId: string | MonitorFleetDefinition,
1150
+ options?: MonitorFleetSyncOptions,
1151
+ ) => Promise<MonitorFleetResult>;
1152
+ /**
1153
+ * Compatibility alias for `deploy`. Pass a definition to admit new source
1117
1154
  * of truth; pass a fleet id to re-plan from the stored definition.
1118
1155
  */
1119
1156
  sync: (
@@ -1145,6 +1182,11 @@ export type MonitorFleetsNamespace = {
1145
1182
  };
1146
1183
 
1147
1184
  export type MonitorBatchNamespace = {
1185
+ /** Deploy an immutable one-off list through the common Monitor Job engine. */
1186
+ deploy: (
1187
+ definitions: MonitorDefinition[],
1188
+ options?: MonitorJobDeployOptions,
1189
+ ) => Promise<MonitorJobResult>;
1148
1190
  submit: (
1149
1191
  options: MonitorBatchSubmitOptions,
1150
1192
  ) => Promise<MonitorBatchOperationResult>;
@@ -1370,10 +1412,12 @@ export type MonitorsNamespace = {
1370
1412
  }) => Promise<MonitorsRepairResult>;
1371
1413
  /** Delivery and convergence health for the workspace or one fleet. */
1372
1414
  health: (options?: { fleetId?: string }) => Promise<MonitorsHealth>;
1373
- /** Durable bulk audit/deactivation/reactivation/repair with retained progress. */
1374
- batch: MonitorBatchNamespace;
1375
1415
  /** Define, reconcile, and control table-backed monitor fleets. */
1376
1416
  fleets: MonitorFleetsNamespace;
1417
+ /** Deploy an immutable one-off list through the common Monitor Job engine. */
1418
+ batch: MonitorBatchNamespace;
1419
+ /** Read, wait for, inspect, or cancel Monitor Jobs. */
1420
+ jobs: MonitorJobsNamespace;
1377
1421
  };
1378
1422
 
1379
1423
  /** One credit grant pool reported by the billing subscription status endpoint. */
@@ -2333,11 +2377,21 @@ export class DeeplineClient {
2333
2377
  repair: (options) => this.repairMonitors(options),
2334
2378
  health: (options) => this.readMonitorsHealth(options),
2335
2379
  batch: {
2380
+ deploy: (definitions, options) =>
2381
+ this.deployMonitorBatch(definitions, options),
2336
2382
  submit: (options) => this.submitMonitorBatch(options),
2337
2383
  get: (operationId, options) =>
2338
2384
  this.getMonitorBatch(operationId, options),
2339
2385
  },
2386
+ jobs: {
2387
+ get: (jobId) => this.getMonitorJob(jobId),
2388
+ logs: (jobId, options) => this.getMonitorJobLogs(jobId, options),
2389
+ cancel: (jobId) => this.cancelMonitorJob(jobId),
2390
+ wait: (jobId, options) => this.waitForMonitorJob(jobId, options),
2391
+ },
2340
2392
  fleets: {
2393
+ deploy: (definitionOrId, options) =>
2394
+ this.syncMonitorFleet(definitionOrId, options),
2341
2395
  sync: (definitionOrId, options) =>
2342
2396
  this.syncMonitorFleet(definitionOrId, options),
2343
2397
  get: (fleetId, options) =>
@@ -6017,6 +6071,92 @@ export class DeeplineClient {
6017
6071
  );
6018
6072
  }
6019
6073
 
6074
+ // ——————————————————————————————————————————————————————————
6075
+ // Monitor Jobs, Batches, and Fleets
6076
+ // ——————————————————————————————————————————————————————————
6077
+
6078
+ private monitorJobIdempotencyKey(operation: string): string {
6079
+ const uuid = globalThis.crypto?.randomUUID?.();
6080
+ return `monitor-job-${operation}-${uuid ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`}`;
6081
+ }
6082
+
6083
+ private monitorJobHeaders(
6084
+ operation: string,
6085
+ idempotencyKey?: string,
6086
+ ): Record<string, string> {
6087
+ return {
6088
+ 'Idempotency-Key':
6089
+ idempotencyKey?.trim() || this.monitorJobIdempotencyKey(operation),
6090
+ };
6091
+ }
6092
+
6093
+ async deployMonitorBatch(
6094
+ definitions: MonitorDefinition[],
6095
+ options?: MonitorJobDeployOptions,
6096
+ ): Promise<MonitorJobResult> {
6097
+ return this.http.request<MonitorJobResult>(
6098
+ '/api/v2/monitors/batch/deploy',
6099
+ {
6100
+ method: 'POST',
6101
+ body: { version: 1, kind: 'batch', monitors: definitions },
6102
+ headers: this.monitorJobHeaders(
6103
+ 'batch-deploy',
6104
+ options?.idempotencyKey,
6105
+ ),
6106
+ maxRetries: 0,
6107
+ exactUrlOnly: true,
6108
+ },
6109
+ );
6110
+ }
6111
+
6112
+ async getMonitorJob(jobId: string): Promise<MonitorJobResult> {
6113
+ return this.http.request<MonitorJobResult>(
6114
+ `/api/v2/monitors/jobs/${encodeURIComponent(jobId)}`,
6115
+ { method: 'GET' },
6116
+ );
6117
+ }
6118
+
6119
+ async getMonitorJobLogs(
6120
+ jobId: string,
6121
+ options?: MonitorJobLogsOptions,
6122
+ ): Promise<MonitorJobResult> {
6123
+ const query =
6124
+ options?.limit === undefined
6125
+ ? ''
6126
+ : `?limit=${encodeURIComponent(String(options.limit))}`;
6127
+ return this.http.request<MonitorJobResult>(
6128
+ `/api/v2/monitors/jobs/${encodeURIComponent(jobId)}/logs${query}`,
6129
+ { method: 'GET' },
6130
+ );
6131
+ }
6132
+
6133
+ async cancelMonitorJob(jobId: string): Promise<MonitorJobResult> {
6134
+ return this.http.request<MonitorJobResult>(
6135
+ `/api/v2/monitors/jobs/${encodeURIComponent(jobId)}/cancel`,
6136
+ { method: 'POST', body: {}, maxRetries: 0, exactUrlOnly: true },
6137
+ );
6138
+ }
6139
+
6140
+ async waitForMonitorJob(
6141
+ jobId: string,
6142
+ options?: MonitorJobWaitOptions,
6143
+ ): Promise<MonitorJobResult> {
6144
+ const timeoutMs = Math.max(1, options?.timeoutMs ?? 10 * 60_000);
6145
+ const pollIntervalMs = Math.max(1, options?.pollIntervalMs ?? 2_000);
6146
+ const terminal = new Set(['succeeded', 'failed', 'cancelled']);
6147
+ const startedAt = Date.now();
6148
+ let latest = await this.getMonitorJob(jobId);
6149
+ while (true) {
6150
+ options?.onProgress?.(latest);
6151
+ const job = latest.job as Record<string, unknown> | undefined;
6152
+ const state = typeof job?.state === 'string' ? job.state : undefined;
6153
+ if (state && terminal.has(state)) return latest;
6154
+ if (Date.now() - startedAt >= timeoutMs) return latest;
6155
+ await sleep(pollIntervalMs);
6156
+ latest = await this.getMonitorJob(jobId);
6157
+ }
6158
+ }
6159
+
6020
6160
  // ——————————————————————————————————————————————————————————
6021
6161
  // Monitor Fleets
6022
6162
  // ——————————————————————————————————————————————————————————
@@ -86,6 +86,12 @@ export type {
86
86
  MonitorUpdateResult,
87
87
  MonitorDeleteResult,
88
88
  MonitorReactivateResult,
89
+ MonitorJobResult,
90
+ MonitorJobDeployOptions,
91
+ MonitorJobLogsOptions,
92
+ MonitorJobWaitOptions,
93
+ MonitorJobsNamespace,
94
+ MonitorBatchNamespace,
89
95
  MonitorFleetDeactivateOptions,
90
96
  MonitorFleetReactivateOptions,
91
97
  MonitorFleetGetOptions,
@@ -113,11 +119,17 @@ export type {
113
119
 
114
120
  // ——— Monitors framework ———
115
121
  export { defineMonitor } from './monitors.js';
122
+ export type { MonitorDefinition, MonitorPayload } from './monitors.js';
123
+ export {
124
+ lintMonitorBatchInput,
125
+ lintMonitorSpecTemplate,
126
+ renderMonitorSpecTemplate,
127
+ } from './monitor-input-contract.js';
116
128
  export type {
117
- MonitorDefinition,
118
- MonitorPayload,
119
- MonitorControls,
120
- } from './monitors.js';
129
+ BatchMonitorInput,
130
+ MonitorInputIssue,
131
+ MonitorSpec as MonitorInputSpec,
132
+ } from './monitor-input-contract.js';
121
133
  export {
122
134
  admitMonitorFleetAuthoringContract,
123
135
  MONITOR_FLEET_AUTHORING_CONTRACT_CHANGELOG,
@@ -128,6 +140,7 @@ export {
128
140
  defineMonitorFleet,
129
141
  fleetColumn,
130
142
  fleetKey,
143
+ lintMonitorFleetInput,
131
144
  validateMonitorFleetDefinition,
132
145
  } from './monitor-fleets.js';
133
146
  export type {
@@ -16,6 +16,7 @@ export {
16
16
  defineMonitorFleet,
17
17
  fleetColumn,
18
18
  fleetKey,
19
+ lintMonitorFleetInput,
19
20
  validateMonitorFleetDefinition,
20
21
  } from '../../shared_libs/integration-contracts/monitor-fleet-contract';
21
22
 
@@ -0,0 +1 @@
1
+ export * from '../../shared_libs/integration-contracts/monitor-input-contract';
@@ -48,30 +48,11 @@ export type MonitorDefinition<
48
48
  name?: string;
49
49
  /** Provider-specific monitor payload (e.g. domain + radar_type). */
50
50
  payload: TPayload;
51
- /** Optional Deepline lifecycle metadata (deploy/reuse controls). */
52
- controls?: MonitorControls;
53
51
  };
54
52
 
55
53
  /** Provider-specific monitor payload. Keys and value types depend on the tool. */
56
54
  export type MonitorPayload = Record<string, unknown>;
57
55
 
58
- /**
59
- * Deepline lifecycle metadata attached to a monitor definition. These are
60
- * Deepline-side deploy/reuse controls, not provider payload fields. The set is
61
- * intentionally open (server-owned) so newer controls do not require an SDK
62
- * bump; known controls are typed for discoverability.
63
- */
64
- export type MonitorControls = {
65
- /**
66
- * Request Deepline Native priority execution for a bounded urgent preview or
67
- * calibration radar. Deepline sends the upstream custom-field marker and
68
- * enforces a maximum of ten active or in-flight priority radars per org.
69
- * Omit this for regular and bulk-scale monitoring.
70
- */
71
- execution_type?: 'priority';
72
- [key: string]: unknown;
73
- };
74
-
75
56
  /**
76
57
  * Define a typed monitor definition.
77
58
  *
@@ -125,13 +106,15 @@ export function defineMonitor<TPayload extends MonitorPayload = MonitorPayload>(
125
106
  if (definition.name !== undefined && typeof definition.name !== 'string') {
126
107
  throw new Error('defineMonitor(definition) "name" must be a string.');
127
108
  }
128
- if (
129
- definition.controls !== undefined &&
130
- (typeof definition.controls !== 'object' ||
131
- Array.isArray(definition.controls))
132
- ) {
109
+ const authored = definition as Record<string, unknown>;
110
+ if ('controls' in authored) {
111
+ throw new Error(
112
+ 'defineMonitor(definition) does not accept "controls". Operational controls are managed by Deepline; bind downstream automation in a Play with sqlListeners.',
113
+ );
114
+ }
115
+ if ('play' in authored) {
133
116
  throw new Error(
134
- 'defineMonitor(definition) "controls" must be a JSON object.',
117
+ 'defineMonitor(definition) does not accept "play". A monitor does not own Play bindings; declare sqlListeners in the Play instead.',
135
118
  );
136
119
  }
137
120
  return definition;
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.97',
203
+ version: '0.3.98',
204
204
  updateSummary:
205
205
  '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.',
206
206
  packageCapabilities: {
@@ -1,2 +1,3 @@
1
1
  export * from './bettercontact/execution-policy';
2
2
  export * from './theirstack/execution-policy';
3
+ export * from './monitor-input-contract';
@@ -1,3 +1,8 @@
1
+ import {
2
+ lintMonitorSpecTemplate,
3
+ type MonitorInputIssue,
4
+ } from './monitor-input-contract';
5
+
1
6
  /**
2
7
  * The canonical Fleet authoring contract, shared by the SDK and server.
3
8
  * Monitor transaction admission reuses this ceiling; it must never drift
@@ -12,9 +17,9 @@ export const MONITOR_FLEET_FRONTIER_MAX_ROWS = 10_000;
12
17
  * edition: a future syntax change can add a reader without reinterpreting a
13
18
  * stored Fleet under new rules.
14
19
  */
15
- export const MONITOR_FLEET_AUTHORING_CONTRACT_EDITION = 1 as const;
20
+ export const MONITOR_FLEET_AUTHORING_CONTRACT_EDITION = 2 as const;
16
21
  export const SUPPORTED_MONITOR_FLEET_AUTHORING_CONTRACT_EDITIONS = [
17
- MONITOR_FLEET_AUTHORING_CONTRACT_EDITION,
22
+ 1, 2,
18
23
  ] as const;
19
24
 
20
25
  export type MonitorFleetAuthoringContractEdition =
@@ -24,7 +29,15 @@ export const MONITOR_FLEET_AUTHORING_CONTRACT_CHANGELOG = [
24
29
  {
25
30
  edition: 1,
26
31
  changed:
27
- 'Initial Fleet JSON contract: bounded sticky table membership, one daily sync, and a seven-day removal grace.',
32
+ 'Initial Fleet JSON contract: bounded sticky table membership, source refresh, and a seven-day removal grace.',
33
+ compatibilityOwner: 'Monitors Runtime',
34
+ newWritesEnd: null,
35
+ readerRemoval: null,
36
+ },
37
+ {
38
+ edition: 2,
39
+ changed:
40
+ 'Canonical table-backed Fleet envelope with one Handlebars MonitorSpec template.',
28
41
  compatibilityOwner: 'Monitors Runtime',
29
42
  newWritesEnd: null,
30
43
  readerRemoval: null,
@@ -50,13 +63,13 @@ export const MONITOR_FLEET_DOCUMENTATION = {
50
63
  id: 'Stable lowercase fleet id used by sync, get, and deactivate.',
51
64
  source:
52
65
  'Customer DB table, unique row key, and optional equality filters that select candidate rows.',
53
- member:
54
- 'Ordinary monitor tool, stable key template, and payload evaluated for each selected source row.',
66
+ monitor:
67
+ 'One Batch-compatible MonitorSpec. Direct Handlebars source-row fields such as {{domain}} are rendered for each selected row.',
55
68
  selection:
56
69
  'Deterministic ranking and an active-member limit between 1 and 1,000.',
57
70
  },
58
71
  fixedPolicy: {
59
- cadence: 'daily',
72
+ cadence: '60s fair bounded source refresh',
60
73
  membership: 'sticky',
61
74
  removalGrace: '7d',
62
75
  onRemoved: 'deactivate',
@@ -67,8 +80,10 @@ export const MONITOR_FLEET_DOCUMENTATION = {
67
80
  },
68
81
  stickyMembership:
69
82
  'Ranking fills vacancies but never replaces a current member merely because another row ranks higher.',
70
- columnExpression:
71
- 'Use { "$fleet": "column", "name": "column_name" } in a member key or payload to read a value from each source row.',
83
+ templating:
84
+ 'Use direct Handlebars fields such as {{domain}} in monitor key, name, or payload. Helpers, blocks, partials, paths, and prototype access are disabled.',
85
+ legacyCompatibility:
86
+ 'Edition 1 member/$fleet files remain readable with a lint warning; new files should use version: 2, kind: "fleet", and monitor.',
72
87
  } as const;
73
88
 
74
89
  export type MonitorFleetColumn = {
@@ -83,7 +98,7 @@ export type MonitorFleetTemplate = {
83
98
 
84
99
  export type MonitorFleetExpression = MonitorFleetColumn | MonitorFleetTemplate;
85
100
 
86
- export type MonitorFleetDefinition = {
101
+ export type MonitorFleetDefinitionV1 = {
87
102
  /** Stable lowercase fleet id, used by CLI and API lifecycle commands. */
88
103
  id: string;
89
104
  /** Customer DB rows that may become members. */
@@ -115,6 +130,28 @@ export type MonitorFleetDefinition = {
115
130
  };
116
131
  };
117
132
 
133
+ /**
134
+ * Edition 2 keeps the public file self-describing and aligns the rendered
135
+ * member with Batch exactly. Only source-row values may be interpolated.
136
+ */
137
+ export type MonitorFleetDefinitionV2 = {
138
+ version: 2;
139
+ kind: 'fleet';
140
+ id: string;
141
+ source: MonitorFleetDefinitionV1['source'];
142
+ monitor: {
143
+ key: string;
144
+ tool: string;
145
+ name?: string;
146
+ payload: Record<string, unknown>;
147
+ };
148
+ selection: MonitorFleetDefinitionV1['selection'];
149
+ };
150
+
151
+ export type MonitorFleetDefinition =
152
+ | MonitorFleetDefinitionV1
153
+ | MonitorFleetDefinitionV2;
154
+
118
155
  export type MonitorFleetContractIssue = {
119
156
  path: string;
120
157
  code: string;
@@ -249,7 +286,7 @@ function walkPayload(
249
286
  }
250
287
  }
251
288
 
252
- export function validateMonitorFleetDefinition(value: unknown): {
289
+ function validateMonitorFleetDefinitionV1(value: unknown): {
253
290
  valid: boolean;
254
291
  definition?: MonitorFleetDefinition;
255
292
  issues: MonitorFleetContractIssue[];
@@ -475,10 +512,130 @@ export function validateMonitorFleetDefinition(value: unknown): {
475
512
  }
476
513
  }
477
514
  return issues.length === 0
478
- ? { valid: true, definition: value as MonitorFleetDefinition, issues }
515
+ ? { valid: true, definition: value as MonitorFleetDefinitionV1, issues }
479
516
  : { valid: false, issues };
480
517
  }
481
518
 
519
+ function v2Issue(issue: MonitorInputIssue): MonitorFleetContractIssue {
520
+ return { path: issue.path, code: issue.code, message: issue.message };
521
+ }
522
+
523
+ function validateMonitorFleetDefinitionV2(value: unknown): {
524
+ valid: boolean;
525
+ definition?: MonitorFleetDefinitionV2;
526
+ issues: MonitorFleetContractIssue[];
527
+ } {
528
+ const issues: MonitorFleetContractIssue[] = [];
529
+ const input = record(value);
530
+ if (!input) {
531
+ return {
532
+ valid: false,
533
+ issues: [
534
+ {
535
+ path: '',
536
+ code: 'invalid_fleet',
537
+ message: 'Fleet must be an object.',
538
+ },
539
+ ],
540
+ };
541
+ }
542
+ exactKeys(
543
+ input,
544
+ ['version', 'kind', 'id', 'source', 'monitor', 'selection'],
545
+ '',
546
+ issues,
547
+ );
548
+ if (input.version !== 2) {
549
+ issues.push({
550
+ path: 'version',
551
+ code: 'unsupported_fleet_version',
552
+ message: 'Canonical Fleet input version must be 2.',
553
+ });
554
+ }
555
+ if (input.kind !== 'fleet') {
556
+ issues.push({
557
+ path: 'kind',
558
+ code: 'invalid_fleet_kind',
559
+ message: 'Canonical Fleet input kind must be "fleet".',
560
+ });
561
+ }
562
+ // Reuse the battle-tested source and selection validator without making
563
+ // edition 2 accept its legacy member grammar.
564
+ const source = record(input.source);
565
+ const sourceKey = record(source?.key);
566
+ const legacyShape = {
567
+ id: input.id,
568
+ source: input.source,
569
+ member: {
570
+ tool: record(input.monitor)?.tool,
571
+ key: {
572
+ $fleet: 'template',
573
+ parts: [
574
+ 'fleet-',
575
+ {
576
+ $fleet: 'column',
577
+ name:
578
+ typeof sourceKey?.column === 'string'
579
+ ? sourceKey.column
580
+ : 'invalid_source_key',
581
+ },
582
+ ],
583
+ },
584
+ payload: record(input.monitor)?.payload,
585
+ },
586
+ selection: input.selection,
587
+ };
588
+ const legacyCheck = validateMonitorFleetDefinitionV1(legacyShape);
589
+ for (const issue of legacyCheck.issues) {
590
+ issues.push({
591
+ ...issue,
592
+ path: issue.path.replace(/^member\./, 'monitor.'),
593
+ });
594
+ }
595
+ const monitorInput = record(input.monitor);
596
+ const monitorKey = monitorInput?.key;
597
+ const monitor = lintMonitorSpecTemplate(input.monitor);
598
+ issues.push(...monitor.issues.map(v2Issue));
599
+ if (
600
+ typeof monitorKey === 'string' &&
601
+ typeof sourceKey?.column === 'string' &&
602
+ !monitorKey.includes(`{{${sourceKey.column}}}`)
603
+ ) {
604
+ issues.push({
605
+ path: 'monitor.key',
606
+ code: 'fleet_key_missing_source_key',
607
+ message:
608
+ 'Monitor key must include the source key as a direct Handlebars expression.',
609
+ });
610
+ }
611
+ return issues.length === 0 && monitor.normalized
612
+ ? {
613
+ valid: true,
614
+ definition: {
615
+ version: 2,
616
+ kind: 'fleet',
617
+ id: String(input.id),
618
+ source: input.source as MonitorFleetDefinitionV2['source'],
619
+ monitor: monitor.normalized,
620
+ selection: input.selection as MonitorFleetDefinitionV2['selection'],
621
+ },
622
+ issues,
623
+ }
624
+ : { valid: false, issues };
625
+ }
626
+
627
+ /** Validate the declared edition, or detect canonical v2 versus legacy v1. */
628
+ export function validateMonitorFleetDefinition(value: unknown): {
629
+ valid: boolean;
630
+ definition?: MonitorFleetDefinition;
631
+ issues: MonitorFleetContractIssue[];
632
+ } {
633
+ const input = record(value);
634
+ return input?.version === 2 || input?.kind === 'fleet'
635
+ ? validateMonitorFleetDefinitionV2(value)
636
+ : validateMonitorFleetDefinitionV1(value);
637
+ }
638
+
482
639
  /**
483
640
  * Compile JSON into the exact authoring snapshot persisted by a Fleet. This
484
641
  * does not inspect the Customer DB or provider; use
@@ -487,11 +644,14 @@ export function validateMonitorFleetDefinition(value: unknown): {
487
644
  */
488
645
  export function admitMonitorFleetAuthoringContract(
489
646
  value: unknown,
490
- edition: number = MONITOR_FLEET_AUTHORING_CONTRACT_EDITION,
647
+ edition?: number,
491
648
  ): MonitorFleetAuthoringContractResult {
649
+ const detectedEdition =
650
+ edition ??
651
+ (record(value)?.version === 2 || record(value)?.kind === 'fleet' ? 2 : 1);
492
652
  if (
493
653
  !SUPPORTED_MONITOR_FLEET_AUTHORING_CONTRACT_EDITIONS.includes(
494
- edition as MonitorFleetAuthoringContractEdition,
654
+ detectedEdition as MonitorFleetAuthoringContractEdition,
495
655
  )
496
656
  ) {
497
657
  return {
@@ -500,17 +660,20 @@ export function admitMonitorFleetAuthoringContract(
500
660
  {
501
661
  path: 'authoring_contract_edition',
502
662
  code: 'unsupported_fleet_authoring_contract_edition',
503
- message: `Monitor Fleet authoring contract edition ${edition} is not supported.`,
663
+ message: `Monitor Fleet authoring contract edition ${detectedEdition} is not supported.`,
504
664
  },
505
665
  ],
506
666
  };
507
667
  }
508
- const result = validateMonitorFleetDefinition(value);
668
+ const result =
669
+ detectedEdition === 2
670
+ ? validateMonitorFleetDefinitionV2(value)
671
+ : validateMonitorFleetDefinitionV1(value);
509
672
  return result.valid && result.definition
510
673
  ? {
511
674
  valid: true,
512
675
  contract: {
513
- edition: edition as MonitorFleetAuthoringContractEdition,
676
+ edition: detectedEdition as MonitorFleetAuthoringContractEdition,
514
677
  // JSON cloning prevents later caller mutation from changing the
515
678
  // admitted snapshot.
516
679
  definition: JSON.parse(
@@ -522,6 +685,45 @@ export function admitMonitorFleetAuthoringContract(
522
685
  : { valid: false, issues: result.issues };
523
686
  }
524
687
 
688
+ /**
689
+ * Fast provider-free admission shared by the Fleet CLI, SDK, and API. Legacy
690
+ * documents remain executable but receive an actionable migration warning.
691
+ */
692
+ export function lintMonitorFleetInput(value: unknown): {
693
+ normalized?: MonitorFleetDefinition;
694
+ issues: Array<MonitorFleetContractIssue & { severity: 'error' | 'warning' }>;
695
+ } {
696
+ const input = record(value);
697
+ const legacy = input?.member !== undefined;
698
+ const admitted = admitMonitorFleetAuthoringContract(value);
699
+ const issues: Array<
700
+ MonitorFleetContractIssue & { severity: 'error' | 'warning' }
701
+ > = admitted.issues.map((issue) => ({
702
+ ...issue,
703
+ severity: 'error' as const,
704
+ }));
705
+ if (legacy) {
706
+ issues.push({
707
+ severity: 'warning',
708
+ code: 'deprecated_fleet_member',
709
+ path: 'member',
710
+ message: 'Legacy Fleet member syntax remains supported.',
711
+ });
712
+ if (JSON.stringify(input).includes('"$fleet"')) {
713
+ issues.push({
714
+ severity: 'warning',
715
+ code: 'deprecated_fleet_expression',
716
+ path: 'member',
717
+ message:
718
+ 'Legacy $fleet expressions remain supported; use direct Handlebars fields in monitor instead.',
719
+ });
720
+ }
721
+ }
722
+ return admitted.valid && admitted.contract
723
+ ? { normalized: admitted.contract.definition, issues }
724
+ : { issues };
725
+ }
726
+
525
727
  export function evaluateMonitorFleetValue(
526
728
  value: unknown,
527
729
  row: Record<string, unknown>,