devcontour-harness 0.1.0-alpha.6 → 0.1.0-alpha.7
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/README.md +20 -2
- package/docs/quickstart.md +1 -1
- package/docs/release-notes/0.1.0-alpha.7.md +71 -0
- package/docs/review-probes.md +79 -0
- package/docs/upgrade.md +42 -25
- package/lib/agents/cli-adapter.js +8 -1
- package/lib/agents/inspect.d.ts +56 -2
- package/lib/agents/inspect.js +264 -14
- package/lib/agents/review-probe.d.ts +50 -0
- package/lib/agents/review-probe.js +211 -46
- package/lib/contracts/execution.d.ts +213 -7
- package/lib/contracts/execution.js +67 -3
- package/lib/contracts/published.d.ts +49 -1
- package/lib/control/review.js +9 -5
- package/lib/engine/budget.d.ts +3 -3
- package/lib/engine/engine.d.ts +28 -2
- package/lib/engine/runner.js +20 -12
- package/lib/environment/process.d.ts +8 -0
- package/lib/environment/process.js +8 -2
- package/lib/storage/document.d.ts +56 -4
- package/lib/verification/gates.d.ts +22 -0
- package/lib/verification/gates.js +64 -23
- package/package.json +1 -1
- package/schemas/execution-document.schema.json +190 -4
package/lib/agents/inspect.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { probeStatuses, } from "./review-probe.js";
|
|
3
|
+
/** Пределы итога: число команд и длины сохраняемых строк (docs/review-probes.md). */
|
|
4
|
+
export const inspectionLimits = {
|
|
5
|
+
commands: 100,
|
|
6
|
+
command: 12000,
|
|
7
|
+
output: 4000,
|
|
8
|
+
note: 2000,
|
|
9
|
+
excluded: 100,
|
|
10
|
+
};
|
|
11
|
+
const noExecution = 'Нет подтверждённого выполнения команд: ни события runtime, ни журнал проверок controller этого вызова reviewer не содержат запущенной команды.';
|
|
2
12
|
export const unobservedReview = () => ({
|
|
3
13
|
mode: 'diff-only',
|
|
4
14
|
source: 'unavailable',
|
|
@@ -8,39 +18,279 @@ export const unobservedReview = () => ({
|
|
|
8
18
|
const completedCommand = z.object({
|
|
9
19
|
type: z.literal('item.completed'),
|
|
10
20
|
item: z.object({
|
|
21
|
+
id: z.string().min(1).max(200).optional(),
|
|
11
22
|
type: z.literal('command_execution'),
|
|
12
|
-
command: z.string().min(1)
|
|
23
|
+
command: z.string().min(1),
|
|
13
24
|
exit_code: z.number().int(),
|
|
14
25
|
aggregated_output: z.string().default(''),
|
|
15
26
|
status: z.enum(['completed', 'failed']),
|
|
16
27
|
}),
|
|
17
28
|
});
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
const identity = (text) => text;
|
|
30
|
+
/**
|
|
31
|
+
* Что reviewer действительно выполнил — по событиям runtime, а не по словам
|
|
32
|
+
* модели. Завершённая команда — выполнение при любом коде выхода: ненулевой
|
|
33
|
+
* код — исход проверки, а не признак того, что её не было. Повтор события
|
|
34
|
+
* того же item ID — одна команда.
|
|
35
|
+
*/
|
|
36
|
+
export function inspectReview(runtime, output, redact = identity) {
|
|
20
37
|
if (runtime !== 'codex')
|
|
21
38
|
return unobservedReview();
|
|
22
39
|
const commands = [];
|
|
40
|
+
const items = new Set();
|
|
23
41
|
for (const line of output.split('\n')) {
|
|
24
42
|
try {
|
|
25
43
|
const event = completedCommand.safeParse(JSON.parse(line));
|
|
26
|
-
if (event.success)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
if (!event.success)
|
|
45
|
+
continue;
|
|
46
|
+
const item = event.data.item;
|
|
47
|
+
if (item.id !== undefined) {
|
|
48
|
+
if (items.has(item.id))
|
|
49
|
+
continue;
|
|
50
|
+
items.add(item.id);
|
|
33
51
|
}
|
|
52
|
+
commands.push({
|
|
53
|
+
// Маскирование — до обрезки: иначе обрезка разрезала бы секрет.
|
|
54
|
+
command: redact(item.command).slice(0, inspectionLimits.command),
|
|
55
|
+
exitCode: item.exit_code,
|
|
56
|
+
output: redact(item.aggregated_output).slice(-inspectionLimits.output),
|
|
57
|
+
origins: [{ type: 'runtime', runtime, item: item.id ?? null }],
|
|
58
|
+
status: item.status,
|
|
59
|
+
});
|
|
34
60
|
}
|
|
35
61
|
catch {
|
|
36
62
|
/* Обрезанная или не-JSON строка не подтверждает выполнения команды. */
|
|
37
63
|
}
|
|
38
64
|
}
|
|
39
|
-
const observed = commands.some((c) => c.exitCode === 0);
|
|
40
65
|
return {
|
|
41
|
-
mode:
|
|
66
|
+
mode: commands.length ? 'commands' : 'diff-only',
|
|
42
67
|
source: 'codex-events',
|
|
43
|
-
commands: commands.slice(-
|
|
44
|
-
reason:
|
|
68
|
+
commands: commands.slice(-inspectionLimits.commands),
|
|
69
|
+
reason: commands.length ? null : noExecution,
|
|
70
|
+
...(commands.length > inspectionLimits.commands ? { total: commands.length } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const statuses = ['passed', 'failed', 'timeout', 'cancelled', 'error'];
|
|
74
|
+
const known = new Set([...statuses, 'budget-exhausted', 'rejected']);
|
|
75
|
+
/** argv как одна строка: аргументы с пробелами и кавычками — в одинарных кавычках. */
|
|
76
|
+
const argvText = (argv) => argv.map((a) => (/^[\w@%+=:,./-]+$/.test(a) ? a : `'${a.replace(/'/g, `'\\''`)}'`)).join(' ');
|
|
77
|
+
/** stdout и stderr в одном ограниченном output; при нехватке места — хвосты поровну. */
|
|
78
|
+
function joinOutput(stdout, stderr, limit) {
|
|
79
|
+
const both = stdout && stderr;
|
|
80
|
+
const joined = both ? `${stdout}\n${stderr}` : stdout || stderr;
|
|
81
|
+
if (joined.length <= limit)
|
|
82
|
+
return joined;
|
|
83
|
+
if (!both)
|
|
84
|
+
return '…' + joined.slice(-(limit - 1));
|
|
85
|
+
const half = Math.floor((limit - 3) / 2);
|
|
86
|
+
const cut = (t, n) => (t.length > n ? '…' + t.slice(-(n - 1)) : t);
|
|
87
|
+
const out = cut(stdout, Math.max(half, limit - 3 - Math.min(stderr.length, half)));
|
|
88
|
+
return `${out}\n${cut(stderr, limit - 1 - out.length)}`.slice(-limit);
|
|
89
|
+
}
|
|
90
|
+
/** Почему запись журнала не засчитана выполнением этого вызова. */
|
|
91
|
+
function exclusion(record, context) {
|
|
92
|
+
if (!known.has(record.status))
|
|
93
|
+
return 'unknown-status';
|
|
94
|
+
// Явно чужие вызов, фаза или SHA — не наблюдение этого ревью.
|
|
95
|
+
const s = record.scope;
|
|
96
|
+
if (s &&
|
|
97
|
+
(s.attempt !== context.attempt ||
|
|
98
|
+
s.phase !== context.phase ||
|
|
99
|
+
s.sha !== context.sha ||
|
|
100
|
+
s.call !== context.call))
|
|
101
|
+
return 'foreign-scope';
|
|
102
|
+
const g = record.gate;
|
|
103
|
+
if (g && ((g.phase && g.phase !== context.phase) || (g.sha && g.sha !== context.sha)))
|
|
104
|
+
return 'foreign-scope';
|
|
105
|
+
// Явный признак старта главнее правила старых записей.
|
|
106
|
+
if (record.started === false)
|
|
107
|
+
return 'not-started';
|
|
108
|
+
if (record.started === true)
|
|
109
|
+
return null;
|
|
110
|
+
// В alpha.6 и ранее признака старта не было: passed/failed — исход завершившейся
|
|
111
|
+
// команды; error/timeout/cancelled без подтверждения старта не засчитываются.
|
|
112
|
+
return record.status === 'passed' || record.status === 'failed' ? null : 'legacy-unconfirmed';
|
|
113
|
+
}
|
|
114
|
+
function controllerCommand(record, redact) {
|
|
115
|
+
return {
|
|
116
|
+
command: redact(argvText(record.argv)).slice(0, inspectionLimits.command),
|
|
117
|
+
// Неизвестный код остаётся неизвестным: из `passed` не выводится 0.
|
|
118
|
+
exitCode: record.exitCode ?? null,
|
|
119
|
+
output: joinOutput(redact(record.stdout ?? ''), redact(record.stderr ?? ''), inspectionLimits.output),
|
|
120
|
+
origins: [{ type: 'controller', runId: record.runId }],
|
|
121
|
+
status: record.status,
|
|
122
|
+
...(record.failure ? { failure: record.failure } : {}),
|
|
123
|
+
...(record.gate?.id ? { gate: record.gate.id } : {}),
|
|
124
|
+
...(record.note ? { note: redact(record.note).slice(0, inspectionLimits.note) } : {}),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Итог `inspection` одного вызова reviewer: события runtime этого вызова и
|
|
129
|
+
* журнал проверок controller этого вызова. Самоотчёт модели (`execution`,
|
|
130
|
+
* `reproduction.executed`, runId в тексте) сюда не входит.
|
|
131
|
+
*
|
|
132
|
+
* Операция одна, если совпадает надёжная идентичность: runId запуска
|
|
133
|
+
* controller, item ID runtime внутри его потока или явная структурированная
|
|
134
|
+
* связь наблюдения runtime с runId controller (origin `controller` у команды
|
|
135
|
+
* runtime). Совпадение текста, вывода или замаскированных аргументов
|
|
136
|
+
* операции не склеивает.
|
|
137
|
+
*
|
|
138
|
+
* Порядок: команды runtime в порядке потока, затем запуски controller в
|
|
139
|
+
* порядке журнала; при превышении предела сохраняются последние
|
|
140
|
+
* {@link inspectionLimits.commands}, `total` — число всех подтверждённых.
|
|
141
|
+
* Mode — по всему набору. Пользовательский текст маскируется до обрезки;
|
|
142
|
+
* перечисления и идентификаторы controller не маскируются.
|
|
143
|
+
*/
|
|
144
|
+
export function reviewInspection(input) {
|
|
145
|
+
const redact = input.redact ?? identity;
|
|
146
|
+
const commands = [];
|
|
147
|
+
const excluded = [];
|
|
148
|
+
const byItem = new Map();
|
|
149
|
+
const byRun = new Map();
|
|
150
|
+
for (const c of input.runtime?.commands ?? []) {
|
|
151
|
+
const origins = c.origins?.length
|
|
152
|
+
? c.origins
|
|
153
|
+
: [
|
|
154
|
+
{
|
|
155
|
+
type: 'runtime',
|
|
156
|
+
runtime: input.runtime?.source === 'codex-events' ? 'codex' : 'unknown',
|
|
157
|
+
item: null,
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
const runtimeOrigin = origins.find((o) => o.type === 'runtime');
|
|
161
|
+
const item = runtimeOrigin?.type === 'runtime' && runtimeOrigin.item
|
|
162
|
+
? `${runtimeOrigin.runtime}\0${runtimeOrigin.item}`
|
|
163
|
+
: undefined;
|
|
164
|
+
const existing = item ? byItem.get(item) : undefined;
|
|
165
|
+
if (existing) {
|
|
166
|
+
// Повтор того же item: связь с запуском controller, пришедшая лишь в
|
|
167
|
+
// повторном наблюдении, — тоже связь этой операции.
|
|
168
|
+
merge(existing, origins);
|
|
169
|
+
for (const o of origins)
|
|
170
|
+
if (o.type === 'controller')
|
|
171
|
+
byRun.set(o.runId, existing);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const command = {
|
|
175
|
+
...c,
|
|
176
|
+
command: redact(c.command).slice(0, inspectionLimits.command),
|
|
177
|
+
output: redact(c.output).slice(-inspectionLimits.output),
|
|
178
|
+
origins: [...origins],
|
|
179
|
+
};
|
|
180
|
+
commands.push(command);
|
|
181
|
+
if (item)
|
|
182
|
+
byItem.set(item, command);
|
|
183
|
+
for (const o of origins)
|
|
184
|
+
if (o.type === 'controller')
|
|
185
|
+
byRun.set(o.runId, command);
|
|
186
|
+
}
|
|
187
|
+
for (const record of input.journal?.records ?? []) {
|
|
188
|
+
const reason = exclusion(record, input.context);
|
|
189
|
+
if (reason) {
|
|
190
|
+
excluded.push({ runId: record.runId, status: record.status, reason });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const linked = byRun.get(record.runId);
|
|
194
|
+
const own = controllerCommand(record, redact);
|
|
195
|
+
if (linked) {
|
|
196
|
+
// Связанная операция: данные controller, происхождение обоих.
|
|
197
|
+
const runtimeOrigins = linked.origins?.filter((o) => o.type === 'runtime') ?? [];
|
|
198
|
+
Object.assign(linked, own, { origins: [...runtimeOrigins, ...own.origins] });
|
|
199
|
+
byRun.delete(record.runId);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
commands.push(own);
|
|
203
|
+
}
|
|
204
|
+
// Связь с runId, которого нет среди засчитанных запусков, не подтверждает
|
|
205
|
+
// запуск controller: остаётся только наблюдение runtime.
|
|
206
|
+
for (const [runId, linked] of byRun)
|
|
207
|
+
linked.origins = linked.origins?.filter((o) => o.type !== 'controller' || o.runId !== runId);
|
|
208
|
+
for (const r of input.journal?.reservations ?? [])
|
|
209
|
+
excluded.push({
|
|
210
|
+
runId: r.runId,
|
|
211
|
+
status: null,
|
|
212
|
+
reason: r.abandoned ? 'abandoned' : 'unfinished',
|
|
213
|
+
});
|
|
214
|
+
const runtimeSeen = commands.some((c) => c.origins?.some((o) => o.type === 'runtime'));
|
|
215
|
+
const controllerSeen = commands.some((c) => c.origins?.some((o) => o.type === 'controller'));
|
|
216
|
+
const source = runtimeSeen && controllerSeen
|
|
217
|
+
? 'runtime-and-controller'
|
|
218
|
+
: controllerSeen
|
|
219
|
+
? 'controller-probes'
|
|
220
|
+
: (input.runtime?.source ?? 'unavailable');
|
|
221
|
+
// Команды runtime, не вошедшие в его ограниченный список, — тоже
|
|
222
|
+
// подтверждённые операции: они учитываются в total и mode.
|
|
223
|
+
const dropped = Math.max(0, (input.runtime?.total ?? 0) - (input.runtime?.commands.length ?? 0));
|
|
224
|
+
const total = commands.length + dropped;
|
|
225
|
+
const observed = total > 0;
|
|
226
|
+
return {
|
|
227
|
+
mode: observed ? 'commands' : 'diff-only',
|
|
228
|
+
source,
|
|
229
|
+
commands: commands.slice(-inspectionLimits.commands),
|
|
230
|
+
reason: observed ? null : noExecution,
|
|
231
|
+
context: input.context,
|
|
232
|
+
total,
|
|
233
|
+
...(input.journal?.invalid ? { invalid: input.journal.invalid } : {}),
|
|
234
|
+
...(excluded.length
|
|
235
|
+
? {
|
|
236
|
+
excluded: excluded.slice(0, inspectionLimits.excluded),
|
|
237
|
+
...(excluded.length > inspectionLimits.excluded
|
|
238
|
+
? { excludedTotal: excluded.length }
|
|
239
|
+
: {}),
|
|
240
|
+
}
|
|
241
|
+
: {}),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Маскирование сохранённого inspection: только свободный текст (`command`,
|
|
246
|
+
* `output`, `note`, `reason`) и с обрезкой до пределов после маски — замена
|
|
247
|
+
* секрета может удлинить строку. Перечисления, origins, контекст и
|
|
248
|
+
* идентификаторы controller не маскируются — короткий секрет, совпавший с
|
|
249
|
+
* их частью, не делает запись невалидной.
|
|
250
|
+
*/
|
|
251
|
+
export function redactInspection(inspection, redact) {
|
|
252
|
+
return {
|
|
253
|
+
...inspection,
|
|
254
|
+
commands: inspection.commands.map((c) => ({
|
|
255
|
+
...c,
|
|
256
|
+
command: redact(c.command).slice(0, inspectionLimits.command),
|
|
257
|
+
output: redact(c.output).slice(-inspectionLimits.output),
|
|
258
|
+
...(c.note !== undefined ? { note: redact(c.note).slice(0, inspectionLimits.note) } : {}),
|
|
259
|
+
})),
|
|
260
|
+
reason: inspection.reason === null ? null : redact(inspection.reason),
|
|
45
261
|
};
|
|
46
262
|
}
|
|
263
|
+
function merge(target, origins) {
|
|
264
|
+
const key = (o) => o.type === 'runtime' ? `r\0${o.runtime}\0${o.item}` : `c\0${o.runId}`;
|
|
265
|
+
const have = new Set((target.origins ?? []).map(key));
|
|
266
|
+
target.origins = [...(target.origins ?? []), ...origins.filter((o) => !have.has(key(o)))];
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Сохранённая проекция `review.json.probes` (alpha.6 и ранее: без кода, вывода и
|
|
270
|
+
* признака старта) как журнал для {@link reviewInspection}. Пересчёт старого
|
|
271
|
+
* состояния не выполняется: это вход для отдельной агрегации старых данных.
|
|
272
|
+
*/
|
|
273
|
+
export function journalFromProjection(probes) {
|
|
274
|
+
const records = [];
|
|
275
|
+
const seen = new Set();
|
|
276
|
+
let invalid = 0;
|
|
277
|
+
for (const p of Array.isArray(probes) ? probes : []) {
|
|
278
|
+
const r = p;
|
|
279
|
+
if (!r ||
|
|
280
|
+
typeof r !== 'object' ||
|
|
281
|
+
typeof r.runId !== 'string' ||
|
|
282
|
+
!r.runId ||
|
|
283
|
+
typeof r.status !== 'string' ||
|
|
284
|
+
!Array.isArray(r.argv) ||
|
|
285
|
+
!r.argv.every((a) => typeof a === 'string')) {
|
|
286
|
+
invalid++;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (seen.has(r.runId))
|
|
290
|
+
continue;
|
|
291
|
+
if (probeStatuses.has(r.status))
|
|
292
|
+
seen.add(r.runId);
|
|
293
|
+
records.push({ ...r });
|
|
294
|
+
}
|
|
295
|
+
return { records, invalid, reservations: [] };
|
|
296
|
+
}
|
|
@@ -74,6 +74,12 @@ export declare const probeSpec: z.ZodObject<{
|
|
|
74
74
|
}, z.core.$strict>>>;
|
|
75
75
|
phase: z.ZodOptional<z.ZodString>;
|
|
76
76
|
sha: z.ZodOptional<z.ZodString>;
|
|
77
|
+
scope: z.ZodOptional<z.ZodObject<{
|
|
78
|
+
attempt: z.ZodString;
|
|
79
|
+
phase: z.ZodString;
|
|
80
|
+
sha: z.ZodString;
|
|
81
|
+
call: z.ZodNumber;
|
|
82
|
+
}, z.core.$strict>>;
|
|
77
83
|
artifacts: z.ZodOptional<z.ZodString>;
|
|
78
84
|
}, z.core.$strip>;
|
|
79
85
|
export type ProbeSpec = z.infer<typeof probeSpec>;
|
|
@@ -152,6 +158,15 @@ export type ProbeResult = {
|
|
|
152
158
|
gate?: ProbeGateRun;
|
|
153
159
|
/** Журнал controller, в котором записан этот запуск. */
|
|
154
160
|
log?: string;
|
|
161
|
+
/**
|
|
162
|
+
* Запуск проверки подтверждён на границе исполнения (`runCheck`): процесс
|
|
163
|
+
* порождён или обёртка песочницы создала метку старта. `false` — команда
|
|
164
|
+
* не начиналась (отказ до старта, бюджет, неизвестный gate, отмена до
|
|
165
|
+
* запуска). У записей alpha.6 и ранее поля нет.
|
|
166
|
+
*/
|
|
167
|
+
started: boolean;
|
|
168
|
+
/** Вызов reviewer, которому принадлежит запуск (из спецификации probe). */
|
|
169
|
+
scope?: ProbeSpec['scope'];
|
|
155
170
|
};
|
|
156
171
|
/** Как сделать gate пригодным для probe: путь отчёта — из переменной controller. */
|
|
157
172
|
export declare const reportRecipe = "\u0420\u0435\u0446\u0435\u043F\u0442: \u043A\u043E\u043C\u0430\u043D\u0434\u0430 gate \u0431\u0435\u0440\u0451\u0442 \u043F\u0443\u0442\u044C JUnit \u0438\u0437 HARNESS_REPORT_PATH (\u0438\u043B\u0438 DEVCONTOUR_REPORT_PATH) \u0441 \u043F\u0440\u0435\u0436\u043D\u0438\u043C \u043F\u0443\u0442\u0451\u043C \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u2014 \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 `sh -c 'gotestsum --junitfile \"${HARNESS_REPORT_PATH:-.reports/junit.xml}\" -- ./...'`, `pytest --junitxml=\"${HARNESS_REPORT_PATH:-.reports/junit.xml}\"`, `--test-reporter-destination=${HARNESS_REPORT_PATH:-.reports/junit.xml}` \u0432 package script (\u0441\u043C. docs/review-probes.md).";
|
|
@@ -236,7 +251,42 @@ export interface ProbeRecord {
|
|
|
236
251
|
gate?: ProbeGateRun;
|
|
237
252
|
/** Журнал controller, где записан запуск (у gate — собственный файл). */
|
|
238
253
|
log?: string;
|
|
254
|
+
/** Код процесса; `null` — неизвестен (таймаут, отмена, не запускался). */
|
|
255
|
+
exitCode?: number | null;
|
|
256
|
+
/** Подтверждённый старт; нет у записей alpha.6 и ранее. */
|
|
257
|
+
started?: boolean;
|
|
258
|
+
/** Вызов reviewer, которому принадлежит запуск. */
|
|
259
|
+
scope?: ProbeResult['scope'];
|
|
260
|
+
/** Пояснение controller (ограничено). */
|
|
261
|
+
note?: string;
|
|
262
|
+
}
|
|
263
|
+
/** Запись журнала с выводом команды — вход итога `inspection`. */
|
|
264
|
+
export interface ProbeJournalRecord extends ProbeRecord {
|
|
265
|
+
stdout?: string;
|
|
266
|
+
stderr?: string;
|
|
239
267
|
}
|
|
268
|
+
/** Журнал проверок вызова reviewer, как его читает controller. */
|
|
269
|
+
export interface ProbeJournal {
|
|
270
|
+
/** Строки исхода в порядке журнала; повтор одного runId — одна запись (первая). */
|
|
271
|
+
records: ProbeJournalRecord[];
|
|
272
|
+
/** Строки, не являющиеся ни исходом, ни учётом бюджета: битый JSON, `{}`, чужая форма. */
|
|
273
|
+
invalid: number;
|
|
274
|
+
/** Резервы без строки исхода: учёт бюджета и recovery, не запуски. */
|
|
275
|
+
reservations: {
|
|
276
|
+
runId: string;
|
|
277
|
+
abandoned: boolean;
|
|
278
|
+
}[];
|
|
279
|
+
}
|
|
280
|
+
/** Статусы исхода probe; иной статус — диагностика, а не исход. */
|
|
281
|
+
export declare const probeStatuses: ReadonlySet<string>;
|
|
282
|
+
/**
|
|
283
|
+
* Журнал проверок reviewer: по записи на запуск. Нечитаемая строка, объект
|
|
284
|
+
* без формы исхода (`runId`, `argv`, `status`) и строки резерва/списания —
|
|
285
|
+
* не запуски; они не роняют чтение остальных строк.
|
|
286
|
+
*/
|
|
287
|
+
export declare function readProbeJournal(log: string): Promise<ProbeJournal>;
|
|
288
|
+
/** Запуск в виде `review.json.probes`: без потоков вывода, пояснение ограничено. */
|
|
289
|
+
export declare function probeRecord(record: ProbeJournalRecord): ProbeRecord;
|
|
240
290
|
/** Журнал проверок reviewer: по записи на запуск; нечитаемая строка — не запуск. */
|
|
241
291
|
export declare function probeRecords(log: string): Promise<ProbeRecord[]>;
|
|
242
292
|
/** Переменная окружения с runId проверки: её наследуют все процессы запуска. */
|