@miraland-labs/conduit-bridge 0.16.157 → 0.16.159
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.js +10 -4
- package/dist/driver.js +1 -1
- package/dist/ensure-land-commit.js +2 -2
- package/dist/ensure-pull-request.js +9 -4
- package/dist/ensure-test-evidence.js +125 -50
- package/dist/execution.js +20 -2
- package/dist/integration-obligations.js +1 -1
- package/dist/investigation.js +14 -8
- package/dist/land-git-reality.js +1 -1
- package/dist/normative-refs.js +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -69,10 +69,16 @@ export class ConduitClient {
|
|
|
69
69
|
signal: init.signal ?? AbortSignal.timeout(CONTROL_PLANE_TIMEOUT_MS),
|
|
70
70
|
headers: { authorization: `Bearer ${this.config.runnerKey}`, "content-type": "application/json", ...init.headers },
|
|
71
71
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const text = await response.text();
|
|
74
|
+
let data = null;
|
|
75
|
+
try {
|
|
76
|
+
data = JSON.parse(text);
|
|
77
|
+
}
|
|
78
|
+
catch { /* A proxy may answer with a non-JSON error page. */ }
|
|
79
|
+
throw new ConduitRequestError(data?.error?.message ?? `Conduit request failed (${response.status})`, response.status, data?.error?.code, data?.error?.details);
|
|
80
|
+
}
|
|
81
|
+
return await response.json();
|
|
76
82
|
}
|
|
77
83
|
async claim(taskId, attemptId, extras) {
|
|
78
84
|
// The lease token is this machine's bearer credential for the attempt. The server stores only
|
package/dist/driver.js
CHANGED
|
@@ -1895,7 +1895,7 @@ const OWN_PROCESS_GROUP = process.platform !== "win32";
|
|
|
1895
1895
|
/** Process groups of the agents running now, stopped when the runner itself is stopped. */
|
|
1896
1896
|
const runningGroups = new Set();
|
|
1897
1897
|
let shutdownHookInstalled = false;
|
|
1898
|
-
function signalGroup(pid, signal) {
|
|
1898
|
+
export function signalGroup(pid, signal) {
|
|
1899
1899
|
try {
|
|
1900
1900
|
process.kill(-pid, signal);
|
|
1901
1901
|
}
|
|
@@ -37,7 +37,7 @@ export async function ensureLandCommit(input) {
|
|
|
37
37
|
return { kind: "skipped" };
|
|
38
38
|
const countAhead = input.countCommitsAhead ?? commitsAheadOfBase;
|
|
39
39
|
const readHead = input.readHeadCommit ?? readHeadCommit;
|
|
40
|
-
const listPending = input.listUncommittedPaths ?? uncommittedPaths;
|
|
40
|
+
const listPending = input.listUncommittedPaths ?? ((workspace) => uncommittedPaths(workspace, { untracked: "all" }));
|
|
41
41
|
const commitPaths = input.commitPaths ?? commitScopedPaths;
|
|
42
42
|
const ahead = await countAhead(input.workspace, base);
|
|
43
43
|
if (ahead !== null && ahead > 0) {
|
|
@@ -69,7 +69,7 @@ async function currentBranch(workspace) {
|
|
|
69
69
|
export async function checkpointWorkInProgress(input) {
|
|
70
70
|
if (!requiresLandCommit({ grants: input.grants, spec: input.spec }))
|
|
71
71
|
return null;
|
|
72
|
-
const pending = filterPathsInScope(await uncommittedPaths(input.workspace), input.spec.change_scope ?? []);
|
|
72
|
+
const pending = filterPathsInScope(await uncommittedPaths(input.workspace, { untracked: "all" }), input.spec.change_scope ?? []);
|
|
73
73
|
if (pending.length) {
|
|
74
74
|
await commitScopedPaths(input.workspace, pending, "Conduit: work in progress at the package budget");
|
|
75
75
|
}
|
|
@@ -8,10 +8,13 @@ import { promisify } from "node:util";
|
|
|
8
8
|
import { normalizeRepositoryUrl } from "./brief.js";
|
|
9
9
|
import { readHeadCommit } from "./git.js";
|
|
10
10
|
import { forgeTransportFailure } from "./transport-fault.js";
|
|
11
|
+
import { ConduitRequestError } from "./client.js";
|
|
12
|
+
import { redactSecrets } from "./secret-redaction.js";
|
|
11
13
|
import { agentNoLandCommitMessage } from "./land-contract.js";
|
|
12
14
|
const execFileAsync = promisify(execFile);
|
|
13
15
|
/** Keep the pull request body inside forge limits, even for a very large delivery packet. */
|
|
14
16
|
const PULL_REQUEST_BODY_MAX_BYTES = 60_000;
|
|
17
|
+
export const CONTROL_PLANE_TRANSIENT_PREFIX = "Control plane did not answer the change request open: ";
|
|
15
18
|
/**
|
|
16
19
|
* What the packet says when the control plane refused to open the change request.
|
|
17
20
|
*
|
|
@@ -289,14 +292,16 @@ export async function ensureDeliveryPullRequest(input) {
|
|
|
289
292
|
head_branch: headBranch,
|
|
290
293
|
head_commit: report.head_commit,
|
|
291
294
|
title: (input.title ?? "Conduit delivery").slice(0, 200),
|
|
292
|
-
body: composePullRequestBody(report),
|
|
295
|
+
body: redactSecrets(composePullRequestBody(report)),
|
|
293
296
|
idempotency_key: `bridge:open-pr:${input.attemptId}`,
|
|
294
297
|
});
|
|
295
298
|
}
|
|
296
299
|
catch (error) {
|
|
297
300
|
const message = error instanceof Error ? error.message : String(error);
|
|
298
|
-
|
|
299
|
-
|
|
301
|
+
if (error instanceof ConduitRequestError && error.status < 500) {
|
|
302
|
+
throw new Error(`${PULL_REQUEST_OPEN_FAILURE_PREFIX}: ${message}${error.code ? ` (${error.code})` : ""}`);
|
|
303
|
+
}
|
|
304
|
+
throw new Error(`${CONTROL_PLANE_TRANSIENT_PREFIX}${message}`);
|
|
300
305
|
}
|
|
301
306
|
const pullRequestUrl = typeof response.pull_request_url === "string" ? response.pull_request_url : "";
|
|
302
307
|
// Any https URL: the control plane resolved which forge this project uses and parsed the response
|
|
@@ -304,7 +309,7 @@ export async function ensureDeliveryPullRequest(input) {
|
|
|
304
309
|
// GitLab merge request — after the branch was already pushed. The head-commit cross-check below is
|
|
305
310
|
// what actually proves the returned change request is this delivery's.
|
|
306
311
|
if (!isAbsoluteHttpsUrl(pullRequestUrl)) {
|
|
307
|
-
throw new Error("Control plane
|
|
312
|
+
throw new Error("Control plane answered without a change request link");
|
|
308
313
|
}
|
|
309
314
|
const openedHead = typeof response.head_commit === "string" ? response.head_commit : "";
|
|
310
315
|
const deliveredHead = report.head_commit ?? "";
|
|
@@ -3,15 +3,13 @@
|
|
|
3
3
|
* commands and attaches their verbatim results. Agent prose may supplement those runs;
|
|
4
4
|
* it never substitutes for it.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { boundedEnvironment, criterionKey, resolveCriterionReference } from "./driver.js";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { boundedEnvironment, criterionKey, resolveCriterionReference, signalGroup, STDIO_DRAIN_MS } from "./driver.js";
|
|
9
8
|
import { isBoundedVerificationCommand, isRunnableVerificationCommand } from "./execution-class.js";
|
|
10
|
-
const execFileAsync = promisify(execFile);
|
|
11
9
|
const TEST_EVIDENCE_DETAILS_MIN = 32;
|
|
12
10
|
/** Cold `cargo test` in an attempt worktree routinely exceeds two minutes of compile. */
|
|
13
11
|
export const VERIFICATION_TIMEOUT_MS = 600_000;
|
|
14
|
-
/** rustc + cargo logs routinely exceed 2 MiB;
|
|
12
|
+
/** rustc + cargo logs routinely exceed 2 MiB; keep a bounded tail per stream. */
|
|
15
13
|
export const VERIFICATION_MAX_BUFFER_BYTES = 32_000_000;
|
|
16
14
|
/**
|
|
17
15
|
* Contract requires test + test_run — Bridge always runs; agent paste is not a substitute.
|
|
@@ -203,6 +201,10 @@ export async function captureVerificationFailure(input) {
|
|
|
203
201
|
// Diagnosis is best-effort: try the next command rather than failing the attempt again.
|
|
204
202
|
continue;
|
|
205
203
|
}
|
|
204
|
+
if (result.timedOut) {
|
|
205
|
+
return { command, detail: boundedTail(outputLines(result).join("\n"), DIAGNOSIS_DETAIL_MAX_CHARS),
|
|
206
|
+
timedOut: true, stdout: result.stdout, stderr: result.stderr };
|
|
207
|
+
}
|
|
206
208
|
if (result.code === 0)
|
|
207
209
|
continue;
|
|
208
210
|
// Same rule as the pre-delivery gate: this is the diagnosis a repair brief is written from, so
|
|
@@ -375,55 +377,128 @@ export async function runBoundedVerificationCommand(command, workspace, timeoutM
|
|
|
375
377
|
throw new Error("Verification command is empty");
|
|
376
378
|
// Chunks are not whole lines. Each stream keeps its partial last line until the line ends, so a
|
|
377
379
|
// stderr write between two stdout chunks cannot split a stdout line.
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
+
const boundedOutput = (limit) => {
|
|
381
|
+
const chunks = [];
|
|
382
|
+
let bytes = 0;
|
|
383
|
+
return {
|
|
384
|
+
append(text) {
|
|
385
|
+
const chunk = Buffer.from(text);
|
|
386
|
+
if (!chunk.length)
|
|
387
|
+
return;
|
|
388
|
+
const last = chunks.at(-1);
|
|
389
|
+
if (last && last.length + chunk.length <= 64 * 1024)
|
|
390
|
+
chunks[chunks.length - 1] = Buffer.concat([last, chunk]);
|
|
391
|
+
else
|
|
392
|
+
chunks.push(chunk);
|
|
393
|
+
bytes += chunk.length;
|
|
394
|
+
while (bytes > limit) {
|
|
395
|
+
const first = chunks[0];
|
|
396
|
+
const excess = bytes - limit;
|
|
397
|
+
if (first.length <= excess) {
|
|
398
|
+
bytes -= first.length;
|
|
399
|
+
chunks.shift();
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
chunks[0] = first.subarray(excess);
|
|
403
|
+
bytes = limit;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
text() {
|
|
408
|
+
const buffer = Buffer.concat(chunks, bytes);
|
|
409
|
+
let start = 0;
|
|
410
|
+
while (start < buffer.length && (buffer[start] & 0xc0) === 0x80)
|
|
411
|
+
start += 1;
|
|
412
|
+
return buffer.subarray(start).toString("utf8");
|
|
413
|
+
},
|
|
414
|
+
clear() { chunks.length = 0; bytes = 0; },
|
|
415
|
+
};
|
|
416
|
+
};
|
|
417
|
+
const lines = boundedOutput(2 * VERIFICATION_MAX_BUFFER_BYTES);
|
|
418
|
+
const partial = { stdout: boundedOutput(VERIFICATION_MAX_BUFFER_BYTES), stderr: boundedOutput(VERIFICATION_MAX_BUFFER_BYTES) };
|
|
380
419
|
const collect = (stream) => (chunk) => {
|
|
381
|
-
const parts =
|
|
382
|
-
partial[stream]
|
|
383
|
-
|
|
420
|
+
const parts = String(chunk).split("\n");
|
|
421
|
+
partial[stream].append(parts[0]);
|
|
422
|
+
if (parts.length === 1)
|
|
423
|
+
return;
|
|
424
|
+
lines.append(`${partial[stream].text()}\n`);
|
|
425
|
+
partial[stream].clear();
|
|
426
|
+
if (parts.length > 2)
|
|
427
|
+
lines.append(`${parts.slice(1, -1).join("\n")}\n`);
|
|
428
|
+
partial[stream].append(parts.at(-1));
|
|
384
429
|
};
|
|
385
|
-
const output = () => [
|
|
386
|
-
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
430
|
+
const output = () => [lines.text().replace(/\n$/, ""), partial.stdout.text(), partial.stderr.text()].filter(Boolean).join("\n");
|
|
431
|
+
return new Promise((resolve, reject) => {
|
|
432
|
+
const ownGroup = process.platform !== "win32";
|
|
433
|
+
const child = spawn(bin, argv.slice(1), { cwd: workspace, env: boundedEnvironment(),
|
|
434
|
+
stdio: ["ignore", "pipe", "pipe"], detached: ownGroup });
|
|
435
|
+
const stop = (signal) => {
|
|
436
|
+
if (ownGroup && child.pid !== undefined)
|
|
437
|
+
signalGroup(child.pid, signal);
|
|
438
|
+
else
|
|
439
|
+
child.kill(signal);
|
|
440
|
+
};
|
|
441
|
+
const stdout = boundedOutput(VERIFICATION_MAX_BUFFER_BYTES);
|
|
442
|
+
const stderr = boundedOutput(VERIFICATION_MAX_BUFFER_BYTES);
|
|
443
|
+
let timedOut = false;
|
|
444
|
+
let settled = false;
|
|
445
|
+
let exited = false;
|
|
446
|
+
let drainTimer = null;
|
|
447
|
+
let graceTimer = null;
|
|
448
|
+
const cleanup = () => {
|
|
449
|
+
clearTimeout(timer);
|
|
450
|
+
if (drainTimer)
|
|
451
|
+
clearTimeout(drainTimer);
|
|
452
|
+
if (graceTimer)
|
|
453
|
+
clearTimeout(graceTimer);
|
|
454
|
+
child.stdout?.destroy();
|
|
455
|
+
child.stderr?.destroy();
|
|
456
|
+
stop("SIGTERM");
|
|
457
|
+
setTimeout(() => stop("SIGKILL"), 5_000).unref?.();
|
|
458
|
+
};
|
|
459
|
+
const settle = (code) => {
|
|
460
|
+
if (settled)
|
|
461
|
+
return;
|
|
462
|
+
settled = true;
|
|
463
|
+
cleanup();
|
|
410
464
|
const stopped = `Bridge stopped the gate: it did not exit within ${Math.round(timeoutMs / 1_000)} seconds.`;
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
output: `${output()}\n${stopped}`.trimStart(),
|
|
415
|
-
code: 1,
|
|
416
|
-
timedOut: true,
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
const stderr = String(err.stderr || err.message || "verification failed");
|
|
420
|
-
return {
|
|
421
|
-
stdout: String(err.stdout ?? ""),
|
|
422
|
-
stderr,
|
|
423
|
-
output: output() || stderr,
|
|
424
|
-
code: typeof err.code === "number" ? err.code : 1,
|
|
465
|
+
resolve(timedOut ? { stdout: stdout.text(), stderr: `${stderr.text()}\n${stopped}`.trimStart(),
|
|
466
|
+
output: `${output()}\n${stopped}`.trimStart(), code: 1, timedOut: true }
|
|
467
|
+
: { stdout: stdout.text(), stderr: stderr.text(), output: output(), code: code ?? 1 });
|
|
425
468
|
};
|
|
426
|
-
|
|
469
|
+
const timer = setTimeout(() => {
|
|
470
|
+
if (exited)
|
|
471
|
+
return;
|
|
472
|
+
timedOut = true;
|
|
473
|
+
stop("SIGTERM");
|
|
474
|
+
setTimeout(() => stop("SIGKILL"), 10_000).unref?.();
|
|
475
|
+
graceTimer = setTimeout(() => settle(null), 20_000);
|
|
476
|
+
}, timeoutMs);
|
|
477
|
+
child.stdout?.setEncoding("utf8");
|
|
478
|
+
child.stderr?.setEncoding("utf8");
|
|
479
|
+
child.stdout?.on("data", (chunk) => { stdout.append(chunk); collect("stdout")(chunk); });
|
|
480
|
+
child.stderr?.on("data", (chunk) => { stderr.append(chunk); collect("stderr")(chunk); });
|
|
481
|
+
child.on("error", (error) => {
|
|
482
|
+
if (settled)
|
|
483
|
+
return;
|
|
484
|
+
if (typeof error.code === "string") {
|
|
485
|
+
settled = true;
|
|
486
|
+
cleanup();
|
|
487
|
+
reject(error);
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
if (!stderr.text())
|
|
491
|
+
stderr.append(error.message);
|
|
492
|
+
settle(1);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
child.on("close", (code) => settle(code));
|
|
496
|
+
child.on("exit", (code) => {
|
|
497
|
+
exited = true;
|
|
498
|
+
clearTimeout(timer);
|
|
499
|
+
drainTimer = setTimeout(() => settle(code), STDIO_DRAIN_MS);
|
|
500
|
+
});
|
|
501
|
+
});
|
|
427
502
|
}
|
|
428
503
|
/**
|
|
429
504
|
* Bridge may attach genuine command output; it must not invent which criteria that output proves.
|
package/dist/execution.js
CHANGED
|
@@ -23,7 +23,7 @@ import { formatResetMinute, parseQuotaResetAt } from "./quota-reset.js";
|
|
|
23
23
|
import { headCommitOrNull } from "./git.js";
|
|
24
24
|
import { BRIDGE_PROTOCOL_VERSION } from "./preflight.js";
|
|
25
25
|
import { bridgeVersion } from "./version.js";
|
|
26
|
-
import { PULL_REQUEST_OPEN_FAILURE_PREFIX, commitsMatch, ensureDeliveryPullRequest } from "./ensure-pull-request.js";
|
|
26
|
+
import { CONTROL_PLANE_TRANSIENT_PREFIX, PULL_REQUEST_OPEN_FAILURE_PREFIX, commitsMatch, ensureDeliveryPullRequest } from "./ensure-pull-request.js";
|
|
27
27
|
import { checkpointWorkInProgress, ensureLandCommit } from "./ensure-land-commit.js";
|
|
28
28
|
import { AGENT_NO_LAND_COMMIT_PREFIX, AgentNoLandCommitError, agentNoLandCommitMessage, requiresLandCommit } from "./land-contract.js";
|
|
29
29
|
import { agentClaimsRepositoryWork, claimsVsGitMismatch, isLandTreeEmpty, reportClaimsRepositoryWork, } from "./land-git-reality.js";
|
|
@@ -219,6 +219,8 @@ const FINALIZE_CONTRACT_PATTERN = /missing required evidence|without the repo_wr
|
|
|
219
219
|
export function classifyFinalizeFailure(message) {
|
|
220
220
|
if (forgeTransportFailure(message))
|
|
221
221
|
return { retryable: true, error: message, failure: bridgeCause("forge_unreachable", message) };
|
|
222
|
+
if (message.startsWith(CONTROL_PLANE_TRANSIENT_PREFIX))
|
|
223
|
+
return { retryable: true, error: message };
|
|
222
224
|
// The control plane refused to open the change request — no forge connection resolves for this
|
|
223
225
|
// organization or project, or the task's repository is not one that connection can open a change
|
|
224
226
|
// request on. No retry and no rework can supply a connection. The cause travels as an envelope so
|
|
@@ -1800,6 +1802,19 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
1800
1802
|
acceptance: spec.acceptance ?? [],
|
|
1801
1803
|
})
|
|
1802
1804
|
: null;
|
|
1805
|
+
if (capturedVerification?.timedOut) {
|
|
1806
|
+
retainAttemptWorktree = true;
|
|
1807
|
+
const response = await queueTerminal(client, taskId, { action: "fail", body: {
|
|
1808
|
+
error: capturedVerification.detail,
|
|
1809
|
+
retryable: false,
|
|
1810
|
+
signal: verificationGateTimeoutSignal({ command: capturedVerification.command,
|
|
1811
|
+
stderr: capturedVerification.stderr ?? "", stdout: capturedVerification.stdout ?? "" }),
|
|
1812
|
+
idempotency_key: `bridge:verification-gate-timeout:${active.attemptId}`,
|
|
1813
|
+
} });
|
|
1814
|
+
retainAttemptWorktree = response.retain_worktree === true;
|
|
1815
|
+
console.error(`Assignment ${taskId} verification gate did not finish: ${redactSecrets(capturedVerification.detail)}`);
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1803
1818
|
const verificationDetail = capturedVerification?.detail ?? null;
|
|
1804
1819
|
// The vendor named the model as the cause, so the model is what the retry must change. The
|
|
1805
1820
|
// model rides in the prefix: the control plane cannot read Bridge's selection any other way.
|
|
@@ -2750,7 +2765,8 @@ async function prepareDelivery(client, attemptId, taskId, report) {
|
|
|
2750
2765
|
const fuelMode = client.attempt(taskId).fuelMode;
|
|
2751
2766
|
const evidence = [];
|
|
2752
2767
|
const evidenceByCriterion = new Map();
|
|
2753
|
-
for (const [index,
|
|
2768
|
+
for (const [index, evidenceItem] of report.evidence.entries()) {
|
|
2769
|
+
const item = redactSecretsDeep(evidenceItem);
|
|
2754
2770
|
const uri = item.uri ?? `urn:conduit:attempt:${attemptId}:evidence:${index + 1}`;
|
|
2755
2771
|
// Publication receipts require a digest on every acceptance artifact. Textual agent
|
|
2756
2772
|
// evidence has no file to hash, so default to a canonical content digest of the
|
|
@@ -3001,6 +3017,7 @@ export async function runDeliveryVerificationGates(input) {
|
|
|
3001
3017
|
results.push({
|
|
3002
3018
|
command,
|
|
3003
3019
|
exitCode: result.code,
|
|
3020
|
+
timedOut: result.timedOut === true,
|
|
3004
3021
|
stdoutTail: boundedTail(result.stdout, DELIVERY_VERIFICATION_GATE_TAIL_CHARS),
|
|
3005
3022
|
stderrTail: boundedTail(result.stderr, DELIVERY_VERIFICATION_GATE_TAIL_CHARS),
|
|
3006
3023
|
});
|
|
@@ -3011,6 +3028,7 @@ export async function runDeliveryVerificationGates(input) {
|
|
|
3011
3028
|
results.push({
|
|
3012
3029
|
command,
|
|
3013
3030
|
exitCode: -1,
|
|
3031
|
+
timedOut: false,
|
|
3014
3032
|
stdoutTail: "",
|
|
3015
3033
|
stderrTail: boundedTail(message, DELIVERY_VERIFICATION_GATE_TAIL_CHARS),
|
|
3016
3034
|
});
|
|
@@ -41,7 +41,7 @@ export function parseIntegrationObligations(value) {
|
|
|
41
41
|
async function symbolReferencedFrom(workspace, symbol, globs) {
|
|
42
42
|
const pathspecs = globs.map((glob) => `:(glob)${glob}`);
|
|
43
43
|
try {
|
|
44
|
-
const { stdout } = await execFileAsync("git", ["-C", workspace, "grep", "-l", "-F", "--untracked", symbol, "--", ...pathspecs], { timeout: 30_000, maxBuffer: 4_000_000 });
|
|
44
|
+
const { stdout } = await execFileAsync("git", ["-C", workspace, "grep", "-l", "-F", "--untracked", "-e", symbol, "--", ...pathspecs], { timeout: 30_000, maxBuffer: 4_000_000 });
|
|
45
45
|
return stdout.trim().length > 0;
|
|
46
46
|
}
|
|
47
47
|
catch (error) {
|
package/dist/investigation.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* held to repo_read + test_run by the same driver projection that enforces read-only diagnosis, and
|
|
6
6
|
* the worktree it reads is deleted whichever way the run ends.
|
|
7
7
|
*/
|
|
8
|
-
import { redactSecrets } from "./secret-redaction.js";
|
|
8
|
+
import { redactSecrets, redactSecretsDeep } from "./secret-redaction.js";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { boundedTail, runBoundedVerificationCommand } from "./ensure-test-evidence.js";
|
|
11
11
|
import { createAttemptWorktree, removeAttemptWorktree } from "./attempt-worktree.js";
|
|
@@ -191,9 +191,10 @@ function formatGatesRunBlock(gates) {
|
|
|
191
191
|
return `${header}\n- none`;
|
|
192
192
|
return `${header}\n${gates.map((gate) => {
|
|
193
193
|
const tail = gate.stderrTail.trim() || gate.stdoutTail.trim();
|
|
194
|
+
const verdict = gate.timedOut ? "did not finish (stopped by Bridge)" : `exit ${gate.exitCode}`;
|
|
194
195
|
return tail
|
|
195
|
-
? `- ${gate.command} —
|
|
196
|
-
: `- ${gate.command} —
|
|
196
|
+
? `- ${gate.command} — ${verdict}\n ${tail.replace(/\n/g, "\n ")}`
|
|
197
|
+
: `- ${gate.command} — ${verdict}`;
|
|
197
198
|
}).join("\n")}`;
|
|
198
199
|
}
|
|
199
200
|
function gateFailureLine(tail) {
|
|
@@ -444,8 +445,8 @@ function sourceProbeVerdict(exitCode, stdout) {
|
|
|
444
445
|
return "fails";
|
|
445
446
|
return "broken";
|
|
446
447
|
}
|
|
447
|
-
function testProbeVerdict(ran, exitCode) {
|
|
448
|
-
if (!ran)
|
|
448
|
+
function testProbeVerdict(ran, exitCode, timedOut) {
|
|
449
|
+
if (!ran || timedOut)
|
|
449
450
|
return "broken";
|
|
450
451
|
return exitCode === 0 ? "holds" : "fails";
|
|
451
452
|
}
|
|
@@ -553,12 +554,14 @@ export async function runDeliveryVerificationProbes(input) {
|
|
|
553
554
|
let stdout = "";
|
|
554
555
|
let stderr = "";
|
|
555
556
|
let ran = false;
|
|
557
|
+
let timedOut = false;
|
|
556
558
|
try {
|
|
557
559
|
const result = await (input.runCommand ?? runBoundedVerificationCommand)(named, input.workspace);
|
|
558
560
|
exitCode = result.code;
|
|
559
561
|
stdout = result.stdout;
|
|
560
562
|
stderr = result.stderr;
|
|
561
563
|
ran = true;
|
|
564
|
+
timedOut = result.timedOut === true;
|
|
562
565
|
}
|
|
563
566
|
catch (error) {
|
|
564
567
|
stderr = error instanceof Error ? error.message : String(error);
|
|
@@ -566,7 +569,7 @@ export async function runDeliveryVerificationProbes(input) {
|
|
|
566
569
|
}
|
|
567
570
|
witnesses.push(witnessRecord({
|
|
568
571
|
key: probe.key, kind: "test", command: named, exitCode, stdout, stderr, checks,
|
|
569
|
-
verdict: testProbeVerdict(ran, exitCode),
|
|
572
|
+
verdict: testProbeVerdict(ran, exitCode, timedOut),
|
|
570
573
|
}));
|
|
571
574
|
continue;
|
|
572
575
|
}
|
|
@@ -688,7 +691,10 @@ export async function runDeliveryVerificationProbesForAttempt(input) {
|
|
|
688
691
|
async function settle(client, investigationId, body) {
|
|
689
692
|
await client.request(`/runner/v1/investigations/${investigationId}/settle`, {
|
|
690
693
|
method: "POST",
|
|
691
|
-
body: JSON.stringify(body
|
|
694
|
+
body: JSON.stringify({ ...body,
|
|
695
|
+
...(body.result !== undefined ? { result: redactSecretsDeep(body.result) } : {}),
|
|
696
|
+
...(body.error !== undefined ? { error: redactSecrets(body.error) } : {}),
|
|
697
|
+
}),
|
|
692
698
|
});
|
|
693
699
|
}
|
|
694
700
|
/**
|
|
@@ -901,7 +907,7 @@ export async function executeNextInvestigation(client, config, workspace, brief,
|
|
|
901
907
|
limitations: observed.limitations,
|
|
902
908
|
criteria: observed.criteria,
|
|
903
909
|
command_failures: deliveryVerification
|
|
904
|
-
? gatesRun.filter((gate) => gate.exitCode !== 0).map(gateFailureFact).slice(0, 10)
|
|
910
|
+
? gatesRun.filter((gate) => gate.exitCode !== 0 && !gate.timedOut).map(gateFailureFact).slice(0, 10)
|
|
905
911
|
: observed.command_failures,
|
|
906
912
|
step_witness: stepWitness,
|
|
907
913
|
no_behaviour: deliveryVerification ? observed.no_behaviour : [],
|
package/dist/land-git-reality.js
CHANGED
|
@@ -18,7 +18,7 @@ export async function isLandTreeEmpty(input) {
|
|
|
18
18
|
const ahead = await countAhead(input.workspace, base);
|
|
19
19
|
if (ahead !== null && ahead > 0)
|
|
20
20
|
return false;
|
|
21
|
-
const pending = filterPathsInScope(await (input.listUncommittedPaths ?? listUncommittedPaths)(input.workspace), input.spec.change_scope ?? []);
|
|
21
|
+
const pending = filterPathsInScope(await (input.listUncommittedPaths ?? ((workspace) => listUncommittedPaths(workspace, { untracked: "all" })))(input.workspace), input.spec.change_scope ?? []);
|
|
22
22
|
return pending.length === 0;
|
|
23
23
|
}
|
|
24
24
|
function deliveryObjectClaimsWork(raw) {
|
package/dist/normative-refs.js
CHANGED
|
@@ -197,7 +197,7 @@ function isConduitPath(path) {
|
|
|
197
197
|
return normalized === ".conduit" || normalized.startsWith(".conduit/");
|
|
198
198
|
}
|
|
199
199
|
async function markerPresent(workspace, marker, paths) {
|
|
200
|
-
const args = ["-C", workspace, "grep", "-l", "-F", "--untracked", marker, "--", ...paths];
|
|
200
|
+
const args = ["-C", workspace, "grep", "-l", "-F", "--untracked", "-e", marker, "--", ...paths];
|
|
201
201
|
try {
|
|
202
202
|
const { stdout } = await execFileAsync("git", args, {
|
|
203
203
|
timeout: 30_000,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miraland-labs/conduit-bridge",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.159",
|
|
4
4
|
"description": "Conduit Bridge CLI \u2014 join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity / Grok Build agents for a Conduit organization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|