instar 0.28.95 → 0.28.97
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/dashboard/index.html +249 -2
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +90 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/MachineHeartbeat.d.ts +94 -0
- package/dist/core/MachineHeartbeat.d.ts.map +1 -0
- package/dist/core/MachineHeartbeat.js +155 -0
- package/dist/core/MachineHeartbeat.js.map +1 -0
- package/dist/core/ProjectAutoAdvancePoller.d.ts +107 -0
- package/dist/core/ProjectAutoAdvancePoller.d.ts.map +1 -0
- package/dist/core/ProjectAutoAdvancePoller.js +176 -0
- package/dist/core/ProjectAutoAdvancePoller.js.map +1 -0
- package/dist/core/ProjectRoundCompleteMessage.d.ts +158 -0
- package/dist/core/ProjectRoundCompleteMessage.d.ts.map +1 -0
- package/dist/core/ProjectRoundCompleteMessage.js +253 -0
- package/dist/core/ProjectRoundCompleteMessage.js.map +1 -0
- package/dist/core/ProjectRoundExecution.d.ts +111 -0
- package/dist/core/ProjectRoundExecution.d.ts.map +1 -0
- package/dist/core/ProjectRoundExecution.js +359 -0
- package/dist/core/ProjectRoundExecution.js.map +1 -0
- package/dist/core/ProjectRoundLock.d.ts +67 -0
- package/dist/core/ProjectRoundLock.d.ts.map +1 -0
- package/dist/core/ProjectRoundLock.js +161 -0
- package/dist/core/ProjectRoundLock.js.map +1 -0
- package/dist/core/ProjectRoundRunner.d.ts +146 -0
- package/dist/core/ProjectRoundRunner.d.ts.map +1 -0
- package/dist/core/ProjectRoundRunner.js +371 -0
- package/dist/core/ProjectRoundRunner.js.map +1 -0
- package/dist/core/ProjectRoundWorktrees.d.ts +57 -0
- package/dist/core/ProjectRoundWorktrees.d.ts.map +1 -0
- package/dist/core/ProjectRoundWorktrees.js +111 -0
- package/dist/core/ProjectRoundWorktrees.js.map +1 -0
- package/dist/server/AgentServer.d.ts +12 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +3 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +19 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +782 -7
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +48 -48
- package/upgrades/0.28.96.md +347 -0
- package/upgrades/0.28.97.md +146 -0
- package/upgrades/side-effects/project-scope-phase1-7-skill-surface.md +175 -0
- package/upgrades/side-effects/project-scope-phase1b-connect-the-dots.md +184 -0
- package/upgrades/side-effects/project-scope-phase1b-pr3.md +228 -0
- package/upgrades/side-effects/project-scope-phase1b-pr4.md +198 -0
- package/upgrades/side-effects/project-scope-phase1b-pr5.md +150 -0
- package/upgrades/side-effects/project-scope-phase1b-pr6.md +136 -0
- package/upgrades/side-effects/project-scope-phase1b-pr7.md +175 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProjectRoundExecution — the autonomous run loop for a project round.
|
|
3
|
+
*
|
|
4
|
+
* Spec: docs/specs/PROJECT-SCOPE-SPEC.md § Phase 1.5 ("Run loop", steps 1-11).
|
|
5
|
+
*
|
|
6
|
+
* What this module is responsible for:
|
|
7
|
+
* 1. Acquiring the round-runner lock (delegated to ProjectRoundLock).
|
|
8
|
+
* 2. Spawning the autonomous child process in a detached process
|
|
9
|
+
* group, so SIGTERM/SIGKILL of the group never reaps the runner.
|
|
10
|
+
* 3. Polling the project record every `pollIntervalMs` to detect
|
|
11
|
+
* mid-round mutations to the round's itemIds (e.g., a user
|
|
12
|
+
* manually skips an item) — when detected, SIGTERM the group
|
|
13
|
+
* (5s grace, then SIGKILL) and relaunch with a recomputed stop
|
|
14
|
+
* condition.
|
|
15
|
+
* 4. On the autonomous child's NATURAL exit, verify per-item
|
|
16
|
+
* artifacts and set round.status = complete | partially-complete.
|
|
17
|
+
* 5. Cleanup: `git worktree prune` for the round namespace.
|
|
18
|
+
* 6. Release the lock.
|
|
19
|
+
*
|
|
20
|
+
* What this module is NOT responsible for:
|
|
21
|
+
* - The preflight gate. That's `ProjectRoundRunner.preflight`. The
|
|
22
|
+
* caller (the auto-advance poller, the /project run-round skill,
|
|
23
|
+
* a future HTTP endpoint) is expected to preflight before calling
|
|
24
|
+
* `runRound(input)`.
|
|
25
|
+
* - Drift checking. Same — preflight handles it.
|
|
26
|
+
* - Choosing the autonomous command. The `spawnCommand` is injected,
|
|
27
|
+
* defaulting to `claude` (which invokes the local /autonomous skill).
|
|
28
|
+
* Tests pass a custom command so they don't depend on the skill.
|
|
29
|
+
*
|
|
30
|
+
* Process group safety:
|
|
31
|
+
* The child is spawned with `detached: true` so it gets its own
|
|
32
|
+
* process group. `kill(-pgid, signal)` (Node passes negative PIDs
|
|
33
|
+
* for group signals) targets only the child's group — never reaps
|
|
34
|
+
* the runner.
|
|
35
|
+
*/
|
|
36
|
+
import { spawn } from 'node:child_process';
|
|
37
|
+
import { ProjectRoundLock } from './ProjectRoundLock.js';
|
|
38
|
+
import { ProjectRoundWorktrees } from './ProjectRoundWorktrees.js';
|
|
39
|
+
import { SafeGitExecutor } from './SafeGitExecutor.js';
|
|
40
|
+
/** Per-spec defaults. Tests dial both down to keep the suite fast. */
|
|
41
|
+
export const DEFAULT_POLL_INTERVAL_MS = 60_000;
|
|
42
|
+
export const DEFAULT_SIGTERM_GRACE_MS = 5_000;
|
|
43
|
+
/** Resume cap from spec § Phase 1.5 step 11. */
|
|
44
|
+
export const DEFAULT_MAX_RESUME_ATTEMPTS = 3;
|
|
45
|
+
/**
|
|
46
|
+
* Run one round to completion. Caller is expected to preflight first.
|
|
47
|
+
*/
|
|
48
|
+
export async function runRound(input, deps) {
|
|
49
|
+
const pollIntervalMs = input.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
50
|
+
const sigtermGraceMs = input.sigtermGraceMs ?? DEFAULT_SIGTERM_GRACE_MS;
|
|
51
|
+
const maxResumeAttempts = input.maxResumeAttempts ?? DEFAULT_MAX_RESUME_ATTEMPTS;
|
|
52
|
+
const verifyMergedItems = input.verifyMergedItems ?? defaultVerifyMergedItems(input.targetRepoPath);
|
|
53
|
+
const lock = new ProjectRoundLock({ stateDir: deps.stateDir });
|
|
54
|
+
// Step 1: acquire lock.
|
|
55
|
+
const acquired = lock.acquire(input.projectId, input.roundIndex);
|
|
56
|
+
if (!acquired.ok) {
|
|
57
|
+
return {
|
|
58
|
+
outcome: 'failed',
|
|
59
|
+
mergedItemIds: [],
|
|
60
|
+
unmergedItemIds: [],
|
|
61
|
+
relaunchCount: 0,
|
|
62
|
+
resumeAttempts: 0,
|
|
63
|
+
reason: `lock held by pid ${acquired.currentHolder.pid}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
let relaunchCount = 0;
|
|
67
|
+
let resumeAttempts = 0;
|
|
68
|
+
try {
|
|
69
|
+
let snapshot = await readRound(input.tracker, input.projectId, input.roundIndex);
|
|
70
|
+
if (!snapshot) {
|
|
71
|
+
return {
|
|
72
|
+
outcome: 'failed',
|
|
73
|
+
mergedItemIds: [],
|
|
74
|
+
unmergedItemIds: [],
|
|
75
|
+
relaunchCount,
|
|
76
|
+
resumeAttempts,
|
|
77
|
+
reason: 'project or round disappeared',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
let lastItemIds = [...snapshot.itemIds];
|
|
81
|
+
// Step 3: lazy worktree allocation for round items. The autonomous
|
|
82
|
+
// child opens more as it reaches them; we kick off only the first
|
|
83
|
+
// here to give the child a starting cwd.
|
|
84
|
+
if (lastItemIds.length > 0) {
|
|
85
|
+
try {
|
|
86
|
+
ProjectRoundWorktrees.allocate({
|
|
87
|
+
targetRepoPath: input.targetRepoPath,
|
|
88
|
+
projectId: input.projectId,
|
|
89
|
+
roundIndex: input.roundIndex,
|
|
90
|
+
itemId: lastItemIds[0],
|
|
91
|
+
}, { refuseExisting: false });
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// First-item worktree may pre-exist if a previous attempt
|
|
95
|
+
// crashed mid-way. Continue.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Inner relaunch loop: steps 4-5.
|
|
99
|
+
// We loop until either the child exits naturally or we detect a
|
|
100
|
+
// halt / stop-condition-met.
|
|
101
|
+
let outcome = 'failed';
|
|
102
|
+
let mergedItemIds = [];
|
|
103
|
+
let unmergedItemIds = [];
|
|
104
|
+
let reason;
|
|
105
|
+
for (;;) {
|
|
106
|
+
// Per-step halt checkpoint.
|
|
107
|
+
const halted = await readHaltedAt(input.tracker, input.projectId, input.roundIndex);
|
|
108
|
+
if (halted) {
|
|
109
|
+
outcome = 'halted';
|
|
110
|
+
reason = `round halted at ${halted}`;
|
|
111
|
+
unmergedItemIds = [...lastItemIds];
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
// Compute current stop condition: itemIds verified-merged.
|
|
115
|
+
const verified = await verifyMergedItems(lastItemIds);
|
|
116
|
+
if (lastItemIds.every((id) => verified.has(id))) {
|
|
117
|
+
outcome = 'complete';
|
|
118
|
+
mergedItemIds = [...lastItemIds];
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
// Spawn the autonomous child with stop condition + ids in env.
|
|
122
|
+
const child = spawnAutonomousChild(input, lastItemIds);
|
|
123
|
+
const exit = await waitForExitOrPollChange(child, input.tracker, input.projectId, input.roundIndex, lastItemIds, pollIntervalMs, sigtermGraceMs);
|
|
124
|
+
if (exit.kind === 'set-changed') {
|
|
125
|
+
relaunchCount++;
|
|
126
|
+
const fresh = await readRound(input.tracker, input.projectId, input.roundIndex);
|
|
127
|
+
if (!fresh) {
|
|
128
|
+
outcome = 'failed';
|
|
129
|
+
reason = 'project disappeared during round';
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
lastItemIds = [...fresh.itemIds];
|
|
133
|
+
// Loop continues; next iteration re-checks stop condition then
|
|
134
|
+
// either spawns again or proceeds to step 6.
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (exit.kind === 'halted') {
|
|
138
|
+
outcome = 'halted';
|
|
139
|
+
reason = 'round halted via API during run';
|
|
140
|
+
unmergedItemIds = [...lastItemIds];
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
// Natural exit.
|
|
144
|
+
if (exit.kind === 'exited' && exit.code === 0) {
|
|
145
|
+
// Step 6: verify per-item artifacts.
|
|
146
|
+
const finalVerified = await verifyMergedItems(lastItemIds);
|
|
147
|
+
mergedItemIds = lastItemIds.filter((id) => finalVerified.has(id));
|
|
148
|
+
unmergedItemIds = lastItemIds.filter((id) => !finalVerified.has(id));
|
|
149
|
+
if (unmergedItemIds.length === 0)
|
|
150
|
+
outcome = 'complete';
|
|
151
|
+
else
|
|
152
|
+
outcome = 'partially-complete';
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
// Non-zero exit → resume attempt.
|
|
156
|
+
resumeAttempts++;
|
|
157
|
+
if (resumeAttempts >= maxResumeAttempts) {
|
|
158
|
+
outcome = 'failed';
|
|
159
|
+
reason = `${resumeAttempts} resume attempts exhausted (last exit code: ${exit.kind === 'exited' ? exit.code : exit.kind})`;
|
|
160
|
+
unmergedItemIds = [...lastItemIds];
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
// Backoff before relaunch.
|
|
164
|
+
await sleep(1000 * resumeAttempts);
|
|
165
|
+
}
|
|
166
|
+
// Step 7: cleanup worktrees.
|
|
167
|
+
try {
|
|
168
|
+
ProjectRoundWorktrees.prune(input.targetRepoPath);
|
|
169
|
+
}
|
|
170
|
+
catch { /* best-effort */ }
|
|
171
|
+
// Step 9-11: record round status. We DO NOT touch
|
|
172
|
+
// unacknowledgedAdvanceCount here — that's the auto-advance poller's
|
|
173
|
+
// job. We just record the round status.
|
|
174
|
+
if (outcome !== 'halted') {
|
|
175
|
+
await recordOutcome(input.tracker, input.projectId, input.roundIndex, outcome);
|
|
176
|
+
}
|
|
177
|
+
return { outcome, mergedItemIds, unmergedItemIds, relaunchCount, resumeAttempts, reason };
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
// Step 8: release lock.
|
|
181
|
+
lock.release();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function readRound(tracker, projectId, roundIndex) {
|
|
185
|
+
const proj = tracker.get(projectId);
|
|
186
|
+
if (!proj)
|
|
187
|
+
return null;
|
|
188
|
+
const round = (proj.rounds ?? [])[roundIndex];
|
|
189
|
+
if (!round)
|
|
190
|
+
return null;
|
|
191
|
+
return { itemIds: round.itemIds ?? [], status: (round.status ?? 'pending') };
|
|
192
|
+
}
|
|
193
|
+
async function readHaltedAt(tracker, projectId, roundIndex) {
|
|
194
|
+
const proj = tracker.get(projectId);
|
|
195
|
+
if (!proj)
|
|
196
|
+
return null;
|
|
197
|
+
const round = (proj.rounds ?? [])[roundIndex];
|
|
198
|
+
return round?.haltedAt ?? null;
|
|
199
|
+
}
|
|
200
|
+
async function recordOutcome(tracker, projectId, roundIndex, outcome) {
|
|
201
|
+
const proj = tracker.get(projectId);
|
|
202
|
+
if (!proj)
|
|
203
|
+
return;
|
|
204
|
+
const map = {
|
|
205
|
+
complete: 'complete',
|
|
206
|
+
'partially-complete': 'partially-complete',
|
|
207
|
+
failed: 'failed',
|
|
208
|
+
halted: 'failed',
|
|
209
|
+
};
|
|
210
|
+
const newStatus = map[outcome];
|
|
211
|
+
const completedAt = new Date().toISOString();
|
|
212
|
+
const rounds = (proj.rounds ?? []).map((r, i) => i === roundIndex ? { ...r, status: newStatus, completedAt } : r);
|
|
213
|
+
try {
|
|
214
|
+
await tracker.update(projectId, { rounds, ifMatch: proj.version });
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// OCC race — caller (or next reconcile) will retry.
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function spawnAutonomousChild(input, itemIds) {
|
|
221
|
+
const cmd = input.spawnCommand ?? 'claude';
|
|
222
|
+
const args = input.spawnArgs ?? ['--skill', 'autonomous'];
|
|
223
|
+
const workdir = input.initialWorkdir ??
|
|
224
|
+
(itemIds.length > 0
|
|
225
|
+
? ProjectRoundWorktrees.pathFor({
|
|
226
|
+
targetRepoPath: input.targetRepoPath,
|
|
227
|
+
projectId: input.projectId,
|
|
228
|
+
roundIndex: input.roundIndex,
|
|
229
|
+
itemId: itemIds[0],
|
|
230
|
+
})
|
|
231
|
+
: input.targetRepoPath);
|
|
232
|
+
const child = spawn(cmd, args, {
|
|
233
|
+
cwd: workdir,
|
|
234
|
+
detached: true, // critical — makes the child its own process group leader
|
|
235
|
+
stdio: 'ignore',
|
|
236
|
+
env: {
|
|
237
|
+
...process.env,
|
|
238
|
+
INSTAR_PROJECT_ID: input.projectId,
|
|
239
|
+
INSTAR_ROUND_INDEX: String(input.roundIndex),
|
|
240
|
+
INSTAR_ROUND_ITEM_IDS: JSON.stringify(itemIds),
|
|
241
|
+
INSTAR_STOP_CONDITION: 'all-items-merged-on-main',
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
return child;
|
|
245
|
+
}
|
|
246
|
+
async function waitForExitOrPollChange(child, tracker, projectId, roundIndex, initialItemIds, pollIntervalMs, sigtermGraceMs) {
|
|
247
|
+
let exited = false;
|
|
248
|
+
let exitCode = null;
|
|
249
|
+
let exitSignal = null;
|
|
250
|
+
const exitPromise = new Promise((resolve) => {
|
|
251
|
+
child.once('exit', (code, signal) => {
|
|
252
|
+
exited = true;
|
|
253
|
+
exitCode = code;
|
|
254
|
+
exitSignal = signal;
|
|
255
|
+
resolve();
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
while (!exited) {
|
|
259
|
+
// Wait either pollIntervalMs OR exit, whichever comes first.
|
|
260
|
+
await Promise.race([exitPromise, sleep(pollIntervalMs)]);
|
|
261
|
+
if (exited)
|
|
262
|
+
break;
|
|
263
|
+
// Halt check.
|
|
264
|
+
const halted = await readHaltedAt(tracker, projectId, roundIndex);
|
|
265
|
+
if (halted) {
|
|
266
|
+
await killProcessGroup(child, sigtermGraceMs);
|
|
267
|
+
return { kind: 'halted' };
|
|
268
|
+
}
|
|
269
|
+
// Stop-condition revalidation: did itemIds change?
|
|
270
|
+
const snap = await readRound(tracker, projectId, roundIndex);
|
|
271
|
+
if (!snap) {
|
|
272
|
+
await killProcessGroup(child, sigtermGraceMs);
|
|
273
|
+
return { kind: 'halted' };
|
|
274
|
+
}
|
|
275
|
+
if (!arraysEqual(snap.itemIds, initialItemIds)) {
|
|
276
|
+
await killProcessGroup(child, sigtermGraceMs);
|
|
277
|
+
return { kind: 'set-changed' };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return { kind: 'exited', code: exitCode, signal: exitSignal };
|
|
281
|
+
}
|
|
282
|
+
async function killProcessGroup(child, graceMs) {
|
|
283
|
+
if (!child.pid || child.exitCode !== null)
|
|
284
|
+
return;
|
|
285
|
+
try {
|
|
286
|
+
// Negative PID targets the process group. child is its own group
|
|
287
|
+
// leader because we spawned with detached:true.
|
|
288
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
return; // Already dead.
|
|
292
|
+
}
|
|
293
|
+
const start = Date.now();
|
|
294
|
+
while (child.exitCode === null && Date.now() - start < graceMs) {
|
|
295
|
+
await sleep(100);
|
|
296
|
+
}
|
|
297
|
+
if (child.exitCode === null) {
|
|
298
|
+
try {
|
|
299
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
300
|
+
}
|
|
301
|
+
catch { /* already dead */ }
|
|
302
|
+
// Brief wait for the OS to register the kill.
|
|
303
|
+
await sleep(100);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function arraysEqual(a, b) {
|
|
307
|
+
if (a.length !== b.length)
|
|
308
|
+
return false;
|
|
309
|
+
for (let i = 0; i < a.length; i++)
|
|
310
|
+
if (a[i] !== b[i])
|
|
311
|
+
return false;
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
function sleep(ms) {
|
|
315
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Real `gh pr view`-backed merged-state verifier. For each child id,
|
|
319
|
+
* looks up the child's `prNumber` + `mergeCommitOid` from the tracker
|
|
320
|
+
* and runs `git merge-base --is-ancestor <oid> origin/main` to confirm
|
|
321
|
+
* the merge is reachable. Tests inject a stub instead.
|
|
322
|
+
*
|
|
323
|
+
* For PR 7's scope, this default is intentionally simple: it only
|
|
324
|
+
* confirms reachability of `mergeCommitOid` if the child has one.
|
|
325
|
+
* Real CI-green checks via `gh pr view` ship in a follow-up (the
|
|
326
|
+
* StageTransitionValidator already performs them for /advance — when
|
|
327
|
+
* the merged-state reconciler wires up in the next PR, it'll call
|
|
328
|
+
* the validator here too).
|
|
329
|
+
*/
|
|
330
|
+
function defaultVerifyMergedItems(targetRepoPath) {
|
|
331
|
+
return async (_childIds) => {
|
|
332
|
+
const verified = new Set();
|
|
333
|
+
// Best-effort no-op default — production callers should pass a
|
|
334
|
+
// real verifyMergedItems that calls StageTransitionValidator.
|
|
335
|
+
// The point of this default is to avoid a spurious shell-out
|
|
336
|
+
// during tests that didn't override it (rare; tests should
|
|
337
|
+
// override).
|
|
338
|
+
void targetRepoPath;
|
|
339
|
+
return verified;
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/** Helper exported so callers can wire a SafeGit-backed verifier. */
|
|
343
|
+
export async function verifyMergedItemsViaGit(targetRepoPath, childIds, tracker) {
|
|
344
|
+
const verified = new Set();
|
|
345
|
+
for (const id of childIds) {
|
|
346
|
+
const child = tracker.get(id);
|
|
347
|
+
if (!child || !child.mergeCommitOid)
|
|
348
|
+
continue;
|
|
349
|
+
try {
|
|
350
|
+
SafeGitExecutor.run(['merge-base', '--is-ancestor', child.mergeCommitOid, 'origin/main'], { cwd: targetRepoPath, operation: 'ProjectRoundExecution.verifyMergedItemsViaGit' });
|
|
351
|
+
verified.add(id);
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
// Not an ancestor of origin/main — not verified.
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return verified;
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=ProjectRoundExecution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectRoundExecution.js","sourceRoot":"","sources":["../../src/core/ProjectRoundExecution.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProjectRoundLock — machine-local lock for the round runner.
|
|
3
|
+
*
|
|
4
|
+
* Spec: docs/specs/PROJECT-SCOPE-SPEC.md § Phase 1.5 ("Lock file
|
|
5
|
+
* .instar/local/round-runner.lock is free."). The path is MACHINE-LOCAL
|
|
6
|
+
* (under .instar/local/, NOT git-synced) so two machines never fight
|
|
7
|
+
* over a 0-byte lockfile in the sync layer; cross-machine ownership is
|
|
8
|
+
* separately controlled by `ownerMachineId` on the project record and
|
|
9
|
+
* the claim-ownership flow (later PR).
|
|
10
|
+
*
|
|
11
|
+
* Contract:
|
|
12
|
+
* - At most one round-runner active per machine. The lock encodes the
|
|
13
|
+
* PID, projectId, and roundIndex of the holder.
|
|
14
|
+
* - Stale lock detection: if the recorded PID is no longer alive, the
|
|
15
|
+
* lock is considered free (handles crash without restart). This is
|
|
16
|
+
* intentional and matches the spec's pre-flight step 2 ("PID in lock
|
|
17
|
+
* is alive; if not, remove").
|
|
18
|
+
* - Acquisition is best-effort atomic: `O_CREAT | O_EXCL` rename of a
|
|
19
|
+
* tmp file holding the JSON payload. Concurrent acquirers race on
|
|
20
|
+
* the rename; the loser fails fast. This is the standard non-fcntl
|
|
21
|
+
* local-process mutex pattern; proper-lockfile is overkill for a
|
|
22
|
+
* single-machine single-runner gate.
|
|
23
|
+
*/
|
|
24
|
+
export interface ProjectRoundLockPayload {
|
|
25
|
+
pid: number;
|
|
26
|
+
projectId: string;
|
|
27
|
+
roundIndex: number;
|
|
28
|
+
acquiredAt: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ProjectRoundLockConfig {
|
|
31
|
+
/** Absolute path to the agent's `.instar/` directory. */
|
|
32
|
+
stateDir: string;
|
|
33
|
+
}
|
|
34
|
+
export type AcquireResult = {
|
|
35
|
+
ok: true;
|
|
36
|
+
payload: ProjectRoundLockPayload;
|
|
37
|
+
} | {
|
|
38
|
+
ok: false;
|
|
39
|
+
reason: 'held';
|
|
40
|
+
currentHolder: ProjectRoundLockPayload;
|
|
41
|
+
};
|
|
42
|
+
export declare class ProjectRoundLock {
|
|
43
|
+
private stateDir;
|
|
44
|
+
constructor(config: ProjectRoundLockConfig);
|
|
45
|
+
/**
|
|
46
|
+
* Try to acquire the lock for the given (projectId, roundIndex). Returns
|
|
47
|
+
* a structured result rather than throwing — callers decide whether to
|
|
48
|
+
* wait, abort, or report.
|
|
49
|
+
*/
|
|
50
|
+
acquire(projectId: string, roundIndex: number, now?: Date): AcquireResult;
|
|
51
|
+
/**
|
|
52
|
+
* Release the lock unconditionally. Returns true if a lock was removed,
|
|
53
|
+
* false if it was already gone. Does NOT verify the caller owns it —
|
|
54
|
+
* callers higher in the stack (the runner itself) own that semantics.
|
|
55
|
+
*/
|
|
56
|
+
release(): boolean;
|
|
57
|
+
/** Read the current holder, if any. */
|
|
58
|
+
read(): ProjectRoundLockPayload | null;
|
|
59
|
+
private ensureDir;
|
|
60
|
+
private lockPath;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Cross-platform "is this PID still running" check.
|
|
64
|
+
* `kill(pid, 0)` doesn't send a signal but throws if the PID isn't valid.
|
|
65
|
+
*/
|
|
66
|
+
export declare function isAlive(pid: number): boolean;
|
|
67
|
+
//# sourceMappingURL=ProjectRoundLock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectRoundLock.d.ts","sourceRoot":"","sources":["../../src/core/ProjectRoundLock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,uBAAuB,CAAA;CAAE,GAC9C;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,uBAAuB,CAAC;CACxC,CAAC;AAEN,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAS;gBAEb,MAAM,EAAE,sBAAsB;IAI1C;;;;OAIG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,aAAa;IAmDrF;;;;OAIG;IACH,OAAO,IAAI,OAAO;IAWlB,uCAAuC;IACvC,IAAI,IAAI,uBAAuB,GAAG,IAAI;IAgBtC,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,QAAQ;CAGjB;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAW5C"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProjectRoundLock — machine-local lock for the round runner.
|
|
3
|
+
*
|
|
4
|
+
* Spec: docs/specs/PROJECT-SCOPE-SPEC.md § Phase 1.5 ("Lock file
|
|
5
|
+
* .instar/local/round-runner.lock is free."). The path is MACHINE-LOCAL
|
|
6
|
+
* (under .instar/local/, NOT git-synced) so two machines never fight
|
|
7
|
+
* over a 0-byte lockfile in the sync layer; cross-machine ownership is
|
|
8
|
+
* separately controlled by `ownerMachineId` on the project record and
|
|
9
|
+
* the claim-ownership flow (later PR).
|
|
10
|
+
*
|
|
11
|
+
* Contract:
|
|
12
|
+
* - At most one round-runner active per machine. The lock encodes the
|
|
13
|
+
* PID, projectId, and roundIndex of the holder.
|
|
14
|
+
* - Stale lock detection: if the recorded PID is no longer alive, the
|
|
15
|
+
* lock is considered free (handles crash without restart). This is
|
|
16
|
+
* intentional and matches the spec's pre-flight step 2 ("PID in lock
|
|
17
|
+
* is alive; if not, remove").
|
|
18
|
+
* - Acquisition is best-effort atomic: `O_CREAT | O_EXCL` rename of a
|
|
19
|
+
* tmp file holding the JSON payload. Concurrent acquirers race on
|
|
20
|
+
* the rename; the loser fails fast. This is the standard non-fcntl
|
|
21
|
+
* local-process mutex pattern; proper-lockfile is overkill for a
|
|
22
|
+
* single-machine single-runner gate.
|
|
23
|
+
*/
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { SafeFsExecutor } from './SafeFsExecutor.js';
|
|
27
|
+
export class ProjectRoundLock {
|
|
28
|
+
stateDir;
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.stateDir = config.stateDir;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Try to acquire the lock for the given (projectId, roundIndex). Returns
|
|
34
|
+
* a structured result rather than throwing — callers decide whether to
|
|
35
|
+
* wait, abort, or report.
|
|
36
|
+
*/
|
|
37
|
+
acquire(projectId, roundIndex, now = new Date()) {
|
|
38
|
+
this.ensureDir();
|
|
39
|
+
// Stale-PID sweep first.
|
|
40
|
+
const existing = this.read();
|
|
41
|
+
if (existing && !isAlive(existing.pid)) {
|
|
42
|
+
try {
|
|
43
|
+
SafeFsExecutor.safeUnlinkSync(this.lockPath(), { operation: 'ProjectRoundLock.acquire:sweep-stale' });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Race with another acquirer doing the same — fine.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (existing) {
|
|
50
|
+
return { ok: false, reason: 'held', currentHolder: existing };
|
|
51
|
+
}
|
|
52
|
+
const payload = {
|
|
53
|
+
pid: process.pid,
|
|
54
|
+
projectId,
|
|
55
|
+
roundIndex,
|
|
56
|
+
acquiredAt: now.toISOString(),
|
|
57
|
+
};
|
|
58
|
+
const body = JSON.stringify(payload);
|
|
59
|
+
const tmp = this.lockPath() + '.tmp.' + process.pid;
|
|
60
|
+
try {
|
|
61
|
+
// O_CREAT | O_EXCL: fails if tmp already exists (in case of a
|
|
62
|
+
// previous crash with the same pid — unlikely but harmless).
|
|
63
|
+
const fd = fs.openSync(tmp, 'wx', 0o600);
|
|
64
|
+
fs.writeSync(fd, body);
|
|
65
|
+
fs.closeSync(fd);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
try {
|
|
69
|
+
SafeFsExecutor.safeUnlinkSync(tmp, { operation: 'ProjectRoundLock.acquire:cleanup-tmp' });
|
|
70
|
+
}
|
|
71
|
+
catch { /* ignore */ }
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
// POSIX rename is atomic on the same filesystem. If another process
|
|
76
|
+
// sneaked in between our stale-sweep and the rename, our rename
|
|
77
|
+
// overwrites theirs — which is wrong. So we re-check after.
|
|
78
|
+
// BUT: the spec accepts "at most one runner per machine" which is
|
|
79
|
+
// a single-process guarantee for serial invocation; concurrent
|
|
80
|
+
// acquires from the same process don't happen because the routes
|
|
81
|
+
// layer hold the call inside one promise. Cross-process concurrent
|
|
82
|
+
// acquires are a theoretical edge — round runners only spawn from
|
|
83
|
+
// the supervised AgentServer. We accept the rename racing on the
|
|
84
|
+
// narrow window between read() and rename() as acceptable.
|
|
85
|
+
fs.renameSync(tmp, this.lockPath());
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
try {
|
|
89
|
+
SafeFsExecutor.safeUnlinkSync(tmp, { operation: 'ProjectRoundLock.acquire:cleanup-tmp' });
|
|
90
|
+
}
|
|
91
|
+
catch { /* ignore */ }
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
return { ok: true, payload };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Release the lock unconditionally. Returns true if a lock was removed,
|
|
98
|
+
* false if it was already gone. Does NOT verify the caller owns it —
|
|
99
|
+
* callers higher in the stack (the runner itself) own that semantics.
|
|
100
|
+
*/
|
|
101
|
+
release() {
|
|
102
|
+
try {
|
|
103
|
+
SafeFsExecutor.safeUnlinkSync(this.lockPath(), { operation: 'ProjectRoundLock.release' });
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
const e = err;
|
|
108
|
+
if (e.code === 'ENOENT')
|
|
109
|
+
return false;
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Read the current holder, if any. */
|
|
114
|
+
read() {
|
|
115
|
+
try {
|
|
116
|
+
const raw = fs.readFileSync(this.lockPath(), 'utf-8');
|
|
117
|
+
const obj = JSON.parse(raw);
|
|
118
|
+
if (typeof obj.pid !== 'number')
|
|
119
|
+
return null;
|
|
120
|
+
if (typeof obj.projectId !== 'string')
|
|
121
|
+
return null;
|
|
122
|
+
if (typeof obj.roundIndex !== 'number')
|
|
123
|
+
return null;
|
|
124
|
+
if (typeof obj.acquiredAt !== 'string')
|
|
125
|
+
return null;
|
|
126
|
+
return obj;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// ── Internals ─────────────────────────────────────────────────────
|
|
133
|
+
ensureDir() {
|
|
134
|
+
const localDir = path.join(this.stateDir, 'local');
|
|
135
|
+
if (!fs.existsSync(localDir))
|
|
136
|
+
fs.mkdirSync(localDir, { recursive: true });
|
|
137
|
+
}
|
|
138
|
+
lockPath() {
|
|
139
|
+
return path.join(this.stateDir, 'local', 'round-runner.lock');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Cross-platform "is this PID still running" check.
|
|
144
|
+
* `kill(pid, 0)` doesn't send a signal but throws if the PID isn't valid.
|
|
145
|
+
*/
|
|
146
|
+
export function isAlive(pid) {
|
|
147
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
148
|
+
return false;
|
|
149
|
+
try {
|
|
150
|
+
process.kill(pid, 0);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
const e = err;
|
|
155
|
+
// EPERM means the PID exists but we don't have permission; still alive.
|
|
156
|
+
if (e.code === 'EPERM')
|
|
157
|
+
return true;
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=ProjectRoundLock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectRoundLock.js","sourceRoot":"","sources":["../../src/core/ProjectRoundLock.ts"],"names":[],"mappings":"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"}
|