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.
- package/dist/bundling-sources/sdk/src/client.ts +144 -4
- package/dist/bundling-sources/sdk/src/index.ts +17 -4
- package/dist/bundling-sources/sdk/src/monitor-fleets.ts +1 -0
- package/dist/bundling-sources/sdk/src/monitor-input-contract.ts +1 -0
- package/dist/bundling-sources/sdk/src/monitors.ts +8 -25
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/integration-contracts/index.ts +1 -0
- package/dist/bundling-sources/shared_libs/integration-contracts/monitor-fleet-contract.ts +218 -16
- package/dist/bundling-sources/shared_libs/integration-contracts/monitor-input-contract.ts +455 -0
- package/dist/bundling-sources/shared_libs/monitors/fleets/plan.ts +6 -1
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +2 -1
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +12 -0
- package/dist/bundling-sources/shared_libs/observability/scheduled-work-manual.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/internal-bootstrap-token.ts +19 -3
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +5 -1
- package/dist/cli/index.js +1005 -290
- package/dist/cli/index.mjs +1005 -290
- package/dist/index.d.mts +43 -24
- package/dist/index.d.ts +43 -24
- package/dist/index.js +604 -41
- package/dist/index.mjs +600 -41
- package/dist/install-integrity.json +6 -0
- package/dist/monitor-fleet-contract.d.mts +44 -8
- package/dist/monitor-fleet-contract.d.ts +44 -8
- package/dist/monitor-fleet-contract.js +354 -27
- package/dist/monitor-fleet-contract.mjs +343 -27
- package/dist/monitor-input-contract.d.mts +33 -0
- package/dist/monitor-input-contract.d.ts +33 -0
- package/dist/monitor-input-contract.js +392 -0
- package/dist/monitor-input-contract.mjs +353 -0
- package/dist/plays/bundle-play-file.mjs +1 -1
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +7 -1
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import Handlebars from 'handlebars';
|
|
2
|
+
|
|
3
|
+
export type MonitorInputIssue = {
|
|
4
|
+
severity: 'error' | 'warning' | 'info';
|
|
5
|
+
code: string;
|
|
6
|
+
path: string;
|
|
7
|
+
message: string;
|
|
8
|
+
suggestion?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type MonitorSpec = {
|
|
12
|
+
key: string;
|
|
13
|
+
tool: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
payload: Record<string, unknown>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type BatchMonitorInput = {
|
|
19
|
+
version: 1;
|
|
20
|
+
kind: 'batch';
|
|
21
|
+
monitors: MonitorSpec[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type UnknownRecord = Record<string, unknown>;
|
|
25
|
+
|
|
26
|
+
function authoredMonitorFieldPath(path: string, field: string): string {
|
|
27
|
+
// Batch JSON is an array-shaped document, so use an array path in the two
|
|
28
|
+
// prescriptive migration errors. Fleet retains its natural `monitor.field`
|
|
29
|
+
// path while both commands point at the exact authored field.
|
|
30
|
+
const batch = /^monitors\.(\d+)$/.exec(path);
|
|
31
|
+
return `${batch ? `monitors[${batch[1]}]` : path}.${field}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function record(value: unknown): UnknownRecord | null {
|
|
35
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
36
|
+
? (value as UnknownRecord)
|
|
37
|
+
: null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function json(value: unknown, path: string, issues: MonitorInputIssue[]): void {
|
|
41
|
+
if (
|
|
42
|
+
value === null ||
|
|
43
|
+
typeof value === 'string' ||
|
|
44
|
+
typeof value === 'boolean' ||
|
|
45
|
+
(typeof value === 'number' && Number.isFinite(value))
|
|
46
|
+
) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
value.forEach((child, index) => json(child, `${path}.${index}`, issues));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const object = record(value);
|
|
54
|
+
if (!object) {
|
|
55
|
+
issues.push({
|
|
56
|
+
severity: 'error',
|
|
57
|
+
code: 'invalid_monitor_json_value',
|
|
58
|
+
path,
|
|
59
|
+
message: 'Monitor values must be JSON values.',
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (const [key, child] of Object.entries(object)) {
|
|
64
|
+
json(child, `${path}.${key}`, issues);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function monitorSpec(
|
|
69
|
+
value: unknown,
|
|
70
|
+
path: string,
|
|
71
|
+
issues: MonitorInputIssue[],
|
|
72
|
+
) {
|
|
73
|
+
const input = record(value);
|
|
74
|
+
if (!input) {
|
|
75
|
+
issues.push({
|
|
76
|
+
severity: 'error',
|
|
77
|
+
code: 'invalid_monitor_spec',
|
|
78
|
+
path,
|
|
79
|
+
message: 'A monitor must be a JSON object.',
|
|
80
|
+
});
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const allowed = new Set(['key', 'tool', 'name', 'payload']);
|
|
84
|
+
for (const key of Object.keys(input)) {
|
|
85
|
+
if (!allowed.has(key)) {
|
|
86
|
+
if (key === 'controls') {
|
|
87
|
+
issues.push({
|
|
88
|
+
severity: 'error',
|
|
89
|
+
code: 'CONTROLS_NOT_SUPPORTED',
|
|
90
|
+
path: authoredMonitorFieldPath(path, 'controls'),
|
|
91
|
+
message:
|
|
92
|
+
'Operational controls are managed by Deepline and cannot be authored in Batch or Fleet JSON.',
|
|
93
|
+
suggestion:
|
|
94
|
+
'Remove controls. Bind downstream automation in a Play with sqlListeners.',
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (key === 'play') {
|
|
99
|
+
issues.push({
|
|
100
|
+
severity: 'error',
|
|
101
|
+
code: 'PLAY_BINDING_NOT_IN_MONITOR_SPEC',
|
|
102
|
+
path: authoredMonitorFieldPath(path, 'play'),
|
|
103
|
+
message: 'A monitor does not own Play bindings.',
|
|
104
|
+
suggestion:
|
|
105
|
+
'Declare sqlListeners in the Play, or run: deepline plays bootstrap monitor-triggered --out on-event.play.ts',
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
issues.push({
|
|
110
|
+
severity: 'error',
|
|
111
|
+
code: 'unknown_monitor_field',
|
|
112
|
+
path: `${path}.${key}`,
|
|
113
|
+
message: `Monitor definitions do not accept '${key}'.`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const key = typeof input.key === 'string' ? input.key.trim() : '';
|
|
118
|
+
const tool = typeof input.tool === 'string' ? input.tool.trim() : '';
|
|
119
|
+
const name =
|
|
120
|
+
input.name === undefined
|
|
121
|
+
? undefined
|
|
122
|
+
: typeof input.name === 'string' && input.name.trim()
|
|
123
|
+
? input.name.trim()
|
|
124
|
+
: null;
|
|
125
|
+
const payload = record(input.payload);
|
|
126
|
+
if (!key) {
|
|
127
|
+
issues.push({
|
|
128
|
+
severity: 'error',
|
|
129
|
+
code: 'invalid_monitor_key',
|
|
130
|
+
path: `${path}.key`,
|
|
131
|
+
message: 'Monitor key must be a non-empty string.',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (!tool) {
|
|
135
|
+
issues.push({
|
|
136
|
+
severity: 'error',
|
|
137
|
+
code: 'invalid_monitor_tool',
|
|
138
|
+
path: `${path}.tool`,
|
|
139
|
+
message: 'Monitor tool must be a non-empty literal string.',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (name === null) {
|
|
143
|
+
issues.push({
|
|
144
|
+
severity: 'error',
|
|
145
|
+
code: 'invalid_monitor_name',
|
|
146
|
+
path: `${path}.name`,
|
|
147
|
+
message: 'Monitor name must be a non-empty string when supplied.',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (!payload) {
|
|
151
|
+
issues.push({
|
|
152
|
+
severity: 'error',
|
|
153
|
+
code: 'invalid_monitor_payload',
|
|
154
|
+
path: `${path}.payload`,
|
|
155
|
+
message: 'Monitor payload must be an object.',
|
|
156
|
+
});
|
|
157
|
+
} else {
|
|
158
|
+
json(payload, `${path}.payload`, issues);
|
|
159
|
+
}
|
|
160
|
+
if (!key || !tool || !payload || name === null) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
key,
|
|
165
|
+
tool,
|
|
166
|
+
...(name ? { name } : {}),
|
|
167
|
+
payload,
|
|
168
|
+
} satisfies MonitorSpec;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function issueOrder(left: MonitorInputIssue, right: MonitorInputIssue) {
|
|
172
|
+
return (
|
|
173
|
+
left.path.localeCompare(right.path) ||
|
|
174
|
+
left.severity.localeCompare(right.severity) ||
|
|
175
|
+
left.code.localeCompare(right.code)
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function lintMonitorBatchInput(value: unknown): {
|
|
180
|
+
normalized?: BatchMonitorInput;
|
|
181
|
+
issues: MonitorInputIssue[];
|
|
182
|
+
} {
|
|
183
|
+
const issues: MonitorInputIssue[] = [];
|
|
184
|
+
// Versionless `{ monitors: [...] }` was the original REST/SDK envelope.
|
|
185
|
+
// Continue to admit it so installed SDKs remain compatible, but normalize it
|
|
186
|
+
// before anything is persisted or sent to the provider.
|
|
187
|
+
const versionlessEnvelope =
|
|
188
|
+
!Array.isArray(value) &&
|
|
189
|
+
(() => {
|
|
190
|
+
const candidate = record(value);
|
|
191
|
+
return (
|
|
192
|
+
candidate !== null &&
|
|
193
|
+
Object.keys(candidate).length === 1 &&
|
|
194
|
+
Object.prototype.hasOwnProperty.call(candidate, 'monitors')
|
|
195
|
+
);
|
|
196
|
+
})();
|
|
197
|
+
const legacy = Array.isArray(value) || versionlessEnvelope;
|
|
198
|
+
const input = legacy ? null : record(value);
|
|
199
|
+
let monitors: unknown[] | null = Array.isArray(value)
|
|
200
|
+
? value
|
|
201
|
+
: versionlessEnvelope
|
|
202
|
+
? (() => {
|
|
203
|
+
const candidate = record(value)?.monitors;
|
|
204
|
+
return Array.isArray(candidate) ? candidate : null;
|
|
205
|
+
})()
|
|
206
|
+
: null;
|
|
207
|
+
if (Array.isArray(value)) {
|
|
208
|
+
issues.push({
|
|
209
|
+
severity: 'warning',
|
|
210
|
+
code: 'deprecated_batch_array',
|
|
211
|
+
path: '',
|
|
212
|
+
message: 'Bare monitor arrays are supported for compatibility.',
|
|
213
|
+
suggestion:
|
|
214
|
+
'Wrap the list in {"version":1,"kind":"batch","monitors":[...]}.',
|
|
215
|
+
});
|
|
216
|
+
} else if (versionlessEnvelope) {
|
|
217
|
+
issues.push({
|
|
218
|
+
severity: 'warning',
|
|
219
|
+
code: 'deprecated_batch_missing_envelope_fields',
|
|
220
|
+
path: '',
|
|
221
|
+
message: 'Versionless monitor envelopes are supported for compatibility.',
|
|
222
|
+
suggestion: 'Add "version": 1 and "kind": "batch".',
|
|
223
|
+
});
|
|
224
|
+
if (!monitors) {
|
|
225
|
+
issues.push({
|
|
226
|
+
severity: 'error',
|
|
227
|
+
code: 'invalid_batch_monitors',
|
|
228
|
+
path: 'monitors',
|
|
229
|
+
message: 'Batch monitors must be an array.',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
} else if (!input) {
|
|
233
|
+
issues.push({
|
|
234
|
+
severity: 'error',
|
|
235
|
+
code: 'invalid_batch',
|
|
236
|
+
path: '',
|
|
237
|
+
message: 'Batch input must be an object or a legacy array.',
|
|
238
|
+
});
|
|
239
|
+
} else {
|
|
240
|
+
for (const key of Object.keys(input)) {
|
|
241
|
+
if (!['version', 'kind', 'monitors'].includes(key)) {
|
|
242
|
+
issues.push({
|
|
243
|
+
severity: 'error',
|
|
244
|
+
code:
|
|
245
|
+
key === 'source' || key === 'monitor'
|
|
246
|
+
? 'batch_fleet_shape_mixup'
|
|
247
|
+
: 'unknown_batch_field',
|
|
248
|
+
path: key,
|
|
249
|
+
message: `Batch input does not accept '${key}'.`,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (input.version !== 1) {
|
|
254
|
+
issues.push({
|
|
255
|
+
severity: 'error',
|
|
256
|
+
code: 'unsupported_batch_version',
|
|
257
|
+
path: 'version',
|
|
258
|
+
message: 'Batch input version must be 1.',
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
if (input.kind !== 'batch') {
|
|
262
|
+
issues.push({
|
|
263
|
+
severity: 'error',
|
|
264
|
+
code: 'invalid_batch_kind',
|
|
265
|
+
path: 'kind',
|
|
266
|
+
message: 'Batch input kind must be "batch".',
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
monitors = Array.isArray(input.monitors) ? input.monitors : null;
|
|
270
|
+
if (!monitors) {
|
|
271
|
+
issues.push({
|
|
272
|
+
severity: 'error',
|
|
273
|
+
code: 'invalid_batch_monitors',
|
|
274
|
+
path: 'monitors',
|
|
275
|
+
message: 'Batch monitors must be an array.',
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (!monitors || monitors.length === 0) {
|
|
280
|
+
if (monitors?.length === 0) {
|
|
281
|
+
issues.push({
|
|
282
|
+
severity: 'error',
|
|
283
|
+
code: 'empty_batch',
|
|
284
|
+
path: 'monitors',
|
|
285
|
+
message: 'A Batch needs at least one monitor.',
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return { issues: issues.sort(issueOrder) };
|
|
289
|
+
}
|
|
290
|
+
const normalized = monitors.map((item, index) =>
|
|
291
|
+
monitorSpec(item, `monitors.${index}`, issues),
|
|
292
|
+
);
|
|
293
|
+
const seen = new Map<string, number>();
|
|
294
|
+
normalized.forEach((item, index) => {
|
|
295
|
+
if (!item) return;
|
|
296
|
+
const first = seen.get(item.key);
|
|
297
|
+
if (first !== undefined) {
|
|
298
|
+
for (const position of [first, index]) {
|
|
299
|
+
issues.push({
|
|
300
|
+
severity: 'error',
|
|
301
|
+
code: 'duplicate_monitor_key',
|
|
302
|
+
path: `monitors.${position}.key`,
|
|
303
|
+
message: `Duplicate monitor key '${item.key}'.`,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
seen.set(item.key, index);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
const ordered = issues.sort(issueOrder);
|
|
311
|
+
return ordered.some((issue) => issue.severity === 'error')
|
|
312
|
+
? { issues: ordered }
|
|
313
|
+
: {
|
|
314
|
+
normalized: {
|
|
315
|
+
version: 1,
|
|
316
|
+
kind: 'batch',
|
|
317
|
+
monitors: normalized as MonitorSpec[],
|
|
318
|
+
},
|
|
319
|
+
issues: ordered,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function validateHandlebars(
|
|
324
|
+
value: string,
|
|
325
|
+
path: string,
|
|
326
|
+
issues: MonitorInputIssue[],
|
|
327
|
+
) {
|
|
328
|
+
if (!value.includes('{{')) return;
|
|
329
|
+
try {
|
|
330
|
+
Handlebars.parse(value);
|
|
331
|
+
} catch (error) {
|
|
332
|
+
issues.push({
|
|
333
|
+
severity: 'error',
|
|
334
|
+
code: 'invalid_handlebars_template',
|
|
335
|
+
path,
|
|
336
|
+
message:
|
|
337
|
+
error instanceof Error ? error.message : 'Invalid Handlebars template.',
|
|
338
|
+
});
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const expressions = value.match(/{{[\s\S]*?}}/g) ?? [];
|
|
342
|
+
if (
|
|
343
|
+
expressions.some(
|
|
344
|
+
(expression) => !/^{{\s*[A-Za-z_][A-Za-z0-9_]*\s*}}$/.test(expression),
|
|
345
|
+
)
|
|
346
|
+
) {
|
|
347
|
+
issues.push({
|
|
348
|
+
severity: 'error',
|
|
349
|
+
code: 'unsupported_handlebars_expression',
|
|
350
|
+
path,
|
|
351
|
+
message:
|
|
352
|
+
'Fleet templates allow only direct source-row fields such as {{domain}}; helpers, blocks, partials, paths, and prototype access are disabled.',
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function walkTemplates(
|
|
358
|
+
value: unknown,
|
|
359
|
+
path: string,
|
|
360
|
+
issues: MonitorInputIssue[],
|
|
361
|
+
): void {
|
|
362
|
+
if (typeof value === 'string') return validateHandlebars(value, path, issues);
|
|
363
|
+
if (Array.isArray(value)) {
|
|
364
|
+
value.forEach((item, index) =>
|
|
365
|
+
walkTemplates(item, `${path}.${index}`, issues),
|
|
366
|
+
);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const input = record(value);
|
|
370
|
+
if (!input) return;
|
|
371
|
+
for (const [key, child] of Object.entries(input)) {
|
|
372
|
+
walkTemplates(child, `${path}.${key}`, issues);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function lintMonitorSpecTemplate(
|
|
377
|
+
value: unknown,
|
|
378
|
+
path = 'monitor',
|
|
379
|
+
): { normalized?: MonitorSpec; issues: MonitorInputIssue[] } {
|
|
380
|
+
const issues: MonitorInputIssue[] = [];
|
|
381
|
+
const normalized = monitorSpec(value, path, issues);
|
|
382
|
+
const input = record(value);
|
|
383
|
+
if (input && typeof input.tool === 'string' && input.tool.includes('{{')) {
|
|
384
|
+
issues.push({
|
|
385
|
+
severity: 'error',
|
|
386
|
+
code: 'templated_monitor_tool',
|
|
387
|
+
path: `${path}.tool`,
|
|
388
|
+
message:
|
|
389
|
+
'Fleet monitor tool must be literal; only key, name, and payload may be templated.',
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (input) {
|
|
393
|
+
for (const field of ['key', 'name', 'payload']) {
|
|
394
|
+
if (input[field] !== undefined)
|
|
395
|
+
walkTemplates(input[field], `${path}.${field}`, issues);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const ordered = issues.sort(issueOrder);
|
|
399
|
+
return ordered.some((issue) => issue.severity === 'error')
|
|
400
|
+
? { issues: ordered }
|
|
401
|
+
: { normalized: normalized!, issues: ordered };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function renderValue(value: unknown, row: Record<string, unknown>): unknown {
|
|
405
|
+
if (typeof value === 'string' && value.includes('{{')) {
|
|
406
|
+
const context = Object.assign(Object.create(null), row);
|
|
407
|
+
return Handlebars.compile(value, {
|
|
408
|
+
noEscape: true,
|
|
409
|
+
strict: true,
|
|
410
|
+
knownHelpersOnly: true,
|
|
411
|
+
preventIndent: true,
|
|
412
|
+
})(context, {
|
|
413
|
+
allowProtoMethodsByDefault: false,
|
|
414
|
+
allowProtoPropertiesByDefault: false,
|
|
415
|
+
helpers: Object.create(null),
|
|
416
|
+
partials: Object.create(null),
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
if (Array.isArray(value)) return value.map((item) => renderValue(item, row));
|
|
420
|
+
const input = record(value);
|
|
421
|
+
if (!input) return value;
|
|
422
|
+
return Object.fromEntries(
|
|
423
|
+
Object.entries(input).map(([key, item]) => [key, renderValue(item, row)]),
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function renderMonitorSpecTemplate(
|
|
428
|
+
template: MonitorSpec,
|
|
429
|
+
row: Record<string, unknown>,
|
|
430
|
+
): { spec?: MonitorSpec; issues: MonitorInputIssue[] } {
|
|
431
|
+
const lint = lintMonitorSpecTemplate(template);
|
|
432
|
+
if (!lint.normalized) return { issues: lint.issues };
|
|
433
|
+
try {
|
|
434
|
+
const rendered = renderValue(lint.normalized, row);
|
|
435
|
+
const issues: MonitorInputIssue[] = [];
|
|
436
|
+
const spec = monitorSpec(rendered, 'monitor', issues);
|
|
437
|
+
return spec
|
|
438
|
+
? { spec, issues: issues.sort(issueOrder) }
|
|
439
|
+
: { issues: issues.sort(issueOrder) };
|
|
440
|
+
} catch (error) {
|
|
441
|
+
return {
|
|
442
|
+
issues: [
|
|
443
|
+
{
|
|
444
|
+
severity: 'error',
|
|
445
|
+
code: 'monitor_template_render_failed',
|
|
446
|
+
path: 'monitor',
|
|
447
|
+
message:
|
|
448
|
+
error instanceof Error
|
|
449
|
+
? error.message
|
|
450
|
+
: 'Fleet template rendering failed.',
|
|
451
|
+
},
|
|
452
|
+
],
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
@@ -47,7 +47,12 @@ export type OwnedFleetMonitor = {
|
|
|
47
47
|
| 'rate_limited'
|
|
48
48
|
| 'settled'
|
|
49
49
|
| 'blocked'
|
|
50
|
-
| 'failed'
|
|
50
|
+
| 'failed'
|
|
51
|
+
/**
|
|
52
|
+
* A policy-suspended member remains owned so the Fleet can explicitly
|
|
53
|
+
* reactivate it, but it has no active convergence work until then.
|
|
54
|
+
*/
|
|
55
|
+
| null;
|
|
51
56
|
desiredHash: string | null;
|
|
52
57
|
appliedHash: string | null;
|
|
53
58
|
firstMissingAt: number | null;
|
|
@@ -698,5 +698,17 @@
|
|
|
698
698
|
"owner": "billing",
|
|
699
699
|
"criticality": "critical",
|
|
700
700
|
"failureAlert": "convex_action_wrapper"
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
"id": "convex.monitor-deployment-job-tick",
|
|
704
|
+
"name": "Monitor deployment job tick",
|
|
705
|
+
"scheduler": "convex_cron",
|
|
706
|
+
"cadence": "every 60s",
|
|
707
|
+
"timezone": "UTC",
|
|
708
|
+
"source": "packages/convex/crons.ts",
|
|
709
|
+
"locator": "monitor deployment job tick",
|
|
710
|
+
"owner": "monitors",
|
|
711
|
+
"criticality": "critical",
|
|
712
|
+
"failureAlert": "convex_action_wrapper"
|
|
701
713
|
}
|
|
702
714
|
]
|
|
@@ -15,6 +15,7 @@ export const MANUALLY_RUNNABLE_CONVEX_CRON_JOB_IDS = [
|
|
|
15
15
|
'convex.cleanup-expired-domain-health-cache',
|
|
16
16
|
'convex.salesforce-oauth-refresh-maintenance',
|
|
17
17
|
'convex.monitor-fleet-convergence-tick',
|
|
18
|
+
'convex.monitor-deployment-job-tick',
|
|
18
19
|
'convex.provider-monitor-global-audit-daily',
|
|
19
20
|
'convex.recover-notification-delivery-work',
|
|
20
21
|
'convex.cleanup-expired-notification-audit-records',
|
|
@@ -167,7 +167,9 @@ export type MonitorFleetDispatchTokenClaims = {
|
|
|
167
167
|
org_id: string;
|
|
168
168
|
purpose: typeof MONITOR_FLEET_DISPATCH_PURPOSE;
|
|
169
169
|
fleet_id?: string;
|
|
170
|
-
|
|
170
|
+
/** Monitor Job dispatches share this bounded credential wrapper. */
|
|
171
|
+
job_id?: string;
|
|
172
|
+
operation: 'dispatch' | 'daily_sync' | 'job_dispatch';
|
|
171
173
|
sub: 'monitor_fleet_dispatcher';
|
|
172
174
|
v: typeof INTERNAL_BOOTSTRAP_TOKEN_VERSION;
|
|
173
175
|
};
|
|
@@ -1116,10 +1118,17 @@ export async function verifyProductNotificationUnsubscribeToken(
|
|
|
1116
1118
|
|
|
1117
1119
|
export async function mintMonitorFleetDispatchToken(input: {
|
|
1118
1120
|
orgId: string;
|
|
1119
|
-
operation: 'dispatch' | 'daily_sync';
|
|
1121
|
+
operation: 'dispatch' | 'daily_sync' | 'job_dispatch';
|
|
1120
1122
|
fleetId?: string;
|
|
1123
|
+
jobId?: string;
|
|
1121
1124
|
nowMs?: number;
|
|
1122
1125
|
}): Promise<string> {
|
|
1126
|
+
if (input.operation === 'job_dispatch' && !input.jobId?.trim()) {
|
|
1127
|
+
throw new Error('Monitor Job dispatch tokens require a job id.');
|
|
1128
|
+
}
|
|
1129
|
+
if (input.operation !== 'job_dispatch' && input.jobId !== undefined) {
|
|
1130
|
+
throw new Error('Only Monitor Job dispatch tokens may carry a job id.');
|
|
1131
|
+
}
|
|
1123
1132
|
const issuedAt = Math.floor((input.nowMs ?? Date.now()) / 1000);
|
|
1124
1133
|
const claims: MonitorFleetDispatchTokenClaims = {
|
|
1125
1134
|
v: INTERNAL_BOOTSTRAP_TOKEN_VERSION,
|
|
@@ -1129,6 +1138,7 @@ export async function mintMonitorFleetDispatchToken(input: {
|
|
|
1129
1138
|
org_id: input.orgId,
|
|
1130
1139
|
operation: input.operation,
|
|
1131
1140
|
...(input.fleetId ? { fleet_id: input.fleetId } : {}),
|
|
1141
|
+
...(input.jobId ? { job_id: input.jobId } : {}),
|
|
1132
1142
|
sub: 'monitor_fleet_dispatcher',
|
|
1133
1143
|
iat: issuedAt,
|
|
1134
1144
|
exp: issuedAt + INTERNAL_BOOTSTRAP_TOKEN_TTL_SECONDS,
|
|
@@ -1168,8 +1178,14 @@ export async function verifyMonitorFleetDispatchToken(
|
|
|
1168
1178
|
claims.sub !== 'monitor_fleet_dispatcher' ||
|
|
1169
1179
|
typeof claims.org_id !== 'string' ||
|
|
1170
1180
|
!claims.org_id ||
|
|
1171
|
-
!['dispatch', 'daily_sync'].includes(
|
|
1181
|
+
!['dispatch', 'daily_sync', 'job_dispatch'].includes(
|
|
1182
|
+
String(claims.operation),
|
|
1183
|
+
) ||
|
|
1172
1184
|
(claims.fleet_id !== undefined && typeof claims.fleet_id !== 'string') ||
|
|
1185
|
+
(claims.job_id !== undefined && typeof claims.job_id !== 'string') ||
|
|
1186
|
+
(claims.operation === 'job_dispatch' &&
|
|
1187
|
+
(typeof claims.job_id !== 'string' || !claims.job_id)) ||
|
|
1188
|
+
(claims.operation !== 'job_dispatch' && claims.job_id !== undefined) ||
|
|
1173
1189
|
typeof claims.iat !== 'number' ||
|
|
1174
1190
|
!Number.isFinite(claims.iat) ||
|
|
1175
1191
|
typeof claims.exp !== 'number' ||
|
|
@@ -1827,7 +1827,11 @@ function importedPlayProxyPlugin(
|
|
|
1827
1827
|
name: 'deepline-imported-play-proxy',
|
|
1828
1828
|
setup(buildContext) {
|
|
1829
1829
|
buildContext.onResolve({ filter: /.*/ }, async (args) => {
|
|
1830
|
-
if (
|
|
1830
|
+
if (
|
|
1831
|
+
!args.importer ||
|
|
1832
|
+
!isLocalSpecifier(args.path) ||
|
|
1833
|
+
args.importer.split(/[\\/]+/).includes('node_modules')
|
|
1834
|
+
) {
|
|
1831
1835
|
return null;
|
|
1832
1836
|
}
|
|
1833
1837
|
|