@miraland-labs/conduit-bridge 0.16.184 → 0.16.186

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/cli.js CHANGED
@@ -676,7 +676,9 @@ async function runner() {
676
676
  if (workspace && onlineDriverIds(config).length && !gateRehearsalRunning) {
677
677
  // Read the workspace again, then publish it. The last brief stands if the read fails.
678
678
  const publishWorkspaceState = async (options = {}) => {
679
- const refresh = await reportedRefresh(workspace, options);
679
+ // Not while an investigation runs: its observation-only check compares the source checkout
680
+ // before and after, and the runner's own fast-forward must not fail that check (CR18).
681
+ const refresh = investigationRunning ? lastRefresh?.result ?? null : await reportedRefresh(workspace, options);
680
682
  const currentBrief = await buildWorkspaceBrief(workspace).catch(() => brief);
681
683
  const preflight = await cachedBridgePreflight({ config, workspace, managedRoot, brief: currentBrief ?? undefined, refresh, agentTimeoutMs: timeoutMs }, options);
682
684
  const hb = await heartbeat(client, config, workspace, currentBrief, preflight, undefined, bootstrapResult);
@@ -687,11 +689,17 @@ async function runner() {
687
689
  };
688
690
  // Observation runs before authoring: an owner waiting on an answer should not queue behind
689
691
  // a long delivery, and the control plane already counted this slot as busy.
690
- progressed = await executeNextInvestigation(client, config, workspace, brief, timeoutMs, {
691
- managedRoot: hb.on_shift?.managed_workspace_root ?? null,
692
- heartbeat: publishHeartbeat,
693
- heartbeatIntervalMs: intervalMs,
694
- }) || progressed;
692
+ investigationRunning = true;
693
+ try {
694
+ progressed = await executeNextInvestigation(client, config, workspace, brief, timeoutMs, {
695
+ managedRoot: hb.on_shift?.managed_workspace_root ?? null,
696
+ heartbeat: publishHeartbeat,
697
+ heartbeatIntervalMs: intervalMs,
698
+ }) || progressed;
699
+ }
700
+ finally {
701
+ investigationRunning = false;
702
+ }
695
703
  progressed = await pumpExecutionSlots(client, config, workspace, brief, timeoutMs, {
696
704
  heartbeat: publishHeartbeat,
697
705
  beforeClaim: async (driverId) => {
@@ -723,6 +731,8 @@ async function runner() {
723
731
  }
724
732
  /** True while the gate rehearsal runs in the background; the loop keeps beating but claims nothing. */
725
733
  let gateRehearsalRunning = false;
734
+ /** An observation fingerprints the source checkout; a fast-forward under it reads as the observer writing. */
735
+ let investigationRunning = false;
726
736
  /** The rehearsals the heartbeat reports, one per on-shift repository; empty reports no field. */
727
737
  let reportedGateRehearsals = [];
728
738
  /** The last on-shift snapshot, so the next cycle knows every repository this computer serves. */
@@ -4,11 +4,13 @@
4
4
  * it never substitutes for it.
5
5
  */
6
6
  import { spawn } from "node:child_process";
7
- import { mkdir, mkdtemp, rm } from "node:fs/promises";
7
+ import { createHash } from "node:crypto";
8
+ import { mkdir, mkdtemp, readFile, realpath, rm } from "node:fs/promises";
8
9
  import { join } from "node:path";
9
10
  import { untrustedCodeTempRoot, untrustedCodeSpawn, untrustedCodeEnvironment, criterionKey, resolveCriterionReference, signalGroup, STDIO_DRAIN_MS, CHILD_STOP_GRACE_MS, OWN_PROCESS_GROUP, trackProcessGroup, untrackProcessGroup } from "./driver.js";
10
11
  import { isBoundedVerificationCommand, isRunnableVerificationCommand } from "./execution-class.js";
11
12
  import { workspaceDependenciesMissingFailure } from "./failure-signal.js";
13
+ import { redactSecrets } from "./secret-redaction.js";
12
14
  /** A typed spawn refusal, distinct from a command that ran and failed. */
13
15
  export class VerificationCommandUnavailableError extends Error {
14
16
  failure;
@@ -381,6 +383,40 @@ export function boundedFailureDetail(command, lines, code, maxChars = FAILURE_DE
381
383
  const middle = dropped > 0 ? [...head, marker(dropped), ...tail] : [...head, ...tail];
382
384
  return [first, ...middle, last].join("\n");
383
385
  }
386
+ const FETCH_STDERR_TAIL_BYTES = 64 * 1024;
387
+ /** Workspace realpath + Cargo.lock sha256 already fetched by this process. */
388
+ const fetchedLocks = new Set();
389
+ /** The runner's own environment: network, the real CARGO_HOME and the PATH that finds cargo. */
390
+ const runFetchOutsideSandbox = (command, argv, cwd) => new Promise((resolve) => {
391
+ const child = spawn(command, argv, { cwd, env: process.env, stdio: ["ignore", "ignore", "pipe"] });
392
+ let stderr = "";
393
+ const timer = setTimeout(() => child.kill("SIGKILL"), VERIFICATION_TIMEOUT_MS);
394
+ child.stderr?.setEncoding("utf8");
395
+ child.stderr?.on("data", (chunk) => { stderr = `${stderr}${chunk}`.slice(-FETCH_STDERR_TAIL_BYTES); });
396
+ child.on("error", (error) => { clearTimeout(timer); resolve({ code: 127, stderr: error.message }); });
397
+ child.on("close", (code) => { clearTimeout(timer); resolve({ code: code ?? 1, stderr }); });
398
+ });
399
+ /**
400
+ * CR19 (2026-10-22): the gate runs without network, so every crate the Cargo.lock pins must already
401
+ * be in CARGO_HOME. `cargo fetch --locked` fills it outside the sandbox; it writes only to CARGO_HOME
402
+ * and refuses to change the lock. Once per lock file per process: an agent that adds a dependency
403
+ * changes the lock, and the next gate fetches again. A failed fetch does not stop the gate: its own
404
+ * output names the cause.
405
+ */
406
+ export async function fetchLockedDependencies(workspace, options = {}) {
407
+ const lock = await readFile(join(workspace, "Cargo.lock")).catch(() => null);
408
+ if (!lock)
409
+ return { ran: false };
410
+ const key = `${await realpath(workspace)}\0${createHash("sha256").update(lock).digest("hex")}`;
411
+ if (fetchedLocks.has(key))
412
+ return { ran: false };
413
+ const result = await (options.run ?? runFetchOutsideSandbox)("cargo", ["fetch", "--locked"], workspace);
414
+ if (result.code === 0)
415
+ fetchedLocks.add(key);
416
+ else
417
+ console.error(redactSecrets(`[bridge] cargo fetch --locked exited ${result.code} in ${workspace}: ${result.stderr.trim().split("\n").slice(-10).join("\n")}`));
418
+ return { ran: true, code: result.code };
419
+ }
384
420
  /**
385
421
  * The bounded runner every Bridge-run verification uses: the declared command, in the given
386
422
  * workspace, with the verification timeout and buffer. A non-zero exit is a result, not a throw.
@@ -449,6 +485,7 @@ export async function runBoundedVerificationCommand(command, workspace, timeoutM
449
485
  partial[stream].append(parts.at(-1));
450
486
  };
451
487
  const output = () => [lines.text().replace(/\n$/, ""), partial.stdout.text(), partial.stderr.text()].filter(Boolean).join("\n");
488
+ await fetchLockedDependencies(workspace);
452
489
  const tempRoot = untrustedCodeTempRoot(workspace);
453
490
  await mkdir(tempRoot, { recursive: true });
454
491
  const scratchHome = await mkdtemp(join(tempRoot, "gate-home-"));
@@ -374,6 +374,8 @@ export const VERIFICATION_FAILS_ON_BASE = "verification fails on the base commit
374
374
  export const WORKSPACE_DEPENDENCIES_MISSING_PATTERN = /(?:^|\n)\s*(?:\/(?:usr\/)?bin\/)?(?:ba|z|da)?sh:\s*(?:(?:\d+:\s*)?[^:\n]{1,120}:\s*(?:command\s+)?not found|command not found:\s*[^\n]+)\s*(?=\n|$)|(?:^|\n)\s*'[^'\n]+' is not recognized as an internal or external command/im;
375
375
  /** A write the toolchain needs that the computer refused before any test ran: the sandbox or disk, not the base commit. */
376
376
  export const WORKSPACE_WRITE_REFUSED_PATTERN = /\b(?:EROFS|ENOSPC)\b|read-only file system|no space left on device/i;
377
+ /** CR19 (2026-10-22): cargo inside the no-network sandbox tried to download a crate the shared cache lacks; the gate never tested the base. */
378
+ export const WORKSPACE_FETCH_REFUSED_PATTERN = /Could not resolve host|failed to download from|Temporary failure in name resolution|Network is unreachable|unable to get packages from source/i;
377
379
  /** CR17e (2026-10-22): vitest could not write its config bundle under the sandboxed install; Bridge held the owner for its own mount. */
378
380
  export function workspaceWriteRefusedFailure(detail) {
379
381
  return {
@@ -613,7 +615,7 @@ export function classifyFailureSignal(signal) {
613
615
  // base commit. It is the computer's checkout that lacks a tool — an environment Hold that
614
616
  // clears when the workspace is bootstrapped, not a contract Hold on the author that never
615
617
  // clears. runner-2's first claim (2026-09-15) sat on the author for a missing `node_modules`.
616
- if (signal.exit_code === 127 || WORKSPACE_DEPENDENCIES_MISSING_PATTERN.test(output)) {
618
+ if (signal.exit_code === 127 || WORKSPACE_DEPENDENCIES_MISSING_PATTERN.test(output) || WORKSPACE_FETCH_REFUSED_PATTERN.test(output)) {
617
619
  return workspaceDependenciesMissingFailure([`$ ${command}`, `exit ${signal.exit_code ?? "unknown"}`, output].filter(Boolean).join("\n"));
618
620
  }
619
621
  if (WORKSPACE_WRITE_REFUSED_PATTERN.test(output)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.16.184",
3
+ "version": "0.16.186",
4
4
  "description": "Conduit Bridge CLI — 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": {