devcontour-harness 0.1.0-alpha.13 → 0.1.0-alpha.18
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 +48 -2
- package/docs/architecture.md +77 -3
- package/docs/contracts.md +100 -0
- package/docs/quickstart.md +1 -1
- package/docs/release-notes/0.1.0-alpha.14.md +69 -0
- package/docs/release-notes/0.1.0-alpha.15.md +62 -0
- package/docs/release-notes/0.1.0-alpha.16.md +36 -0
- package/docs/release-notes/0.1.0-alpha.17.md +86 -0
- package/docs/release-notes/0.1.0-alpha.18.md +48 -0
- package/docs/review-probes.md +9 -3
- package/docs/review-protocol.md +9 -0
- package/docs/upgrade.md +16 -14
- package/lib/agents/cli-adapter.js +14 -10
- package/lib/agents/review-probe.d.ts +9 -0
- package/lib/agents/review-probe.js +23 -1
- package/lib/agents/schemas.d.ts +206 -0
- package/lib/agents/schemas.js +37 -1
- package/lib/agents/usage.d.ts +22 -0
- package/lib/agents/usage.js +112 -6
- package/lib/contracts/checks.d.ts +119 -0
- package/lib/contracts/checks.js +64 -1
- package/lib/contracts/cli.d.ts +30 -0
- package/lib/contracts/diagnosis.d.ts +3 -1
- package/lib/contracts/diagnosis.js +2 -0
- package/lib/contracts/events.d.ts +12 -0
- package/lib/contracts/execution.d.ts +109 -5
- package/lib/contracts/execution.js +58 -0
- package/lib/contracts/failure.d.ts +17 -3
- package/lib/contracts/failure.js +4 -2
- package/lib/contracts/published.d.ts +67 -1
- package/lib/contracts/repair-policy.d.ts +7 -0
- package/lib/contracts/repair-policy.js +17 -0
- package/lib/contracts/review.d.ts +12 -0
- package/lib/contracts/review.js +19 -0
- package/lib/control/harness.d.ts +2 -2
- package/lib/control/harness.js +2 -1
- package/lib/control/recovery.d.ts +26 -1
- package/lib/engine/engine.d.ts +41 -14
- package/lib/engine/engine.js +92 -8
- package/lib/engine/failure.d.ts +1 -1
- package/lib/engine/failure.js +1 -1
- package/lib/engine/property-recovery.d.ts +1 -0
- package/lib/engine/property-recovery.js +4 -1
- package/lib/engine/repair.d.ts +26 -0
- package/lib/engine/repair.js +83 -1
- package/lib/engine/runner.d.ts +7 -0
- package/lib/engine/runner.js +207 -34
- package/lib/explain/state.js +4 -2
- package/lib/index.d.ts +6 -4
- package/lib/index.js +6 -4
- package/lib/projections/stop.d.ts +1 -1
- package/lib/projections/stop.js +16 -0
- package/lib/storage/document.d.ts +48 -8
- package/lib/verification/acceptance.js +2 -1
- package/lib/verification/already-satisfied.d.ts +44 -0
- package/lib/verification/already-satisfied.js +49 -0
- package/lib/verification/checks.d.ts +28 -0
- package/lib/verification/checks.js +270 -22
- package/lib/verification/corpus.d.ts +50 -0
- package/lib/verification/gate-scope.d.ts +36 -0
- package/lib/verification/gate-scope.js +97 -0
- package/lib/verification/gates-at.d.ts +67 -0
- package/lib/verification/gates-at.js +173 -0
- package/lib/verification/gates.d.ts +5 -0
- package/lib/verification/gates.js +20 -5
- package/lib/verification/property.d.ts +9 -0
- package/lib/verification/property.js +11 -2
- package/lib/verification/self-check.js +12 -12
- package/package.json +1 -1
- package/schemas/contract-document.schema.json +161 -0
- package/schemas/execution-document.schema.json +134 -0
- package/schemas/run-event.schema.json +4 -0
- package/schemas/stop-diagnosis.schema.json +2 -0
package/lib/engine/runner.js
CHANGED
|
@@ -7,11 +7,13 @@ import { roleBinding, selectReviewer, mcpEnvironment, HarnessError, digestOf, co
|
|
|
7
7
|
import { git } from "../environment/process.js";
|
|
8
8
|
import { executionEnvironment, runSteps, withEnvironment } from "../environment/lifecycle.js";
|
|
9
9
|
import { redactDeep } from "../environment/redaction.js";
|
|
10
|
-
import { implementationOutputSchema, reviewOutputSchema } from "../agents/schemas.js";
|
|
10
|
+
import { alreadySatisfiedReviewOutputSchema, implementationOutputSchema, reviewOutputSchema, } from "../agents/schemas.js";
|
|
11
11
|
import { reviewInspection } from "../agents/inspect.js";
|
|
12
12
|
import { reviewerRunsChecks } from "../agents/cli-adapter.js";
|
|
13
13
|
import { gateProbePrompt, observeReproductions, probeCommand, probeGates, probeLimits, probeRecord, readProbeJournal, recoverProbeLog, probeTool, } from "../agents/review-probe.js";
|
|
14
14
|
import { runGate } from "../verification/gates.js";
|
|
15
|
+
import { orderedScope } from "../verification/gate-scope.js";
|
|
16
|
+
import { alreadySatisfiedVerdict, contractCheckGates, withinWriteScope, } from "../verification/already-satisfied.js";
|
|
15
17
|
import { assertDependencies, dependencyFreshness, removeSnapshots, snapshotDependencies, } from "../verification/dependencies.js";
|
|
16
18
|
import { CleanupFailure, cleanupEnvironment } from "../environment/lifecycle.js";
|
|
17
19
|
import { defaultResourceDatabase, withResources } from "../environment/resources.js";
|
|
@@ -19,31 +21,20 @@ import { REQUIREMENT_GATE, assertRequirements } from "../verification/requiremen
|
|
|
19
21
|
import { BlockedError, classifyFailure } from "./classify.js";
|
|
20
22
|
import { TaskFailure } from "./failure.js";
|
|
21
23
|
import { prompt } from "./prompt.js";
|
|
22
|
-
import { contractDocuments, orderCheckManifest } from "../verification/checks.js";
|
|
24
|
+
import { contractDocuments, orderCheckManifest, referenceCodeNote, referenceSetFiles, setFileReference, } from "../verification/checks.js";
|
|
23
25
|
import { CorpusStore, localRoots } from "../verification/corpus.js";
|
|
24
26
|
import { submitCase } from "../verification/confirm.js";
|
|
25
27
|
import { runSpike } from "../verification/spike.js";
|
|
26
|
-
import { propertySeed } from "../verification/property.js";
|
|
28
|
+
import { expectsReference, propertySeed } from "../verification/property.js";
|
|
27
29
|
import { selfCheckNote, writeSelfCheck } from "../verification/self-check.js";
|
|
28
30
|
import { measuredInvoke } from "./measured.js";
|
|
29
31
|
import { continuableDraft, implementationBasis } from "./reuse.js";
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (seen.has(id) || !byId.has(id))
|
|
37
|
-
return;
|
|
38
|
-
seen.add(id);
|
|
39
|
-
for (const dep of byId.get(id)?.dependsOn ?? [])
|
|
40
|
-
if (ids.includes(dep))
|
|
41
|
-
visit(dep);
|
|
42
|
-
out.push(byId.get(id));
|
|
43
|
-
};
|
|
44
|
-
ids.forEach(visit);
|
|
45
|
-
return out;
|
|
46
|
-
};
|
|
32
|
+
/** Честный путь исполнителя, когда работу уже сделала зависимость. */
|
|
33
|
+
const ALREADY_SATISFIED_WRITER_NOTE = 'Implement the task. Only if the work is verifiably already present on the base commit — a dependency of this task already changed the files of its write scope, or the approved check contract of this task already passes — leave the tree unchanged and report completed: true, naming the commit and the files in the summary. An unchanged tree is never accepted on a generic passing test alone: without such a basis it stops for the lead, and a wrong claim is an empty result.';
|
|
34
|
+
/** Указание reviewer попытки без изменений (кандидат — база). */
|
|
35
|
+
const ALREADY_SATISFIED_REVIEW_NOTE = 'The writer made no changes: the candidate is the base commit itself. Approve only if the acceptance criteria are already satisfied on it (for example, by a dependency). Then set "alreadySatisfied" to the ids of the gates of this task whose passing evidence on this SHA shows it — at least one test gate and every gate of the approved check contracts — and "evidence" to what in that evidence and in the code proves each criterion. Otherwise set approved: false and alreadySatisfied: null; the attempt is then an empty result.';
|
|
36
|
+
/** Порядок области gates — общий с проверкой на SHA вне попытки (`orderedScope`). */
|
|
37
|
+
const ordered = orderedScope;
|
|
47
38
|
const within = (path, prefixes) => prefixes.some((p) => p === '.' || path === p || path.startsWith(p.endsWith('/') ? p : p + '/'));
|
|
48
39
|
/**
|
|
49
40
|
* Исполнитель очереди. Один действующий controller; попытка проходит
|
|
@@ -254,8 +245,12 @@ export class Runner {
|
|
|
254
245
|
/** Штатная остановка: пауза `shutdown`, прерывание попыток, освобождение роли controller. */
|
|
255
246
|
async stop() {
|
|
256
247
|
this.engine.pause('shutdown');
|
|
257
|
-
|
|
248
|
+
// Прерванная остановкой попытка не упала: поручение вернётся в очередь
|
|
249
|
+
// и продолжится с кандидата или черновика (см. catch в `execute`).
|
|
250
|
+
for (const job of this.jobs.values()) {
|
|
251
|
+
job.shutdown = true;
|
|
258
252
|
job.controller.abort();
|
|
253
|
+
}
|
|
259
254
|
await Promise.all([...this.jobs.values()].map((j) => j.promise));
|
|
260
255
|
this.release();
|
|
261
256
|
}
|
|
@@ -371,7 +366,7 @@ export class Runner {
|
|
|
371
366
|
binding,
|
|
372
367
|
cwd: input.cwd,
|
|
373
368
|
prompt: input.prompt,
|
|
374
|
-
outputSchema: review ? reviewOutputSchema : implementationOutputSchema,
|
|
369
|
+
outputSchema: input.outputSchema ?? (review ? reviewOutputSchema : implementationOutputSchema),
|
|
375
370
|
session: { kind: 'fresh' },
|
|
376
371
|
sandbox: {
|
|
377
372
|
mode: this.policy.isolation.mode,
|
|
@@ -465,7 +460,9 @@ export class Runner {
|
|
|
465
460
|
return adapter;
|
|
466
461
|
}
|
|
467
462
|
/** Область записи кандидата: защищённые пути, пустой результат, выход за область поручения и роли. */
|
|
468
|
-
async assertScope(work, repo, cwd, context, range
|
|
463
|
+
async assertScope(work, repo, cwd, context, range,
|
|
464
|
+
/** Пустой результат допустим (возможное «приёмка уже выполнена»): вернуть true вместо отказа. */
|
|
465
|
+
allowEmpty = false) {
|
|
469
466
|
const changed = (await (range
|
|
470
467
|
? git(cwd, 'diff', '--no-renames', '--name-only', '-z', ...range)
|
|
471
468
|
: git(cwd, 'diff', '--cached', '--no-renames', '--name-only', '-z')))
|
|
@@ -491,12 +488,34 @@ export class Runner {
|
|
|
491
488
|
const forbidden = changed.filter((p) => within(p, protectedList));
|
|
492
489
|
if (forbidden.length)
|
|
493
490
|
throw new TaskFailure('scope-violation', `Изменены защищённые файлы: ${forbidden.join(', ')}`);
|
|
494
|
-
|
|
491
|
+
// Ответы эталонных наборов лежат в рабочем дереве: изменённый код,
|
|
492
|
+
// который называет файл набора строковым литералом, читает эталон, а не
|
|
493
|
+
// решает. Тесты исполнителя (не артефакты контракта) проверяются так же:
|
|
494
|
+
// через них ответы тоже можно подать реализации.
|
|
495
|
+
const sets = this.engine.read((v) => referenceSetFiles(contractDocuments(work.order, v), repo.id));
|
|
496
|
+
if (sets.length) {
|
|
497
|
+
const named = [];
|
|
498
|
+
for (const path of changed) {
|
|
499
|
+
const text = await git(cwd, 'show', `${range ? range[1] : ''}:${path}`).catch(() => '');
|
|
500
|
+
if (text.length > 4 * 1024 * 1024)
|
|
501
|
+
continue;
|
|
502
|
+
const hit = setFileReference(path, text, sets);
|
|
503
|
+
if (hit)
|
|
504
|
+
named.push(`${path} → ${hit}`);
|
|
505
|
+
}
|
|
506
|
+
if (named.length)
|
|
507
|
+
throw new TaskFailure('scope-violation', `Реализация ссылается на файлы эталонных наборов: ${named.join(', ')}`);
|
|
508
|
+
}
|
|
509
|
+
if (!changed.length) {
|
|
510
|
+
if (allowEmpty)
|
|
511
|
+
return true;
|
|
495
512
|
throw new TaskFailure('empty-result', 'Изменений нет: поручение отчиталось о выполнении пустым коммитом');
|
|
513
|
+
}
|
|
496
514
|
const role = roleBinding(this.policy, work.order.role, work.order.repositoryId)?.writePaths;
|
|
497
515
|
const outside = changed.filter((p) => !within(p, work.order.writeScope) || (role && !within(p, role)));
|
|
498
516
|
if (outside.length)
|
|
499
517
|
throw new TaskFailure('scope-violation', 'Изменены файлы вне области поручения/роли: ' + outside.join(', '));
|
|
518
|
+
return false;
|
|
500
519
|
}
|
|
501
520
|
/** Стадия попытки с замером: начало, конец и исход записываются под fencing. */
|
|
502
521
|
async timed(attempt, stage, fn) {
|
|
@@ -757,6 +776,7 @@ export class Runner {
|
|
|
757
776
|
notes: [
|
|
758
777
|
...this.notes(attempt, false),
|
|
759
778
|
...(await this.hostNotes(work, attempt, false)),
|
|
779
|
+
ALREADY_SATISFIED_WRITER_NOTE,
|
|
760
780
|
],
|
|
761
781
|
// Открытые находки ревью с выданными ID — из истории
|
|
762
782
|
// поручения; только когда reviewer ведёт протокол
|
|
@@ -771,14 +791,28 @@ export class Runner {
|
|
|
771
791
|
const result = implementationResultSchema.safeParse(response.output);
|
|
772
792
|
if (!result.success)
|
|
773
793
|
throw new Error('Ответ исполнителя вне схемы: ' + result.error.issues[0]?.message);
|
|
794
|
+
// Не доделано — свой класс: повтор продолжит с черновика,
|
|
795
|
+
// а не остановится как неизвестный отказ.
|
|
774
796
|
if (!result.data.completed)
|
|
775
|
-
throw new
|
|
797
|
+
throw new TaskFailure('unfinished', 'Исполнитель сообщил о незавершённой работе: ' +
|
|
798
|
+
result.data.summary.slice(0, 500));
|
|
776
799
|
this.engine.heartbeat(attempt.id, attempt.token);
|
|
777
800
|
signal.throwIfAborted();
|
|
778
801
|
if ((await git(cwd, 'rev-parse', 'HEAD')) !== base)
|
|
779
802
|
throw new TaskFailure('scope-violation', 'Исполнитель изменил HEAD; интеграцией владеет harness');
|
|
780
803
|
await git(cwd, 'add', '-A');
|
|
781
|
-
|
|
804
|
+
// Пустой результат — не отказ сразу: приёмку могла уже
|
|
805
|
+
// выполнить зависимость. Кандидат — сама база (без
|
|
806
|
+
// коммита); gates задачи на ней и подтверждение reviewer
|
|
807
|
+
// (`alreadySatisfied`) решают, принять ли его.
|
|
808
|
+
if (await this.assertScope(work, repo, cwd, context, undefined, true)) {
|
|
809
|
+
await this.options.hooks?.implemented?.({
|
|
810
|
+
attempt,
|
|
811
|
+
output: result.data,
|
|
812
|
+
sha: base,
|
|
813
|
+
});
|
|
814
|
+
return base;
|
|
815
|
+
}
|
|
782
816
|
await git(cwd, '-c', 'core.hooksPath=/dev/null', 'commit', '--no-gpg-sign', '-q', '-m', `${work.id}: ${work.order.title}`);
|
|
783
817
|
const committed = await git(cwd, 'rev-parse', 'HEAD');
|
|
784
818
|
await this.options.hooks?.implemented?.({
|
|
@@ -791,7 +825,13 @@ export class Runner {
|
|
|
791
825
|
rearm();
|
|
792
826
|
this.engine.phase(attempt.id, attempt.token, 'verifying', { candidateSha: sha });
|
|
793
827
|
for (const gate of gates)
|
|
794
|
-
await this.gate(attempt, cwd, sha, 'candidate', gate, artifacts, signal, execution)
|
|
828
|
+
await this.gate(attempt, cwd, sha, 'candidate', gate, artifacts, signal, execution).catch((error) => {
|
|
829
|
+
// Без изменений непрошедший gate значит одно: работа не
|
|
830
|
+
// сделана, — это пустой результат, а не дефект кода.
|
|
831
|
+
if (sha !== base || !(error instanceof TaskFailure))
|
|
832
|
+
throw error;
|
|
833
|
+
throw new TaskFailure('empty-result', 'Изменений нет, а проверки задачи на базе не проходят: ' + error.message);
|
|
834
|
+
});
|
|
795
835
|
this.requirements(attempt, work, repoPath, cwd, sha, 'candidate');
|
|
796
836
|
rearm();
|
|
797
837
|
this.engine.phase(attempt.id, attempt.token, 'reviewing');
|
|
@@ -825,6 +865,32 @@ export class Runner {
|
|
|
825
865
|
: error instanceof Error
|
|
826
866
|
? error.message
|
|
827
867
|
: String(error);
|
|
868
|
+
// Штатная остановка оборвала попытку: что бы ни выбросила прерванная
|
|
869
|
+
// операция, это не отказ работы. Черновик сохраняется, поручение
|
|
870
|
+
// возвращается в очередь, бюджет попыток не тратится.
|
|
871
|
+
const shutdown = controller.signal.aborted && this.jobs.get(attempt.id)?.shutdown === true;
|
|
872
|
+
if (shutdown)
|
|
873
|
+
try {
|
|
874
|
+
await this.keepDraft(attempt, await this.workOf(attempt), artifacts);
|
|
875
|
+
const message = 'Попытку прервала штатная остановка controller; поручение возвращено в очередь и продолжится с кандидата или черновика';
|
|
876
|
+
const failed = this.engine.fail(attempt.id, attempt.token, message, {
|
|
877
|
+
kind: 'environment',
|
|
878
|
+
interrupted: true,
|
|
879
|
+
});
|
|
880
|
+
// Интегрированную попытку движок в очередь не вернул: это обычный
|
|
881
|
+
// отказ, и хост называет его причину.
|
|
882
|
+
if (!failed.interrupted)
|
|
883
|
+
await this.options.hooks?.failed?.({
|
|
884
|
+
attempt,
|
|
885
|
+
kind: 'environment',
|
|
886
|
+
message: 'Штатная остановка прервала интегрированную попытку; нужна проверка recovery',
|
|
887
|
+
});
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
catch {
|
|
891
|
+
// Потерявшая владение попытка не публикует поздний отказ.
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
828
894
|
try {
|
|
829
895
|
const kind = expired && !(error instanceof TaskFailure) ? 'timeout' : classifyFailure(error, message);
|
|
830
896
|
// Незакоммиченная работа сохраняется при любом отказе до кандидата:
|
|
@@ -990,6 +1056,10 @@ export class Runner {
|
|
|
990
1056
|
await this.timed(attempt, stage, () => this.runOneGate(attempt, cwd, sha, phase, gate, artifacts, signal, execution, consumer));
|
|
991
1057
|
}
|
|
992
1058
|
async runOneGate(attempt, cwd, sha, phase, gate, artifacts, signal, execution, consumer) {
|
|
1059
|
+
// Генеративный gate — всегда; gate эталонных наборов — когда манифест
|
|
1060
|
+
// поручения ждёт от него отчёта (без договора — прежний уровень).
|
|
1061
|
+
const run = this.propertyRun(attempt, gate, consumer);
|
|
1062
|
+
const property = gate.property || expectsReference(run.expected) ? run : undefined;
|
|
993
1063
|
await runGate({
|
|
994
1064
|
policy: this.policy,
|
|
995
1065
|
record: (e) => this.engine.evidence(attempt.id, attempt.token, e),
|
|
@@ -1004,7 +1074,7 @@ export class Runner {
|
|
|
1004
1074
|
execution,
|
|
1005
1075
|
boundary: this.boundary(consumer?.dependencies ?? attempt.dependencies),
|
|
1006
1076
|
...(consumer ? { consumer: consumer.repositoryId } : {}),
|
|
1007
|
-
...(
|
|
1077
|
+
...(property ? { property } : {}),
|
|
1008
1078
|
});
|
|
1009
1079
|
}
|
|
1010
1080
|
/**
|
|
@@ -1223,6 +1293,50 @@ export class Runner {
|
|
|
1223
1293
|
* команды — засчитывается всем резервом, записанным до запуска, а его
|
|
1224
1294
|
* пережившие процессы останавливаются (R3-F01).
|
|
1225
1295
|
*/
|
|
1296
|
+
/**
|
|
1297
|
+
* Решение по подтверждению «приёмка уже выполнена» (`alreadySatisfiedVerdict`).
|
|
1298
|
+
* Основание по зависимости controller ищет сам: зависимость поручения,
|
|
1299
|
+
* чей принятый результат входит в проверяемый SHA, а её собственный
|
|
1300
|
+
* кандидат (база → кандидат) изменил файлы области записи поручения.
|
|
1301
|
+
*/
|
|
1302
|
+
async alreadySatisfied(work, repoPath, sha, gates, claim) {
|
|
1303
|
+
const { contractGates, dependencies } = this.engine.read((v) => ({
|
|
1304
|
+
contractGates: contractCheckGates(contractDocuments(work.order, v)),
|
|
1305
|
+
dependencies: work.order.dependsOn.flatMap((id) => {
|
|
1306
|
+
const w = v.work(id);
|
|
1307
|
+
const a = v
|
|
1308
|
+
.attempts(id)
|
|
1309
|
+
.findLast((x) => x.status === 'succeeded' && x.integrationSha === w?.resultSha);
|
|
1310
|
+
return w?.resultSha && a?.baseSha && a.candidateSha && a.candidateSha !== a.baseSha
|
|
1311
|
+
? [{ workId: id, resultSha: w.resultSha, base: a.baseSha, candidate: a.candidateSha }]
|
|
1312
|
+
: [];
|
|
1313
|
+
}),
|
|
1314
|
+
}));
|
|
1315
|
+
let dependency;
|
|
1316
|
+
for (const d of dependencies) {
|
|
1317
|
+
const inBase = await git(repoPath, 'merge-base', '--is-ancestor', d.resultSha, sha)
|
|
1318
|
+
.then(() => true)
|
|
1319
|
+
.catch(() => false);
|
|
1320
|
+
if (!inBase)
|
|
1321
|
+
continue;
|
|
1322
|
+
const paths = (await git(repoPath, 'diff', '--no-renames', '--name-only', '-z', d.base, d.candidate))
|
|
1323
|
+
.split('\0')
|
|
1324
|
+
.filter((p) => p && withinWriteScope(p, work.order.writeScope))
|
|
1325
|
+
.slice(0, 50);
|
|
1326
|
+
if (paths.length) {
|
|
1327
|
+
dependency = { kind: 'dependency', workId: d.workId, sha: d.candidate, paths };
|
|
1328
|
+
break;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return alreadySatisfiedVerdict({
|
|
1332
|
+
scope: gates,
|
|
1333
|
+
contractGates,
|
|
1334
|
+
writeScope: work.order.writeScope,
|
|
1335
|
+
dependsOn: work.order.dependsOn,
|
|
1336
|
+
claim,
|
|
1337
|
+
...(dependency ? { dependency } : {}),
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1226
1340
|
async probeSpent(runs, repositoryId, phase) {
|
|
1227
1341
|
let spent = 0;
|
|
1228
1342
|
for (const runId of runs) {
|
|
@@ -1275,9 +1389,13 @@ export class Runner {
|
|
|
1275
1389
|
const round = history.round + 1;
|
|
1276
1390
|
// Запрос одинаков для всех вызовов логического ревью: меняется только
|
|
1277
1391
|
// диагностика ошибки протокола.
|
|
1392
|
+
// Попытка без изменений: кандидат — сама база. Принять её можно только
|
|
1393
|
+
// по явному подтверждению reviewer, что приёмка уже выполнена.
|
|
1394
|
+
const unchanged = phase === 'candidate' ? base === sha : attempt.candidateSha === attempt.baseSha;
|
|
1278
1395
|
const notes = [
|
|
1279
1396
|
...this.notes(attempt, true, sha),
|
|
1280
1397
|
...(await this.hostNotes(work, attempt, true)),
|
|
1398
|
+
...(unchanged ? [ALREADY_SATISFIED_REVIEW_NOTE] : []),
|
|
1281
1399
|
];
|
|
1282
1400
|
let issues = lastIssues;
|
|
1283
1401
|
const stage = phase + '-review';
|
|
@@ -1340,6 +1458,7 @@ export class Runner {
|
|
|
1340
1458
|
...(probe ? { probe: probe.server } : {}),
|
|
1341
1459
|
...(legacy ? {} : { findings: requestFindings(history) }),
|
|
1342
1460
|
timeoutMs: remaining,
|
|
1461
|
+
...(unchanged ? { outputSchema: alreadySatisfiedReviewOutputSchema } : {}),
|
|
1343
1462
|
}, signal);
|
|
1344
1463
|
// Reviewer не меняет закреплённые зависимости, как и своё дерево.
|
|
1345
1464
|
await assertDependencies(attempt.dependencies ?? []);
|
|
@@ -1481,6 +1600,7 @@ export class Runner {
|
|
|
1481
1600
|
{
|
|
1482
1601
|
property: r.property,
|
|
1483
1602
|
input: r.input,
|
|
1603
|
+
...(r.inputClass?.trim() ? { inputClass: r.inputClass.trim().slice(0, 500) } : {}),
|
|
1484
1604
|
expected: r.expected,
|
|
1485
1605
|
actual: r.actual,
|
|
1486
1606
|
command: r.command,
|
|
@@ -1493,7 +1613,14 @@ export class Runner {
|
|
|
1493
1613
|
});
|
|
1494
1614
|
if (reproductions.length)
|
|
1495
1615
|
this.engine.recordReproductions(attempt.id, attempt.token, sha, reproductions);
|
|
1496
|
-
|
|
1616
|
+
// Без изменений одобрение засчитывается только с подтверждением
|
|
1617
|
+
// `alreadySatisfied`, которое опирается на прошедшее evidence этой фазы.
|
|
1618
|
+
const claim = verdict.result.alreadySatisfied ?? undefined;
|
|
1619
|
+
const decision = unchanged && verdict.result.approved
|
|
1620
|
+
? await this.alreadySatisfied(work, this.repoPath(repo), sha, gates, claim)
|
|
1621
|
+
: undefined;
|
|
1622
|
+
const unconfirmed = decision && !decision.ok ? decision.problem : undefined;
|
|
1623
|
+
const passed = verdict.result.approved && !unconfirmed;
|
|
1497
1624
|
// Вердикт, новые находки с выданными ID и переходы прежних — одна запись
|
|
1498
1625
|
// под fencing: попытка, утратившая владение, их не сохраняет.
|
|
1499
1626
|
this.engine.evidence(attempt.id, attempt.token, {
|
|
@@ -1507,7 +1634,17 @@ export class Runner {
|
|
|
1507
1634
|
exitCode: passed ? 0 : 1,
|
|
1508
1635
|
log,
|
|
1509
1636
|
digest,
|
|
1510
|
-
summary: redact(`[${inspection.mode}] ${record.summary}`
|
|
1637
|
+
summary: redact(`[${inspection.mode}] ${record.summary}` +
|
|
1638
|
+
(unconfirmed ? ` (не подтверждено, что приёмка уже выполнена: ${unconfirmed})` : '')).slice(0, 4000),
|
|
1639
|
+
...(unchanged && passed && claim && decision?.ok
|
|
1640
|
+
? {
|
|
1641
|
+
alreadySatisfied: {
|
|
1642
|
+
gates: claim.gates,
|
|
1643
|
+
evidence: redact(claim.evidence).slice(0, 4000),
|
|
1644
|
+
basis: decision.basis,
|
|
1645
|
+
},
|
|
1646
|
+
}
|
|
1647
|
+
: {}),
|
|
1511
1648
|
findings: (record.findings ?? []).map((f, i) => ({
|
|
1512
1649
|
severity: f.severity,
|
|
1513
1650
|
message: f.message,
|
|
@@ -1532,10 +1669,21 @@ export class Runner {
|
|
|
1532
1669
|
.map((f) => f.reproduction?.property)
|
|
1533
1670
|
.filter((p) => !!p);
|
|
1534
1671
|
const declared = new Set(work.order.properties ?? []);
|
|
1535
|
-
|
|
1672
|
+
// Без изменений отказ ревью — пустой результат: работа не сделана.
|
|
1673
|
+
if (unchanged)
|
|
1674
|
+
throw new TaskFailure(decision && !decision.ok ? decision.kind : 'empty-result', unconfirmed
|
|
1675
|
+
? 'Изменений нет, и ревью не подтвердило, что приёмка уже выполнена: ' + unconfirmed
|
|
1676
|
+
: 'Изменений нет, и ревью не подтвердило, что приёмка уже выполнена: ' +
|
|
1677
|
+
redact(record.summary));
|
|
1678
|
+
// Класс входов — из замаскированной записи: он попадает в состояние.
|
|
1679
|
+
const classes = inputClassesOf(record.findings ?? []);
|
|
1680
|
+
const pairs = classifiedOf(record.findings ?? []);
|
|
1681
|
+
throw new TaskFailure('review', 'Независимое ревью отклонило результат: ' + redact(record.summary), named.length || classes.length
|
|
1536
1682
|
? {
|
|
1537
1683
|
bound: [...new Set(named.filter((p) => declared.has(p)))],
|
|
1538
1684
|
unbound: [...new Set(named.filter((p) => !declared.has(p)))],
|
|
1685
|
+
...(classes.length ? { classes } : {}),
|
|
1686
|
+
...(pairs.length ? { pairs } : {}),
|
|
1539
1687
|
}
|
|
1540
1688
|
: undefined);
|
|
1541
1689
|
}
|
|
@@ -1588,12 +1736,13 @@ export class Runner {
|
|
|
1588
1736
|
// Воспроизведения прошлых ревью — входы, на которых поручение уже
|
|
1589
1737
|
// ломалось. Диагностика: gate они не расширяют.
|
|
1590
1738
|
if (reproductions.length)
|
|
1591
|
-
out.push('Reproductions recorded by earlier reviews of this work order (diagnostic, not a gate; "observed" means the controller saw the reviewer run the command). Check the change against each input: ' +
|
|
1739
|
+
out.push('Reproductions recorded by earlier reviews of this work order (diagnostic, not a gate; "observed" means the controller saw the reviewer run the command). Check the change against each input, and fix the whole inputClass it names rather than the single instance: ' +
|
|
1592
1740
|
JSON.stringify(reproductions
|
|
1593
1741
|
.slice(-20)
|
|
1594
|
-
.map(({ property, input, expected, actual, command, observed, runs }) => ({
|
|
1742
|
+
.map(({ property, input, inputClass, expected, actual, command, observed, runs }) => ({
|
|
1595
1743
|
property,
|
|
1596
1744
|
input,
|
|
1745
|
+
...(inputClass ? { inputClass } : {}),
|
|
1597
1746
|
expected,
|
|
1598
1747
|
actual,
|
|
1599
1748
|
command,
|
|
@@ -1602,11 +1751,14 @@ export class Runner {
|
|
|
1602
1751
|
}))));
|
|
1603
1752
|
// Согласованные контракты поручения — их текстом, по закреплённому digest.
|
|
1604
1753
|
const contracts = contractDocuments(v.work(attempt.workId).order, v);
|
|
1754
|
+
const sets = referenceCodeNote(referenceSetFiles(contracts), review);
|
|
1755
|
+
if (sets)
|
|
1756
|
+
out.push(sets);
|
|
1605
1757
|
if (contracts.length)
|
|
1606
1758
|
out.push('Approved contracts: ' +
|
|
1607
1759
|
JSON.stringify(contracts.map((c) => ({ id: c.id, title: c.title, content: c.content }))));
|
|
1608
1760
|
if (review)
|
|
1609
|
-
out.push('When a finding fails on a concrete input, fill reproduction: property (the testId or acceptance criterion it violates), input (the exact input as JSON text), expected and actual, and command (argv) that shows it. If you ran it through the controller check tool, set run to the runId it returned and executed=true; the controller confirms the run from its own log and records the command that actually ran. Otherwise run=null and it is a hypothesis. Use null for findings without such an input.');
|
|
1761
|
+
out.push('When a finding fails on a concrete input, fill reproduction: property (the testId or acceptance criterion it violates; required for a blocking finding), input (the exact input as JSON text), inputClass (the class of inputs this one belongs to: the generator or family that produces such failures, e.g. "ties between equal optima with more than 6 items", not the instance itself; required for a blocking finding), expected and actual, and command (argv) that shows it. If you ran it through the controller check tool, set run to the runId it returned and executed=true; the controller confirms the run from its own log and records the command that actually ran. Otherwise run=null and it is a hypothesis. Use null for findings without such an input.');
|
|
1610
1762
|
// Reviewer видит, что controller сам записал о проверяемом SHA: gates и
|
|
1611
1763
|
// их итоги, а не пересказ исполнителя.
|
|
1612
1764
|
if (review && sha) {
|
|
@@ -1842,3 +1994,24 @@ function startedCalls(view, repositoryId, runId, stage) {
|
|
|
1842
1994
|
(r.outcome === 'active' ? Math.max(0, Date.now() - Date.parse(r.startedAt)) : 0)), 0),
|
|
1843
1995
|
};
|
|
1844
1996
|
}
|
|
1997
|
+
/** Свойство и класс одной блокирующей находки: оба названы. */
|
|
1998
|
+
function classifiedOf(findings) {
|
|
1999
|
+
const out = new Map();
|
|
2000
|
+
for (const f of findings) {
|
|
2001
|
+
const property = f.reproduction?.property?.trim();
|
|
2002
|
+
const inputClass = f.reproduction?.inputClass?.trim();
|
|
2003
|
+
if (f.severity !== 'blocking' || !property || !inputClass)
|
|
2004
|
+
continue;
|
|
2005
|
+
const pair = { property: property.slice(0, 200), inputClass: inputClass.slice(0, 500) };
|
|
2006
|
+
out.set(JSON.stringify(pair), pair);
|
|
2007
|
+
}
|
|
2008
|
+
return [...out.values()].slice(0, 20);
|
|
2009
|
+
}
|
|
2010
|
+
/** Классы входов блокирующих находок: без пустых и повторов, в пределах записи попытки. */
|
|
2011
|
+
function inputClassesOf(findings) {
|
|
2012
|
+
const classes = findings.flatMap((f) => {
|
|
2013
|
+
const c = f.severity === 'blocking' ? f.reproduction?.inputClass?.trim() : undefined;
|
|
2014
|
+
return c ? [c.slice(0, 500)] : [];
|
|
2015
|
+
});
|
|
2016
|
+
return [...new Set(classes)].slice(0, 20);
|
|
2017
|
+
}
|
package/lib/explain/state.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HarnessError } from "../contracts/errors.js";
|
|
2
|
-
import { repairPolicy } from "../contracts/repair-policy.js";
|
|
3
|
-
import { repairDecision } from "../engine/repair.js";
|
|
2
|
+
import { MAX_NEUTRAL_FAILURES, repairPolicy } from "../contracts/repair-policy.js";
|
|
3
|
+
import { acceptanceGap, repairDecision } from "../engine/repair.js";
|
|
4
4
|
const phaseOf = {
|
|
5
5
|
running: 'implementation',
|
|
6
6
|
verifying: 'gates',
|
|
@@ -295,6 +295,8 @@ function outlookOf(v, graphId, repairsUsed, options, control, budgetHeld) {
|
|
|
295
295
|
: rule.repeats + 1;
|
|
296
296
|
const retry = rule.action === 'retry' &&
|
|
297
297
|
identical - 1 < rule.repeats &&
|
|
298
|
+
!acceptanceGap(v, w) &&
|
|
299
|
+
(w.neutralFailures ?? 0) < MAX_NEUTRAL_FAILURES &&
|
|
298
300
|
w.attempt < options.maxAttempts &&
|
|
299
301
|
used + i < options.repairBudget;
|
|
300
302
|
if (!retry)
|
package/lib/index.d.ts
CHANGED
|
@@ -31,8 +31,8 @@ export { callDurationMs, callRecords, usageTotals } from './engine/usage.ts';
|
|
|
31
31
|
* ожидаемая проверка поручения, сверка отчёта свойств, закреплённые версии
|
|
32
32
|
* корпуса, подтверждение примера эталоном и переносимая форма корпуса.
|
|
33
33
|
*/
|
|
34
|
-
export { contractDocuments, declarationProblems, corpusBindingHolds, effectiveChecks, orderCheckManifest, orderPropertyProblems, suiteBasis, } from './verification/checks.ts';
|
|
35
|
-
export { corpusWitness, manifestDigest, propertyCoverage, propertyFailure, propertySeed, readPropertyReport, type CheckManifest, } from './verification/property.ts';
|
|
34
|
+
export { contractDocuments, declarationProblems, corpusBindingHolds, effectiveChecks, isReference, obligationGates, orderCheckManifest, orderPropertyProblems, referenceCodeNote, referenceNote, referenceSetFiles, setFileReference, suiteBasis, } from './verification/checks.ts';
|
|
35
|
+
export { corpusWitness, expectsReference, manifestDigest, propertyCoverage, propertyFailure, propertySeed, readPropertyReport, type CheckManifest, } from './verification/property.ts';
|
|
36
36
|
export { CorpusStore, canonicalJson, caseId, corpusFile, parseCanonical, versionOf, portableSuite, scrubLocalPaths, scrubPortable, verifySuite, type PortableSuite, } from './verification/corpus.ts';
|
|
37
37
|
export { caseHooks, caseInputSchema, staleArtifacts, submitCase, type CaseRequest, type ConfirmContext, } from './verification/confirm.ts';
|
|
38
38
|
export { assertResult, pinnedCorpus, propertyProofProblem, resultProblems, type ReceiverContext, type ReceiverPolicy, } from './verification/acceptance.ts';
|
|
@@ -51,18 +51,20 @@ export { builtinProfiles, builtinProfilesRoot, profilePin, profileRoles, reposit
|
|
|
51
51
|
* маскирование и ограниченный разбор по свойству.
|
|
52
52
|
*/
|
|
53
53
|
export { WorkflowQueue, GRAPH_STAGES, type WorkflowSubjects } from './control/workflow.ts';
|
|
54
|
-
export { repairDecision, type RepairDecision } from './engine/repair.ts';
|
|
54
|
+
export { acceptanceGap, repairDecision, type AcceptanceGap, type RepairDecision, } from './engine/repair.ts';
|
|
55
55
|
export { technicalReview, type TechnicalReviewInput } from './control/review.ts';
|
|
56
56
|
export { JointRunner, type JointRunnerOptions } from './engine/joint-runner.ts';
|
|
57
57
|
export { moveBase, fencedRefMove, type BaseMoved, type RefMove } from './control/base-move.ts';
|
|
58
58
|
export { junitSummary, prepareReportPath, runCheck, runGate, sandboxBlockedHosts, type GateRun, } from './verification/gates.ts';
|
|
59
|
+
export { runGatesAt, type GatesAtContext, type GatesAtInput, type GatesAtResult, } from './verification/gates-at.ts';
|
|
60
|
+
export { explicitPrerequisites, gatePrerequisites, orderedScope, standaloneScope, } from './verification/gate-scope.ts';
|
|
59
61
|
export { requirementProof, uncoveredGates, unprovenReason } from './verification/proof.ts';
|
|
60
62
|
export { ReviewProbes, probeLimits, observeReproductions } from './agents/review-probe.ts';
|
|
61
63
|
export { composeRedactors, outputRedactor, redactDeep, redactError, redactor, type Redactor, } from './environment/redaction.ts';
|
|
62
64
|
export { closeIfBasisChanged } from './engine/property-recovery.ts';
|
|
63
65
|
export { analyzeRecovery, applyCases, recoveryBrief, recoveryOutputSchema, recoveryResultSchema, } from './control/recovery.ts';
|
|
64
66
|
export { BlockedError, classifyFailure, providerRefusal } from './engine/classify.ts';
|
|
65
|
-
export { TaskFailure, failureFingerprint, repairPolicy, type RepairRule, } from './engine/failure.ts';
|
|
67
|
+
export { TaskFailure, failureFingerprint, repairPolicy, MAX_NEUTRAL_FAILURES, type RepairRule, } from './engine/failure.ts';
|
|
66
68
|
export * from './projections/index.ts';
|
|
67
69
|
export { inspectPath, checkDestination, listRegularFiles, planFileWrites, applyFileWrites, type FileConflict, type FileIntent, type FileWriteInput, type FilePlanEntry, type FilePlan, } from './setup/files.ts';
|
|
68
70
|
export { initProject, readProjectManifest, defaultStateDir, projectIdOf, type InitOptions, type InitData, } from './init/project.ts';
|
package/lib/index.js
CHANGED
|
@@ -31,8 +31,8 @@ export { callDurationMs, callRecords, usageTotals } from "./engine/usage.js";
|
|
|
31
31
|
* ожидаемая проверка поручения, сверка отчёта свойств, закреплённые версии
|
|
32
32
|
* корпуса, подтверждение примера эталоном и переносимая форма корпуса.
|
|
33
33
|
*/
|
|
34
|
-
export { contractDocuments, declarationProblems, corpusBindingHolds, effectiveChecks, orderCheckManifest, orderPropertyProblems, suiteBasis, } from "./verification/checks.js";
|
|
35
|
-
export { corpusWitness, manifestDigest, propertyCoverage, propertyFailure, propertySeed, readPropertyReport, } from "./verification/property.js";
|
|
34
|
+
export { contractDocuments, declarationProblems, corpusBindingHolds, effectiveChecks, isReference, obligationGates, orderCheckManifest, orderPropertyProblems, referenceCodeNote, referenceNote, referenceSetFiles, setFileReference, suiteBasis, } from "./verification/checks.js";
|
|
35
|
+
export { corpusWitness, expectsReference, manifestDigest, propertyCoverage, propertyFailure, propertySeed, readPropertyReport, } from "./verification/property.js";
|
|
36
36
|
export { CorpusStore, canonicalJson, caseId, corpusFile, parseCanonical, versionOf, portableSuite, scrubLocalPaths, scrubPortable, verifySuite, } from "./verification/corpus.js";
|
|
37
37
|
export { caseHooks, caseInputSchema, staleArtifacts, submitCase, } from "./verification/confirm.js";
|
|
38
38
|
export { assertResult, pinnedCorpus, propertyProofProblem, resultProblems, } from "./verification/acceptance.js";
|
|
@@ -51,11 +51,13 @@ export { builtinProfiles, builtinProfilesRoot, profilePin, profileRoles, reposit
|
|
|
51
51
|
* маскирование и ограниченный разбор по свойству.
|
|
52
52
|
*/
|
|
53
53
|
export { WorkflowQueue, GRAPH_STAGES } from "./control/workflow.js";
|
|
54
|
-
export { repairDecision } from "./engine/repair.js";
|
|
54
|
+
export { acceptanceGap, repairDecision, } from "./engine/repair.js";
|
|
55
55
|
export { technicalReview } from "./control/review.js";
|
|
56
56
|
export { JointRunner } from "./engine/joint-runner.js";
|
|
57
57
|
export { moveBase, fencedRefMove } from "./control/base-move.js";
|
|
58
58
|
export { junitSummary, prepareReportPath, runCheck, runGate, sandboxBlockedHosts, } from "./verification/gates.js";
|
|
59
|
+
export { runGatesAt, } from "./verification/gates-at.js";
|
|
60
|
+
export { explicitPrerequisites, gatePrerequisites, orderedScope, standaloneScope, } from "./verification/gate-scope.js";
|
|
59
61
|
export { requirementProof, uncoveredGates, unprovenReason } from "./verification/proof.js";
|
|
60
62
|
export { ReviewProbes, probeLimits, observeReproductions } from "./agents/review-probe.js";
|
|
61
63
|
export { composeRedactors, outputRedactor, redactDeep, redactError, redactor, } from "./environment/redaction.js";
|
|
@@ -64,7 +66,7 @@ export { analyzeRecovery, applyCases, recoveryBrief, recoveryOutputSchema, recov
|
|
|
64
66
|
// Классы отказов и правила отказов: хост бросает их из своих хуков и
|
|
65
67
|
// читает класс отказа попытки теми же правилами, что и движок.
|
|
66
68
|
export { BlockedError, classifyFailure, providerRefusal } from "./engine/classify.js";
|
|
67
|
-
export { TaskFailure, failureFingerprint, repairPolicy, } from "./engine/failure.js";
|
|
69
|
+
export { TaskFailure, failureFingerprint, repairPolicy, MAX_NEUTRAL_FAILURES, } from "./engine/failure.js";
|
|
68
70
|
export * from "./projections/index.js";
|
|
69
71
|
export { inspectPath, checkDestination, listRegularFiles, planFileWrites, applyFileWrites, } from "./setup/files.js";
|
|
70
72
|
export { initProject, readProjectManifest, defaultStateDir, projectIdOf, } from "./init/project.js";
|
|
@@ -6,7 +6,7 @@ import type { Actor, FailureKind, NextAction, PauseReason, WorkStatus } from './
|
|
|
6
6
|
* (его ставит classifyFailure в точке отказа), текст здесь не разбирается.
|
|
7
7
|
*/
|
|
8
8
|
export declare function stopCodeOf(kind: FailureKind | undefined): ErrorCode;
|
|
9
|
-
export declare const stopCauses: readonly ["provider-auth", "environment", "timeout", "stale-base", "empty-result", "scope-violation", "gate", "review", "review-protocol", "merge", "cancelled", "budget", "unknown", "runtime-missing", "controller-busy", "operator-pause", "approval-required", "not-ready"];
|
|
9
|
+
export declare const stopCauses: readonly ["provider-auth", "environment", "timeout", "stale-base", "empty-result", "unfinished", "scope-violation", "gate", "review", "review-protocol", "merge", "cancelled", "budget", "needs-lead", "unknown", "runtime-missing", "controller-busy", "operator-pause", "approval-required", "not-ready"];
|
|
10
10
|
export type StopCause = (typeof stopCauses)[number];
|
|
11
11
|
export declare const stopPhases: readonly ["preflight", "claim", "implementation", "gates", "review", "integration", "delivery"];
|
|
12
12
|
export type StopPhase = (typeof stopPhases)[number];
|
package/lib/projections/stop.js
CHANGED
|
@@ -13,6 +13,8 @@ export function stopCodeOf(kind) {
|
|
|
13
13
|
return 'GATE_FAILED';
|
|
14
14
|
case 'review':
|
|
15
15
|
return 'REVIEW_REJECTED';
|
|
16
|
+
case 'needs-lead':
|
|
17
|
+
return 'APPROVAL_REQUIRED';
|
|
16
18
|
case 'review-protocol':
|
|
17
19
|
return 'REVIEW_PROTOCOL_INVALID';
|
|
18
20
|
case 'scope-violation':
|
|
@@ -38,6 +40,7 @@ export const stopCauses = [
|
|
|
38
40
|
'timeout',
|
|
39
41
|
'stale-base',
|
|
40
42
|
'empty-result',
|
|
43
|
+
'unfinished',
|
|
41
44
|
'scope-violation',
|
|
42
45
|
'gate',
|
|
43
46
|
'review',
|
|
@@ -45,6 +48,7 @@ export const stopCauses = [
|
|
|
45
48
|
'merge',
|
|
46
49
|
'cancelled',
|
|
47
50
|
'budget',
|
|
51
|
+
'needs-lead',
|
|
48
52
|
'unknown',
|
|
49
53
|
'runtime-missing',
|
|
50
54
|
'controller-busy',
|
|
@@ -68,6 +72,7 @@ const causeText = {
|
|
|
68
72
|
timeout: 'Попытка исчерпала отведённое время.',
|
|
69
73
|
'stale-base': 'База прогонов отстала от рабочей ветки.',
|
|
70
74
|
'empty-result': 'Исполнитель отчитался о выполнении, не изменив ни одного файла.',
|
|
75
|
+
unfinished: 'Исполнитель не доделал работу и сообщил об этом.',
|
|
71
76
|
'scope-violation': 'Изменения вышли за область поручения или роли.',
|
|
72
77
|
gate: 'Настроенная проверка не прошла.',
|
|
73
78
|
review: 'Независимое ревью отклонило результат.',
|
|
@@ -75,6 +80,7 @@ const causeText = {
|
|
|
75
80
|
merge: 'Слияние с веткой интеграции не сошлось.',
|
|
76
81
|
cancelled: 'Работа отменена человеком.',
|
|
77
82
|
budget: 'Бюджет расходов или времени исчерпан либо не определён.',
|
|
83
|
+
'needs-lead': 'Пустой результат без механического основания считать работу сделанной: решение ведущего.',
|
|
78
84
|
unknown: 'Класс отказа не определён.',
|
|
79
85
|
'runtime-missing': 'Назначенный политикой runtime не найден или не отвечает на --version.',
|
|
80
86
|
'controller-busy': 'Очередь ведёт другой процесс controller.',
|
|
@@ -89,6 +95,8 @@ const failurePhase = {
|
|
|
89
95
|
timeout: 'implementation',
|
|
90
96
|
unknown: 'implementation',
|
|
91
97
|
'empty-result': 'implementation',
|
|
98
|
+
unfinished: 'implementation',
|
|
99
|
+
'needs-lead': 'review',
|
|
92
100
|
gate: 'gates',
|
|
93
101
|
review: 'review',
|
|
94
102
|
'review-protocol': 'review',
|
|
@@ -262,6 +270,14 @@ function workOutcome(input, retry) {
|
|
|
262
270
|
actor: 'user',
|
|
263
271
|
action: 'fix-environment',
|
|
264
272
|
};
|
|
273
|
+
case 'needs-lead':
|
|
274
|
+
return {
|
|
275
|
+
code: stopCodeOf(kind),
|
|
276
|
+
cause: kind,
|
|
277
|
+
phase,
|
|
278
|
+
actor: 'lead',
|
|
279
|
+
action: 'diagnose-failure',
|
|
280
|
+
};
|
|
265
281
|
case 'review-protocol':
|
|
266
282
|
case 'unknown':
|
|
267
283
|
return {
|