@telora/daemon 0.23.16 → 0.23.17
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/build-info.json +2 -2
- package/dist/ci/candidate-ref.d.ts +13 -0
- package/dist/ci/candidate-ref.d.ts.map +1 -1
- package/dist/ci/candidate-ref.js +18 -0
- package/dist/ci/candidate-ref.js.map +1 -1
- package/dist/composition/execution-merge-context.d.ts +7 -1
- package/dist/composition/execution-merge-context.d.ts.map +1 -1
- package/dist/composition/execution-merge-context.js +42 -6
- package/dist/composition/execution-merge-context.js.map +1 -1
- package/dist/composition/execution-recovery-context.d.ts.map +1 -1
- package/dist/composition/execution-recovery-context.js +2 -0
- package/dist/composition/execution-recovery-context.js.map +1 -1
- package/dist/focus/crash-recovery.d.ts +7 -0
- package/dist/focus/crash-recovery.d.ts.map +1 -1
- package/dist/focus/crash-recovery.js +26 -13
- package/dist/focus/crash-recovery.js.map +1 -1
- package/dist/merge/candidate-rework.d.ts +3 -3
- package/dist/merge/candidate-rework.d.ts.map +1 -1
- package/dist/merge/candidate-rework.js +30 -10
- package/dist/merge/candidate-rework.js.map +1 -1
- package/dist/merge/completion-hold-record.d.ts +16 -0
- package/dist/merge/completion-hold-record.d.ts.map +1 -1
- package/dist/merge/completion-hold-record.js +60 -1
- package/dist/merge/completion-hold-record.js.map +1 -1
- package/dist/merge/completion-judge.d.ts +44 -0
- package/dist/merge/completion-judge.d.ts.map +1 -0
- package/dist/merge/completion-judge.js +36 -0
- package/dist/merge/completion-judge.js.map +1 -0
- package/dist/merge/completion-local-judge.d.ts +90 -0
- package/dist/merge/completion-local-judge.d.ts.map +1 -0
- package/dist/merge/completion-local-judge.js +193 -0
- package/dist/merge/completion-local-judge.js.map +1 -0
- package/dist/merge/completion-merge-driver.d.ts +31 -7
- package/dist/merge/completion-merge-driver.d.ts.map +1 -1
- package/dist/merge/completion-merge-driver.js +36 -8
- package/dist/merge/completion-merge-driver.js.map +1 -1
- package/dist/merge/completion-merge-gate.d.ts +10 -2
- package/dist/merge/completion-merge-gate.d.ts.map +1 -1
- package/dist/merge/completion-merge-gate.js +65 -19
- package/dist/merge/completion-merge-gate.js.map +1 -1
- package/dist/merge/merge-back-loop.d.ts +9 -1
- package/dist/merge/merge-back-loop.d.ts.map +1 -1
- package/dist/merge/merge-back-loop.js +28 -1
- package/dist/merge/merge-back-loop.js.map +1 -1
- package/dist/queries/api-client/shared.js +1 -1
- package/dist/testing/fixtures/api-client.js +1 -1
- package/dist/testing/fixtures/completion-judge-stubs.d.ts +15 -0
- package/dist/testing/fixtures/completion-judge-stubs.d.ts.map +1 -0
- package/dist/testing/fixtures/completion-judge-stubs.js +32 -0
- package/dist/testing/fixtures/completion-judge-stubs.js.map +1 -0
- package/dist/testing/fixtures/index.js +1 -1
- package/dist/verify/deterministic-gate/verification-checkout.d.ts.map +1 -1
- package/dist/verify/deterministic-gate/verification-checkout.js +1 -0
- package/dist/verify/deterministic-gate/verification-checkout.js.map +1 -1
- package/dist/verify/deterministic-gate/verification-deterministic.d.ts.map +1 -1
- package/dist/verify/deterministic-gate/verification-deterministic.js +1 -0
- package/dist/verify/deterministic-gate/verification-deterministic.js.map +1 -1
- package/dist/verify/gate-config/verification-manifest.d.ts +21 -1
- package/dist/verify/gate-config/verification-manifest.d.ts.map +1 -1
- package/dist/verify/gate-config/verification-manifest.js +25 -1
- package/dist/verify/gate-config/verification-manifest.js.map +1 -1
- package/dist/verify/run-selection/verification-default-on.d.ts +2 -0
- package/dist/verify/run-selection/verification-default-on.d.ts.map +1 -1
- package/dist/verify/run-selection/verification-default-on.js +1 -1
- package/dist/verify/run-selection/verification-default-on.js.map +1 -1
- package/dist/worktree/focus-worktree-state.d.ts.map +1 -1
- package/dist/worktree/focus-worktree-state.js +1 -0
- package/dist/worktree/focus-worktree-state.js.map +1 -1
- package/package.json +2 -2
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// PINNED-BY-END
|
|
6
6
|
/**
|
|
7
7
|
* A completion merge that cannot proceed returns its focus to coding as re-work:
|
|
8
|
-
* a red (or
|
|
8
|
+
* a red integration candidate (CI's, or the daemon's local completion run's), a final merge of the
|
|
9
9
|
* integration tip that conflicts (aborted, never resolved by the daemon), or a
|
|
10
10
|
* conflict-deciding merge after the approved head. ONE Bug on the delivery that
|
|
11
11
|
* owns the work, and that done delivery re-opened through the single-writer
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { DELIVERY_STATUS } from '@telora/daemon-core';
|
|
30
30
|
import { resolveReconciledWrite } from '../delivery/delivery-reconciler-apply.js';
|
|
31
|
+
import { deliveryHoldsCompletion } from './completion-merge-gate.js';
|
|
31
32
|
import { candidateBranchForFocus } from '../ci/candidate-ref.js';
|
|
32
33
|
import { createLogger } from '../log/daemon-log.js';
|
|
33
34
|
const log = createLogger({ module: 'completion-merge' });
|
|
@@ -38,6 +39,8 @@ export function candidateReworkTitle(sha, cause) {
|
|
|
38
39
|
return `Final merge of integration into ${short} conflicts`;
|
|
39
40
|
if (cause === 'unreviewed_merge')
|
|
40
41
|
return `Merge at ${short} decided a conflict no review judged`;
|
|
42
|
+
if (cause === 'local_red')
|
|
43
|
+
return `Integration candidate ${short} failed the completion suite`;
|
|
41
44
|
return `Integration candidate ${short} failed CI`;
|
|
42
45
|
}
|
|
43
46
|
function listed(items, empty) {
|
|
@@ -60,10 +63,29 @@ function conflictDescription(subject, sha, conflict) {
|
|
|
60
63
|
'the gate and review before the completion merge is tried again.',
|
|
61
64
|
];
|
|
62
65
|
}
|
|
66
|
+
function isConflict(detail) {
|
|
67
|
+
return detail !== null && 'conflictPaths' in detail;
|
|
68
|
+
}
|
|
69
|
+
function isLocalFailure(detail) {
|
|
70
|
+
return detail !== null && 'command' in detail;
|
|
71
|
+
}
|
|
72
|
+
function localFailureDescription(subject, sha, failure) {
|
|
73
|
+
return [
|
|
74
|
+
`The daemon ran the product's completion suite${failure ? ` (\`${failure.command}\`)` : ''} on the focus's integration ` +
|
|
75
|
+
`candidate ${sha} (the focus branch merged with the integration tip), and it FAILED` +
|
|
76
|
+
`${failure?.rationale ? `: ${failure.rationale}` : '.'}`,
|
|
77
|
+
'',
|
|
78
|
+
...(failure?.logPath ? [`Full log on the daemon host: ${failure.logPath}`, ''] : []),
|
|
79
|
+
`Integration does not move until a candidate passes. Reproduce it on ${subject.branchName} (the candidate is its ` +
|
|
80
|
+
'HEAD), fix it, and commit; the next review and completion run judge the fix.',
|
|
81
|
+
];
|
|
82
|
+
}
|
|
63
83
|
/** The Bug's description. Pure. */
|
|
64
|
-
export function candidateReworkDescription(subject, sha, cause,
|
|
84
|
+
export function candidateReworkDescription(subject, sha, cause, detail) {
|
|
65
85
|
if (cause === 'conflict')
|
|
66
|
-
return conflictDescription(subject, sha,
|
|
86
|
+
return conflictDescription(subject, sha, isConflict(detail) ? detail : null).join('\n');
|
|
87
|
+
if (cause === 'local_red')
|
|
88
|
+
return localFailureDescription(subject, sha, isLocalFailure(detail) ? detail : null).join('\n');
|
|
67
89
|
if (cause === 'unreviewed_merge') {
|
|
68
90
|
return [
|
|
69
91
|
`The focus head ${sha} carries a merge after the approved head that decided a conflict (git's re-merge of it is ` +
|
|
@@ -73,11 +95,8 @@ export function candidateReworkDescription(subject, sha, cause, conflict) {
|
|
|
73
95
|
'review before the completion merge is tried again.',
|
|
74
96
|
].join('\n');
|
|
75
97
|
}
|
|
76
|
-
const what = cause === 'red'
|
|
77
|
-
? `CI's full suite FAILED on the focus's integration candidate ${sha} (the focus branch merged with the integration tip).`
|
|
78
|
-
: `CI published no verdict for the focus's integration candidate ${sha} within the wait window.`;
|
|
79
98
|
return [
|
|
80
|
-
|
|
99
|
+
`CI's full suite FAILED on the focus's integration candidate ${sha} (the focus branch merged with the integration tip).`,
|
|
81
100
|
'',
|
|
82
101
|
`Integration does not move until a candidate is green. Read the failing run with \`ci-logs\` (its ref is ` +
|
|
83
102
|
`refs/heads/${candidateBranchForFocus(subject.focusId)}), fix it on ${subject.branchName}, and commit; ` +
|
|
@@ -127,9 +146,10 @@ async function reopenOwner(owner, issues, deps) {
|
|
|
127
146
|
* File the candidate's Bug on its owner and re-open that delivery; say what was
|
|
128
147
|
* applied. Throws on a failed read or write (the driver retries).
|
|
129
148
|
*/
|
|
130
|
-
export async function routeCandidateRework(subject, sha, cause,
|
|
149
|
+
export async function routeCandidateRework(subject, sha, cause, detail, deps) {
|
|
131
150
|
const deliveries = await deps.getFocusDeliveries(subject.focusId);
|
|
132
|
-
|
|
151
|
+
// The gate's own predicate (completion-merge-gate.ts): a delivery that owes no work (planning, cancelled) is not open work.
|
|
152
|
+
if (deliveries.some((d) => deliveryHoldsCompletion(d.executionStatus)))
|
|
133
153
|
return 'already_open';
|
|
134
154
|
const owner = chooseReworkOwner(deliveries, deps.listBranchDeliveryTrailers(subject.repoPath, subject.branchName));
|
|
135
155
|
if (owner === null) {
|
|
@@ -139,7 +159,7 @@ export async function routeCandidateRework(subject, sha, cause, conflict, deps)
|
|
|
139
159
|
let issues = await deps.getDeliveryIssues(owner.id);
|
|
140
160
|
const title = nextReworkTitle(issues, candidateReworkTitle(sha, cause));
|
|
141
161
|
if (title !== null) {
|
|
142
|
-
await deps.createIssue(owner.id, title, candidateReworkDescription(subject, sha, cause,
|
|
162
|
+
await deps.createIssue(owner.id, title, candidateReworkDescription(subject, sha, cause, detail), 'Bug', 'P0');
|
|
143
163
|
issues = await deps.getDeliveryIssues(owner.id);
|
|
144
164
|
}
|
|
145
165
|
if (!(await reopenOwner(owner, issues, deps))) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"candidate-rework.js","sourceRoot":"","sources":["../../src/merge/candidate-rework.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,iEAAiE;AACjE,kEAAkE;AAClE,8DAA8D;AAC9D,gBAAgB;AAEhB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,eAAe,EAAwB,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"candidate-rework.js","sourceRoot":"","sources":["../../src/merge/candidate-rework.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,22 @@ export declare function candidateWaitPath(productId: string, focusId: string, di
|
|
|
15
15
|
* first sighting.
|
|
16
16
|
*/
|
|
17
17
|
export declare function stampCandidateWait(productId: string, focusId: string, sha: string, now: number, dir?: string): number | null;
|
|
18
|
+
/** Whether the focus's candidate `sha` was already re-pushed for a missing CI verdict (merge/completion-merge-driver.ts). */
|
|
19
|
+
export declare function readCandidateRepushed(productId: string, focusId: string, sha: string, dir?: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Record that `sha` was re-pushed at `now`: its wait window restarts from `now`
|
|
22
|
+
* and it is never re-pushed again. Best-effort (an unwritable record is logged;
|
|
23
|
+
* the driver's in-memory clock still bounds the wait).
|
|
24
|
+
*/
|
|
25
|
+
export declare function markCandidateRepushed(productId: string, focusId: string, sha: string, now: number, dir?: string): void;
|
|
26
|
+
/** Whether a completion hold is recorded for the focus: its completed merge is still pending on this host. */
|
|
27
|
+
export declare function hasCompletionHold(productId: string, focusId: string, dir?: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* The focus ids with a recorded completion hold for a product, read from the
|
|
30
|
+
* record directory. A crash-recovery pass reads this to keep the worktree of a
|
|
31
|
+
* focus whose completed merge is still pending (focus/crash-recovery.ts).
|
|
32
|
+
*/
|
|
33
|
+
export declare function listCompletionHoldFocusIds(productId: string, dir?: string): string[];
|
|
18
34
|
/** Forget the focus's first hold and candidate wait (it merged, or it is no longer complete). Best-effort. */
|
|
19
35
|
export declare function clearCompletionHold(productId: string, focusId: string, dir?: string): void;
|
|
20
36
|
//# sourceMappingURL=completion-hold-record.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"completion-hold-record.d.ts","sourceRoot":"","sources":["../../src/merge/completion-hold-record.ts"],"names":[],"mappings":"AA4CA,+FAA+F;AAC/F,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,MAAM,CAEtG;AAgBD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,MAAM,GAAG,IAAI,CAW3H;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,MAAM,CAErG;AAmBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,MAAM,GAAG,IAAI,CAavI;AAED,8GAA8G;AAC9G,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,IAAI,CAOrG"}
|
|
1
|
+
{"version":3,"file":"completion-hold-record.d.ts","sourceRoot":"","sources":["../../src/merge/completion-hold-record.ts"],"names":[],"mappings":"AA4CA,+FAA+F;AAC/F,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,MAAM,CAEtG;AAgBD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,MAAM,GAAG,IAAI,CAW3H;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,MAAM,CAErG;AAmBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,MAAM,GAAG,IAAI,CAavI;AAED,6HAA6H;AAC7H,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,OAAO,CAGvH;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,IAAI,CAMjI;AAED,8GAA8G;AAC9G,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,OAAO,CAOtG;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,MAAM,EAAE,CAoB/F;AAED,8GAA8G;AAC9G,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,IAAI,CAOrG"}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
*
|
|
30
30
|
* @module merge/completion-hold-record
|
|
31
31
|
*/
|
|
32
|
-
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
32
|
+
import { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
33
33
|
import { resolve } from 'node:path';
|
|
34
34
|
import { resolveGlobalStateDir } from '../daemon-state/daemon-process.js';
|
|
35
35
|
import { createLogger } from '../log/daemon-log.js';
|
|
@@ -117,6 +117,65 @@ export function stampCandidateWait(productId, focusId, sha, now, dir = holdDir()
|
|
|
117
117
|
return null;
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
+
/** Whether the focus's candidate `sha` was already re-pushed for a missing CI verdict (merge/completion-merge-driver.ts). */
|
|
121
|
+
export function readCandidateRepushed(productId, focusId, sha, dir = holdDir()) {
|
|
122
|
+
const existing = readCandidateWait(candidateWaitPath(productId, focusId, dir));
|
|
123
|
+
return existing !== null && existing.sha === sha && existing.repushed === true;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Record that `sha` was re-pushed at `now`: its wait window restarts from `now`
|
|
127
|
+
* and it is never re-pushed again. Best-effort (an unwritable record is logged;
|
|
128
|
+
* the driver's in-memory clock still bounds the wait).
|
|
129
|
+
*/
|
|
130
|
+
export function markCandidateRepushed(productId, focusId, sha, now, dir = holdDir()) {
|
|
131
|
+
try {
|
|
132
|
+
writeAtomic(dir, candidateWaitPath(productId, focusId, dir), { productId, focusId, sha, since: now, repushed: true });
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
log.warn(`[completion-merge] candidate-wait record for focus ${focusId} cannot record the re-push: ${err.message}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** Whether a completion hold is recorded for the focus: its completed merge is still pending on this host. */
|
|
139
|
+
export function hasCompletionHold(productId, focusId, dir = holdDir()) {
|
|
140
|
+
try {
|
|
141
|
+
return readFirstHeldAt(completionHoldPath(productId, focusId, dir)) !== 'absent';
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// An unreadable record still says a hold was written: treat it as pending (it only ever KEEPS a branch in view).
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The focus ids with a recorded completion hold for a product, read from the
|
|
150
|
+
* record directory. A crash-recovery pass reads this to keep the worktree of a
|
|
151
|
+
* focus whose completed merge is still pending (focus/crash-recovery.ts).
|
|
152
|
+
*/
|
|
153
|
+
export function listCompletionHoldFocusIds(productId, dir = holdDir()) {
|
|
154
|
+
let names;
|
|
155
|
+
try {
|
|
156
|
+
names = readdirSync(dir);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
const out = [];
|
|
162
|
+
for (const name of names) {
|
|
163
|
+
if (!name.endsWith('.json') || name.endsWith('-candidate.json'))
|
|
164
|
+
continue;
|
|
165
|
+
try {
|
|
166
|
+
const parsed = JSON.parse(readFileSync(resolve(dir, name), 'utf8'));
|
|
167
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
168
|
+
continue;
|
|
169
|
+
const rec = parsed;
|
|
170
|
+
if (rec.productId === productId && typeof rec.focusId === 'string')
|
|
171
|
+
out.push(rec.focusId);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
120
179
|
/** Forget the focus's first hold and candidate wait (it merged, or it is no longer complete). Best-effort. */
|
|
121
180
|
export function clearCompletionHold(productId, focusId, dir = holdDir()) {
|
|
122
181
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"completion-hold-record.js","sourceRoot":"","sources":["../../src/merge/completion-hold-record.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,uEAAuE;AACvE,8DAA8D;AAC9D,gBAAgB;AAEhB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"completion-hold-record.js","sourceRoot":"","sources":["../../src/merge/completion-hold-record.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What judges a completed focus's integration candidate -- the focus head merged
|
|
3
|
+
* with the integration tip -- before integration fast-forwards to it. The
|
|
4
|
+
* PRODUCT declares it (telora.verify.json `completionJudge`), read from the
|
|
5
|
+
* candidate commit itself, so the declaration travels with the code it judges:
|
|
6
|
+
*
|
|
7
|
+
* - `ci` -- the product's remote CI publishes refs/ci-status for the
|
|
8
|
+
* candidate branch (ci/candidate-ref.ts). Only a product that
|
|
9
|
+
* says so is waited on: a CI that publishes nothing would hold
|
|
10
|
+
* every finished focus forever.
|
|
11
|
+
* - `local` -- the daemon runs the product's completion command on exactly
|
|
12
|
+
* the candidate commit (merge/completion-local-judge.ts). The
|
|
13
|
+
* default for any repo that ships a manifest.
|
|
14
|
+
* - `none` -- the repo ships no manifest: no suite is declared, so the merge
|
|
15
|
+
* rests on the focus's review approval alone, and says so.
|
|
16
|
+
* - `unreadable` -- git could not read the manifest at the candidate, or the
|
|
17
|
+
* manifest is malformed: the merge holds (fail closed), naming why.
|
|
18
|
+
*
|
|
19
|
+
* Why: docs/DECISIONS.md, "The completion candidate is judged where the
|
|
20
|
+
* product declares".
|
|
21
|
+
*
|
|
22
|
+
* @module merge/completion-judge
|
|
23
|
+
*/
|
|
24
|
+
import type { JudgedCommitRead } from '../verify/run-selection/verification-default-on.js';
|
|
25
|
+
/** Default bound on a local completion run: a full suite, not a targeted gate. */
|
|
26
|
+
export declare const DEFAULT_COMPLETION_TIMEOUT_MS: number;
|
|
27
|
+
/** The judge a candidate commit declares. */
|
|
28
|
+
export type CompletionJudge = {
|
|
29
|
+
kind: 'ci';
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'local';
|
|
32
|
+
command: string;
|
|
33
|
+
timeoutMs: number;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'none';
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'unreadable';
|
|
38
|
+
reason: string;
|
|
39
|
+
};
|
|
40
|
+
/** The judge the manifest read at the candidate commit declares. Pure. */
|
|
41
|
+
export declare function completionJudgeFrom(read: JudgedCommitRead): CompletionJudge;
|
|
42
|
+
/** One line naming a judge, for the merge log and holds. Pure. */
|
|
43
|
+
export declare function describeCompletionJudge(judge: CompletionJudge): string;
|
|
44
|
+
//# sourceMappingURL=completion-judge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"completion-judge.d.ts","sourceRoot":"","sources":["../../src/merge/completion-judge.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oDAAoD,CAAC;AAE3F,kFAAkF;AAClF,eAAO,MAAM,6BAA6B,QAAiB,CAAC;AAE5D,6CAA6C;AAC7C,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GACd;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3C,0EAA0E;AAC1E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,eAAe,CAW3E;AAED,kEAAkE;AAClE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAWtE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/merge/completion-judge.test.ts
|
|
3
|
+
// PINNED-BY-END
|
|
4
|
+
/** Default bound on a local completion run: a full suite, not a targeted gate. */
|
|
5
|
+
export const DEFAULT_COMPLETION_TIMEOUT_MS = 60 * 60 * 1000;
|
|
6
|
+
/** The judge the manifest read at the candidate commit declares. Pure. */
|
|
7
|
+
export function completionJudgeFrom(read) {
|
|
8
|
+
if ('unreadable' in read)
|
|
9
|
+
return { kind: 'unreadable', reason: read.unreadable };
|
|
10
|
+
if (read.malformed !== undefined)
|
|
11
|
+
return { kind: 'unreadable', reason: read.malformed };
|
|
12
|
+
const manifest = read.manifest;
|
|
13
|
+
if (manifest === undefined)
|
|
14
|
+
return { kind: 'none' };
|
|
15
|
+
if (manifest.completionJudge === 'ci')
|
|
16
|
+
return { kind: 'ci' };
|
|
17
|
+
return {
|
|
18
|
+
kind: 'local',
|
|
19
|
+
command: manifest.completionCommand ?? manifest.command,
|
|
20
|
+
timeoutMs: manifest.completionTimeoutMs ?? manifest.timeoutMs ?? DEFAULT_COMPLETION_TIMEOUT_MS,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** One line naming a judge, for the merge log and holds. Pure. */
|
|
24
|
+
export function describeCompletionJudge(judge) {
|
|
25
|
+
switch (judge.kind) {
|
|
26
|
+
case 'ci':
|
|
27
|
+
return 'CI (refs/ci-status)';
|
|
28
|
+
case 'local':
|
|
29
|
+
return `the daemon running \`${judge.command}\``;
|
|
30
|
+
case 'none':
|
|
31
|
+
return 'no completion suite (the repo ships no telora.verify.json)';
|
|
32
|
+
case 'unreadable':
|
|
33
|
+
return `an unreadable declaration (${judge.reason})`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=completion-judge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"completion-judge.js","sourceRoot":"","sources":["../../src/merge/completion-judge.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,gEAAgE;AAChE,gBAAgB;AA4BhB,kFAAkF;AAClF,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAS5D,0EAA0E;AAC1E,MAAM,UAAU,mBAAmB,CAAC,IAAsB;IACxD,IAAI,YAAY,IAAI,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;IACjF,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;IACxF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC/B,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACpD,IAAI,QAAQ,CAAC,eAAe,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7D,OAAO;QACL,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,QAAQ,CAAC,iBAAiB,IAAI,QAAQ,CAAC,OAAO;QACvD,SAAS,EAAE,QAAQ,CAAC,mBAAmB,IAAI,QAAQ,CAAC,SAAS,IAAI,6BAA6B;KAC/F,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,uBAAuB,CAAC,KAAsB;IAC5D,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,IAAI;YACP,OAAO,qBAAqB,CAAC;QAC/B,KAAK,OAAO;YACV,OAAO,wBAAwB,KAAK,CAAC,OAAO,IAAI,CAAC;QACnD,KAAK,MAAM;YACT,OAAO,4DAA4D,CAAC;QACtE,KAAK,YAAY;YACf,OAAO,8BAA8B,KAAK,CAAC,MAAM,GAAG,CAAC;IACzD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { StrategyResult } from '../verify/deterministic-gate/verification-deterministic.js';
|
|
2
|
+
import type { VerifyCheckout } from '../verify/deterministic-gate/verification-checkout.js';
|
|
3
|
+
import type { WorktreeProvisioningStatus } from '../worktree/worktree-package-provisioning.js';
|
|
4
|
+
/** How long a run that could not START waits before it is tried again. */
|
|
5
|
+
export declare const LOCAL_RUN_START_BACKOFF_MS: number;
|
|
6
|
+
/** A completed run's verdict on one candidate commit. */
|
|
7
|
+
export interface LocalCompletionVerdict {
|
|
8
|
+
productId: string;
|
|
9
|
+
focusId: string;
|
|
10
|
+
sha: string;
|
|
11
|
+
outcome: 'passed' | 'failed';
|
|
12
|
+
command: string;
|
|
13
|
+
rationale: string;
|
|
14
|
+
exitCode: number | null;
|
|
15
|
+
timedOut: boolean;
|
|
16
|
+
logPath: string | null;
|
|
17
|
+
at: number;
|
|
18
|
+
}
|
|
19
|
+
/** What a failed local run leaves for the focus's re-work Bug. */
|
|
20
|
+
export interface LocalSuiteFailure {
|
|
21
|
+
command: string;
|
|
22
|
+
rationale: string;
|
|
23
|
+
logPath: string | null;
|
|
24
|
+
}
|
|
25
|
+
/** The candidate's standing under the local judge. */
|
|
26
|
+
export type LocalCandidateStatus = 'green' | 'red' | 'running' | 'unknown';
|
|
27
|
+
/** Why a start did or did not begin a run. */
|
|
28
|
+
export type LocalRunStart = {
|
|
29
|
+
kind: 'started';
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'running';
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'busy';
|
|
34
|
+
holder: string;
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'not_ready';
|
|
37
|
+
reason: string;
|
|
38
|
+
};
|
|
39
|
+
/** The focus a run judges. */
|
|
40
|
+
export interface LocalJudgeSubject {
|
|
41
|
+
productId: string;
|
|
42
|
+
focusId: string;
|
|
43
|
+
focusName: string;
|
|
44
|
+
worktreePath: string;
|
|
45
|
+
}
|
|
46
|
+
/** The judge's collaborators; bound at the composition root (composition/execution-merge-context.ts). */
|
|
47
|
+
export interface LocalJudgeDeps {
|
|
48
|
+
readProvisioning: (worktreePath: string) => WorktreeProvisioningStatus;
|
|
49
|
+
/** A detached checkout of the focus worktree's HEAD (verification-checkout.ts createVerifyCheckout). */
|
|
50
|
+
createCheckout: (worktreePath: string, checkoutKey: string) => VerifyCheckout | null;
|
|
51
|
+
run: (config: {
|
|
52
|
+
command: string;
|
|
53
|
+
timeoutMs: number;
|
|
54
|
+
}, cwd: string, logContext: {
|
|
55
|
+
deliveryId: string;
|
|
56
|
+
startedAtMs: number;
|
|
57
|
+
}) => Promise<StrategyResult>;
|
|
58
|
+
writeVerdict: (verdict: LocalCompletionVerdict) => void;
|
|
59
|
+
now: () => number;
|
|
60
|
+
}
|
|
61
|
+
/** The record file for one candidate; `dir` defaults to the global state dir. Exported for tests. */
|
|
62
|
+
export declare function localVerdictPath(productId: string, focusId: string, sha: string, dir?: string): string;
|
|
63
|
+
/** Write a verdict atomically (temp + rename). Throws when it cannot. */
|
|
64
|
+
export declare function writeLocalVerdict(verdict: LocalCompletionVerdict, dir?: string): void;
|
|
65
|
+
/** The recorded verdict on a candidate, or null when none is recorded (or it is unreadable). */
|
|
66
|
+
export declare function readLocalVerdict(productId: string, focusId: string, sha: string, dir?: string): LocalCompletionVerdict | null;
|
|
67
|
+
/** Reset the judge's memory -- tests start from a clean slate. */
|
|
68
|
+
export declare function __resetLocalJudgeForTests(): void;
|
|
69
|
+
/** Resolves when the run in flight (if any) has settled -- tests await a run they started. */
|
|
70
|
+
export declare function __settleLocalJudgeForTests(): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* The candidate's standing: its recorded verdict, else running when its run is
|
|
73
|
+
* in flight, else unknown (no run yet -- the gate names the run step).
|
|
74
|
+
*/
|
|
75
|
+
export declare function readLocalCandidateStatus(productId: string, focusId: string, sha: string, readVerdict: (productId: string, focusId: string, sha: string) => LocalCompletionVerdict | null): LocalCandidateStatus;
|
|
76
|
+
/** What a failed verdict leaves for the re-work Bug, or null when the candidate has no failed verdict. */
|
|
77
|
+
export declare function readLocalFailure(productId: string, focusId: string, sha: string, readVerdict: (productId: string, focusId: string, sha: string) => LocalCompletionVerdict | null): LocalSuiteFailure | null;
|
|
78
|
+
/** A run's result as a verdict, or null when the run never ran (it could not start). Pure. */
|
|
79
|
+
export declare function verdictOf(subject: LocalJudgeSubject, sha: string, command: string, result: StrategyResult, at: number): LocalCompletionVerdict | null;
|
|
80
|
+
/**
|
|
81
|
+
* Start the local completion run of `sha` (the focus worktree's HEAD) unless one
|
|
82
|
+
* is in flight, the slot is taken, the worktree is not provisioned, the last
|
|
83
|
+
* start failed within the backoff, or no checkout of `sha` can be made. Never
|
|
84
|
+
* awaits the run.
|
|
85
|
+
*/
|
|
86
|
+
export declare function startLocalCandidateRun(subject: LocalJudgeSubject, sha: string, judge: {
|
|
87
|
+
command: string;
|
|
88
|
+
timeoutMs: number;
|
|
89
|
+
}, deps: LocalJudgeDeps): LocalRunStart;
|
|
90
|
+
//# sourceMappingURL=completion-local-judge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"completion-local-judge.d.ts","sourceRoot":"","sources":["../../src/merge/completion-local-judge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/merge/completion-local-judge.test.ts
|
|
3
|
+
// PINNED-BY-END
|
|
4
|
+
/**
|
|
5
|
+
* The LOCAL completion judge: the daemon runs a product's completion command on
|
|
6
|
+
* exactly a completed focus's integration candidate (the focus head after the
|
|
7
|
+
* integration tip was merged in) and records the verdict, so integration
|
|
8
|
+
* fast-forwards only to a commit whose combined tree passed the product's full
|
|
9
|
+
* suite -- without depending on a remote CI that may publish nothing
|
|
10
|
+
* (merge/completion-judge.ts; docs/DECISIONS.md, "The completion candidate is
|
|
11
|
+
* judged where the product declares").
|
|
12
|
+
*
|
|
13
|
+
* The run happens in a detached checkout of the focus worktree's HEAD, made the
|
|
14
|
+
* way a deterministic verify makes one (verify/deterministic-gate/verification-checkout.ts):
|
|
15
|
+
* the focus worktree must be provisioned from the candidate's own lockfiles (a
|
|
16
|
+
* merge-out that changed them waits for the install), the checkout must hold
|
|
17
|
+
* the candidate sha (a head that moved is not judged), and a verdict rendered
|
|
18
|
+
* while the linked dependencies moved is discarded. The command runs through the
|
|
19
|
+
* same process-group runner, with the declared timeout.
|
|
20
|
+
*
|
|
21
|
+
* A run is long, so it is STARTED by the merge-back loop and never awaited by
|
|
22
|
+
* it: the verdict lands in a durable record (one file per product, focus and
|
|
23
|
+
* sha under the global state dir), which the completion gate reads on a later
|
|
24
|
+
* tick. One run at a time per daemon -- a full suite is heavy and the host also
|
|
25
|
+
* runs teams. What counts as a verdict is a run that exited (any exit code) or
|
|
26
|
+
* was killed by its timeout; a run that could not start is NOT a verdict -- it is
|
|
27
|
+
* logged and retried after a backoff, never charged to the focus.
|
|
28
|
+
*
|
|
29
|
+
* @module merge/completion-local-judge
|
|
30
|
+
*/
|
|
31
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
32
|
+
import { resolve } from 'node:path';
|
|
33
|
+
import { resolveGlobalStateDir } from '../daemon-state/daemon-process.js';
|
|
34
|
+
import { createLogger } from '../log/daemon-log.js';
|
|
35
|
+
const log = createLogger({ module: 'completion-merge' });
|
|
36
|
+
/** How long a run that could not START waits before it is tried again. */
|
|
37
|
+
export const LOCAL_RUN_START_BACKOFF_MS = 10 * 60 * 1000;
|
|
38
|
+
// ── Durable verdict record ───────────────────────────────────────────
|
|
39
|
+
function verdictDir() {
|
|
40
|
+
return resolve(resolveGlobalStateDir(), 'completion-verdicts');
|
|
41
|
+
}
|
|
42
|
+
/** The record file for one candidate; `dir` defaults to the global state dir. Exported for tests. */
|
|
43
|
+
export function localVerdictPath(productId, focusId, sha, dir = verdictDir()) {
|
|
44
|
+
return resolve(dir, `${productId}-${focusId}-${sha}.json`.replace(/[^a-zA-Z0-9._-]/g, '_'));
|
|
45
|
+
}
|
|
46
|
+
/** Write a verdict atomically (temp + rename). Throws when it cannot. */
|
|
47
|
+
export function writeLocalVerdict(verdict, dir = verdictDir()) {
|
|
48
|
+
mkdirSync(dir, { recursive: true });
|
|
49
|
+
const path = localVerdictPath(verdict.productId, verdict.focusId, verdict.sha, dir);
|
|
50
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
51
|
+
writeFileSync(tmp, JSON.stringify(verdict));
|
|
52
|
+
renameSync(tmp, path);
|
|
53
|
+
}
|
|
54
|
+
/** The recorded verdict on a candidate, or null when none is recorded (or it is unreadable). */
|
|
55
|
+
export function readLocalVerdict(productId, focusId, sha, dir = verdictDir()) {
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(readFileSync(localVerdictPath(productId, focusId, sha, dir), 'utf8'));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
64
|
+
return null;
|
|
65
|
+
const v = parsed;
|
|
66
|
+
if (v.sha !== sha || (v.outcome !== 'passed' && v.outcome !== 'failed') || typeof v.command !== 'string')
|
|
67
|
+
return null;
|
|
68
|
+
return {
|
|
69
|
+
productId,
|
|
70
|
+
focusId,
|
|
71
|
+
sha,
|
|
72
|
+
outcome: v.outcome,
|
|
73
|
+
command: v.command,
|
|
74
|
+
rationale: typeof v.rationale === 'string' ? v.rationale : '',
|
|
75
|
+
exitCode: typeof v.exitCode === 'number' ? v.exitCode : null,
|
|
76
|
+
timedOut: v.timedOut === true,
|
|
77
|
+
logPath: typeof v.logPath === 'string' ? v.logPath : null,
|
|
78
|
+
at: typeof v.at === 'number' ? v.at : 0,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// ── In-flight runs ───────────────────────────────────────────────────
|
|
82
|
+
/** The one run in flight (a full suite at a time per daemon). */
|
|
83
|
+
let slot = null;
|
|
84
|
+
/** When a run of (focus, sha) last failed to START, for the backoff. */
|
|
85
|
+
const startFailures = new Map();
|
|
86
|
+
function key(productId, focusId) {
|
|
87
|
+
return `${productId}:${focusId}`;
|
|
88
|
+
}
|
|
89
|
+
/** Reset the judge's memory -- tests start from a clean slate. */
|
|
90
|
+
export function __resetLocalJudgeForTests() {
|
|
91
|
+
slot = null;
|
|
92
|
+
startFailures.clear();
|
|
93
|
+
}
|
|
94
|
+
/** Resolves when the run in flight (if any) has settled -- tests await a run they started. */
|
|
95
|
+
export function __settleLocalJudgeForTests() {
|
|
96
|
+
return slot?.done ?? Promise.resolve();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The candidate's standing: its recorded verdict, else running when its run is
|
|
100
|
+
* in flight, else unknown (no run yet -- the gate names the run step).
|
|
101
|
+
*/
|
|
102
|
+
export function readLocalCandidateStatus(productId, focusId, sha, readVerdict) {
|
|
103
|
+
const verdict = readVerdict(productId, focusId, sha);
|
|
104
|
+
if (verdict !== null)
|
|
105
|
+
return verdict.outcome === 'passed' ? 'green' : 'red';
|
|
106
|
+
if (slot !== null && slot.key === key(productId, focusId) && slot.sha === sha)
|
|
107
|
+
return 'running';
|
|
108
|
+
return 'unknown';
|
|
109
|
+
}
|
|
110
|
+
/** What a failed verdict leaves for the re-work Bug, or null when the candidate has no failed verdict. */
|
|
111
|
+
export function readLocalFailure(productId, focusId, sha, readVerdict) {
|
|
112
|
+
const verdict = readVerdict(productId, focusId, sha);
|
|
113
|
+
if (verdict === null || verdict.outcome !== 'failed')
|
|
114
|
+
return null;
|
|
115
|
+
return { command: verdict.command, rationale: verdict.rationale, logPath: verdict.logPath };
|
|
116
|
+
}
|
|
117
|
+
/** A run's result as a verdict, or null when the run never ran (it could not start). Pure. */
|
|
118
|
+
export function verdictOf(subject, sha, command, result, at) {
|
|
119
|
+
const timedOut = result.timedOut === true;
|
|
120
|
+
const exited = typeof result.exitCode === 'number';
|
|
121
|
+
if (!timedOut && !exited)
|
|
122
|
+
return null;
|
|
123
|
+
return {
|
|
124
|
+
productId: subject.productId,
|
|
125
|
+
focusId: subject.focusId,
|
|
126
|
+
sha,
|
|
127
|
+
outcome: !timedOut && result.outcome === 'passed' ? 'passed' : 'failed',
|
|
128
|
+
command,
|
|
129
|
+
rationale: result.rationale ?? (result.outcome === 'passed' ? 'passed' : 'failed'),
|
|
130
|
+
exitCode: exited ? result.exitCode : null,
|
|
131
|
+
timedOut,
|
|
132
|
+
logPath: result.logPath ?? null,
|
|
133
|
+
at,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
async function runToVerdict(subject, sha, judge, checkout, deps) {
|
|
137
|
+
const k = `${key(subject.productId, subject.focusId)}:${sha}`;
|
|
138
|
+
try {
|
|
139
|
+
const result = await deps.run(judge, checkout.path, { deliveryId: `completion-${subject.focusId}`, startedAtMs: deps.now() });
|
|
140
|
+
if (checkout.dependenciesMoved()) {
|
|
141
|
+
log.warn(`[completion-merge] "${subject.focusName}": dependencies moved during the local run of ${sha.slice(0, 9)}; verdict discarded, re-run next tick`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const verdict = verdictOf(subject, sha, judge.command, result, deps.now());
|
|
145
|
+
if (verdict === null) {
|
|
146
|
+
startFailures.set(k, deps.now());
|
|
147
|
+
log.warn(`[completion-merge] "${subject.focusName}": the local completion run of ${sha.slice(0, 9)} did not run (${result.rationale ?? 'no exit'}); retried after a backoff`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
deps.writeVerdict(verdict);
|
|
151
|
+
log.info(`[completion-merge] "${subject.focusName}": local completion run of ${sha.slice(0, 9)} ${verdict.outcome.toUpperCase()} (\`${judge.command}\`: ${verdict.rationale})${verdict.logPath ? ` -- log ${verdict.logPath}` : ''}`);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
startFailures.set(k, deps.now());
|
|
155
|
+
log.warn(`[completion-merge] "${subject.focusName}": the local completion run of ${sha.slice(0, 9)} errored: ${err.message}; retried after a backoff`);
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
checkout.dispose();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Start the local completion run of `sha` (the focus worktree's HEAD) unless one
|
|
163
|
+
* is in flight, the slot is taken, the worktree is not provisioned, the last
|
|
164
|
+
* start failed within the backoff, or no checkout of `sha` can be made. Never
|
|
165
|
+
* awaits the run.
|
|
166
|
+
*/
|
|
167
|
+
export function startLocalCandidateRun(subject, sha, judge, deps) {
|
|
168
|
+
const k = key(subject.productId, subject.focusId);
|
|
169
|
+
if (slot !== null) {
|
|
170
|
+
return slot.key === k && slot.sha === sha ? { kind: 'running' } : { kind: 'busy', holder: `"${slot.focusName}" at ${slot.sha.slice(0, 9)}` };
|
|
171
|
+
}
|
|
172
|
+
const failedAt = startFailures.get(`${k}:${sha}`);
|
|
173
|
+
if (failedAt !== undefined && deps.now() - failedAt < LOCAL_RUN_START_BACKOFF_MS) {
|
|
174
|
+
return { kind: 'not_ready', reason: `the last run of ${sha.slice(0, 9)} could not start; retried after ${Math.round(LOCAL_RUN_START_BACKOFF_MS / 60_000)}m` };
|
|
175
|
+
}
|
|
176
|
+
const provisioning = deps.readProvisioning(subject.worktreePath);
|
|
177
|
+
if (!provisioning.ready)
|
|
178
|
+
return { kind: 'not_ready', reason: `the focus worktree is not provisioned (${provisioning.state}: ${provisioning.reason})` };
|
|
179
|
+
const checkout = deps.createCheckout(subject.worktreePath, `completion-${subject.focusId}`);
|
|
180
|
+
if (checkout === null)
|
|
181
|
+
return { kind: 'not_ready', reason: 'no detached checkout of the focus head could be made' };
|
|
182
|
+
if (checkout.sha !== sha) {
|
|
183
|
+
checkout.dispose();
|
|
184
|
+
return { kind: 'not_ready', reason: `the focus head is ${checkout.sha.slice(0, 9)}, not the candidate ${sha.slice(0, 9)}` };
|
|
185
|
+
}
|
|
186
|
+
const done = runToVerdict(subject, sha, judge, checkout, deps).finally(() => {
|
|
187
|
+
if (slot?.key === k && slot.sha === sha)
|
|
188
|
+
slot = null;
|
|
189
|
+
});
|
|
190
|
+
slot = { key: k, sha, focusName: subject.focusName, done };
|
|
191
|
+
return { kind: 'started' };
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=completion-local-judge.js.map
|